As everyone knows, the life of a mobile gaming developer is not easy. He must find his way on a very narrow path. On the one hand, the requirements of game designers, confidently rushing to infinity. More functionality, more beautiful graphics, more effects, more animations, more sounds. On the other hand, limited resources of the mobile device. And first of all, as a rule, RAM ends.
For example, iPad 2 - just 512 MB of RAM in it. However, only about 275 MB are available to the application. When the memory occupied by the application approaches this boundary, the operating system will send the so-called “Memory warning” - gently but aggressively offers to free the memory. And if the limit is still exceeded, the operating system will stop the application. The user will think that your game has fallen and will run to write an angry letter to the support.

')
The main consumer of memory is, of course, graphics. In this article, we will try to talk about a slightly complicated but effective method that is used to reduce the memory occupied by textures, as well as to increase the rendering speed.
Textures and Atlases
As everyone again knows, drawing textures directly from source files is a bad idea. Instead, they are packaged in textural atlases.
Texture Atlas is a large image obtained by gluing small textures. This saves memory and, more importantly, decreases the number of batch files when drawing. There are two videos that vividly demonstrate why the atlas is good, and the individual textures are bad:
one and
two times . Thus, the task of packing a texture atlas usually comes down to the following: take rectangles of different sizes (original textures) and place them as closely as possible into one large rectangle, or, more often, a square. We are not at the Olympics and we don’t need the perfect packaging, and it’s not hard to write an algorithm that packs textures tightly within a reasonable time. We have been using such an algorithm for a long time, which generates texture atlases for us. Some such:

As it is easy to notice, there is a lot of free space in it and I really want to pack textures more tightly. The reason for inefficient packaging is that many textures contain fairly large transparent areas. Therefore, once we decided to try to abandon the usual rectangular packaging and do what eventually became known as the "polygonal atlas". For this you need to solve 2 problems:
1. For each original texture, it is necessary to find such a set of triangles so that it contains all its opaque pixels. Then, if we draw all these triangles, the original texture will appear on the screen. At the same time there is an important limitation - there should not be too many triangles, otherwise drawing will be very slow. Thus, a compromise is needed between the number of triangles and the area they occupy. Therefore, at first, an algorithm was written with a variety of parameters for fine tuning, and only then, conducting experiments on real textures, we selected these parameters.
2. The resulting figures of complex shape (the same polygons) should be packed as closely as possible into the atlas. Here, one of the variants of the
bee swarm algorithm was used, which, conducting numerous attempts using a random number generator, tried to find a sufficiently dense packaging variant. It took a different kind of compromise - between the quality of the packaging and the time spent on packaging.
Having traveled a rather long way and attempted to pack a test atlas about 100,000 times, we finally achieved the result:

As you can see, the packaging is really much denser. Depending on the texture, the gain can be up to 1/4 of their area. You can also create a debug version of the atlas, which shows the breakdown of textures into triangles.

In addition to the atlas itself, its description is generated, in which the names of the source textures and the coordinates of the resulting triangles are indicated.
When using polygonal atlases, the drawing process, on the one hand, accelerates, on the other hand, it slows down. It is accelerated because the total drawn area has become smaller, and, therefore, the overdraw value has also become less. And it slows down because if before any texture was drawn with just two triangles, now, as can be seen in the example, they have become much more. In general, with reasonable settings of the atlas packer, we have not had a big change in the speed of the rendering system.
There is one more problem that we encountered when switching to new atlases. Often it is required to draw not a whole texture, but only a part of it. For example, if you need to make a gradual appearance of the object on the screen or some progress indicator. When using conventional atlases, the problem is easily solved by correcting the uv-coordinates. In the case of polygonal atlases, everything becomes more complicated. Let's look at an example. The blue part of the image highlights the part of the texture to be drawn:
Would need:
- Exclude from drawing triangles that do not fall into the drawing part of the texture
- Find the intersection of the new texture border with the original triangles. The result may no longer be a triangle, and it may be necessary to split it into 2 new triangles.
The solution of such a problem will provide an opportunity to practice well in the school course of geometry. In addition, this algorithm may not work very quickly.
There are more complex cases. For example, circular progress or some kind of distortion of the texture. Therefore, it turned out that it is easier to pack some textures into ordinary atlases or not to pack them at all, so as not to complicate the rendering process.
Some technical details. The packing time of polygonal atlas is highly dependent on the quality setting of the packaging. Since at any change in the texture set, atlases must be re-compiled, this has to be done quite often. Therefore, we usually have the “minimum density, maximum speed” mode enabled. In this mode, 8 atlases of size 2048x2048 are packaged for about 5 minutes. In general terms, the packaging process looks like this:
- textures are divided into triangles;
- textures are sorted by decrease in height;
- pre-pack textures. All textures are selected in order and each one is searched for free space in the atlas;
- several attempts are made to repack textures more densely. The number of attempts depends on the quality settings;
- if the original packaging was improved, then additional textures are added to the atlas.
As a rule, pre-packaging is already quite dense. When trying to improve the quality, the packing time grows very strongly - up to several hours per 1 atlas - and the gain can be 2-3 additional packed textures.
In the games we use our own engine. To go to the polygonal atlases had to modify it a bit. Fortunately, we already had an abstract drawable entity - the Drawable class:
class Drawable { virtual int Width() const; virtual int Height() const; virtual bool HitTest(int x, int y) const; virtual void Draw(SpriteBatch* batch, const FPoint& position); }
It already had functions for getting the size, checking the pixel opacity for handling clicks and rendering. It only took to create another PolygonalSprite class that would correctly implement these functions, taking into account the splitting of the texture into triangles.
Also, so that the client code could get a list of texture triangles and perform some transformations with them, a function was added to the interface.
virtual void GetGeometry(std::vector<QuadVert>& geometry) const;
For regular textures and ordinary atlases, this function returns four vertices that form a rectangle corresponding to the original texture. And for textures from polygonal atlases - an array of vertices of triangles into which the texture is broken.
Now polygonal atlases are used in many of our projects and we can assume that they have passed the test of time. For example, in our next free-to-play project, Gardenscapes, the world release of which will take place
very soon , the main part of the gameplay takes place in the player’s garden.
For him, just drawn a huge variety of textures, some of them are used in this article as examples. Now all these textures fit in 8 polygonal atlases 2048x2048. But if we packed atlases in the usual way, they would have already been 11. Thus, we get savings in RAM, depending on the graphic format used, from 6 to 48 MB. And game designers can offer a quarter more pretty textures more!
About the author: Sergey Shestakov, technical director of the company Playrix.