DirectX コンテナ

概要

DirectX コンテナ (DXContainer) ファイル形式は、DirectX ランタイムをターゲットとするコンパイル済みシェーダー用のバイナリファイル形式です。このファイル形式は、DXIL コンテナまたは DXBC ファイル形式とも呼ばれます。このファイル形式は DXIL または DXBC コンパイル済みシェーダーを含めるために使用できるため、LLVM における用語は単に DirectX コンテナとなっています。

DirectX コンテナファイルは、コンパイラや関連ツール、DirectX ランタイム、プロファイリングツール、その他のユーザーによって読み取られます。このドキュメントは、多くのユーザーのためにファイル形式をより完全に文書化するために、LLVM での実装を補完するものです。

基本構造

DXContainer ファイルはヘッダーで始まり、その後、オブジェクトファイルのセクションに似た「パート」のシーケンスが続きます。各パートにはパートヘッダーと、ヘッダー後の定義された形式のデータが何バイトか含まれています。

DX コンテナのデータ構造は、バイナリファイルではリトルエンディアンでエンコードされています。

このファイルで説明または参照されるすべてのデータ構造の LLVM バージョンは、llvm/include/llvm/BinaryFormat/DXContainer.h で定義されています。このドキュメントの理解を容易にするために、以下のブロックに疑似コードが記載されていますが、ヘッダーを利用して読むことで最も明確になります。

ファイルヘッダー

struct Header {
  uint8_t Magic[4];
  uint8_t Digest[16];
  uint16_t MajorVersion;
  uint16_t MinorVersion;
  uint32_t FileSize;
  uint32_t PartCount;
};

DXContainer ヘッダーは上記の疑似定義と一致します。ファイル形式を示すために、値 DXBC を持つ 4 文字のコード (マジックナンバー) で始まります。

Digest は、独自のアルゴリズムで計算され、バイトコードバリデーターによってバイナリにエンコードされた 128 ビットハッシュダイジェストです。

MajorVersionMinorVersion は、ファイル形式バージョン 1.0 をエンコードします。

残りのフィールドは、ファイルサイズとパート数を表す 32 ビット符号なし整数をエンコードします。

パートヘッダーに続いて、各パートヘッダーのオフセットを指定する PartCount 個の 32 ビット符号なし整数の配列が続きます。

パートデータ

struct PartHeader {
  uint8_t Name[4];
  uint32_t Size;
}

各パートはパートヘッダーで始まります。パートヘッダーには、4 文字のパート名と、パートデータのサイズを指定する 32 ビット符号なし整数が含まれます。パートヘッダーの後に、パートを構成する Size バイトのデータが続きます。この形式では、パートの 32 ビットアライメントは明示的に必須ではありませんが、LLVM はライターコードでこの制限を実装しています (これは良い考えであるため)。LLVM オブジェクトリーダーコードは、他のコンパイラによって生成されたアライメントが正しくない入力による未定義の動作を回避するために、入力が正しくアライメントされているとは想定していません。

パートフォーマット

パート名は、パートデータの形式を示します。DXC および FXC によって使用されるパートヘッダーは 24 個あります。コンパイルされたすべてのシェーダーにすべてのパートが含まれているわけではありません。以下のリストでは、DXC によってのみ生成されるパートには † が、FXC によってのみ生成されるパートには * が付いています。

  1. DXIL† - DXIL バイトコードを格納します。

  2. HASH† - シェーダーの MD5 ハッシュを格納します。

  3. ILDB† - モジュールに埋め込まれた LLVM デバッグ情報を含む DXIL バイトコードを格納します。

  4. ILDN† - 外部デバッグ情報用のシェーダーデバッグ名を格納します。

  5. ISG1 - Shader Model 5.1+ の入力署名を格納します。

  6. ISGN* - Shader Model 4 以前の入力署名を格納します。

  7. OSG1 - Shader Model 5.1+ の出力署名を格納します。

  8. OSG5* - Shader Model 5 の出力署名を格納します。

  9. OSGN* - Shader Model 4 以前の出力署名を格納します。

  10. PCSG* - Shader Model 5.1 以前のパッチ定数署名を格納します。

  11. PDBI† - PDB 情報を格納します。

  12. PRIV - 任意のプライベートデータを格納します (FXC または DXC のどちらでもエンコードされません)。

  13. PSG1 - Shader Model 6+ のパッチ定数署名を格納します。

  14. PSV0 - パイプライン状態検証データを格納します。

  15. RDAT† - ランタイムデータを格納します。

  16. RDEF* - リソース定義を格納します。

  17. RTS0 - コンパイル済みのルート署名を格納します。

  18. SFI0 - シェーダー機能フラグを格納します。

  19. SHDR* - コンパイル済みの DXBC バイトコードを格納します。

  20. SHEX* - コンパイル済みの DXBC バイトコードを格納します。

  21. DXBC* - コンパイル済みの DXBC バイトコードを格納します。

  22. SRCI† - シェーダーソース情報を格納します。

  23. STAT† - シェーダーステータスを格納します。

  24. VERS† - シェーダーコンパイラのバージョン情報を格納します。

