glslangとDirectXShaderCompiler

# はじめに
DirectX 12とVulkanのために, glslangとDirectXShaderCompiler (dxc) のライブラリを作成し, プログラム内でシェーダのコンパイルができるようにします.

ツールで事前コンパイルの方法にしないのは, 単純に開発時やチュートリアルのためのプログラムを作るときに楽だからです.

シェーダ言語と中間言語とバイナリの関係は, 現在は次のようになっています. 歴史的に, GLSLがHLSLに比較して機能が少なく当初は中間言語がなかったり, HLSLは当初は中間言語ではなくアセンブラだったりします.

```
HLSL -> DXIL -> Vendor Specific Binary
GLSL -> SPIR-V -> Vendor Specific Binary 
```

dxcには, HLSLからSPIR-Vへのコンパイル機能があります.
glslangについては, HLSLをSPIR-Vへ変換する機能は開発中です.

> Status: Partially complete. Semantics are not reference quality and input is not validated. This is in contrast to the DXC project, which receives a much larger investment and attempts to have definitive/reference-level semantics.

[KhronosGroup/glslang](https://github.com/KhronosGroup/glslang)から

最適解はまだわかりませんが, HLSLからDXILとSPIR-Vが安定しているのではないかと思います. HLSLとGLSLを両方メンテナンスすることは避けたいです.

基本的なソフトウェアエンジニアリングについて知っていることが前提です. Visual Studio, Make, CMake, Python, Ninjaの説明もしません.

# ビルド
## Windows

### glslang
[リポジトリGitHub](https://github.com/KhronosGroup/glslang).
Visual StudioからInstallを行いたい場合, ファイル属性もローカルに反映してしまうため, 次のようにして抑制します.
`git config --global core.fileMode false`

Win32 Runtimeを変更するには, `CMakeLists.txt`の中で`ChooseMSVCCRT.cmake`をincludeする前に指定します.

```
set(LLVM_USE_CRT_RELEASE "MT")
set(LLVM_USE_CRT_DEBUG "MTd")
include(ChooseMSVCCRT.cmake)
```

glslangディレクトリ内で, ビルド環境を作成します.

```
$ mkdir build
$ cd build
$ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="path" -G "generator" ..
```

### DirectXShaderCompiler
#### ビルド

結果を先に書くと, ビルド済みのものをダウンロードする方が安定すると思います.
Installが上手く動かず必要なバイナリやヘッダーがわかりにくいです.

[リポジトリGitHub](https://github.com/Microsoft/DirectXShaderCompiler.git).
ATLと.Net Framework 4.5が必要です, Visual Studio Installerでインストールします. 

```
$ git clone https://github.com/Microsoft/DirectXShaderCompiler.git
$ cd DirectXShaderCompiler
$ git submodule update --init
$ .\utils\hct\hctstart.cmd -x64 [path-to-sources] [path-to-build]
$ .\utils\hct\hctbuild -vs2019 -spirv -x64
```

使いそうなものだけ, `utils\hct\hctbuild --help`で説明が見れますが, `utils\hct\hctbuild`を直接見た方が早いです. `-spirv`のようにヘルプに説明がないものがある.

- `-s`: プロジェクトファイルを生成するだけ
    -  Windowsでは, Visual Studioで開いてビルド, インストールするのが正解. 後述の`-Debug`, `-Release`オプションではデバッグ・リリースビルドの区別ができない.
- `-no-parallel`: Ninjaでは効果ないように見える. Visual Studioなら自分で修正すればいいので, 存在価値が不明, make用か?
- `-no-dxilconv`
- `-vs2017`, `-vs2019`: コンパイラ
- `-ninja`: ビルド環境
- `-x86`, `-x64`, `-arm`, `-arm64`: アーキテクチャ
- `-Debug`, `-Release`

#### 使い方

まず, HLSLからDXILへコンパイル.

[Wiki: Using-dxc.exe-and-dxcompiler.dll](https://github.com/microsoft/DirectXShaderCompiler/wiki/Using-dxc.exe-and-dxcompiler.dll)

<details><summary>HLSL to DXIL</summary><div>

```cpp
{//HLSL -> DXIL
        CComPtr<IDxcBlobEncoding> sourceBlob = nullptr;
        utils->CreateBlob(hlsl, sizeof(hlsl), 65001, &sourceBlob);
        DxcBuffer source;
        source.Ptr = sourceBlob->GetBufferPointer();
        source.Size = sourceBlob->GetBufferSize();
        source.Encoding = DXC_CP_ACP;

        LPCWSTR args[] = {
            L"shader.hlsl",     // Optional shader source file name for error reporting and for PIX shader source view.
            L"-E", L"main",     // Entry point.
            L"-T", L"vs_6_0",   // Target.
            L"-Zi",             // Enable debug information.
            //L"-Qstrip_reflect", // Strip reflection into a separate blob.
        };

        CComPtr<IDxcResult> results;
        compiler->Compile(
            &source,
            args,
            _countof(args),
            includeHandler,
            IID_PPV_ARGS(&results));

        CComPtr<IDxcBlobUtf8> errors = nullptr;
        results->GetOutput(DXC_OUT_ERRORS, IID_PPV_ARGS(&errors), nullptr);
        if(nullptr != errors && 0 < errors->GetStringLength()) {
            printf("[Error]\n%s\n", errors->GetStringPointer());
        }

        HRESULT status = E_FAIL;
        results->GetStatus(&status);
        if(FAILED(status)) {
            return EXIT_FAILURE;
        }

        // shader binary
        CComPtr<IDxcBlob> shaderBlob = nullptr;
        CComPtr<IDxcBlobUtf16> shaderName = nullptr;
        results->GetOutput(DXC_OUT_OBJECT, IID_PPV_ARGS(&shaderBlob), &shaderName);
        if(nullptr != shaderBlob) {
            printf("A shader binary exists.\n");
        }

        // pdb
        CComPtr<IDxcBlob> pdbBlob = nullptr;
        CComPtr<IDxcBlobUtf16> pdbName = nullptr;
        results->GetOutput(DXC_OUT_PDB, IID_PPV_ARGS(&pdbBlob), &pdbName);
        if(nullptr != pdbBlob) {
            printf("A shader pdb %ls exists.\n", pdbName->GetStringPointer());
        }

        // shader hash
        CComPtr<IDxcBlob> hashBlob = nullptr;
        results->GetOutput(DXC_OUT_SHADER_HASH, IID_PPV_ARGS(&hashBlob), nullptr);
        if(nullptr != hashBlob) {
            DxcShaderHash* hash = (DxcShaderHash*)hashBlob->GetBufferPointer();
            printf("A shader hash: ");
            for(size_t i = 0; i < _countof(hash->HashDigest); ++i) {
                printf("%X", hash->HashDigest[i]);
            }
            printf("\n");
        }

        // separated reflection
        CComPtr<IDxcBlob> reflectionBlob;
        results->GetOutput(DXC_OUT_REFLECTION, IID_PPV_ARGS(&reflectionBlob), nullptr);
        if(nullptr != reflectionBlob) {
            DxcBuffer reflectionBuffer;
            reflectionBuffer.Ptr = reflectionBlob->GetBufferPointer();
            reflectionBuffer.Size = reflectionBlob->GetBufferSize();
            reflectionBuffer.Encoding = DXC_CP_ACP;

            CComPtr<ID3D12ShaderReflection> reflection;
            utils->CreateReflection(&reflectionBuffer, IID_PPV_ARGS(&reflection));

            D3D12_SHADER_DESC shaderDesc;
            if(SUCCEEDED(reflection->GetDesc(&shaderDesc))){
                printf("shader %d\n", shaderDesc.Version);
            }
        }
    }
```
</div></details>

HLSLからSPIR-Vへコンパイル, オプションは使いそうなものを選んだが, 実際に試験してみないとどれが必要かわからない.

[Wiki: SPIR‐V CodeGen](https://github.com/microsoft/DirectXShaderCompiler/wiki/SPIR%E2%80%90V-CodeGen)
[Wiki: HLSL to SPIR-V Feature Mapping Manual](https://github.com/Microsoft/DirectXShaderCompiler/blob/master/docs/SPIR-V.rst#vulkan-command-line-options)
オプションが異なるだけです. ただし, デバッグ情報もリフレクション情報も生成されないので調査中です.

<details><summary>HLSL to SPIR-V</summary><div>

```cpp
LPCWSTR args[] = {
    L"-spirv",        // Generates SPIR-V code.
    L"-E", L"main",   // Entry point.
    L"-T", L"vs_6_0", // Shader profile.
    L"-Zi",           // Enable debug information.
    L"-fvk-use-gl-layout", // Uses strict OpenGL std140/std430 layout rules for resources.
    //L"-fvk-use-dx-layout", //Uses DirectX layout rules for resources.
    L"-fspv-reflect", // Emits additional SPIR-V instructions to aid reflection.
    L"-fspv-flatten-resource-arrays", // Flattens arrays of textures and samplers into individual resources.
}
```
</div></details>

## Linux
まだライブラリをビルドしたところまで, コードでは試していません.

 - C/C++コンパイラとツールチェイン
 - Git
 - Python
 - CMake
 - Ninja

### glslang
```
$ git clone https://github.com/KhronosGroup/glslang.git
$ mkdir build && cd build
$ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="XXX"
$ make install
```

### DirectXShaderCompiler

`libexecinfo-dev`が必要かもしれません.

```
$ mkdir build && cd build
$ cmake .. -GNinja $(cat ../utils/cmake-predefined-config-params) -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="XXX"
$ ninja build
$ ninja install
```

# まとめ
とりあえず, dxcでHLSLをコンパイルするところまで. 後に追記するかも.