Table of Contents

Textures

Game engines and 3D tools keep their textures in DDS files: block-compressed so the GPU reads them as they are, with a chain of mips for distance, and with cube maps for skies and reflections. Egami.Formats.Extended reads every DDS format DirectXTex reads and writes the uncompressed formats and BC1 to BC7, so a pipeline can turn PNGs and EXRs into textures, or textures back into images, without a native tool. The DDS format page lists every format, option and detail; this guide is about choosing them.

Register the format once with WithDds() or WithExtendedFormats() (Extended formats).

Choosing a block format

Content Format Bits a pixel Notes
Colour with alpha, best quality Bc7 8 Eight modes and partitions; the default choice for colour on any GPU from Direct3D 11 on
Colour, smallest Bc1 4 5-6-5 colour, alpha only on or off
Colour with smooth alpha, old hardware Bc3 8 BC1 colour and an interpolated alpha block
One channel (height, roughness, masks) Bc4 4 The raster's gray
Normal maps Bc5 8 Red and green; the shader rebuilds blue
HDR (light maps, skies) Bc6h, Bc6hSigned 8 Half-float RGB without alpha

Colour textures are usually sRGB: set DdsWriterOptions.Srgb so the GPU decodes them to linear light when it samples, and the mips are averaged in linear light as they should be. Normal, height and other data maps stay plain UNORM.

Effort trades encoding time for quality, from 0 to 6 like WebP's. The default 4 is meant for shipping textures; 0 to 2 suit quick previews and iteration. The bytes are the same for any number of threads.

Mips

Save writes the full mip chain by default. Set MipLevels = 1 for user-interface images and anything always drawn at its own size, or a number for a shorter chain. A texture loaded from DDS and saved again unchanged keeps the mips it had, so a tool that edits only metadata, or converts between headers, does not blur hand-made mips.

Recipes

Turn a PNG into a texture

// A BC3 texture with its full mip chain: the format, mip count, effort (0 to 6) and header are options.
using (FileStream output = File.Create("duck.dds"))
{
    image.Save(output, DdsFormat.Info, new DdsWriterOptions { Format = DdsTextureFormat.Bc3, Effort = 5 });
}

// An sRGB colour texture without mips, in the DX10 header.
using (FileStream output = File.Create("duck-srgb.dds"))
{
    image.Save(output, DdsFormat.Info, new DdsWriterOptions { Format = DdsTextureFormat.Bc1, Srgb = true, MipLevels = 1 });
}

// Six faces back into a cube map; SaveAll without Cube writes a texture array.
using RasterList faces = Raster.LoadAll("sky.dds", context);
using (FileStream output = File.Create("sky-bc1.dds"))
{
    Raster.SaveAll(faces, output, DdsFormat.Info, new DdsWriterOptions { Cube = true, Format = DdsTextureFormat.Bc1 });
}

Read every face, slice and mip

// One surface and mip: cube face -Y (faces go +X, -X, +Y, -Y, +Z, -Z), third mip.
using Raster face = Raster.Load("sky.dds", context, new DdsReaderOptions { Surface = 3, MipLevel = 2 });

// Every face at mip 0, or with every mip after its face.
using RasterList faces = Raster.LoadAll("sky.dds", context);
using RasterList all = Raster.LoadAll("sky.dds", context, new DdsReaderOptions { IncludeMipLevels = true });

// A thumbnail reads the smallest mip that still covers the size, a fraction of the file.
using Raster thumbnail = Raster.Load("sky.dds", context, new ReaderOptions { MinimumSize = new Size(8, 8) });

// Two-channel normal maps (BC5, R8G8) load with blue 0, or with Z rebuilt.
using Raster normals = Raster.Load("sky.dds", context, new DdsReaderOptions { ReconstructNormalZ = true });

Make a normal map texture and read it back with Z

// BC5 keeps red and green; blue (the normal's Z) is rebuilt by the shader, or on load.
using (FileStream output = File.Create("normals.dds"))
{
    normals.Save(output, DdsFormat.Info, new DdsWriterOptions { Format = DdsTextureFormat.Bc5 });
}

EgamiContext context = EgamiContext.Default.WithDds();
using Raster<Rgb8> back = Raster.Load<Rgb8>("normals.dds", context, new DdsReaderOptions { ReconstructNormalZ = true });

Compress an HDR image

// BC6H keeps half-float colour at 8 bits a pixel; light values above 1 survive.
using (FileStream output = File.Create("sky-hdr.dds"))
{
    sky.Save(output, DdsFormat.Info, new DdsWriterOptions { Format = DdsTextureFormat.Bc6h });
}

using Raster<RgbaF16> back = Raster.Load<RgbaF16>("sky-hdr.dds", EgamiContext.Default.WithDds());