Formato BC6H

O formato BC6H é um formato de compactação de textura projetado para dar suporte a espaços de cores HDR (alto alcance dinâmico) nos dados de origem.

Sobre BC6H/DXGI_FORMAT_BC6H

O formato BC6H fornece compactação de alta qualidade para imagens que usam três canais de cores HDR, com um valor de 16 bits para cada canal de cores do valor (16:16:16). Não há suporte para um canal alfa.

BC6H é especificado pelos seguintes valores de enumeração DXGI_FORMAT:

  • DXGI_FORMAT_BC6H_TYPELESS.
  • DXGI_FORMAT_BC6H_UF16. Esse formato BC6H não usa um bit de sinal nos valores de canal de cor de ponto flutuante de 16 bits.
  • DXGI_FORMAT_BC6H_SF16. Esse formato BC6H usa um bit de sinal nos valores de canal de cor de ponto flutuante de 16 bits.

Observação

O formato de ponto flutuante de 16 bits para os canais de cor é geralmente conhecido como um formato de ponto flutuante "parcial". Esse formato tem o seguinte layout de bit:

Formatar Layout de bit
UF16 (float sem sinal) 5 bits expoentes + 11 bits de mantissa
SF16 (float assinado) 1 bit de sinal + 5 bits expoentes + 10 bits de mantissa

 

 

O formato BC6H pode ser usado para recursos de textura Texture2D (incluindo matrizes), Texture3D ou TextureCube (incluindo matrizes). Da mesma forma, esse formato se aplica a quaisquer superfícies de mapa MIP associadas a esses recursos.

BC6H usa um tamanho de bloco fixo de 16 bytes (128 bits) e um tamanho de bloco fixo de 4x4 texels. Assim como acontece com os formatos BC anteriores, imagens de textura maiores que o tamanho do bloco com suporte (4x4) são compactadas usando vários blocos. Essa identidade de endereçamento também se aplica a imagens tridimensionais, mapas de MIP, cubemaps e matrizes de textura. Todos os blocos de imagem devem ter o mesmo formato.

Algumas observações importantes sobre o formato BC6H:

  • O BC6H dá suporte à desnormalização de ponto flutuante, mas não dá suporte a INF (infinito) e NaN (não é um número). A exceção é o modo assinado de BC6H (DXGI_FORMAT_BC6H_SF16), que dá suporte a -INF (infinito negativo). Observe que esse suporte para -INF é apenas um artefato do próprio formato e não tem suporte especificamente para codificadores para esse formato. Em geral, quando os codificadores encontram dados de entrada INF (positivo ou negativo) ou NaN, eles devem converter esses dados no valor máximo permitido de representação não INF e mapear NaN para 0 antes da compactação.
  • BC6H não dá suporte a um canal alfa.
  • O decodificador BC6H executa a descompactação antes de executar a filtragem de textura.
  • A descompactação BC6H deve ser precisa de bits. Ou seja, o hardware deve retornar resultados idênticos ao decodificador descrito nesta documentação.

Implementação de BC6H

Um bloco BC6H consiste em bits de modo, pontos de extremidade compactados, índices compactados e um índice de partição opcional. Esse formato especifica 14 modos diferentes.

Uma cor de ponto de extremidade é armazenada como um trigêmeo RGB. O BC6H define uma paleta de cores em uma linha aproximada em vários pontos de extremidade de cor definidos. Além disso, dependendo do modo, um bloco pode ser dividido em duas regiões ou tratado como uma única região, em que um bloco de duas regiões tem um conjunto separado de pontos de extremidade de cor para cada região. O BC6H armazena um índice de paleta por texel.

No caso de duas regiões, há 32 partições possíveis.

Decodificação do formato BC6H

O pseudocódigo abaixo mostra as etapas para descompactar o pixel em (x,y) dado o bloco BC6H de 16 bytes.

decompress_bc6h(x, y, block)
{
    mode = extract_mode(block);
    endpoints;
    index;
    
    if(mode.type == ONE)
    {
        endpoints = extract_compressed_endpoints(mode, block);
        index = extract_index_ONE(x, y, block);
    }
    else //mode.type == TWO
    {
        partition = extract_partition(block);
        region = get_region(partition, x, y);
        endpoints = extract_compressed_endpoints(mode, region, block);
        index = extract_index_TWO(x, y, partition, block);
    }
    
    unquantize(endpoints);
    color = interpolate(index, endpoints);
    finish_unquantize(color);
}

