Using ASTC with graphics APIs

OpenGL, using an extension, and Vulkan both support ASTC. This support allows easy integration and hardware utilization.

The ASTC functionality is specified as a set of three feature profiles:

  • 2D LDR profile
  • 2D LDR + HDR profile
  • 2D LDR + HDR and 3D LDR + HDR profile

The 2D LDR profile is mandatory in OpenGL ES 3.2, and a standardized optional feature for Vulkan. This means that the 2D LDR profile is widely supported on contemporary mobile devices. The 2D HDR profile is not mandatory, but is widely supported.

Khronos OpenGL ES extensions

Official Khronos extensions exist for the feature profiles of ASTC:

There is also a convenience extension, which provides the full feature set implied by supporting all three Khronos extensions:

Note: There is a distinction between sliced 3D, where each slice can be compressed independently, and 3D formats like 4x4x4. OES_texture_compression_astc is the only extension that brings in the 3D formats, and is a superset of the KHR extensions.

ASTC decompresses texels into fp16 intermediate values, except for sRGB which always decompresses into 8-bit UNORM intermediates. For many use cases, this gives more dynamic range and precision than required, and can cause a reduction in texturing efficiency due to the larger data size.

The following extensions, supported on recent mobile GPUs, allow applications to reduce the intermediate precision to either UNORM8 or RGB9e5:

UNORM8 is recommended for LDR textures, and RGB9e5 is recommended for HDR textures.

For more information about using the ASTC decode mode extension to select decoding precision when decoding ASTC image blocks, see OpenGL ES SDK for Android: ASTC low precision tutorial.

ASTC header

With both OpenGL and Vulkan, you must know certain texture image properties before you can process ASTC data. For example, you must know the ASTC block dimensions and sizes to calculate texture dimensions and compressed data size. You can find this information in the ASTC header at the beginning of compressed data.

The ASTC header has the following format:

/* ASTC header declaration. */
typedef struct
{
    unsigned char magic[4];
    unsigned char blockdim_x;
    unsigned char blockdim_y;
    unsigned char blockdim_z;
    unsigned char xsize[3];
    unsigned char ysize[3];
    unsigned char zsize[3];
} astc_header;

OpenGL ES

To use ASTC formats, you must run your application on hardware which supports the corresponding OpenGL ES extensions:

  • GL_KHR_texture_compression_astc_ldr
  • GL_KHR_texture_compression_astc_hdr

If the hardware does not support these extensions, you see messages about invalid format or internal format passing through glCompressedTex* functions to the underlying graphics driver.

To prepare the texture, perform the following steps:

  1. Open the texture image file and load it into a local memory buffer.
  2. Map the local buffer onto the ASTC header.
  3. Read the texture properties from the ASTC header and compute the required values of arguments to pass to the glCompressedTexImage2D function.

    The total number of bytes to be used by this call is computed by multiplying number of bytes per block and the number of blocks, as shown in the following code:

    /* Merge x,y,z-sizes from 3 chars into one integer value. */
    xsize = astc_data_ptr->xsize[0] + (astc_data_ptr->xsize[1] << 8) + (astc_data_ptr->xsize[2] << 16);
    ysize = astc_data_ptr->ysize[0] + (astc_data_ptr->ysize[1] << 8) + (astc_data_ptr->ysize[2] << 16);
    zsize = astc_data_ptr->zsize[0] + (astc_data_ptr->zsize[1] << 8) + (astc_data_ptr->zsize[2] << 16);
    
    /* Compute number of blocks in each direction. */
    xblocks = (xsize + astc_data_ptr->blockdim_x - 1) / astc_data_ptr->blockdim_x;
    yblocks = (ysize + astc_data_ptr->blockdim_y - 1) / astc_data_ptr->blockdim_y;
    zblocks = (zsize + astc_data_ptr->blockdim_z - 1) / astc_data_ptr->blockdim_z;
    
    /* Each block is encoded on 16 bytes, so calculate total compressed image data size. */
    n_bytes_to_read = xblocks * yblocks * zblocks << 4;
  4. Invoke glCompressedTexImage2D, as shown in the following code:

    glGenTextures(1, &to_id);
    glBindTexture(GL_TEXTURE_2D, to_id);
    /* Upload texture data to ES. */
    glCompressedTexImage2D(GL_TEXTURE_2D,
                           0,
                           compressed_data_internal_format,
                           xsize,
                           ysize,
                           0,
                           n_bytes_to_read,
                           (const GLvoid*) astc_data_ptr));
  5. Configure texture parameters for the GL_TEXTURE_2D target:

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,     GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,     GL_REPEAT);

For more information, see the full example at the Arm OpenGL ES SDK website.

Vulkan

ASTC is a core feature in Vulkan, but supporting the format is optional. Check that the textureCompressionASTC_LDR physical device feature is enabled.

To detect if ASTC is supported on your GPU, use the following code:

VkFormatProperties properties;

vkGetPhysicalDeviceFormatProperties(pContext->getPhysicalDevice(),
   VK_FORMAT_ASTC_4x4_UNORM_BLOCK, &properties);

bool supports_astc = (properties.optimalTilingFeatures &
   VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) != 0;

Uploading ASTC textures uses the same mechanism as uploading other images. The only real difference between uploading compressed textures and uncompressed textures is that ASTC textures use a different format: VK_FORMAT_ASTC_*.

Use vkCmdCopyBufferToImage to copy ASTC data into the texture, as shown in the following code:

VkBufferImageCopy region = {};
region.bufferOffset = 0;
region.bufferRowLength = 0; // Tightly packed.
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.layerCount = 1;
region.imageExtent.width = width;
region.imageExtent.height = height;
region.imageExtent.depth = 1;
// Copy the buffer to our optimally tiled image. 
// No difference between ASTC and uncompressed textures.
vkCmdCopyBufferToImage(cmd, stagingBuffer.buffer, image, 
   VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);

For more information, see the full example at the Arm Vulkan SDK website.

Previous Next