DXIL パート

DXIL パートは、ProgramHeaderBitcodeHeader、およびビットコードシリアライズされた LLVM 3.7 IR モジュールの 3 つのデータ構造で構成されます。

ProgramHeader には、シェーダーモデルのバージョンとパイプラインステージの列挙値が含まれています。これにより、含まれるシェーダービットコードのターゲットプロファイルが識別されます。

BitcodeHeader には、DXIL バージョン情報が含まれており、ビットコードデータの開始位置を参照します。

HASH パート

HASH パートには、シェーダーハッシュフラグを含む 32 ビット符号なし整数と、128 ビットの MD5 ハッシュダイジェストが含まれています。フラグフィールドには、フラグがないことを示す値 0、または、バイナリを生成したソースコードを含むファイルハッシュが計算されたことを示す値 1 を指定できます。

プログラム署名 (SG1) パート

struct ProgramSignatureHeader {
  uint32_t ParamCount;
  uint32_t FirstParamOffset;
}

プログラム署名パート (ISG1、OSG1、PSG1) はすべて、入力、出力、およびパッチ情報をエンコードするために同じデータ構造を使用します。ProgramSignatureHeader には、署名パラメーターの数と最初のパラメーターのオフセットを指定する 2 つの 32 ビット符号なし整数が含まれています。

ProgramSignatureHeader の先頭から FirstParamOffset バイトの位置から、ParamCount 個の ProgramSignatureElement 構造体が書き込まれます。ProgramSignatureElements に続いて、32 バイトアライメントでパディングされた、null で終わる文字列の文字列テーブルがあります。この文字列テーブルは、LLVM で実装されている DWARF 文字列テーブル形式と一致します。

ProgramSignatureElement は、文字列テーブルへのオフセットを指定する NameOffset 値をエンコードします。値 0 は名前がないことを示します。ここでエンコードされるオフセットは、文字列テーブルの先頭ではなく、ProgramSignatureHeader の先頭からのオフセットです。

ProgramSignatureElement には、llvm/include/llvm/BinaryFormat/DXContainerConstants.def で定義されている複数の列挙フィールドが含まれています。これらのフィールドは、D3D システム値、データの種類、およびその精度要件をエンコードします。

PSV0 パート

パイプライン状態検証データは、バージョン管理されたランタイム情報構造をエンコードします。これらの構造体は、バージョン番号をエンコードする代わりに、構造体のサイズをエンコードし、構造体の新しい各バージョンが加算的であるスキームを使用します。これにより、リーダーはエンコードされたサイズを既知の構造体のサイズと比較することにより、構造体のバージョンを推測できます。エンコードされたサイズが既知の構造体よりも大きい場合、最大の既知の構造体は、既知の構造体で表されるデータを正しく解析できます。

LLVMでは、関連するデータ構造のバージョンを、llvm::dxbc::PSV名前空間(例:v0v1)の下のバージョン付き名前空間で表現します。v0名前空間の各構造体はベースバージョンであり、v1名前空間の構造体はv0名前空間から継承し、v2構造体はv1構造体から継承するといった具合です。

PSVデータの高レベル構造は以下のとおりです。

  1. RuntimeInfo構造体

  2. リソースバインディング

  3. シグネチャ要素

  4. マスクベクター(出力、入力、入力パッチ、パッチ出力)

