The Ins and Outs of Native Client
Curious about Native Client? Google's new technology allows native C/C++ code to be run in a web browser -- which is great -- but what are the practical issues around getting a game to run with it? What are the practical pitfalls and drawbacks? Developer Jeff Ward, who's shipped an NaCl game, explains.
Curious about Native Client? Google's new technology allows native C/C++ code to be run in a web browser -- which is great -- but what are the practical issues around getting a game to run with it? What are the practical pitfalls and drawbacks? Developer Jeff Ward, who's shipped an NaCl game, explains.
Earlier this year, Google announced a technology to allow native, C/C++ code to be run in the browser as safely as JavaScript called Native Client (abbreviated NaCl).
The NaCl environment also provides access to tech already available in the browser, including access to the HTML 5 file system, WebGL using an OpenGL ES 2.0 interface, as well as other browser specific APIs.
What NaCl means for you is that you can finally take your C / C++ game engine, libraries and middleware and ship it directly to users on the web, without the user having to install a plugin. NaCl runs on anything Chrome runs on (note: mobile Chrome is excluded for the moment).
Fire Hose's title, Go Home Dinosaurs, is in beta on Chrome Web Store right now, and is using multiple open source libraries that already have NaCl support, including OGRE and Mono. We were even able to bring over our own custom sound engine from our previous game without too many code changes.
Native Client is now automatically available as part of Chrome, and enabled for any games that ship on Chrome Web Store. Most importantly, for the first time your web delivered game can utilize the full power of the CPU and GPU, as if it were running as a native application, on Mac, Windows and Linux.
This doesn't mean porting to Native Client is a completely painless process. I've got a few massive "gotchas" and some tips to get you started.
Getting Started
Getting started with Native Client is fairly easy, and starts at Google's home for everything NaCl, www.gonacl.com. Here you can download the SDK updater, and from there, the SDK of your choice.
SDKs for NaCl roughly line up with versions of Chrome. If you type naclsdk list, you'll see a list of the SDK versions available and roughly where they are in development. SDKs that are listed as beta or development usually line up with beta and development channels of Chrome, while versions listed as stable are part of stable, released versions of Chrome.
Unless you plan on releasing very soon, I almost always recommend going with the most recent version of the SDK.
From there, you'll want to compile and run the samples, following Google's getting started tutorial. The samples use MAKE almost across the board, but for the most part so long as you can replace the compiler with the SDK's provided compilers, you can use the tool of your choice (FH uses a python based build system called waf, for example, and a Visual Studio 2010 plugin will ship with the Pepper 22 SDK.)
Assuming you're still with me, you've probably gotten things up and running, and I don't want to bore you with what Google has already provided. Instead, I'll give you some tips for working with NaCl that we've put into practice and have worked fairly well.
NaCl and Pepper
First, a quick note about terminology:
Native Client (NaCl) is a piece of technology that runs native code securely in the browser, though it possible (with the help of some tools provided in the SDK) to run NaCl binaries outside the browser, using utilities provided in the SDK. However, these stand-alone loaders don't provide methods for doing things like rendering to the screen.
This is where Pepper comes in. Pepper is a plugin API for Chrome, and it gives NaCl applications access to the lower level resource provided by the browser, such as file I/O and rendering. Specifically, NaCl applications must target the Pepper API (PPAPI) in order to interact with the underlying system.
You can learn about Native Client runs and how it interacts with the browser by looking at Google's official documentation.
Three Major "Gotchas"
There are three major things you need to be aware of when working with Native Client.
First, NaCl currently has a restriction that any system calls must occur on the "main" or "Pepper" thread. This is a temporary restriction, and should be resolved in a future SDK release, but for now it's something you have to keep in mind. System calls include all OpenGL calls, all file operation calls, all calls to perform browser operations and all calls to fetch URLs. In addition, these system calls cannot block the Pepper thread. The Pepper API does a fairly good job of not letting these calls block, but it does mean you have to write some functions to receive callbacks when things like File I/O are complete.
To prevent blocking the main thread, it's common for applications to spawn a new thread for performing logic, making that the "main" thread and pushing all system calls to the main thread in such a way that they appear to be blocking calls. There's a listing for how to do this later in the article.
Secondly, file I/O is a bit different than you might expect. All file I/O goes through Pepper's URL api, but depending on whether you're considering doing a hosted application versus a packaged application, this can affect how and when you want to load content. In hosted applications all of your file I/O is going to go through web requests, and will be dependent on Chrome's default caching systems unless you tell it otherwise. This means you have to spend a good amount of time making sure your assets are (at least) well optimized and hopefully packaged for easy deployment both to your host and to your user.
We ended up splitting our assets into several zip files which were downloaded in priority order, so the initial levels would load quickly, without waiting for many more assets.
Lastly, Native Client uses OpenGL ES2 for rendering. This isn't a big deal for those people that already have a GLES2 renderer for their mobile games, but if your engine is heavily based on DirectX, you might have a problem. One good resource is the OpenGL ES 2.0 Programming Guide, also known as the GLES2 Blue Book.
Tip: If You're Porting, Start with Angle
Most games written specifically for PC don't support OpenGL ES2 rendering, so your first step is most likely going to be porting your renderer. The problem is, you don't want to do this completely blind, wondering if rendering errors are due weirdness with Chrome (which can happen) or weirdness with your code. More importantly, when things do go wrong, you want the ability to diagnose the issue.
Enter the Angle Project. It turns out that OpenGL drivers for Win32 are spotty at best, while DirectX drivers are actually quite good. With that in mind, for Windows builds of Chrome, Google created an OpenGL ES2 to DirectX 9 translator called Angle. It's actually not that difficult to build on your own, and by using it you can use your windows build to debug any rendering issues you may have.
This includes being able to use PIX to debug individual render calls and state changes, as well as (some) shader debugging. The initialization code will be slightly different, but the rest of the rendering, loading, and destruction code (provided you're doing all of this on the same thread) will all be the same.
At Fire Hose, we solved many rendering issues by building our own release version of Angle and utilizing PIX to locate incorrect render calls, bad state changes, and incorrectly generated shaders. The result was a much smoother transition into GLES2 rendering than we could have ever hoped for going into Chrome directly (or using OpenGL directly).
Tip: Have a "Trusted Plugin" Version of Your Build
Native Client Developer Advocate Colt "Main Roach" McAnlis has a series of blog posts detailing how you can get your application to work as a Trusted Plugin in Chrome. The most important side effect of this is that it allows you to debug your port with Visual Studio more easily than if you were going straight to Native Client.
One of the major problems we had at Fire Hose bringing our game to Native Client was trying to deal with debugging the running application, and porting the game to a Trusted Plugin architecture first would have alleviated that pain some.
Tip: Tricks for Easier Testing
There are a few tips I have to make testing easier for you and your team.
First, check in a version of Chrome that you'll be using for testing purposes. My recommendation is that you find a nice stable continuous build that utilizes the API you're targeting, and check it in. Chrome has a great "auto update" feature that keeps all users up to date. But while this is great for most users -- and for your deployed application -- it can cause problems for developers, since the underlying platform in changing.
By checking in a version of Chrome, you are able make sure that all developers and testers are on the same version of Chrome, and will be reporting bugs that can be consistently reproduced. The other two side benefits to this are that the continuous build of Chrome can run side-by-side with other versions of Chrome, which means your developers' browsing habits won't be interrupted.
Second, turn off caching in your browser. Chrome is very aggressive about caching downloaded data, which is great for users, but can be a pain when you're trying to make iterative changes to your program. It's almost impossible to tell if you're running the most recently modified version of the executable and resources, or cached versions. Turning off caching ensures that you will always get a fresh copy. On the plus side, if you're using a checked in version of chrome, you can adjust this setting for just the checked in version, and your day-to-day browsing will remain unaffected.
Third, even if you're checking in Chrome, I recommend using Incognito or a secondary profile when launching your app. In our case, we use a secondary profile (though many samples / examples will use Incognito). This means that you can specify that your profile data (including settings and cache) are kept in a specific location (in our case, in the source tree) and can be cleared / inspected as needed. Although this isn't strictly necessary with nightlies with Chrome (since nightlies hold their profiles in a separate directory from regular Chrome anyway) we found keeping the profile in the source tree, separate from other user settings, invaluable when attempting to debug things like file system access and caching issues.
Fourth: Google has added all sorts of environment variables to make Native Client easier to debug, but they're all fairly hidden. The NaCl team is working hard on integrating better debugging, but right now you will frequently find yourself falling back on printf debugging. To make this work, you'll need to redirect stdout, stderr, and nacl_out. If you're on Linux, this is fairly easy (it works pretty much exactly how you'd expect) but on Windows it requires some Environment variable goodness. The three that are important right now are:
NACLVERBOSITY: A value of 1-5 determining how verbose NaCL is when outputting information about what it's doing. Use 5 at your own risk.
NACL_ENABLE_PPAPI_DEV: Certain interfaces for NaCL are marked as unstable, or dev, and NaCl won't let you use them unless you mark this environment variable. I just leave this on, even though most of the APIs we're using are now out of dev.
NACLLOG: This is the path to a text file to output NaCL log information. This is directly related to the NACLVERBOSITY environment variable.
NACL_EXE_STDOUT: This is path to a text file for logging standard out.
NACL_EXE_STDERR: The same, but for logging standard error.
Some of these (NACLLOG, NACL_EXE_STDOUT, NACL_EXE_STDERR) require that you also run Chrome with the flag "-no-sandbox" so that Chrome is allowed to write to those paths that you specify.
In addition, the NaCl SDK recently added a sample that shows how to capture and decode a callstack. This also requires its own environment variable, and you should look at the "Debugging" sample in the SDK for more info. Note that the Pepper 20 SDK improved the interface for this significantly, and you should start working there.
Finally, and you've probably guessed this already, you will want to create scripts for starting Chrome and your app. There are a lot of things you have to set up for working with Chrome in NaCl, and the easiest way to do this is with batch files or scripts, rather than telling every developer to set all the proper environment variables and how / where to start httpd, etc, providing a script gets everyone up and running quickly. The things a good script should do:
Start httpd in the proper location.
Set up all environment variables
Run the correct version of Chrome, pointing to the correct manifest (debug or release)
Pass all the correct arguments (I recommend '-enable-nacl', '-show-fps-counter', '-no-sandbox' and '-user-data-dir=<path>'
Tip: "Easier" Debugging
The NaCl team is working hard on debugging, including debugging using Visual Studio and WinGDB. However, VS debugging isn't quite there yet. Until the NaCl team gets full debugging capabilities, working in NaCl frequently requires some amount of printf debugging, which can be a pain, but there are a few things that you can do to help yourself.
First, and most commonly, you can open a separate "console" window in JavaScript as soon as your app starts, and post messages to that. Some sample code for doing this is provided in Listing 1 (appended to the end of this article) from Roger Hanna and Ryan D. Williams of Glug Glug. You can also use the same approach to post to the JavaScript console, using console.log.
Second, by using the NACL_EXE_STDOUT you can output easily to a file on your hard drive, and take a look any time something goes wrong. However, to really make use of this, you'll need to output a lot of information, which can be a drain on your processor time and your disk.
My recommendation is to take a page out of most enterprise app developer's standard toolkit and utilize a logging system with logging levels, most commonly Trace, Log, Warn, and Error, and just turn off Trace and Log in most situations, turning them on when you're trying to track down a particularly heinous bug.
Tip: Be Very Aware of Your System Calls
As I said in the intro, one thing you have to be careful about when developing for NaCL is that any calls to the Pepper API, which includes any calls to OpenGL, must happen on the pepper thread. If you're running your game on that same thread and expect things like File I/O to block until they're complete, you're going to have some problems.
There are three main approaches to this. Which you use is up to you, up to how much you want your NaCl build to be your "main" build, and how long you're willing to make your users wait for a download.
Frontload all asset loading. This is used in the Ogre sample. Instead of grabbing assets on demand from the web server, Ogre downloads all of the assets as a zip, than loads the whole zip into memory. This prevents any further File I/O operations, and means the whole game can run on the Pepper thread. This is not particularly memory efficient or speedy for your uses, but may be a viable option for smaller games, especially if they can't or don't want to deal with multithreaded loading.
Threaded calls. Generally, larger games will usually have a loading thread, one that takes care of all the File I/O. You can still do this in NaCl, but you have to have your loading thread call into the main thread and then wait for downloading / loading. This is not as hard as it sounds since the Pepper API provides a method CallOnMainThread.
One thing that may help you is this template class I've written to perform a main thread call, and then wait on its completion. (See Listing 2, appended at the end of this article). Note that Semaphore is a wrapper around various platform specific implementations of Semaphore (NaCl uses POSIX-style threading primitives). I have not included the code for doing this, though it should be fairly simple. This template can be recreated for any number of parameters, or generated for any number of parameters, though we use it so sparingly that we've only needed a 0, 1, and 2 parameter version.
Separate your logic and rendering threads. Again, most of us do this already, but by separating out your logic and render threads it allows you to more easily combat the fact that you need to perform all render calls on the Pepper thread. As discussed before, we put all of our logic on a spawned thread which became our "main" thread, and left rendering on the "Pepper thread." This makes making blocking calls much easier, and it means you can start your logic in the middle of the wait on v-sync and wait on other main thread calls, even if your logic isn't completely thread safe.
Tip: Use the File System API for Permanent Caching
One key element of making sure your users have the best experience possible is that, even if you're running a hosted app, they should only have to sit through a long download of the game once. To accomplish this, after you download a file, you use NaCl's file system API to write that file to a sandboxed area of the user's file system. In the future, before grabbing the file from the web, you instead use file system API.
This is the same file system as is used for HTML5 file system access, which you can use to your advantage, because Chrome extensions made for the HTML5 file system (such as the HTML5 File System Explorer) work with files stored through the NaCl file system API as well.
Note that when your game is shipping on the Chrome Web Store, your manifest will request the space on the user's machine for this permanent caching. However, in development you will have to ask for permission in JavaScript. This is as simple as a call to:
window.webkitStorageInfo.requestQuota(PERSISTENT, size);
Unfortunately this isn't really documented anywhere in the Native Client documentation.
Tip: Learn from Web Developers
One of the most exciting things about Native Client (other than the whole native code in a browser thing) is that you can use it to utilize really interesting web content if you so choose. If you're into doing this, make sure you follow some best practices from the web (or from MMOs, which usually follow the same or similar practices).
First, if developers are going to consistently be working with the web calls, make sure your developers are able to set up a full web stack (LAMP/WAMP if needed, or just IIS if not) that the code can utilize fairly easily. Otherwise there's going to be lots of swearing coming from the programming pit throughout the project. There are a few ways to do this, including using Mongoose or even using Node.js; just make sure the programmers can run the full stack when they need to. It will also assist in local testing as well.
Secondly, most web developers, even small ones, keep at least three environments: test, staging, and production, and you should too. Test is a bare bones environment and is updated frequently, and is simply there to assist with testing builds (continuous builds if you have them) in an easily resettable environment.
Staging, on the other hand, is updated infrequently, and is used to test builds that are about to go into production. Staging serves as a test bed to make sure that what you're about to ship to your clients works, so that you don't get any nasty surprises when the game ships.
Lastly, you're going to want to automate the process of moving builds to each environment. We all recognize the importance of automated builds, but sometimes we forget that automated deployment is just as important, and for the same reasons.
If you're working with multiple environments, you can even use your staging environment to test the whole process. At Fire Hose, we used Jenkins in combination with the pipeline plugin to automate our build deployments to test servers all the way up to production.
Tip: Leave Time for the "Web Stuff"
If you're a development house that hasn't done too much web stuff, leave some time to do all of the "web stuff." Fire Hose didn't really have any web "experts," and exactly zero JavaScript programmers, and therefore some of the development and testing of things like online authentication, HTML dialogs, checking capabilities, etc, got pushed to the end of development. In reality, we probably should have frontloaded a lot of that, which would have given us more time to really make it do more amazing things.
One of the great things about Native Client is that, because it's web-delivered, you can (and should!) use the capabilities of the browser to your advantage, and you get to use some awesome "webby" technology for things like microtransactions, social media hookups, cloud storage and other internet-based buzz words. The negative slide is that you have to use these things in a lot of cases, so you have to leave time to work with them (and deal with some of their quirks).
Conclusion
While it may sound like Native Client can be a hassle, Google is improving the platform and the development experience every day, to the point where this article had to be edited several times to keep up with its development.
In addition, Native Client gives developers the ability to provide games that take full advantage of modern hardware with a very low-barrier of entry, and in an almost completely platform agnostic way. The integration with the browser also means that developers can take advantage of web APIs and social APIs, such as Google+ and Facebook, in their native environment.
I think it will be interesting to see not only where Google goes with technology over the next few months, and what games will do with the new options Native Client provides.
If you want to see Native Client in action, you can check out Go Home Dinosaurs on the Chrome Web Store, and to get started, visit www.gonacl.com.
Listing 1: HTML Popup Log
index.html
var popuplog = null;
var popupwindow = null;
function log(msg) {
// find elements (for initialization)
if(popuplog === null && popupwindow !== null)
{
popuplog = popupwindow. document.getElementById('log');
if(popuplog.length == 0) {
popuplog = null;
}
}
// if we have a popup window, append the logged message to it
if(popuplog !== null) {
popuplog.innerHTML += "<li>" + msg + "</li>";
}
}
function bodyLoad() {
naclModule = document.getElementById('nacl-module');
naclModule.addEventListener('message', naclMessage, false);
// try to open it up offset from the main window (doesn't actually work that great)
popupwindow = window.open("popup.html", "Debug Log", "left=" + (window.innerWidth+window.screenLeft) + ",top=" + window.screenTop + ",width=1024,height=" + window.innerHeight);
// close the popup window when the game closes
window.onbeforeunload = function() {
popupwindow.close();
};
}
function naclMessage(opt_message) {
// This logs directly to the javascript log
console.log(opt_message);
// This logs to the popup window
if(popuplog === null && popupwindow !== null)
{
popuplog = popupwindow.document.getElementById('log');
if(popuplog.length == 0) {
popuplog = null;
}
}
if(popuplog !== null)
{
popuplog.innerHTML += "<li>" + msg + "</li>";
}
}
popup.html
var lostFocus = true;
// if one window gets clicked on, we ideally want both to come to the front.
// this does half of that.
function doubleFocus()
{
if (lostFocus)
{
window.opener.focus();
window.focus();
setTimeout("doubleFocus2()", 10);
}
}
function doubleFocus2()
{
lostFocus = false;
}
function doubleFocusBlur()
{
lostFocus = true;
}
<body onblur="doubleFocusBlur()" onmouseup="doubleFocus()">
Listing 2: Helper Class for calling a function on the Pepper Thread, wait for return
template <class This, class Param1>
class SyncPPAPICall1
{
public:
typedef void (This::*CallFunction)(Param1);
#ifdef NACL
struct CallStruct
{
CallStruct(This* aThis, CallFunction aFunc, Param1 aParam1) :
_This(aThis), _Func(aFunc), _Param1(aParam1), _Waiter(0, 1)
{
}
This* _This;
CallFunction _Func;
Param1 _Param1;
Semaphore _Waiter;
};
#endif
void Call(This* aThis, CallFunction aFunc, Param1 aParam1)
{
#ifdef NACL
CallStruct* params = new CallStruct(aThis, aFunc, aParam1);
pp::CompletionCallback callback(TheFunc, params);
pp::Module::Get()->core()->CallOnMainThread(0, callback);
params->_Waiter.Wait();
delete params;
#else
(aThis->*aFunc)(aParam1);
#endif
}
#ifdef NACL
private:
static void TheFunc(void* aParams, int32_t)
{
CallStruct* params = (CallStruct*)aParams;
(params->_This->*(params->_Func))(params->_Param1);
params->_Waiter.Signal();
}
#endif
};
Read more about:
FeaturesAbout the Author
You May Also Like