Zebrafish
Zebrafish

Reputation: 14308

How are you supposed to make the mip map images for block compressed formats?

If you have an image that's 128 x 64 the mips are:

Mip 0: 128 x 64
Mip 1: 64 x 32
Mip 3: 32 x 16
Mip 4: 16 x 8
Mip 5: 8 x 4
Mip 6: 4 x 2
Mip 7: 2 x 1
Mip 8: 1 x 1

Now, with block compressed formats they have to be a multiple of 4. So how are you supposed to make the mip levels with a block compressed format like BC1?

I can't believe how hard something like this is to find out. The Vulkan spec only mentions mip sizes with respect to non-block formats.

Two LLM's I asked said you STOP generating mipmaps when you reach 4x4.

DirectX says:

Sampling hardware uses the virtual size; when the texture is sampled, the memory padding is ignored. For mipmap levels that are smaller than 4×4, only the first four texels will be used for a 2×2 map, and only the first texel will be used by a 1×1 block. However, there is no API structure that exposes the physical size (including the memory padding).

So you continue generating the mips that are smaller than 4x4 of the original image, but then you have to pad up to 4x4 before you block compress. But how is this even done? What am I supposed to pad the extra part of the 4x4 block with?

Upvotes: 2

Views: 64

Answers (2)

Chuck Walbourn
Chuck Walbourn

Reputation: 41127

The general model for Block Compressed Textures with mipmaps is to create the texture in a non-compressed format, generate mipmaps, and then compress each level of the mip to get the BC version of the texture.

Traditionally the DXTn/BC compressed formats have required the top-most level of the mipchain to be a multiple of 4 in each dimension, but the hardware has to handle non-multiple-of-4 surfaces since they can be easily generated in a mipmap chain if the original size is not a power of 2. Furthermore, the 'bottom' mips must be a minimum of 1 block in size, and there are common replication patterns used for the various ways to put a 4x1, 1x4, 2x2, 2x4, 4x2, 1x1, etc. into the 4x4 block required for the format.

Here's some psuedo-code of the way this works in practice

TEXTURE1D/TEXTURE2D

const size_t blockSize = (fmt == BC1 || fmt == BC4) ? 8 : 16;

for(size_t item = 0; item < arraySize; ++item)
{
    size_t width = /* width of top level, probably a multiple of 4 */
    size_t height = /* height of top level, probably a multiple of 4 */
 
    for(mip = 0; mip < mipLevels; ++mip)
    {
        size_t nbw = std::max(1, width + 3u) / 4u);
        size_t nbh = std::max(1, height + 3u) / 4u);
        pitchBytes = nbw * blockSize;
    
        if (height > 1)
            height >>= 1;

        if (width > 1)
            width >>= 1;
    }
}

TEXTURE3D

const size_t blockSize = (fmt == BC1 || fmt == BC4) ? 8 : 16;

size_t width = /* width of top level, probably a multiple of 4 */
size_t height = /* height of top level, probably a multiple of 4 */
size_t depth = /* depth of volume texture */
 
for(mip = 0; mip < mipLevels; ++mip)
{
    size_t nbw = std::max(1, width + 3u) / 4u);
    size_t nbh = std::max(1, height + 3u) / 4u);
    pitchBytes = nbw * blockSize;
    sliceBytes = pitch * nbh;

    for(size_t slice = 0; slice < depth; ++slice)
    {
    }
    
    if (height > 1)
        height >>= 1;

    if (width > 1)
        width >>= 1;

    if (depth > 1)
        depth >>= 1;
}

For the case of a block not being 4 x 4 in size as you go down the mips:

// 'block' is an array of 16 pixels

if (pw != 4 || ph != 4)
{
    // Replicate pixels for partial block
    static const size_t uSrc[] = { 0, 0, 0, 1 };

    if (pw < 4)
    {
        for (size_t t = 0; t < ph && t < 4; ++t)
        {
            for (size_t s = pw; s < 4; ++s)
            {
                block[(t << 2) | s] = block[(t << 2) | uSrc[s]];
            }
        }
    }

    if (ph < 4)
    {
        for (size_t t = ph; t < 4; ++t)
        {
            for (size_t s = 0; s < 4; ++s)
            {
                block[(t << 2) | s] = block[(uSrc[t] << 2) | s];
            }
        }
    }
}

As part of Direct3D 12, the D3D12_FEATURE_DATA_D3D12_OPTIONS8.UnalignedBlockTexturesSupported optional feature indicates that the driver can accept the top-level texture size to be a non-multiple-of-4 so it's official supported in these scenarios. It's been supported for ages, but the runtime debug layer would emit an error if you passed a non-multiple-of-4 size for the top-level prior to adding this.

For a full implementation of mip-map generation, pixel format conversion, BC compression, etc. see DirectXTex.

Upvotes: 2

solidpixel
solidpixel

Reputation: 12229

You need to pad the input images out to the next block size before compression, so you end up with an integer number of compressed blocks.

The texture compressor will (probably) just look at the whole block and compress it as normal, so it's best to fill the pad region with colors that match the real data (e.g. extend the last real pixel edge color into the pad region). Check your compressor documentation because a lot of compressors will do the padding for you, so you may not need to do it manually.

Upvotes: 3

Related Questions