BC7 Format

Le format BC7 est un format de compression de texture utilisé pour la compression de haute qualité des données RVB et RVBA.

Pour plus d’informations sur les modes de bloc du format BC7, consultez Informations de référence sur le mode de format BC7.

À propos de BC7/DXGI_FORMAT_BC7

BC7 est spécifié par les valeurs d’énumération DXGI_FORMAT suivantes :

  • DXGI_FORMAT_BC7_TYPELESS.
  • DXGI_FORMAT_BC7_UNORM.
  • DXGI_FORMAT_BC7_UNORM_SRGB.

Le format BC7 peut être utilisé pour les ressources de texture Texture2D (y compris les tableaux), Texture3D ou TextureCube (y compris les tableaux). De même, ce format s’applique à toutes les surfaces MIP-map associées à ces ressources.

BC7 utilise une taille de bloc fixe de 16 octets (128 bits) et une taille de vignette fixe de 4x4 texels. Comme avec les formats BC précédents, les images de texture supérieures à la taille de vignette prise en charge (4x4) sont compressées à l’aide de plusieurs blocs. Cette identité d’adressage s’applique également aux images tridimensionnelles et aux cartes MIP, aux cubemaps et aux tableaux de texture. Toutes les vignettes d’image doivent avoir le même format.

BC7 compresse les images de données fixes à trois canaux (RVB) et à quatre canaux (RVBA). En règle générale, les données sources sont de 8 bits par composant de couleur (canal), bien que le format soit capable d’encoder des données sources avec des bits plus élevés par composant de couleur. Toutes les vignettes d’image doivent avoir le même format.

Le décodeur BC7 effectue la décompression avant l’application du filtrage de texture.

Le matériel de décompression BC7 doit être précis en bits ; autrement dit, le matériel doit retourner des résultats identiques aux résultats retournés par le décodeur décrit dans ce document.

Implémentation BC7

Une implémentation BC7 peut spécifier l’un des 8 modes, le mode étant spécifié dans le bit le moins significatif du bloc de 16 octets (128 bits). Le mode est encodé par zéro ou plusieurs bits avec une valeur 0 suivie d’un 1.

Un bloc BC7 peut contenir plusieurs paires de points de terminaison. Pour les besoins de cette documentation, l’ensemble d’index qui correspondent à une paire de points de terminaison peut être appelé « sous-ensemble ». En outre, dans certains modes de bloc, la représentation du point de terminaison est encodée sous une forme qui, dans le cadre de cette documentation, doit être appelée « RVBP », où le bit « P » représente un bit partagé le moins significatif pour les composants de couleur du point de terminaison. Par exemple, si la représentation du point de terminaison pour le format est « RVB 5.5.5.1 », le point de terminaison est interprété comme une valeur RVB 6.6.6, où l’état du P-bit définit le bit le moins significatif de chaque composant. De même, pour les données sources avec un canal alpha, si la représentation du format est « RGBAP 5.5.5.5.1 », le point de terminaison est interprété comme RVBA 6.6.6.6. Selon le mode bloc, vous pouvez spécifier le bit le moins significatif partagé pour les deux points de terminaison d’un sous-ensemble individuellement (2 P bits par sous-ensemble), ou partagé entre les points de terminaison d’un sous-ensemble (1 P-bit par sous-ensemble).

Pour les blocs BC7 qui n’encodent pas explicitement le composant alpha, un bloc BC7 se compose de bits de mode, de bits de partition, de points de terminaison compressés, d’index compressés et d’un P-bit facultatif. Dans ces blocs, les points de terminaison ont une représentation RVB uniquement et le composant alpha est décodé en tant que 1.0 pour tous les texels dans les données sources.

Pour les blocs BC7 qui ont des composants de couleur et alpha combinés, un bloc se compose de bits de mode, de points de terminaison compressés, d’index compressés, de bits de partition facultatifs et d’un P-bit. Dans ces blocs, les couleurs des points de terminaison sont exprimées au format RVBA, et les valeurs des composants alpha sont interpolées en même temps que les valeurs des composants de couleur.

