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.