Trending
Opinion: How will Project 2025 impact game developers?
The Heritage Foundation's manifesto for the possible next administration could do great harm to many, including large portions of the game development community.
In this reprinted <a href="http://altdevblogaday.com/">#altdevblogaday</a> in-depth piece, Google's game developer advocate and Blizzard veteran Colt McAnlis explains texture atlases and the benefits of using them for your HTML5 game.
In this reprinted #altdevblogaday in-depth piece, Google's game developer advocate and Blizzard veteran Colt McAnlis explains texture atlases and the benefits of using them for your HTML5 game. HTML5 is an amazing technology for designing web sites. The general flexibility of HTML5 markup and JavaScript often leads web developers to create their content using individual image elements. This approach works well for small sites with low overhead, but for games or other high-load websites, using droves of single image elements leads to long load times and slow performance, resulting in a poor end-user experience. In an ecosystem where three seconds may cause you to lose half your users, it's important to use the proper tool to address this issue: texture atlasing.
In real-time graphics, a texture atlas is a single large image that contains many smaller sub-images, each of which is referenced independently. Texture atlases sprang up with the advent of 3D games, and have been around as long as we've had such games (for instance, here's one of the original lightmaps used in Quake 2). Atlasing was originally used to accelerate the performance of 3D games, where there is significant overhead when swapping out or referencing new textures during the rasterization stages of a 3D pipeline. By combining all the smaller textures into one larger one, the graphics pipeline incurs less overhead from swapping, resulting in better performance. A texture atlas from the HTML5 game GRITS. This single texture holds all the smaller images from the game that contain alpha values. Loading these images in a single texture reduces load time and improves runtime performance. A side note on terminology: Many of you fancy HTML5 folks out there might say that a texture atlas is the same thing as a sprite sheet, but I don't believe that's correct. A sprite sheet is an atlas that contains only sprites. In contrast, an atlas can contain many charts; each chart is a large image that holds a single type of graphic resource – e.g., sprites (for animation), UI textures (which are not animated), glyphs (for font-processing), and so on. An atlas is a technical concept, while a sprite sheet has a specific functional notion.
Using a texture atlas in your HTML5 game can help your game load faster and run faster, and also reduces your bandwidth cost.
As I explained in my Google I/O talk, a 4k x 4k texture fetched from a server can take around 241ms to download, which is pretty fast. If you were to chop up that single download into 4096 separate requests, at 64×64 pixels each, the total load time changes drastically for the same number of pixels: It would increase from 241 ms to 4.3 seconds, an increase by a factor of 17x. The timing charts below illustrate this difference graphically: Fetching a 4096×4096 texture from a server requires a single HTTP request, and the request resolves quickly. Fetching assets individually with multiple HTTP requests takes a long time. The long, lighter-shaded left-hand portion of the duration bars represent time where the browser blocked the connection because too many requests were being issued. It's important to understand that there exists an upper limit on the number of requests that a browser can make to a single server. The browser itself sets this upper limit, and when the limit is reached, the browser blocks subsequent requests until an open connection becomes available. This is the primary reason for the performance difference we see with individual assets versus atlased assets. If you have 4,000 pending HTTP requests, and only six connections are available, all the requests get stacked. In the figure above, the lighter-shaded portion of each duration bar is where Chrome blocked, waiting for an active connection to come along. In addition to reducing overall load time, atlasing also helps reduce the number of HTTP requests from your app. This is a hyper-critical issue that the developers of HTML5 version of Command & Conquer found out the hard way: During development their app went viral, and their hosting service suspended their account until their request load dropped.
Using individual texture assets can also have a very large impact on your game's runtime performance. Web browsers have two main components: a JavaScript VM and a DOM/Layout engine (usually WebKit). To optimize reload times, WebKit keeps an in-memory cache of resources that are loaded from the network, so that the resources do not have to be re-downloaded (or reloaded from the disk cache) when they are requested in the future. For example, when a DOM element such as an image is deleted, the garbage collection process in WebKit can optionally keep the element in memory even though the JavaScript engine no longer needs it. To use the cache properly, WebKit can detect when the system is resource-constrained (i.e., when the amount of available RAM is low), and instantiate a cache-eviction process to remove unneeded resources in an effort to improve performance. For applications with a small number of DOM elements, the cache-eviction process is generally not an issue, but it can become a problem as the number of cacheable objects increases and the garbage collection algorithm spends more time looking for dead objects to reclaim. For 2D, image-based games this can be a considerable problem, e.g., when 2,000+ animated sprites and background textures are loaded and referenced individually in a browser. For these apps, images are generally the biggest offenders, and atlasing can really help: By combining the individual images into larger atlases, the number of unique cacheable resources decreases, allowing WebKit to spend less time in its cache-eviction process in resource-constrained environments. (To be clear, the WebKit in-memory caching behavior described here has nothing to do with the JavaScript garbage collection algorithm and the problems you can get from incorrectly managing JavaScript objects.)
In Chrome, the 2D canvas element on a page has access to hardware acceleration if it's available. That means all of your draws, images, and transforms are handled by the GPU, which significantly improves performance. The catch, however, is that there are a few abstraction layers between your API calls and what the hardware is doing. It's been known for quite some time that reducing state changes in your graphics application yields higher performance (see for example Chapter 3 in this old NVIDIA GPU Programming Guide (pdf)). Working with a hardware-accelerated canvas is no different. Each texture must be bound to the GPU before the primitive quad can be drawn, and with a limited number of texture slots for a given GPU, there's quite a bit of time spent swapping textures in and out of the proper sampling units. Atlasing reduces this overhead by allowing the GPU to bind a single texture (or a smaller number of textures) to the graphics driver, eliminating the extra overhead of the swap.
With the exception of dumping the pixel data directly (ie, a .RAW file) most file formats used for game development come with additional header data that needs to be transferred with the file. This data is there, regardless of the dimensions of the file, or type of data inside it. For instance, a DDS file, typically used for DirectX implementations of compressed textures, has a general overhead of 128 bytes for the header, which may not seem huge when compared to the data within it; However consider that 2000 loose texture files (not uncommon for a AAA game) would incur an extra overhead of 250k that needs to be transferred across the wire for each user loading your game. Packing these textures into a single atlas removes the redundant overhead of these header bytes, reducing the overall size of your program.
Creating a texture atlas is a tricky engineering task. Texture packing is a type of bin packing problem, which has been proven to be NP-hard. The problem is so challenging that I frequently use a variant of this algorithm as an interview question to evaluate the skills of potential hires. TexturePacker is a great off-the-shelf tool that fits into most content pipelines quickly; it will generate the atlas data given a list of textures, alongside a data file that maps the individual source images to their final locations in the atlas. If you're going to roll your own texture packer, I suggest you begin with some research. Here are some great places to start:
Blackpawn.com posted one of the first open bin-packing algorithm I remember seeing; there's now a great JavaScript example that runs in real time, so you can see the algorithm in motion
If you're looking to do highly complex packing (e.g., font glyph packing), most font rasterization libraries come with very aggressive atlas packers. Freetype-gl has one of the best ones I've seen; it uses the Rectangle Bin Pack algorithm. Once your data has been packed into charts, you'll also need to output a mapping file that lists where each leaf-image (individual image) is in the atlas.
Use of atlases in HTML5 games varies by game type:
A pure DOM game can use a CSS sprite sheet, and set CSS properties on a DOM image after loading the atlas data file.
A canvas game can use the canvas.drawImage API, specifying a subsection of an image to be drawn to the canvas location.
A WebGL game can modify the UV-Coordinates of a vertex to reference a subsection of a larger image using normalized floating point values.
Web browsers are amazing programs: They abstract out the underlying complexities of loading and rendering web pages, hiding the under-the-hood machinery from the average web developer. For high-performance web games, however, this is less than ideal. The layers of abstraction make it generally unclear how to organize data for maximum performance, and can lead to increased overhead. As with most development platforms, the key to high performance is a thoughtful understanding of what's really going on, and knowing what tools are available to aid in developing real-time applications. Stepping back a bit, it's clear that HTML5 games have great promise. The ability to iterate quickly and deploy content across the Internet is a powerful incentive for developers. But it's important to approach HTML5 game development with the proper perspective. Regardless of the unicorn dust of the web, working in HTML5 has many of the same pitfalls as console, mobile, and desktop development. Game developers need to continually explore and find great solutions for difficult problems. For a take on HTML5 game development from the point of view of a traditional game developer, take a look at the following videos:
[This piece was reprinted from #AltDevBlogADay, a shared blog initiative started by @mike_acton devoted to giving game developers of all disciplines a place to motivate each other to write regularly about their personal game development passions.]
You May Also Like