BC6H-Format

Das BC6H-Format ist ein Texturkomprimierungsformat, das für die Unterstützung von HDR-Farbräumen (High Dynamic Range) in Quelldaten entwickelt wurde.

Über BC6H/DXGI_FORMAT_BC6H

Das BC6H-Format bietet eine hochwertige Komprimierung für Bilder, die drei HDR-Farbkanäle verwenden, mit einem 16-Bit-Wert für jeden Farbkanal des Wertes (16:16:16). Es gibt keine Unterstützung für einen Alphakanal.

BC6H wird durch die folgenden Aufzählungswerte von DXGI_FORMAT festgelegt:

  • DXGI_FORMAT_BC6H_TYPELESS.
  • DXGI_FORMAT_BC6H_UF16. Dieses BC6H-Format verwendet kein Vorzeichenbit in den 16-Bit-Gleitkommawerten für Farbkanäle.
  • DXGI_FORMAT_BC6H_SF16.Dieses BC6H-Format verwendet ein Vorzeichenbit in den 16-Bit-Gleitkommawerten für Farbkanäle.

Hinweis

Das 16-Bit-Gleitkommaformat für Farbkanäle wird häufig als „halbes“ Gleitkommaformat bezeichnet. Dieses Format weist das folgende Bitlayout auf:

Format Bitlayout
UF16 (Gleitkomma ohne Vorzeichen) 5 Exponentenbits + 11 Mantissebits
SF16 (Gleitkomma mit Vorzeichen) 1 Vorzeichenbit + 5 Exponentenbits + 10 Mantissenbits

 

 

Das BC6H-Format kann für Texturressourcen der Formate Texture2D (einschließlich Arrays), Texture3D oder TextureCube (einschließlich Arrays) verwendet werden. Gleichermaßen gilt dieses Format für alle MIP-Kartenflächen, die diesen Ressourcen zugeordnet sind.

BC6H verwendet eine feste Blockgröße von 16 Byte (128 Bit) und eine feste Kachelgröße von 4x4 Texel. Wie bei früheren BC-Formaten werden Texturbilder, die größer als die unterstützte Kachelgröße (4x4) sind, durch Verwendung mehrerer Blöcke komprimiert. Diese Adressierungsidentität gilt auch für dreidimensionale Bilder, MIP-Karten, Cubemaps und Texturarrays. Alle Bildkacheln müssen das gleiche Format haben.

Einige wichtige Hinweise zum BC6H-Format:

  • BC6H unterstützt die Denormalisierung von Gleitkommazahlen, nicht aber INF (Unendlichkeit) und NaN (keine Zahl). Eine Ausnahme bildet der vorzeichenbehaftete Modus von BC6H (DXGI_FORMAT_BC6H_SF16), der -INF (negative Unendlichkeit) unterstützt. Beachten Sie, dass diese Unterstützung für -INF lediglich ein Artefakt des Formats selbst ist und nicht speziell von Encodern für dieses Format unterstützt wird. Im Allgemeinen sollten Encoder, wenn sie auf INF- (positiv oder negativ) oder NaN-Eingabedaten stoßen, diese Daten in den maximal zulässigen Nicht-INF-Darstellungswert umwandeln und NaN vor der Komprimierung 0 zuordnen.
  • BC6H unterstützt keinen Alphakanal.
  • Der BC6H-Decoder führt eine Dekomprimierung durch, bevor er die Texturfilterung vornimmt.
  • Die BC6H-Dekomprimierung muss bitgenau sein, d. h. die Hardware muss Ergebnisse liefern, die mit dem in dieser Dokumentation beschriebenen Decoder identisch sind.

Implementierung von BC6H

Ein BC6H-Block besteht aus Modusbits, komprimierten Endpunkten, komprimierten Indizes und einem optionalen Partitionsindex. In diesem Format sind 14 verschiedene Modi angegeben.

Eine Endpunktfarbe wird als RGB-Tripel gespeichert. BC6H definiert eine Palette von Farben auf einer ungefähren Linie über eine Reihe von definierten Farbendpunkten. Außerdem kann eine Kachel je nach Modus in zwei Regionen unterteilt oder als eine einzige Region behandelt werden, wobei eine Kachel mit zwei Regionen für jede Region einen eigenen Satz von Farbendpunkten hat. BC6H speichert einen Palettenindex pro Texel.

Im Fall von zwei Regionen gibt es 32 mögliche Partitionen.

Dekodierung des BC6H-Formats

