# ctt
[](https://docs.rs/ctt)
A Rust library with [C bindings](#c-api) and CLI for GPU texture compression. ctt provides a unified interface over multiple encoder backends.
## Encoders
ctt binds to established open-source compression libraries and exposes them through a common `Encoder` trait. Each backend is compiled as an optional feature and can be enabled independently.
| [**bc7enc-rdo**](https://github.com/richgel999/bc7enc_rdo) | `bc7e_` | `encoder-bc7enc` | Perceptual BC7 encoder with RDO support. |
| [**Intel ISPC Texture Compressor**](https://github.com/GameTechDev/ISPCTextureCompressor) | `intel_` | `encoder-intel` | SIMD-optimized BCn and ETC encoder. |
| [**etcpak**](https://github.com/wolfpld/etcpak) | `etcpak_` | `encoder-etcpak` | Fast ETC/EAC and BCn encoder. |
| [**AMD Compressonator**](https://github.com/GPUOpen-Tools/compressonator) | `amd_` | `encoder-amd` | AMD's BCn encoder suite. |
| [**astcenc**](https://github.com/ARM-software/astc-encoder) | `astcenc_` | `encoder-astcenc` | ARM ASTC encoder. |
Encoders are listed in priority order — when multiple encoders support the same format (e.g. BC7), the first one in the table is used. To override this, prefix the format with an encoder name (e.g. `intel_bc7`).
Use `ctt --list-encoders` to see what's available in your build:
```text
$ ctt --list-encoders
Encoder Priority Formats
------- -------- -------
bc7e 1 bc7
intel 2 bc1, bc3, bc4, bc5, bc6h, bc7, etc1
etcpak 3 etc1, etc2_rgba, eac_r, eac_rg, bc1, bc3, bc4, bc5
amd 4 bc1, bc2, bc3, bc4, bc4s, bc5, bc5s, bc6h, bc6hsf, bc7
astcenc 5 astc
```
## Formats
| BC1 | `bc1` | RGB with 1-bit alpha. Opaque textures and simple cutouts. |
| BC2 | `bc2` | RGBA with explicit 4-bit alpha. Sharp alpha transitions. |
| BC3 | `bc3` | RGBA with interpolated alpha. General-purpose transparency. |
| BC4 | `bc4` | Single channel. Grayscale, heightmaps, roughness. |
| BC4S | `bc4s` | Single channel, signed. |
| BC5 | `bc5` | Two channels. Normal maps. |
| BC5S | `bc5s` | Two channels, signed. |
| BC6H | `bc6h` | HDR half-float RGB. Environment maps, HDR textures. |
| BC6H SF | `bc6hsf` | HDR signed float RGB. |
| BC7 | `bc7` | High-quality RGBA. Best LDR quality, supports alpha. |
| ETC1 | `etc1` | Mobile-friendly RGB. |
| ETC2 RGBA | `etc2_rgba` | Mobile-friendly RGBA. |
| EAC R | `eac_r` | Single channel 11-bit. |
| EAC RG | `eac_rg` | Two channel 11-bit. |
| ASTC | `astc_WxH` | Adaptive scalable texture compression. Variable block sizes (4x4 to 12x12). |
Compression accepts a quality preset from `ultra-fast` to `very-slow` (default `basic`); each encoder maps presets onto its native effort levels.
Uncompressed formats are also supported using WebGPU names (e.g. `rgba8unorm`, `rgb10a2unorm`) or Vulkan names (e.g. `r8g8b8a8_unorm`, `e5b9g9r9_ufloat_pack32`).
## Output containers
The container format is inferred from the output file extension (`.ktx2` or `.dds`), or can be set explicitly with `--container`.
- **KTX2** — Khronos cross-platform container. Supports all formats. Optional zstd or zlib supercompression.
- **DDS** — DirectX standard. Does not support ETC/EAC or ASTC formats.
## Installation
```sh
# Install the CLI (includes all encoders)
cargo install ctt-cli
# Or add the library to your project
cargo add ctt
```
By default the library enables all encoders. To select specific encoders:
```sh
cargo add ctt --no-default-features --features encoder-bc7enc,encoder-intel,ispc-prebuilt
```
## Library usage
The library API mirrors the CLI. Build a `Surface`, wrap it in an `Image`, and call `convert`:
```rust,no_run
use ctt::{convert, ConvertSettings, Container, TargetFormat, Format};
use ctt::{Image, Surface, ColorSpace, AlphaMode, TextureKind};
use ctt::encoders::Encoder;
fn main() -> Result<(), ctt::Error> {
let pixel_bytes = vec![0u8; 512 * 512 * 4];
let surface = Surface {
data: pixel_bytes,
width: 512,
height: 512,
depth: 1,
stride: 512 * 4,
slice_stride: 0,
format: Format::R8G8B8A8_UNORM,
color_space: ColorSpace::Srgb,
alpha: AlphaMode::Straight,
};
let image = Image {
surfaces: vec![vec![surface]],
kind: TextureKind::Texture2D,
};
let _ktx2_bytes = convert(image, ConvertSettings {
format: Some(TargetFormat::Compressed {
format: Format::BC7_UNORM_BLOCK,
encoder: Encoder::Auto,
}),
container: Container::ktx2(),
..Default::default()
})?;
Ok(())
}
```
See the [API documentation](https://docs.rs/ctt) for the full `ConvertSettings` options and the lower-level pipeline API.
### Parallel compression
The Rust library is serial unless its default-off `rayon` feature is enabled. With that feature, each compressed surface is split into block-row jobs on the active Rayon pool. `ctt` does not configure Rayon's global pool or add a thread count to `ConvertSettings`; callers that need a fixed worker budget can install conversion into their own pool:
```rust,ignore
let pool = rayon::ThreadPoolBuilder::new().num_threads(4).build()?;
Without an explicit `install`, Rayon uses its lazily initialized global pool.
## C API
C bindings are available as a separate crate. See [`crates/ctt-c-api/README.md`](crates/ctt-c-api/README.md) for build, link, and usage instructions; the full API reference lives in the generated header at `crates/ctt-c-api/include/ctt.h`.
## CLI usage
```text
ctt <INPUT>... --output <PATH> [--format <FORMAT>] [OPTIONS]
```
When `--format` is omitted the input format is preserved without compression.
### Examples
Compress to BC7 (auto-selects bc7enc-rdo when available):
```sh
ctt diffuse.png -o diffuse.ktx2 -f bc7
```
Force the Intel ISPC encoder for BC7:
```sh
ctt diffuse.png -o diffuse.ktx2 -f intel_bc7
```
Normal map to BC5 as DDS:
```sh
ctt normal.png -o normal.dds -f bc5 --input-color-space linear
```
High quality:
```sh
ctt diffuse.png -o diffuse.ktx2 -f bc7 --quality slow
```
Choose the compression worker count with `--threads` / `-t`. Zero (the default) uses Rayon's platform default, one is effectively serial, and positive values request that exact number of workers:
```sh
ctt diffuse.png -o diffuse.ktx2 -f bc7 --threads 4
```
Cubemap from a cross layout. `--cubemap-layout cross` (the default) accepts
both the horizontal 4×3 and vertical 3×4 cross arrangements; `--cubemap-layout
strip` accepts a horizontal 6×1 strip:
```sh
ctt skybox_cross.png -o skybox.ktx2 -f bc6h --cubemap --cubemap-layout cross
```
Cubemap from six separate faces (order: +X, -X, +Y, -Y, +Z, -Z):
```sh
ctt px.png nx.png py.png ny.png pz.png nz.png -o skybox.ktx2 -f bc7 --cubemap
```
Cubemap array from N×6 face images. Pass a multiple of six inputs with
`--cubemap` and every group of six becomes one cube, stacked in argv order
(you can also pass several already-assembled cubemaps):
```sh
ctt px0.png nx0.png py0.png ny0.png pz0.png nz0.png \
px1.png nx1.png py1.png ny1.png pz1.png nz1.png \
-o cube_array.ktx2 -f bc7 --cubemap
```
2D array from multiple plain inputs. Passing more than one image without
`--cubemap` or `--volume` assembles a 2D array texture, one layer per input
(argv order). All inputs must share dimensions, format, and mip count:
```sh
ctt layer0.png layer1.png layer2.png -o array.ktx2 -f bc7
```
3D (volume) texture. Each input is stacked as a Z slice in argv order.
3D textures are written as-is (passthrough only): compression, swizzle, and
mipmap generation are not supported for them, so omit `--format`. Both KTX2
and DDS output are supported:
```sh
ctt slice0.png slice1.png slice2.png slice3.png -o volume.ktx2 --volume
```
Generate mipmaps:
```sh
ctt diffuse.png -o diffuse.ktx2 -f bc7 --mipmap
```
With supercompression (KTX2 only). Use `--zstd` or `--zlib`; each optionally
takes a compression level (`--zstd` accepts negative through 22, default 0;
`--zlib` accepts 1 through 10, default 6):
```sh
ctt diffuse.png -o diffuse.ktx2 -f bc7 --zstd
ctt diffuse.png -o diffuse.ktx2 -f bc7 --zlib=9
```
Swizzle channels:
```sh
ctt input.png -o output.ktx2 -f bc7 --swizzle bgra
```
Converting a texture with straight alpha to a format without alpha preserves
the RGB values and emits a warning. Use `--output-alpha premultiplied` to bake
alpha into RGB, `--output-alpha opaque` to explicitly discard it, or
`--allow-discarding-alpha` to acknowledge the default discard and silence the
warning without changing pixels.
### Per-encoder tuning
Each backend exposes its own low-level knobs through a
`--<encoder>-opts "key=val;key=val"` flag: `--bc7e-opts`, `--intel-opts`,
`--etcpak-opts`, `--amd-opts`, and `--astcenc-opts`. The opts apply only when
the selected format resolves to that encoder; opts for a different encoder are
ignored with a warning. Run `ctt --help-encoder <name>` (e.g. `bc7e`, `intel`,
`etcpak`, `amd`, `astcenc`) to list the available keys, their types, and
descriptions for the encoders compiled into your build:
```sh
ctt --help-encoder bc7e
ctt diffuse.png -o diffuse.ktx2 -f bc7e_bc7 --bc7e-opts "uber-level=4;perceptual=true"
```
Run `ctt --help` for a full list of options.
## Minimum Supported Rust Version (MSRV)
The MSRV is **1.90** (edition 2024). MSRV bumps are considered breaking changes.
## Prebuilt binaries
By default, ctt ships prebuilt ISPC static libraries for all supported platforms. A default build requires only a Rust toolchain and a C++ compiler.
Every prebuilt binary has a [GitHub Artifact Attestation](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds) that cryptographically proves it was produced by this repository's CI. See [`docs/prebuilt-binaries.md`](docs/prebuilt-binaries.md) for full details on the build process, attestation guarantees, and how to verify them.
To build from source instead (requires [`ispc.exe`](https://github.com/ispc/ispc/releases) on `PATH`):
```sh
cargo install ctt-cli --no-default-features --features ispc-build-from-source
```
## License
Licensed under any of:
- MIT License
- Apache License, Version 2.0
- Zlib License
at your option.