1.14.0.5
Hummingbird
A modern user interface library for games
Texture Packing

Atlas Creator tool

Introduction

Texture packing or Atlasing is a way to group multiple textures into a single larger texture, usually referred to as an atlas. This is usually done in order to reduce the number of draw calls to the GPU. Hummingbird supports texture atlasing internally to provide increased performance for its users. The SDK comes with an easy-to-use and easy-to-automate command line tool for packing. The Atlas Creator takes in a list of textures to pack and produces an atlas or atlases containing the textures. It also produces a JSON-formatted file, containing the mapping between each texture and the location in it's respective atlas.

Usage

To use the tool simply navigate to the Tools/AtlasCreator folder in your Hummingbird package, where you will find the AtlasCreatorTool.exe file. The tool currently only a Windows version and operates in the following way:

  1. You should specify:
    • The textures to pack via a directory, a list of files, or a combination of the two.
    • A root path so that all generated texture mappings will be relative to it.
    • An output directory, atlas name and atlas dimensions. (optional)
  2. The Atlas Creator will output:
    • An atlas or multiple atlases(depending on the setup) in a PNG or TGA format, containing your textures.
    • A mappings file, containing information about the coordinates of each texture in the atlas(es), including their respective atlas and coordinates inside the atlas.\

Example usage: AtlasCreator.exe -w 1024 -h 1024 -c configFile.txt -d E:\textures\ -s –coui-root E:\ -o E:\atlasOutput\

  • Note: The SampleTextureAtlasing(accessible from the samples solution) that comes with Hummingbird has a pre-build step that runs the AtlasCreator tool. You can look at the commands by opening the AtlasCreatorCommands.bat file in the Tools/AtlasCreator folder.*

Mapping file format

For every atlas created there will be an object in the JSON file containing the atlas name, width, height and an array of belonging textures. The array of textures will contain one object per texture packed in the atlas, containing the x, y, width, height and name, where x and y are the coordinates of the top-left corner of the packed texture inside the respective atlas. The final output should look like this:

[
{
"name": "HummingbirdAtlas0.png",
"width": 2044,
"height": 2019,
"textures":
[
{
"URL": "Path/To/Your/Image.png",
"top": 1392,
"left": 1984,
"width": 57,
"height":61
},
{
"URL": "Path/To/Your/Image2.png",
"top": 203,
"left": 632,
"width": 530,
"height":182
},
...
]
},
{
"name": "HummingbirdAtlas1.png",
"width": 2024,
"height": 1091,
"textures":
[
...
]
}
...
]

Additional info:

  • The supported input image types are JPG, PNG, BMP, TGA.
  • The Atlas Creator can take parameters from a configuration file in order to make texture batching automation easier. The configuration file should contain the command line parameters you wish to pass to the tool. Any parameters passed to the command line will override parameters in the configuration file.
  • Any generated atlases will be clamped to their minimal possible size unless the --no-clamp parameter is specified.
  • If your textures do not fit into a single atlas multiple atlases will be generated with the --single-atlas parameter is specified.
  • The atlas creator tool automatically adds padding to your textures in order to ensure that they work on all platforms. The padding is as follows: the height of each texture is padded by one pixel, the width of each texture is padded to a multiple of four and if it is already a multiple of four a padding of four pixels is added. This does not affect the final width and height values of a texture in the mappings file, it just modifies the texture coordinates inside the atlas.
  • The atlas creator tool takes in non-premultiplied textures and produces premultiplied atlases with regards to the alpha channel of the images.

Command line parameters table

Argument Shorthand Description Example
--height "value" -h Specifies the max height of the generated texture atlas. Defaults to 2048. -h 1024
--width "value" -w Specifies the max width of the generated texture atlas. Defaults to 2048. -w 1024
--config "value" -c This parameter should provide a path to a config text file. The config file should contain parameter-value pairs, separated by an interval in the format PARAMETER VALUE, separated by whitespace. Any parameters passed to the command line will override parameters in the config file.-c E:\files\config.txt
--input-dir "value" -d Specifies a folder path, containing textures that need to be packed. They can have the following types: JPG, PNG, BMP, TGA. -d E:\textures\
--input-file-list "value" -i Specifies a path to a file, which includes a list of textures to pack into the atlas. The file should contain file names separated by newlines. -i E:\files\inputFiles.txt
--regex "value" -r Specifies a regular expression in the c++ "regex" format and includes all the files matching the expression inside the –input-dir as textures to pack.-r \D*\d.png this would match img1.png, myimg3.png, etc.
--coui-root "value" None Specifies the root path. All mappings are generated relative to the root path. If you want to use the atlas in Hummingbird this should point to the same directory as “coui://”. Defaults to the current directory. --coui-root E:\textures\
--atlas-name "value" -n Specifies the output atlas name. Output file type is PNG. Defaults to HummingbirdAtlas.-n myAtlas
--output-dir "value" -o Specifies the folder where atlas(es) and mappings file should be generated. Defaults to the current directory. -o E:\output\
--no-clamp None Specifies that the atlas will always be of the specified size even if there is leftover space after packing. If not specified the atlas will always be clamped to the minimum possible size.--no-clamp
--single-atlas "value" -s Specified for the tool to create only a single atlas. The tool will exit with an error if the given textures exceed the size of the atlas.-s
--input-texture-max-height "value" None Specifies the maximum allowed height for the input textures. Any textures not meeting that requirement will not be processed. --input-texture-max-height 100
--input-texture-min-height "value" None Specifies the minimum allowed height for the input textures. Any textures not meeting that requirement will not be processed. --input-texture-min-height 100
--input-texture-max-width "value" None Specifies the maximum allowed width for the input textures. Any textures not meeting that requirement will not be processed. --input-texture-max-width 100
--input-texture-min-width "value" None Specifies the minimum allowed width for the input textures. Any textures not meeting that requirement will not be processed. --input-texture-min-width 100
--output-format -f Specifies the atlas output format. Supported formats are: PNG, TGA. Defaults to PNG. -f PNG
--help None Prints out the possible parameters with their corresponding descriptions. --help

