Dynamic Texture Atlas Allocation

Take Vos
2019-05-30

For the TTauri GUI toolkit it is required to draw high quality user interface elements (widgets) like buttons onto an image surface that is displayed as a texture on a quad (two triangles) by the GPU. Each widget may have several of these images that it may choose to display. Multiple widgets may share these images between them to reduce memory usage and rendering time.

For performance reasons a texture atlas is often used by game engines. A texture atlas is a large texture that is subdivided into smaller textures. Multiple 3D objects can share the atlas between them, and be drawn in a single draw call.

The TTauri GUI toolkit uses an atlas for displaying multiple widgets. The challenge comes from when a widget needs to redraw it self. A redraw may involve a resize of surfaces, sharing or unsharing with other widgets. This requires deallocation and allocation of different size images, a process which may cause fragmentation. In the next few chapters I will explain the solution that is used in the TTauri GUI toolkit.

Atlas page-table

The solution I selected is based on the concept of a memory management unit's page table such as exists in a x86 CPU. As an analogy we treat the texture atlas as physical memory and subdivide it into fixed sized pages, for example 64x64 pixels in size.

Instead of two triangles, the quad will also be subdivided into triangles to match the size of each page in the atlas. Each triangle's texture coordinate points into the atlas, in effect this representation is analogue to a page table.

Rendering can be done in staging texture map. When rendering has finished the pieces of the staging texture map are copied into the pages of the texture atlas in GPU-local memory.

Deallocation of memory of fixed pages will return the complete amount, that can be reallocated without loss. This means there is no leakage of memory over repeated allocations and deallocation.

Although there is no external fragmentation due to the use of page tables, there will be internal fragmentation in the parts of the pages that remain unused. The size of pages could be modified to reduce internal fragmentation in exchange for more triangles for each quad.

Rotation, staircase effect, page-edge interpolation

If one would rotate a quad, each of the triangles edges would show severe staircase effect. Using linear texture interpolation solves this but it introduces a new problem. The linear interpolation on the edge of each page would cause it to interpolate to the neighbouring physical page, and the neighbouring page will have probably nothing to do with the current page. This is displayed as incorrectly coloured lined between the triangles.

The solution is to include a single pixel border around each page in the atlas. During the copying from the staging texture to the atlas, we simply copy one pixel border extra for each page. It is required that the staging texture also as an pixel border.

The border around the staging texture should be specifically prepared. The border colour should be the same as the neighboring pixel of the actual image, while the alpha channel should be set to fully transparent. That way linear interpolation on the edge of the quad will have the correct colour as well.

Vulkan dynamically increasing the atlas size

In case you do not know how much atlas memory is required for your application, or if the library is used for different kind of application, you may want to start with a small atlas which can increase in size as the application is starting to fill it up.

One way of solving this is to have an array of ImageViews bounded to the shader as an array of input-textures. At the start all ImageViews in the array point to the first atlas Image. When more space is needed you can allocate another atlas Image and point the second ImageView to it. This can be done without having to changing the shader or the pipeline.