PSV0パートのパートヘッダーの直後には、続くRuntimeInfo構造体のサイズを指定する32ビット符号なし整数があります。

RuntimeInfo構造体の直後には、リソースバインディングの数を指定する32ビット符号なし整数があります。リソース数が0より大きい場合は、さらにResourceBindInfo構造体のサイズを指定する32ビット符号なし整数が続きます。これに続いて、指定されたサイズの指定された数の構造体が続きます(これにより構造体のバージョンが推測できます)。

データのバージョン0の場合、これでパートデータは終了です。

PSV0シグネチャ要素

シグネチャ要素は概念的には単一の概念ですが、データは3つの異なるブロックでエンコードされます。最初のブロックは文字列テーブル、2番目のブロックはインデックステーブル、3番目のブロックは要素自体であり、これらはさらに入力、出力、パッチ定数またはプリミティブ要素で分離されます。

シグネチャ要素は、SG1パートでキャプチャされるデータとほぼ同じデータをキャプチャします。インデックステーブルを使用することで、よりコンパクトな最終表現のためにデータの重複を排除できます。

文字列テーブルは、テーブルサイズを指定する32ビット符号なし整数で始まります。この文字列テーブルは、LLVMに実装されているDXContainer形式を使用します。この形式では、文字列テーブルの先頭にnullバイトが付加され、オフセット0がnull文字列となり、32バイトアラインメントにパディングされます。

インデックステーブルは、テーブルのサイズを指定する32ビット符号なし整数で始まり、その後にテーブルを表すその数の32ビット符号なし整数が続きます。インデックステーブルは、繰り返されるシーケンスを重複排除する場合があります(DXCとClangの両方が実行します)。インデックスは、シグネチャ要素が記述するフラット化された集約表現におけるインデックスを示します。単一のセマンティックは、メンバーの異なる属性を示すために、このテーブルに複数のエントリを持つ場合があります。

たとえば、次のコードがあるとします。

struct VSOut_1
{
    float4 f3 : VOUT2;
    float3 f4 : VOUT3;
};


struct VSOut
{
    float4 f1 : VOUT0;
    float2 f2[4] : VOUT1;
    VSOut_1 s;
    int4 f5 : VOUT4;
};

void main(out VSOut o1 : A) {
}

セマンティックAは、5つの出力シグネチャ要素に展開されます。これらの要素は次のとおりです。

注意

以下の例では、行がインデックスと一致するのは偶然ですが、複数のセマンティックを持つより複雑な例ではそうではありません。

  1. インデックス0は行0から始まり、4つの列を含み、float32です。これはソースのf1を表します。

  2. インデックス1、2、3、および4は行1から始まり、2つの列を含み、float32です。これはソースのf2を表し、行1〜4にまたがっています。

  3. インデックス5は行5から始まり、4つの列を含み、float32です。これはソースのf3を表します。

  4. インデックス6は行6から始まり、3つの列を含み、float32です。これはf4を表します。

  5. インデックス7は行7から始まり、4つの列を含み、符号付き32ビット整数です。これはソースのf5を表します。

LLVM obj2yamlツールは、このデータをPSVから解析し、人間が読めるYAMLで表示できます。上記の例では、次の出力が生成されます。

SigOutputElements:
  - Name:            A
    Indices:         [ 0 ]
    StartRow:        0
    Cols:            4
    StartCol:        0
    Allocated:       true
    Kind:            Arbitrary
    ComponentType:   Float32
    Interpolation:   Linear
    DynamicMask:     0x0
    Stream:          0
  - Name:            A
    Indices:         [ 1, 2, 3, 4 ]
    StartRow:        1
    Cols:            2
    StartCol:        0
    Allocated:       true
    Kind:            Arbitrary
    ComponentType:   Float32
    Interpolation:   Linear
    DynamicMask:     0x0
    Stream:          0
  - Name:            A
    Indices:         [ 5 ]
    StartRow:        5
    Cols:            4
    StartCol:        0
    Allocated:       true
    Kind:            Arbitrary
    ComponentType:   Float32
    Interpolation:   Linear
    DynamicMask:     0x0
    Stream:          0
  - Name:            A
    Indices:         [ 6 ]
    StartRow:        6
    Cols:            3
    StartCol:        0
    Allocated:       true
    Kind:            Arbitrary
    ComponentType:   Float32
    Interpolation:   Linear
    DynamicMask:     0x0
    Stream:          0
  - Name:            A
    Indices:         [ 7 ]
    StartRow:        7
    Cols:            4
    StartCol:        0
    Allocated:       true
    Kind:            Arbitrary
    ComponentType:   SInt32
    Interpolation:   Constant
    DynamicMask:     0x0
    Stream:          0

