Canvas fill rate

Graphic card benchmarks often have at least one test measuring the fill rate, e.g. how fast multiple animated textures can fill the available screen area. These textures are often on separate layers, so that transparency can be used to observe the effects of their complex interactions. A faster fill rate means that objects will appear faster and more smoothly on the screen. This is related to the texture quality itself, because a high-resolution texture may require much more processing time.

The graphics card is the device that handles the stunning visuals we are able to see (not only in games, but also in websites). But to fully utilize its processing power, often special software needs to be written—one that is closer to the hardware. This software increases the throughput and allows communication to happen much more frequently, enabling faster frame rates.

A website that is too media-heavy can easily exhaust the fill rate that the graphics card can provide. In this case, the browser is effectively locked until the computation is finished. This means that the rush to provide more interactive and engaging content can easily end in a site that is hard to navigate/scroll, that reacts too slowly on actions (waiting three seconds until a video starts playing isn’t ok; showing the hover effect on the button we came from isn’t ok), and whose performance is highly variable, depending on what is currently visible. A full-screen background animation that changes photos every 5 seconds looks beautiful, but it may take a lot of resources. This could still be fine, if you are fully aware of it and compensate by making the rest of the content completely static. But if you try to have too much dynamic content, like parallax effects with changing opacity, CSS transitions of some playing figures and a video explaining the story behind this, your users will have a hard time. Not only that, but if your site crashes the browser, this means that whatever they had open in other tabs may be completely lost. I had a couple of cases when, to my surprise, the tabs weren’t restored after the browser was restarted. Another thing I saw recently on a website was a video that in its default state was almost as big as full screen. When a person started talking, it was impossible to stop the video.

Again, much of the smoothness of the site seems to depend on the fill rate of the graphics card and not only on the CPU speed itself (although a faster CPU can make both the GPU and RAM more responsive). Many people tend to think that bigger content (fonts, images, video containers) is better, but it utilizes an increasing proportion of the GP’s fill rate. It is not a coincidence that resizing images is slow and that we are talking about <picture> elements and srcset attributes to enable the most accurate (static!) image to be chosen by the browser. Media queries can also “attack” the fill rate once many things on the screen start to rearrange, which is why if we use them, we need to ensure that only the smallest needed change happens at any given time.

When we talk about fill rates, we should examine <canvas> too, without expecting much in advance. Normal animations and games are smooth when they can speak C++ with the hardware. With <canvas> this isn’t the case (although there are attempts to make it GPU accelerated). It is available only in a browser, which is on top of the OS, so any resources that these two need when a bottleneck is reached are likely to affect the canvas performance.

First, we will draw some simple shapes to get an intutive feeling of what canvas can do. We could create math-heavy animations, but then the fill rate may suffer. Smooth animation means at least 30fps—the more the better. Simple shapes have the advantage that they are created from a lower number of function calls, and when we execute these functions many times per second, we may assume that the combined execution time will stay small. The more complex a shape (e.g. a polygon path), the more function calls will be needed (like moveTo() and lineTo() for each side of the polygon after we have computed the point coordinates). Here is the result from using requestAnimationFrame with <canvas>.

Browsers (and various devices) will behave differently while attempting to draw so many lines and shapes on the screen. It is said that mobile devices can perform poorly with canvas. The latest version of Firefox does quite well on desktop, while Chrome doesn’t seem to be as fast. Here are the captured frames:

Chrome timeline frames for a canvas example where lines and shapes are drawn

Looking at the green area, we can say that most of the time we were having slightly over 30fps, but there are also many cases where the performance is significantly worse. (The lower the bar is, the higher the fps. Here we see that the portion of the bar above the line is in many cases higher than the one below it.) As a reminder, green means painting time, yellow means script execution time and magenta, if available, means rendering/layout recalculation time. We may think that just because green has a higher contrast than the white part of the bar, it is more important. Not so. The last refers to “other things”, which can take a significant amount of time. It sets the upper bound, identifying how long this frame took. Clearly, this means that the performance was no way near 30fps as we have thought initially.

Drawing lines and shapes is nice, but it doesn’t say anything about real-world scenarios, where a lot of interaction with the user might be needed. This requires additional code, which increases complexity and in turn can make the code even slower. Still, it is through the interactions that the canvas becomes more useful. But they would be impossible on a graphics card which has no fill rate left.

We may think that drawing a line would be faster than drawing multiple points. After all, a line will color all points at once; moving through individual pixels and changing their color would be more tedious. Does this make a line more important than a pixel? If we wanted a greater precision while drawing, an individual point could be more flexible. We could even combine points to form objects; point transformations could be used to animate 3D objects. Graphic cards have pixel shaders to calculate effects for every pixel. This means that in estimating fill rate, we can’t escape from the reality of how well we will communicate with the smallest unit on the screen. In reverse: if we can make point fill rates faster, we could possibly construct lines faster. Here is another attempt to check how well canvas can draw with pixels. Black and white pixels are alternated, and we repeatedly draw choosing either of these colors and applying it on top of the other. Clearing the canvas isn’t necessary here, as we want to draw as many points as we can, while previous ones are preserved. We aren’t drawing with pixels directly, but using the method to draw rectangles of size 1x1 pixels.

What we can see is that drawing pixel-by-pixel doesn’t seem to fill the screen as fast as lines and shapes did. But it still takes around 10sec to draw 640*480/4 = 76800 pixels. This number might seem high and we may be tempted to think that the number of objects drawn in this case is bigger than in the previous case, but what we have seen is that lines and pixels are drawn with the same method. In the previous case we have used a lot of randomization in combination with manually created shapes, which have somewhat decreased the number of objects painted. Hence, the difference depends more on the actual implementations rather than a feature.

The examples weren’t particularly useful, but I hope that they will encourage you to experiment and paint even more canvases.