Using atlases in Hummingbird

The code snippets below have been shortened in order to make this tutorial easier. You can find the full implementation of the code used here in the TextureAtlasingSample.

Generally speaking, using an atlas in Hummingbird consists of three steps:

  1. Reading the JSON mapping file produced by the Atlas Creator and storing all atlased textures
  2. Preloading the atlas textures using your rendering backend
  3. When a resource request from Hummingbird comes in and the requested texture is in an atlas -> passing the atlased texture and the corresponding coordinates instead of the original image. All of those steps are demonstrated below.

Reading the mapping file

The ResourceLoader class provided in the package already has a LoadAtlasMapping(const std::string& mappingFilePath) method in it:

// Parse the JSON file using a parser of your choice. In our case that's the nlohmannJSON parser.
auto mapping = json::parse(jsonContent);
for (auto atlas : mapping)
{
std::string name = atlas["name"];
// For each texture in each atlas
for (const auto& texture : atlas["textures"])
{
// Store the relevant information in the mapping file
// about the position of the texture in the atlas
AtlasedTextureInfo currentTextureInfo;
auto textureURL = texture["URL"].get<std::string>();
currentTextureInfo.AtlasName = name;
currentTextureInfo.X = texture["left"];
currentTextureInfo.Y = texture["top"];
currentTextureInfo.Width = texture["width"];
currentTextureInfo.Height = texture["height"];
// Put the texture->atlas mapping information inside an unordered_map so we can look it up later
m_AtlasMapping.emplace(textureURL, currentTextureInfo);
}
atlases.push_back(name);
}
// Return a vector of all atlas names found for convenience
return atlases;

Loading the atlas textures

Hummingbird samples come with an ImageLoader class that can be used to load images on the GPU. Here is some of the code inside it's PreloadImage function

resources::UserImageData* PreloadImage(const char* src)
{
// Get the raw pixels from the image
std::vector<unsigned char> image;
// In our case we're decoding with the lodepng library, but you can use any library you want
unsigned error = lodepng::decode(image, props.Width, props.Height, src);
// Create the texture on the backend
ITexture* result = m_Renderer->CreateTexture(props, image.data());
std::unique_ptr<resources::PreloadedImageData> userImageData(new resources::PreloadedImageData);
userImageData->Texture.reset(result);
std::string imageName;
// Put the data on the GPU
FillUserImageData(m_RendererType, src, *userImageData.get(), imageName);
// Return a plain pointer to the generated userImageData, which contains the texture
return userImageData.release();
}

Modifying the resource loader

In order to actually use the loaded atlases you need to pass them instead of the requested textures. Below is a snippet from the sample ResourceLoader's OnResourceRequest method.

// Check the mapping to see if the texture is in an atlas
auto atlasedImageIt = m_AtlasMapping.find(path);
if (atlasedImageIt != m_AtlasMapping.end())
{
auto& atlasedTextureInfo = atlasedImageIt->second;
auto& atlasImage = m_PreloadedAtlases[atlasedTextureInfo.AtlasName];
// Generate the default data for the atlas containing the texture as if we were going to draw the entire atlas.
auto data = atlasImage->GenerateUserImageData();
// Replace the coordinates, width and height of the texture with those provided by the mapping file
data.ContentRectX = atlasedTextureInfo.X;
data.ContentRectY = atlasedTextureInfo.Y;
data.ContentRectWidth = atlasedTextureInfo.Width;
data.ContentRectHeight = atlasedTextureInfo.Height;
// Add a user-defined identifier that will be used to identify textures belonging to the same atlas.
// This should be the same for all textures belonging to the same atlas so a pointer to the atlas texture is sufficient
data.TextureBatchingHint = atlasImage->GetTexture();
// Pass the modified data to Hummingbird
response->ReceiveUserImage(data);
// Signall success
response->Finish(cohtml::IAsyncResourceResponse::Status::Success);
return;
}

Making it all work together

Here is a snippet from the AtlasingSample's Initialize method. It uses the functionality described above and demonstrates the workflow of adding an atlas to your program.

// This can be the output folder from the AtlasCreator tool or any other location you move your atlases to.
std::string atlasesLocation = "uiresources/Kits/FPS-Kit/FPS-HUD/atlases/";
// We load the mapping into the resource handler by providing a path to the JSON file.
// Again this could be the output folder from the Atlas Creator tool or any other location you choose.
auto atlases = m_AppResourceHandler->LoadAtlasMapping("cohtml/Samples/uiresources/Kits/FPS-Kit/FPS-HUD/atlases/HummingbirdAtlas.json");
for (auto atlasName : atlases)
{
// For every atlas we generate the full name
std::string atlasPath = atlasesLocation + atlasName;
// We preload the atlas images via the ImageLoader class and create an atlas->atlasTexture mapping for the ResourceLoader to use
m_AppResourceHandler->AddAtlas(atlasName, resources::UserImageDataPtr(m_ImageLoader.PreloadImage(atlasPath.c_str())));
}

Should you set up your application in the way shown above, all atlased textures will be read from their respective atlas and everything else will be loaded from disk.