各タイプのシグネチャ要素の数は、llvm::dxbc::PSV::v1::RuntimeInfo構造体にエンコードされています。要素カウント値のいずれかがゼロ以外の場合、ProgramSignatureElement構造体のサイズが、その構造体のバージョン管理を可能にするために、次にエンコードされます。現時点では、バージョンは1つのみです。サイズフィールドに続いて、入力、出力、パッチ定数またはプリミティブの順に、指定された数のシグネチャ要素があります。

シグネチャ要素に続いて、マスクベクターのシーケンスが32ビット整数のシーケンスとしてエンコードされます。マスク内の各32ビット整数は、8つの入力/出力/パッチまたはプリミティブ要素の値をエンコードします。マスクベクターは、最下位ビットから最上位ビットへと埋められ、追加された各要素が前の要素を左にシフトします。リーダーは、マスクベクターの読み方を知るために、RuntimeInfo構造体でエンコードされたベクターの合計数を確認する必要があります。

シェーダーがRuntimeInfoUsesViewIDを有効にしている場合、出力マスクベクターが含まれます。出力マスクベクターは、32ビット符号なし整数の4つの配列です。4つの配列のそれぞれは、出力ストリームに対応します。ジオメトリシェーダーは最大4つの出力ストリームを持ち、他のすべてのシェーダーステージは1つの出力ストリームのみをサポートします。マスクベクターの各ビットは、ViewIDに依存する出力シグネチャからの1つの列を識別します。

シェーダーがUsesViewIDを有効にしており、ハルシェーダーであり、パッチ定数またはプリミティブベクター要素がある場合、パッチ定数またはプリミティブベクターマスクが含まれます。これは出力マスクベクターと同じ構造です。マスクベクターの各ビットは、ViewIDに依存するパッチ定数出力の1つの列を識別します。

次のマスクベクターのシリーズは、出力マスクベクターと構造が似ていますが、追加の次元が含まれています。

シェーダーに入力と出力がある場合は、出力/入力マップが次にエンコードされます。出力/入力マスクは、各入力の各列によってどの出力が影響を受けるかをエンコードします。各マスクベクターのサイズは、出力最大ベクターのサイズ * 入力数 * 4(各コンポーネントの場合)です。マスクベクターの各ビットは、出力の1つの列と入力の1つの列を識別します。値が1の場合、出力が入力の影響を受けることを意味します。

シェーダーがハルシェーダーであり、入力とパッチ出力がある場合は、入力からパッチへのマップが次に含まれます。これは出力/入力マップと同じ構造です。次元は、パッチ定数またはプリミティブベクターマスクのサイズ * 入力数 * 4(各コンポーネントの場合)によって定義されます。マスクベクターの各ビットは、パッチ定数出力の1つの列と入力の1つの列を識別します。値が1の場合、出力が入力の影響を受けることを意味します。

シェーダーがドメインシェーダーであり、出力とパッチ出力がある場合は、出力パッチマップが次に含まれます。これは出力/入力マップと同じ構造です。次元は、パッチ定数またはプリミティブベクターマスクのサイズ * 出力数 * 4(各コンポーネントの場合)によって定義されます。マスクベクターの各ビットは、パッチ定数入力の1つの列と出力の1つの列を識別します。値が1の場合、出力がプリミティブ入力の影響を受けることを意味します。

SFI0パート

SFI0パートは、機能フラグの64ビット符号なし整数ビットマスクをエンコードします。これは、シェーダーが必要とするオプションの機能を指定します。フラグ値は、llvm/include/llvm/BinaryFormat/DXContainerConstants.defで定義されています。