A tabela a seguir contém a contagem de bits e os valores para cada um dos 14 formatos possíveis para blocos BC6H.

Mode Índices de partição Partição Pontos de extremidade de cor Bits de modo
1 46 bits 5 bits 75 bits (10.555, 10.555, 10.555) 2 bits (00)
2 46 bits 5 bits 75 bits (7666, 7666, 7666) 2 bits (01)
3 46 bits 5 bits 72 bits (11.555, 11.444, 11.444) 5 bits (00010)
4 46 bits 5 bits 72 bits (11.444, 11.555, 11.444) 5 bits (00110)
5 46 bits 5 bits 72 bits (11.444, 11.444, 11.555) 5 bits (01010)
6 46 bits 5 bits 72 bits (9555, 9555, 9555) 5 bits (01110)
7 46 bits 5 bits 72 bits (8666, 8555, 8555) 5 bits (10010)
8 46 bits 5 bits 72 bits (8555, 8666, 8555) 5 bits (10110)
9 46 bits 5 bits 72 bits (8555, 8555, 8666) 5 bits (11010)
10 46 bits 5 bits 72 bits (6666, 6666, 6666) 5 bits (11110)
11 63 bits 0 bits 60 bits (10.10, 10.10, 10.10) 5 bits (00011)
12 63 bits 0 bits 60 bits (11.9, 11.9, 11.9) 5 bits (00111)
13 63 bits 0 bits 60 bits (12.8, 12.8, 12.8) 5 bits (01011)
14 63 bits 0 bits 60 bits (16.4, 16.4, 16.4) 5 bits (01111)

 

Cada formato nesta tabela pode ser identificado exclusivamente pelos bits de modo. Os dez primeiros modos são usados para blocos de duas regiões e o campo de bits do modo pode ter dois ou cinco bits de comprimento. Esses blocos também têm campos para os pontos de extremidade de cor compactados (72 ou 75 bits), a partição (5 bits) e os índices de partição (46 bits).

Para os pontos de extremidade de cor compactados, os valores na tabela anterior observam a precisão dos pontos de extremidade RGB armazenados e o número de bits usados para cada valor de cor. Por exemplo, o modo 3 especifica um nível de precisão de ponto de extremidade de cor de 11 e o número de bits usados para armazenar os valores delta dos pontos de extremidade transformados para as cores vermelha, azul e verde (5, 4 e 4, respectivamente). O modo 10 não usa compactação delta e armazena explicitamente todos os quatro pontos de extremidade de cor.

Os últimos quatro modos de bloco são usados para blocos de uma região, em que o campo de modo é de 5 bits. Esses blocos têm campos para os pontos de extremidade (60 bits) e os índices compactados (63 bits). O modo 11 (como o Modo 10) não usa compactação delta e armazena os dois pontos de extremidade de cor explicitamente.

Os modos 10011, 10111, 11011 e 11111 (não mostrados) são reservados. Não os use no codificador. Se o hardware receber blocos com um desses modos especificados, o bloco descompactado resultante deverá conter todos os zeros em todos os canais, exceto no canal alfa.

Para BC6H, o canal alfa sempre deve retornar 1.0 independentemente do modo.

Conjunto de partições BC6H

Há 32 conjuntos de partições possíveis para um bloco de duas regiões e que são definidos na tabela abaixo. Cada bloco 4x4 representa uma única forma.

table of bc6h partition sets

Nesta tabela de conjuntos de partições, a entrada em negrito e sublinhada é o local do índice de correção para o subconjunto 1 (que é especificado com um bit a menos). O índice de correção para o subconjunto 0 sempre é o índice 0, pois o particionamento é sempre organizado de modo que o índice 0 esteja sempre no subconjunto 0. A ordem de partição vai do canto superior esquerdo para o canto inferior direito, movendo-se da esquerda para a direita e, em seguida, de cima para baixo.

Formato de ponto de extremidade compactado BC6H

bit fields for bc6h compressed endpoint formats

