HTML and CSS Reference
An Issue with ReadPixels
The problem with scaling your canvas as a solution to your rendering issues tends to go much deeper than just simple
upscaling, however, and extends all the way to how browsers draw the data that eventually end up on-screen to the
underlying canvas element. When you draw onto a canvas context, the browser is actually writing to an underlying
central processing unit- (CPU-) side memory region called a backing store . The backing store is used as a form of
double buffering, as you may be drawing to the canvas object while a monitor refresh event occurs. Such an event
would present itself in the form of visual glitches to the user. Instead, when the browser draws to the screen, the
browser reads the backing store data to paint the previously composed pixels rather than the ones you're drawing at
that nanosecond. The device or browser on which your code is running dictates how the backing store is created and
maintained, which can have a dramatic impact on memory, performance, and quality.
For instance, when a backing store is created for a canvas on a HiDPI screen (such as Macbook Pro with Retina
display), some browsers create the backing store at a scale different from a 1:1 pixel ratio; some browsers scale at 2,
whereas others scale at 1.3 or 2.4. It all depends on the device/browser and the arbitrary engineering decisions that
created those code paths. The critical point here is that, as you're drawing an image to the canvas, the browser needs
to upscale the image to the matching backing store resolution before copying those pixels to their screen destination.
Just as the devicePixelRatio property is used to map hardware pixels to CSS pixels, so, too, a property helps map
canvas pixels to backing store pixels: backingStoragePixelData . This property defines the resolution dimensions of
the backing store in relation to the canvas. To be clear: devicePixelRatio is the ratio of hardware pixels to CSS pixels,
whereas b ackingStoragePixelData is the ratio of backing store dimensions to canvas element dimensions.
Unfortunately, all browsers implement the sizing for the backing store independently of each other. This is fine
for most games, because they can safely rely on the hardware to upscale the images on their behalf. However, games
that use read-back functions, such getImageData() , can run into problems, as the data returned from that function
must be scaled down from the backing store size to the canvas size before being returned (for example, a 480×320
canvas on mobile Safari is a 960×640 canvas in the backing store). The result returns downscaled pixels, which often
results in visual anomalies. To address this issue, some browsers are adding specific functions to fetch the backing
store resolution data directly.
For instance, Safari implements webkitGetImageDataHD() , which will return an image buffer matching the
backing store size. To retrieve the correct image data, you have to draw the images to the canvas with dimensions
that are divided by the backingStoragePixelData . Once the backing store is created, it's scaled to the right size, thus
returning the correct image data. In contrast, Chrome just creates the backing storage at exactly the same size as the
canvas. So, what are the trade-offs? With Chrome, you still have to do all the heavy lifting for HiDPI screens if you don't
want image blurring. The benefit is that you aren't forced into using more resources, as creating a larger backing store
requires a lot more memory. The game's code can be deployed cross-platform and played on Chrome without having
to plan specifically for odd backing store creation.
Laying out the position of content on your screen is an exceptionally burdensome task. Not only is it prone to massive
revisions, between artists, designers, and programmers, but it's also time-consuming and repetitive in cases in which
you lack a higher-level development toolchain to handle the layout for you. To complicate things further, you need to
do this for each screen resolution.
The most basic developers will simply hand code their offsets into their source code, a technique that works for
rapid prototyping but that often falls short once you move beyond that. Savvy developers will embrace a set of files that
define the layout per resolution, but, looking at Figure 13-5 , you will quickly see that that gets out of control, usually
forcing developers to choose a subset of direct resolutions to support and letting the one-offs have visual problems.