Skip to content
AZ Tools

DDS Texture Inspector

Open a .dds texture in the browser: header flags, BC1–BC7 pixel format, the mip chain with sizes and offsets, cube faces, and a decoded preview.

How to use

  1. Drop a .dds file onto the box. Textures exported from Photoshop, NVIDIA Texture Tools, texconv, Compressonator or straight out of a game archive all read the same way.
  2. Check the pixel format and encoding cards first: a FourCC such as DXT5 names the format directly, while "DX10" defers to the DXGI number in the extension header below.
  3. Read the mip table for the level sizes and their byte offsets — those offsets are what you need to slice a level out of the file, and they are derived rather than read from the header.
  4. Look at the layout block to see whether the surfaces add up to the file: a shortfall means a truncated chain, and a surplus means something was appended after the last surface.
  5. Use the preview to confirm the file is what you think it is. If it says the format is not decoded here, the header and mip table are still complete.

About this tool

Show

Reads a DirectDraw Surface the way a game engine loader does: four magic bytes, a fixed 124-byte header, the 32-byte DDS_PIXELFORMAT inside it, and — when the FourCC is "DX10" — a further 20 bytes carrying the DXGI format, the resource dimension, the array size and the alpha mode. After that a DDS is unmarked bytes, so every mip level, cube face and depth slice has to be located by re-deriving its size from the width, the height and the format. That arithmetic is the whole job, and it is where DDS files go wrong. Block-compressed levels are stored as whole 4×4 blocks, so a 13×7 BC3 texture is four blocks by two, and its 1×1 tail level still occupies a full 16-byte block rather than one pixel; a viewer that multiplies width by height loses alignment from the second level on. pitchOrLinearSize is never trusted here for the same reason — writers store the wrong number often enough that the field is unusable (Pillow writes (width + 3) × 4 for every block-compressed file), so the derived value is used and the declared one is shown beside it when they disagree. The chain, the six cube faces in their +X −X +Y −Y +Z −Z order, and volume depth that halves with every level are all totalled and compared against the real file length, so a truncated chain, a partial cube map and bytes appended after the last surface are each named rather than quietly absorbed. BC1, BC2, BC3, BC4 and BC5 are decoded here for the preview, including BC1's three-colour block where index 3 is transparent, and the uncompressed layouts are unpacked from their channel masks. BC6H and BC7 are reported in full but not decoded, and the page says so instead of drawing something wrong. It also cannot tell you what a texture means: whether it is sRGB or linear, whether a two-channel BC5 surface is a normal map whose Z is rebuilt in a shader, or whether the mip chain was generated from the top level at all.

Frequently asked questions

Why does this page ignore pitchOrLinearSize, when the header provides it?

Because writers fill it in inconsistently and Direct3D itself recomputes the value rather than trusting it. The field is meant to be the row pitch for an uncompressed surface or the total byte size of the top level for a block-compressed one, but plenty of exporters write something else: Pillow, for instance, stores (width + 3) × 4 for every block-compressed file, which is 76 for a 16×16 DXT1 whose data is 128 bytes. Nothing rejects such a file, because nothing needs the field — the loader derives the layout from width, height and format anyway. This page does the same and shows the declared number next to the derived one, so you can see the disagreement instead of inheriting it.

Why is the 1×1 level of my compressed texture 8 or 16 bytes instead of one pixel?

Block-compressed formats store 4×4 blocks and nothing smaller. A level of 1×1, 2×2 or 3×3 pixels still occupies one whole block — 8 bytes for BC1 and BC4, 16 for BC2, BC3, BC5, BC6H and BC7 — with the unused texels simply ignored on decode. The same padding applies at every level of a non-power-of-two texture: a 13×7 surface is ceil(13/4) × ceil(7/4) = 4 × 2 blocks, which is 8 blocks for 91 pixels. Summing width × height × bytes-per-pixel instead of counting blocks under-counts every level, and since levels are stored back to back with no headers, one wrong size misaligns everything after it.

What does the DX10 header add, and why do so many modern files use it?

The original pixel format block can only name a format through a four-character code or a set of channel masks, and it has nowhere to put a texture array, a typed-but-uncompressed format or the newer block formats. DDS_HEADER_DXT10 solves that with a numeric DXGI format enum (BC7_UNORM is 98, R8G8B8A8_UNORM is 28), a resource dimension, a misc flag whose 0x4 bit marks a cube map, an array size and an alpha mode that records whether the stored colours are straight or premultiplied. It sits between the 128-byte header and the pixel data, which is why the data offset shown here becomes 148 for those files — a reader that assumes 128 decodes the header as though it were pixels.

Why is there no preview for my BC7 or BC6H texture?

BC7 has eight block modes with different partition tables, endpoint precisions and index layouts, and BC6H is a half-float HDR format that needs tone mapping before it can be shown on an ordinary display at all. Both are decodable in principle, but a wrong implementation of either produces plausible-looking garbage rather than an obvious failure, so this page reports the format, the mip chain and the whole header, and says plainly that it is not decoding the pixels. To see them, convert with texconv or Compressonator, or open the file in a viewer that hands the blocks to the GPU.

My normal map opens as a red and green image with no blue. Is the file damaged?

Almost certainly not. BC5 stores exactly two channels, each as an independent 8-bit interpolated block of the same kind BC3 uses for alpha, and normal maps are stored that way on purpose: X and Y are kept, and Z is reconstructed in the shader as sqrt(1 − X² − Y²) because a unit normal has only two degrees of freedom. Nothing is missing. The same is true of BC4, which keeps one channel and is used for roughness, height or ambient occlusion masks; it is shown here as greyscale because a single channel has no colour of its own.

Why does a cube map file look six times too big, and can faces be missing?

A cube map holds a complete mip chain per face, stored in the order +X, −X, +Y, −Y, +Z, −Z, with no separator between them; the caps2 field carries one bit per face plus the 0x200 cube-map bit. Six faces of a 16×16 BC1 texture with a full chain is 1104 bytes of surface data rather than 184. Faces genuinely can be absent — the format allows any subset, and the file then holds only the faces whose bits are set, which is why the expected size here is computed from the bits rather than assuming six. When the bits claim more faces than the file can hold, that shows up as a shortfall in the layout block.

Related tools