Der nachstehende Pseudocode zeigt die Schritte zur Dekomprimierung des Pixels an (x,y) anhand des 16-Byte-Blocks BC6H.

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);
}

Die folgende Tabelle enthält die Bitanzahl und die Werte für jedes der 14 möglichen Formate für BC6H-Blöcke.

Mode Partitionsindizes Partition Farbendpunkte Modusbits
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)

 

Jedes Format in dieser Tabelle kann durch die Modusbits eindeutig identifiziert werden. Die ersten zehn Modi werden für Kacheln mit zwei Bereichen verwendet, und das Modus-Bitfeld kann entweder zwei oder fünf Bits lang sein. Diese Blöcke enthalten auch Felder für die komprimierten Farbendpunkte (72 oder 75 Bit), die Partition (5 Bit) und die Partitionsindizes (46 Bit).

Für die komprimierten Farbendpunkte geben die Werte in der vorstehenden Tabelle die Genauigkeit der gespeicherten RGB-Endpunkte und die Anzahl der für jeden Farbwert verwendeten Bits an. Der Modus 3 legt beispielsweise eine Farbendpunktpräzisionsstufe von 11 und die Anzahl der Bits fest, die zum Speichern der Delta-Werte der transformierten Endpunkte für die Farben Rot, Blau und Grün verwendet werden (5, 4 bzw. 4). Modus 10 verwendet keine Deltakompression und speichert stattdessen alle vier Farbendpunkte explizit.

Die letzten vier Blockmodi werden für Kacheln mit einem Bereich verwendet, bei denen das Modusfeld aus 5 Bits besteht. Diese Blöcke enthalten Felder für die Endpunkte (60 Bit) und die komprimierten Indizes (63 Bit). Modus 11 (wie Modus 10) verwendet keine Deltakompression und speichert stattdessen beide Farbendpunkte explizit.

Die Modi 10011, 10111, 11011 und 11111 (nicht abgebildet) sind reserviert. Verwenden Sie diese nicht in Ihrem Encoder. Wenn der Hardware Blöcke mit einem dieser Modi übergeben werden, muss der resultierende dekomprimierte Block alle Nullen in allen Kanälen außer dem Alphakanal enthalten.

Bei BC6H muss der Alphakanal unabhängig vom Modus immer 1,0 zurückgeben.

BC6H-Partitionssatz

Es gibt 32 mögliche Partitionssätze für eine Kachel mit zwei Bereichen, die in der folgenden Tabelle definiert sind. Jeder 4x4-Block stellt eine einzelne Form dar.

table of bc6h partition sets

In dieser Tabelle der Partitionssätze ist der fettgedruckte und unterstrichene Eintrag der Ort des Korrekturindexes für Teilsatz 1 (der mit einem Bit weniger angegeben ist). Der Fix-up-Index für die Teilmenge 0 ist immer der Index 0, da die Partitionierung immer so angelegt ist, dass der Index 0 immer in der Teilmenge 0 liegt. Die Reihenfolge der Partitionen verläuft von oben links nach unten rechts, von links nach rechts und dann von oben nach unten.

BC6H-komprimiertes Endpunktformat

bit fields for bc6h compressed endpoint formats

In dieser Tabelle sind die Bitfelder für die komprimierten Endpunkte in Abhängigkeit vom Endpunktformat dargestellt, wobei jede Spalte eine Codierung und jede Zeile ein Bitfeld angibt. Bei diesem Ansatz werden 82 Bit für Kacheln mit zwei Bereichen und 65 Bit für Kacheln mit einem Bereich benötigt. Ein Beispiel: Die ersten 5 Bits der obigen [16 4]-Codierung für einen Bereich (insbesondere die Spalte ganz rechts) sind die Bits m[4:0], die nächsten 10 Bits sind die Bits rw[9:0], und so weiter, wobei die letzten 6 Bits bw[10:15] enthalten.

Die Feldnamen in der obigen Tabelle sind wie folgt definiert:

Feld Variable
m Modus
d Form-Index
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], wobei i entweder 0 oder 1 ist, bezieht sich auf den 0. bzw. 1. Satz von Endpunkten.

Sign-Erweiterung für Endpunktwerte

Für Kacheln mit zwei Bereichen gibt es vier Endpunktwerte, die durch Vorzeichen erweitert werden können. Endpt[0].A ist nur signiert, wenn das Format ein signiertes Format ist; die anderen Endpunkte sind nur signiert, wenn der Endpunkt transformiert wurde oder wenn das Format ein signiertes Format ist. Der folgende Code veranschaulicht den Algorithmus zur Erweiterung des Vorzeichens von Endpunktwerten in zwei Bereichen.

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]);
      }
    }
}

