Image Tile Cache
This describes the implementation of the tile cache for images, to support loading many large images with less memory. This consists of support for:
- Loading from the .tx image file format that stores all mipmap levels, in fixed size tiles.
- Support for tiled images in ImBuf and texture sampling from these tiles.
- Cache to dynamically load/unload tiles so only a subset of all images need to be in memory.
We use the .tx file format. This is a standard tiff image that can be loaded by libtiff. It stores the mipmap levels as "directories" and enabled tiles for storage. Compression can be used as well. Renderman render engines use a similar file format though there appear to be some differences between engines, currently we load images as generated by maketx included in OpenImageIO. Images are always loaded as 8bit RGBA images.
Most image editing programs do not support editing .tx files directly. So it can be treated as a kind of cache that needs to be regenerated once in a while for more efficient memory usage. To enable this, when loading an image file, Blender will automatically look for images with the same name but with the .tx extension in the same directory, and use that instead, but only if the .tx file is newer than e.g. the .png. Currently Blender also has no support for saving in this file format, so utilities like maketx should be used.
Tiled Images in ImBuf
When getting an image buffer from an image, the ImageUser defines if the tile cache is used for storage with the IMA_TILECACHE flag. Most places in Blender do no expect tiles, so if this flag is not set, the ImBuf will be loaded regularly, or converted to a regular pixel rect. This conversion is permanent so once a single place access the image non-tiled it will take up more memory.
If the flag is set, loading the image will create an ImBuf and all it's mipmap levels, with NULL *rect pointers, but with the width, height, tile sizes initialized. IMB_gettile must then be used to retrieve tiles, which will be loaded as needed. The ImBuf remembers the original filename and type.
Tiles are stored as fixed size tilex*tiley rects in the **tiles pointer. These should not be directly access in order to support automatic loading/unloading, but retrieved through IMB_gettile instead.
We use a two level cache here. A per-thread cache with limited number of tiles. This can be accessed without locking and so is hoped to lead to most tile access being lock-free. The global cache is shared between all threads and requires slow locking to access, and contains all tiles.
The per-thread cache should be sufficiently small to avoid much memory usage and slow list lookups but big enough that one might hope to not fall back to the global cache every pixel.
Locking code is handled in two places. BKE_image_get_ibuf is the first place that will create the ImBuf and all it's mipmap levels. All locking there happens inside this function with a global lock. If the images is already loaded, it tries to avoid acquiring any lock as this can be slow. It only does this if there is no ImBuf, under the assumption that no ImBuf is every removed while multiple threads are accessing it.
In case the tile cache is used, there will be no pixels loaded yet, but the width/height/tilesize will be set, for all mipmap levels. So the texture sampling code can then pick the right mipmap level ImBuf and tile.
IMB_gettile will then check if the tile exists in the per-thread cache, and do locking to access it from the global cache. When the tile is not in the global cache and needs to be loaded from disk, it will release the lock again while loading and mark that tile as being loaded. This makes it possible for multiple threads to load different tiles at the same time, though they will still be blocked when accessing the same tile.
- The memory limit only applies to tiled images, regular images are not counted (or unloaded). Adding these into the cache may work, but there is a risk of severe performance problems if a big image is loaded/unloaded continuously.
- Currently the memory limit is disabled by default, mainly because it's difficult to find a good automatic limit. We can't use all available memory because subsequent allocations for other things may fail then. But we should also be careful not to see the limit to low to avoid performance problems.
- Image thumbnails and previews still load the full image, this should be made to use tiles & mipmap levels when available.