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. Prysm 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 its respective atlas.
To use the tool simply navigate to the Tools/AtlasCreator folder in your Prysm package, where you will find the AtlasCreatorTool.exe file. The tool currently only has a Windows version and operates in the following way:
Example usage: AtlasCreator.exe -w 1024 -h 1024 -c configFile.txt -d E:\textures\ -s –coui-root E:\ -o E:\atlasOutput\
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": "UIAtlas0.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": "UIAtlas1.png","width": 2024,"height": 1091,"textures":[...]}...]
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 Cohtml 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 UIAtlas. | -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 |
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 Cohtml consists of three steps:
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 atlasfor (const auto& texture : atlas["textures"]){// Store the relevant information in the mapping file// about the position of the texture in the atlasAtlasedTextureInfo 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 laterm_AtlasMapping.emplace(textureURL, currentTextureInfo);}atlases.push_back(name);}// Return a vector of all atlas names found for conveniencereturn atlases;
Cohtml 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 imagestd::vector<unsigned char> image;// In our case we're decoding with the lodepng library, but you can use any library you wantunsigned error = lodepng::decode(image, props.Width, props.Height, src);// Create the texture on the backendITexture* 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 GPUFillUserImageData(m_RendererType, src, *userImageData.get(), imageName);// Return a plain pointer to the generated userImageData, which contains the texturereturn userImageData.release();}
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 atlasauto 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 filedata.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 sufficientdata.TextureBatchingHint = atlasImage->GetTexture();// Pass the modified data to Cohtmlresponse->ReceiveUserImage(data);// Signall successresponse->Finish(cohtml::IAsyncResourceResponse::Status::Success);return;}
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/UIAtlas.json");for (auto atlasName : atlases){// For every atlas we generate the full namestd::string atlasPath = atlasesLocation + atlasName;// We preload the atlas images via the ImageLoader class and create an atlas->atlasTexture mapping for the ResourceLoader to usem_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.