Bei Kacheln mit einem Bereich ist das Verhalten dasselbe, nur dass endpt[1] entfernt wurde.

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]);
    }
}

Transformieren der Inversion für Endpunktwerte

Bei Kacheln mit zwei Bereichen wendet die Transformation die Umkehrung der Differenzcodierung an und addiert den Basiswert bei endpt[0].A zu den drei anderen Einträgen, was insgesamt 9 Additionsoperationen ergibt. In der folgenden Abbildung wird der Basiswert als „A0“ dargestellt und hat die höchste Gleitkommagenauigkeit. „A1“, „B0“ und „B1“ sind Deltas, die aus dem Ankerwert berechnet werden, und diese Deltawerte werden mit geringerer Präzision dargestellt. (A0 entspricht endpt[0].A, B0 entspricht endpt[0].B, A1 entspricht endpt[1].A und B1 entspricht endpt[1].B.)

calculation of transform inversion endpoint values

Für Kacheln mit einem Bereich gibt es nur einen Delta-Offset und daher nur 3 Additionsoperationen.

Der Dekomprimierer muss sicherstellen, dass die Ergebnisse der inversen Transformation die Genauigkeit von endpt[0].a nicht überschreiten. Im Falle eines Überlaufs müssen die Werte, die sich aus der inversen Transformation ergeben, innerhalb der gleichen Anzahl von Bits umbrochen werden. Wenn die Genauigkeit von A0 „p“-Bits beträgt, dann lautet der Transformationsalgorithmus:

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

Bei vorzeichenbehafteten Formaten müssen die Ergebnisse der Deltarechnung ebenfalls vorzeichenerweitert werden. Wenn die Vorzeichenerweiterung die Erweiterung beider Vorzeichen berücksichtigt, wobei 0 positiv und 1 negativ ist, dann sorgt die Vorzeichenerweiterung von 0 für die obige Klammer. Entsprechend muss nach der obigen Klammer nur ein Wert von 1 (negativ) mit Vorzeichen erweitert werden.

Entquantisierung von Farbendpunkten

Angesichts der unkomprimierten Endpunkte besteht der nächste Schritt darin, eine erste Unquantisierung der Farbendpunkte durchzuführen. Dies umfasst drei Schritte:

  • Eine Entquantisierung der Farbpaletten
  • Interpolation der Paletten
  • Abschluss der Entquantisierung

Die Aufteilung des Entquantisierungsprozesses in zwei Teile (Entquantisierung der Farbpalette vor der Interpolation und endgültige Entquantisierung nach der Interpolation) reduziert die Anzahl der erforderlichen Multiplikationsoperationen im Vergleich zu einem vollständigen Entquantisierungsprozess vor der Paletteninterpolation.

Der folgende Code veranschaulicht das Verfahren zum Abrufen von Schätzungen der ursprünglichen 16-Bit-Farbwerte und zum anschließenden Hinzufügen von 6 zusätzlichen Farbwerten zur Palette anhand der bereitgestellten Gewichtungswerte. Der gleiche Vorgang wird für jeden Kanal durchgeführt.

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);
}

Das nächste Codebeispiel demonstriert den Interpolationsprozess mit den folgenden Beobachtungen:

  • Da der gesamte Bereich der Farbwerte für die Funktion unquantize (unten) von -32768 bis 65535 reicht, wird der Interpolator mit 17-Bit-Arithmetik mit Vorzeichen implementiert.
  • Nach der Interpolation werden die Werte an die Funktion finish_unquantize (beschrieben im dritten Beispiel in diesem Abschnitt) übergeben, welche die endgültige Skalierung vornimmt.
  • Alle Hardware-Dekompressoren müssen mit diesen Funktionen bit-genaue Ergebnisse liefern.
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 wird nach der Paletteninterpolation aufgerufen. Die Funktion unquantize verschiebt die Skalierung um 31/32 für vorzeichenbehaftete und 31/64 für vorzeichenlose Werte. Dieses Verhalten ist erforderlich, um den endgültigen Wert in den gültigen halben Bereich (-0x7BFF ~ 0x7BFF) zu bringen, nachdem die Paletteninterpolation abgeschlossen ist, um die Anzahl der notwendigen Multiplikationen zu reduzieren. finish_unquantize wendet die endgültige Skalierung an und gibt einen vorzeichenlosen kurzen Wert zurück, der in die Hälfte uminterpretiert wird.

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);
    }
}

Texturblockkomprimierung in Direct3D 11