Esta tabela mostra os campos de bits para os pontos de extremidade compactados como uma função do formato de ponto de extremidade, com cada coluna especificando uma codificação e cada linha especificando um campo de bits. Essa abordagem ocupa 82 bits para blocos de duas regiões e 65 bits para blocos de uma região. Por exemplo, os primeiros 5 bits para a codificação de uma região [16 4] acima (especificamente, a coluna mais à direita) são bits m[4:0], os próximos 10 bits são bits rw[9:0] e assim por diante com os últimos bits 6 contendo bw[10:15].

Os nomes de campo na tabela acima são definidos da seguinte maneira:

Campo Variável
m mode
d índice de forma
rw endpt[0].A[0]
rx endpt[0].B[0]
ry endpt[1].A[0]
rz endpt[1].B[0]
gw endpt[0].A[1]
gx endpt[0].B[1]
gy endpt[1].A[1]
gz endpt[1].B[1]
bw endpt[0].A[2]
bx endpt[0].B[2]
by endpt[1].A[2]
bz endpt[1].B[2]

 

Endpt[i], onde i é 0 ou 1, refere-se ao conjunto de pontos de extremidade de número 0 ou 1 respectivamente.

Assinar extensão para valores de ponto de extremidade

Para blocos de duas regiões, há quatro valores de ponto de extremidade que podem ser assinados estendidos. Endpt[0].A é assinado somente se o formato for um formato assinado; os outros pontos de extremidade são assinados somente se o ponto de extremidade foi transformado, ou se o formato é um formato assinado. O código a seguir demonstra o algoritmo para estender o sinal de valores de ponto de extremidade de duas regiões.

static void sign_extend_two_region(Pattern &p, IntEndpts endpts[NREGIONS_TWO])
{
    for (int i=0; i<NCHANNELS; ++i)
    {
      if (BC6H::FORMAT == SIGNED_F16)
        endpts[0].A[i] = SIGN_EXTEND(endpts[0].A[i], p.chan[i].prec);
      if (p.transformed || BC6H::FORMAT == SIGNED_F16)
      {
        endpts[0].B[i] = SIGN_EXTEND(endpts[0].B[i], p.chan[i].delta[0]);
        endpts[1].A[i] = SIGN_EXTEND(endpts[1].A[i], p.chan[i].delta[1]);
        endpts[1].B[i] = SIGN_EXTEND(endpts[1].B[i], p.chan[i].delta[2]);
      }
    }
}

Para blocos de uma região, o comportamento é o mesmo, apenas com endpt[1] removido.

static void sign_extend_one_region(Pattern &p, IntEndpts endpts[NREGIONS_ONE])
{
    for (int i=0; i<NCHANNELS; ++i)
    {
    if (BC6H::FORMAT == SIGNED_F16)
        endpts[0].A[i] = SIGN_EXTEND(endpts[0].A[i], p.chan[i].prec);
    if (p.transformed || BC6H::FORMAT == SIGNED_F16) 
        endpts[0].B[i] = SIGN_EXTEND(endpts[0].B[i], p.chan[i].delta[0]);
    }
}

Transformar a inversão para valores de ponto de extremidade

Para os blocos de duas regiões, a transformação aplica o inverso da codificação de diferença, adicionando o valor base em endpt[0].A a três outras entradas para um total de 9 operações de adição. Na imagem abaixo, o valor base é representado como "A0" e tem a precisão de ponto flutuante mais alta. "A1", "B0" e "B1" são todos deltas calculados a partir do valor da âncora e esses valores delta são representados com menor precisão. (A0 corresponde ao endpt[0].A, B0 corresponde ao endpt[0].B, A1 corresponde ao endpt[1].A e B1 corresponde ao endpt[1].B.)

calculation of transform inversion endpoint values

Para blocos de uma região, há apenas um deslocamento delta e, portanto, apenas 3 operações de adição.

O decompressor deve garantir que os resultados da transformação inversa não irão exceder a precisão de endpt[0].a. No caso de um estouro, os valores resultantes da transformação inversa devem encapsular dentro do mesmo número de bits. Se a precisão de A0 for "p", o algoritmo de transformação será:

B0 = (B0 + A0) & ((1 << p) - 1)

Para formatos assinados, os resultados do cálculo delta também devem ser assinados estendidos. Se a operação de extensão de sinal considerar a extensão de ambos os sinais, em que 0 é positivo e 1 é negativo, a extensão de sinal de 0 cuida do grampo acima. Equivalentemente, após o grampo acima, apenas um valor de 1 (negativo) precisa ser assinado estendido.