Pour les blocs BC7 qui ont des composants de couleur et alpha distincts, un bloc se compose de bits de mode, de bits de rotation, de points de terminaison compressés, d’index compressés et d’un bit de sélecteur d’index facultatif. Ces blocs ont un vecteur RVB efficace [R, G, B] et un canal alpha scalaire [A] encodé séparément.

Le tableau suivant répertorie les composants de chaque type de bloc.

Le bloc BC7 contient... bits de mode bits de rotation bit du sélecteur d’index bits de partition points de terminaison compressés P-bit index compressés
composants de couleur uniquement obligatoire N/A N/A obligatoire obligatoire facultatif obligatoire
couleur + alpha combiné obligatoire N/A N/A facultatif obligatoire facultatif obligatoire
couleur et alpha séparés obligatoire obligatoire facultatif N/A obligatoire N/A obligatoire

 

BC7 définit une palette de couleurs sur une ligne approximative entre deux points de terminaison. La valeur de mode détermine le nombre de paires de points de terminaison interpolantes par bloc. BC7 stocke un index de palette par texel.

Pour chaque sous-ensemble d’index qui correspond à une paire de points de terminaison, l’encodeur corrige l’état d’un bit des données d’index compressées pour ce sous-ensemble. Pour ce faire, il choisit un ordre de point de terminaison qui permet à l’index de l’index « correctif » désigné de définir son bit le plus significatif sur 0, et qui peut ensuite être ignoré, en enregistrant un bit par sous-ensemble. Pour les modes de bloc avec un seul sous-ensemble, l’index de correction est toujours index 0.

Décodage du format BC7

Le pseudocode suivant décrit les étapes à suivre pour décompresser le pixel à (x,y) compte tenu du bloc BC7 de 16 octets.

decompress_bc7(x, y, block)
{
    mode = extract_mode(block);
    
    //decode partition data from explicit partition bits
    subset_index = 0;
    num_subsets = 1;
    
    if (mode.type == 0 OR == 1 OR == 2 OR == 3 OR == 7)
    {
        num_subsets = get_num_subsets(mode.type);
        partition_set_id = extract_partition_set_id(mode, block);
        subset_index = get_partition_index(num_subsets, partition_set_id, x, y);
    }
    
    //extract raw, compressed endpoint bits
    UINT8 endpoint_array[2 * num_subsets][4] = extract_endpoints(mode, block);
    
    //decode endpoint color and alpha for each subset
    fully_decode_endpoints(endpoint_array, mode, block);
    
    //endpoints are now complete.
    UINT8 endpoint_start[4] = endpoint_array[2 * subset_index];
    UINT8 endpoint_end[4]   = endpoint_array[2 * subset_index + 1];
        
    //Determine the palette index for this pixel
    alpha_index     = get_alpha_index(block, mode, x, y);
    alpha_bitcount  = get_alpha_bitcount(block, mode);
    color_index     = get_color_index(block, mode, x, y);
    color_bitcount  = get_color_bitcount(block, mode);

    //determine output
    UINT8 output[4];
    output.rgb = interpolate(endpoint_start.rgb, endpoint_end.rgb, color_index, color_bitcount);
    output.a   = interpolate(endpoint_start.a,   endpoint_end.a,   alpha_index, alpha_bitcount);
    
    if (mode.type == 4 OR == 5)
    {
        //Decode the 2 color rotation bits as follows:
        // 00 – Block format is Scalar(A) Vector(RGB) - no swapping
        // 01 – Block format is Scalar(R) Vector(AGB) - swap A and R
        // 10 – Block format is Scalar(G) Vector(RAB) - swap A and G
        // 11 - Block format is Scalar(B) Vector(RGA) - swap A and B
        rotation = extract_rot_bits(mode, block);
        output = swap_channels(output, rotation);
    }
    
}

Le pseudocode suivant décrit les étapes à suivre pour décoder entièrement la couleur du point de terminaison et les composants alpha pour chaque sous-ensemble avec un bloc BC7 de 16 octets.