Desquantização de pontos de extremidade de cor

Considerando os pontos de extremidade não compactados, a próxima etapa é executar uma desquantização inicial dos pontos de extremidade de cor. Isso envolve três etapas:

  • Uma desquantização das paletas de cores
  • Interpolação das paletas
  • Finalização de desquantização

Separar o processo de desquantização em duas partes (desquantização da paleta de cores antes da interpolação e da desquantização final após a interpolação) reduz o número de operações de multiplicação necessárias quando comparado a um processo de desquantização completo antes da interpolação de paleta.

O código a seguir ilustra o processo de recuperação de estimativas dos valores de cores originais de 16 bits e, em seguida, o uso dos valores de peso fornecidos para adicionar seis valores de cores adicionais à paleta. A mesma operação é executada em cada canal.

int aWeight3[] = {0, 9, 18, 27, 37, 46, 55, 64};
int aWeight4[] = {0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64};

// c1, c2: endpoints of a component
void generate_palette_unquantized(UINT8 uNumIndices, int c1, int c2, int prec, UINT16 palette[NINDICES])
{
    int* aWeights;
    if(uNumIndices == 8)
        aWeights = aWeight3;
    else  // uNumIndices == 16
        aWeights = aWeight4;

    int a = unquantize(c1, prec); 
    int b = unquantize(c2, prec);

    // interpolate
    for(int i = 0; i < uNumIndices; ++i)
        palette[i] = finish_unquantize((a * (64 - aWeights[i]) + b * aWeights[i] + 32) >> 6);
}

O próximo exemplo de código demonstra o processo de interpolação, com as seguintes observações:

  • Como a gama completa de valores de cores para a função desquantizada (abaixo) é de -32768 a 65535, o interpolador é implementado usando aritmética assinada de 17 bits.
  • Após a interpolação, os valores são passados para a função finish_unquantize (descrita no terceiro exemplo nesta seção), que aplica a escala final.
  • Todos os descompactadores de hardware são necessários para retornar resultados precisos de bit com essas funções.
int unquantize(int comp, int uBitsPerComp)
{
    int unq, s = 0;
    switch(BC6H::FORMAT)
    {
    case UNSIGNED_F16:
        if(uBitsPerComp >= 15)
            unq = comp;
        else if(comp == 0)
            unq = 0;
        else if(comp == ((1 << uBitsPerComp) - 1))
            unq = 0xFFFF;
        else
            unq = ((comp << 16) + 0x8000) >> uBitsPerComp;
        break;

    case SIGNED_F16:
        if(uBitsPerComp >= 16)
            unq = comp;
        else
        {
            if(comp < 0)
            {
                s = 1;
                comp = -comp;
            }

            if(comp == 0)
                unq = 0;
            else if(comp >= ((1 << (uBitsPerComp - 1)) - 1))
                unq = 0x7FFF;
            else
                unq = ((comp << 15) + 0x4000) >> (uBitsPerComp-1);

            if(s)
                unq = -unq;
        }
        break;
    }
    return unq;
}

finish_unquantize é chamada após a interpolação de paleta. A função unquatize adia o dimensionamento em 31/32 para assinado, 31/64 para não assinado. Esse comportamento é necessário para obter o valor final em meio intervalo válido (-0x7BFF ~ 0x7BFF) após a interpolação de paleta ser concluída para reduzir o número de multiplicações necessárias. finish_unquantize aplica a escala final e retorna um valor unsigned curto que é reinterpretado em half.

unsigned short finish_unquantize(int comp)
{
    if(BC6H::FORMAT == UNSIGNED_F16)
    {
        comp = (comp * 31) >> 6;                                         // scale the magnitude by 31/64
        return (unsigned short) comp;
    }
    else // (BC6H::FORMAT == SIGNED_F16)
    {
        comp = (comp < 0) ? -(((-comp) * 31) >> 5) : (comp * 31) >> 5;   // scale the magnitude by 31/32
        int s = 0;
        if(comp < 0)
        {
            s = 0x8000;
            comp = -comp;
        }
        return (unsigned short) (s | comp);
    }
}

Compactação de bloco de textura no Direct3D 11