fully_decode_endpoints(endpoint_array, mode, block)
{
    //first handle modes that have P-bits
    if (mode.type == 0 OR == 1 OR == 3 OR == 6 OR == 7)
    {
        for each endpoint i
        {
            //component-wise left-shift
            endpoint_array[i].rgba = endpoint_array[i].rgba << 1;
        }
        
        //if P-bit is shared
        if (mode.type == 1) 
        {
            pbit_zero = extract_pbit_zero(mode, block);
            pbit_one = extract_pbit_one(mode, block);
            
            //rgb component-wise insert pbits
            endpoint_array[0].rgb |= pbit_zero;
            endpoint_array[1].rgb |= pbit_zero;
            endpoint_array[2].rgb |= pbit_one;
            endpoint_array[3].rgb |= pbit_one;  
        }
        else //unique P-bit per endpoint
        {  
            pbit_array = extract_pbit_array(mode, block);
            for each endpoint i
            {
                endpoint_array[i].rgba |= pbit_array[i];
            }
        }
    }

    for each endpoint i
    {
        // Color_component_precision & alpha_component_precision includes pbit
        // left shift endpoint components so that their MSB lies in bit 7
        endpoint_array[i].rgb = endpoint_array[i].rgb << (8 - color_component_precision(mode));
        endpoint_array[i].a = endpoint_array[i].a << (8 - alpha_component_precision(mode));

        // Replicate each component's MSB into the LSBs revealed by the left-shift operation above
        endpoint_array[i].rgb = endpoint_array[i].rgb | (endpoint_array[i].rgb >> color_component_precision(mode));
        endpoint_array[i].a = endpoint_array[i].a | (endpoint_array[i].a >> alpha_component_precision(mode));
    }
        
    //If this mode does not explicitly define the alpha component
    //set alpha equal to 1.0
    if (mode.type == 0 OR == 1 OR == 2 OR == 3)
    {
        for each endpoint i
        {
            endpoint_array[i].a = 255; //i.e. alpha = 1.0f
        }
    }
}

Pour générer chaque composant interpolé pour chaque sous-ensemble, utilisez l’algorithme suivant : laissez « c » être le composant à générer ; laissez « e0 » être le composant du point de terminaison 0 du sous-ensemble ; et laissez « e1 » être le composant du point de terminaison 1 du sous-ensemble.

UINT16 aWeights2[] = {0, 21, 43, 64};
UINT16 aWeights3[] = {0, 9, 18, 27, 37, 46, 55, 64};
UINT16 aWeights4[] = {0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64};

UINT8 interpolate(UINT8 e0, UINT8 e1, UINT8 index, UINT8 indexprecision)
{
    if(indexprecision == 2)
        return (UINT8) (((64 - aWeights2[index])*UINT16(e0) + aWeights2[index]*UINT16(e1) + 32) >> 6);
    else if(indexprecision == 3)
        return (UINT8) (((64 - aWeights3[index])*UINT16(e0) + aWeights3[index]*UINT16(e1) + 32) >> 6);
    else // indexprecision == 4
        return (UINT8) (((64 - aWeights4[index])*UINT16(e0) + aWeights4[index]*UINT16(e1) + 32) >> 6);
}

Le pseudocode suivant montre comment extraire des index et des nombres de bits pour les composants de couleur et alpha. Les blocs avec une couleur et une alpha distinctes ont également deux jeux de données d’index : un pour le canal vectoriel et un pour le canal scalaire. Pour le mode 4, ces index sont de largeurs différentes (2 ou 3 bits), et il existe un sélecteur un bits qui spécifie si les données vectorielles ou scalaires utilisent les index 3 bits. (L’extraction du nombre de bits alpha est similaire à l’extraction du nombre de bits de couleur, mais avec un comportement inverse basé sur le bit idxMode .)

bitcount get_color_bitcount(block, mode)
{
    if (mode.type == 0 OR == 1)
        return 3;
    
    if (mode.type == 2 OR == 3 OR == 5 OR == 7)
        return 2;
    
    if (mode.type == 6)
        return 4;
        
    //The only remaining case is Mode 4 with 1-bit index selector
    idxMode = extract_idxMode(block);
    if (idxMode == 0)
        return 2;
    else
        return 3;
}

Compression de bloc de texture dans Direct3D 11