GPU の機械語コードの逆アセンブリを読んでみる - RDNA2


# はじめに

[グラフィックス全般 Advent Calendar 2023](https://qiita.com/advent-calendar/2023/graphics) 13日目の記事になります.

# きっかけ

GLSL で `mix` を使って2つの値から1つを選択する際に, `a` に `bvec3` を直接用いずに `vec3` に変換して渡しているコードを見かけた.
`bvec3` で渡す場合と `vec3` で渡す場合の, 最終的にGPUで実行されるコードの違いが気になったので, 簡単に調べてみることにした.

関連する GLSL の `mix` のオーバーロード:
```glsl
// vecN を取るもの
genType mix(genType x, genType y, genType a);
// bvecN を取るもの
genType mix(genType x, genType y, genBType a);
```

# 題材となる GLSL によるフラグメントシェーダ

`egui_glow` の実装に含まれる, リニアから sRGB への変換をするためのコード (MIT, Apache).
https://github.com/emilk/egui/blob/8d4de866d4da1835b31e85f9068a5257e6ccbccb/crates/egui_glow/src/shader/fragment.glsl#L20-L25

`mix` の第3引数 `a` に `bvec3 cutoff` をそのまま渡さず, `vec3` に変換して渡している.
GPUが最終的に実行するコードでは, どのような違いが出るだろうか?

```diff_glsl
#version 450

layout(location = 0) out vec4 out_color;

layout(location = 0) in vec3 in_color;

// リニア から ガンマ(sRGB) へ変換する
vec3 srgb_gamma_from_linear(vec3 rgb) {
    bvec3 cutoff = lessThan(rgb, vec3(0.0031308));
    vec3 lower = rgb * vec3(12.92);
    vec3 higher = vec3(1.055) * pow(rgb, vec3(1.0 / 2.4)) - vec3(0.055);
    // 👇 コード生成の違いは?
-   return mix(higher, lower, vec3(cutoff));
+   return mix(higher, lower, cutoff);
}

void main()
{
    vec3 converted = srgb_gamma_from_linear(in_color);
	out_color = vec4(converted, 1.0);
}
```

# ISA コード とは
ISA とは _Instruction Set Architecture_ (命令セットアーキテクチャ) の頭字語だが,
GPU の文脈では,
テキストのシェーディング言語や IR (SPIR-V や DXIL/DXBC) からドライバによって変換され,
最終的に実行される GPU 固有のコードがよくその GPU の 「_ISA コード_」 と呼ばれており,
さらに 「_ISA コード_」 が単純に 「_ISA_」 と呼ばれることもよくある.

## 作られるタイミング
最終的な ISA コードが生成されるタイミングは例えば, コンピュートやレイトレーシングパイプラインを除けば,
[`vkCreateGraphicsPipelines`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCreateGraphicsPipelines.html)(Vulkan), [`ID3D12Device::CreateGraphicsPipelineState`](https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-creategraphicspipelinestate)(DX12), [`MTLDevice::newRenderPipelineStateWithDescriptor`](https://developer.apple.com/documentation/metal/mtldevice/1433369-newrenderpipelinestatewithdescri?language=objc)(Metal) 
になる.

つまり, 基本的にはシェーダコードのほかに,
パイプラインステートやレンダーターゲットの構成などが全て定まり描画に必要な情報が全て揃った段階になる[^pso_shader_patching].

## なぜ RDNA2 か
AMD の RDNA2[^rdna2_spec] や RDNA3[^rdna3_spec] などのアーキテクチャではその ISA の仕様書が公開されており,
ドライバとは分離したコンパイラも利用できるため今回のような目的には都合がいい.
(他には Intel も情報公開が多い模様 - 「ドライバを一から書けるくらい」とか[^you_compiled_this_driver_trust_me][^intel_hawrdware_specs])

[^rdna2_spec]: ["RDNA 2" Instruction Set Architecture: Reference Guide (PDF)](https://www.amd.com/content/dam/amd/en/documents/radeon-tech-docs/instruction-set-architectures/rdna2-shader-instruction-set-architecture.pdf)
[^rdna3_spec]: ["RDNA3" Instruction Set Architecture: Reference Guide (PDF)](https://www.amd.com/content/dam/amd/en/documents/radeon-tech-docs/instruction-set-architectures/rdna3-shader-instruction-set-architecture-feb-2023_0.pdf)
[^you_compiled_this_driver_trust_me]: [You Compiled This, Driver. Trust Me…. – The Burning Basis Vector](http://www.joshbarczak.com/blog/?p=1028)
[^intel_hawrdware_specs]: [Hardware Specifications](https://www.intel.com/content/www/us/en/docs/graphics-for-linux/developer-reference/1-0/hardware-specs.html)

# RDNA2 の ISA コード の逆アセンブリ
[RGA (Radeon™ GPU Analyzer)](https://github.com/GPUOpen-Tools/radeon_gpu_analyzer) が独立して公開されているが,
ここでは [Shader Playground](https://shader-playground.timjones.io/) の機能にインテグレーションされたものを用いる.

コンパイル条件:
```
Radeon GPU Analyzer 2.7.1, gfx1035, OpenGL
```

`vec3` バージョンの RDNA2 の ISA コード:
```
sgpr_count(6)
vgpr_count(8)
wave_size(64)

s_version     UC_VERSION_GFX10 | UC_VERSION_W64_BIT   // 000000000000: B0802004
s_inst_prefetch  0x0003                               // 000000000004: BFA00003
s_mov_b32     m0, s6                                  // 000000000008: BEFC0306
v_interp_p1_f32  v2, v0, attr0.x                      // 00000000000C: C8080000
v_interp_p1_f32  v3, v0, attr0.y                      // 000000000010: C80C0100
v_interp_p1_f32  v0, v0, attr0.z                      // 000000000014: C8000200
v_interp_p2_f32  v2, v1, attr0.x                      // 000000000018: C8090001
v_interp_p2_f32  v3, v1, attr0.y                      // 00000000001C: C80D0101
v_interp_p2_f32  v0, v1, attr0.z                      // 000000000020: C8010201
v_log_f32     v1, v2                                  // 000000000024: 7E024F02
v_log_f32     v4, v3                                  // 000000000028: 7E084F03
v_log_f32     v5, v0                                  // 00000000002C: 7E0A4F00
v_mul_legacy_f32  v1, lit(0x3ed55555), v1             // 000000000030: 0E0202FF 3ED55555
s_mov_b32     s0, lit(0x3f870a3d)                     // 000000000038: BE8003FF 3F870A3D
v_cmp_gt_f32  vcc, lit(0x3b4d2e1c), v2                // 000000000040: 7C0804FF 3B4D2E1C
v_mul_legacy_f32  v4, lit(0x3ed55555), v4             // 000000000048: 0E0808FF 3ED55555
v_exp_f32     v1, v1                                  // 000000000050: 7E024B01
v_mul_legacy_f32  v5, lit(0x3ed55555), v5             // 000000000054: 0E0A0AFF 3ED55555
v_exp_f32     v4, v4                                  // 00000000005C: 7E084B04
v_exp_f32     v5, v5                                  // 000000000060: 7E0A4B05
v_fmaak_f32   v1, s0, v1, lit(0xbd6147ae)             // 000000000064: 5A020200 BD6147AE
v_cndmask_b32  v6, 0, 1.0, vcc                        // 00000000006C: D5010006 01A9E480
v_fmaak_f32   v4, s0, v4, lit(0xbd6147ae)             // 000000000074: 5A080800 BD6147AE
v_fma_f32     v7, v2, lit(0x414eb852), -v1            // 00000000007C: D54B0007 8405FF02 414EB852
v_cmp_gt_f32  vcc, lit(0x3b4d2e1c), v3                // 000000000088: 7C0806FF 3B4D2E1C
v_fmaak_f32   v5, s0, v5, lit(0xbd6147ae)             // 000000000090: 5A0A0A00 BD6147AE
v_cndmask_b32  v2, 0, 1.0, vcc                        // 000000000098: D5010002 01A9E480
v_cmp_gt_f32  vcc, lit(0x3b4d2e1c), v0                // 0000000000A0: 7C0800FF 3B4D2E1C
v_fmac_legacy_f32  v1, v7, v6                         // 0000000000A8: 0C020D07
v_fma_f32     v3, v3, lit(0x414eb852), -v4            // 0000000000AC: D54B0003 8411FF03 414EB852
v_cndmask_b32  v6, 0, 1.0, vcc                        // 0000000000B8: D5010006 01A9E480
v_fma_f32     v0, v0, lit(0x414eb852), -v5            // 0000000000C0: D54B0000 8415FF00 414EB852
v_fma_legacy_f32  v2, v3, v2, v4                      // 0000000000CC: D5400002 04120503
v_mov_b32     v3, 1.0                                 // 0000000000D4: 7E0602F2
v_fma_legacy_f32  v0, v0, v6, v5                      // 0000000000D8: D5400000 04160D00
exp           mrt0, v1, v2, v0, v3 done vm            // 0000000000E0: F800180F 03000201
s_endpgm                                              // 0000000000E8: BF810000
...
```

`bvec3` バージョンの RDNA2 の ISA コード:
```
sgpr_count(6)
vgpr_count(12)
wave_size(64)

s_version     UC_VERSION_GFX10 | UC_VERSION_W64_BIT   // 000000000000: B0802004
s_inst_prefetch  0x0003                               // 000000000004: BFA00003
s_mov_b32     m0, s6                                  // 000000000008: BEFC0306
v_interp_p1_f32  v2, v0, attr0.x                      // 00000000000C: C8080000
v_interp_p1_f32  v3, v0, attr0.y                      // 000000000010: C80C0100
v_interp_p1_f32  v0, v0, attr0.z                      // 000000000014: C8000200
v_interp_p2_f32  v2, v1, attr0.x                      // 000000000018: C8090001
v_interp_p2_f32  v3, v1, attr0.y                      // 00000000001C: C80D0101
v_interp_p2_f32  v0, v1, attr0.z                      // 000000000020: C8010201
v_log_f32     v1, v2                                  // 000000000024: 7E024F02
v_log_f32     v4, v3                                  // 000000000028: 7E084F03
v_log_f32     v5, v0                                  // 00000000002C: 7E0A4F00
v_mul_legacy_f32  v1, lit(0x3ed55555), v1             // 000000000030: 0E0202FF 3ED55555
s_mov_b32     s0, lit(0x3f870a3d)                     // 000000000038: BE8003FF 3F870A3D
v_mul_f32     v8, lit(0x414eb852), v0                 // 000000000040: 101000FF 414EB852
v_mul_legacy_f32  v4, lit(0x3ed55555), v4             // 000000000048: 0E0808FF 3ED55555
v_exp_f32     v1, v1                                  // 000000000050: 7E024B01
v_mul_legacy_f32  v5, lit(0x3ed55555), v5             // 000000000054: 0E0A0AFF 3ED55555
v_exp_f32     v4, v4                                  // 00000000005C: 7E084B04
v_exp_f32     v5, v5                                  // 000000000060: 7E0A4B05
v_fmaak_f32   v1, s0, v1, lit(0xbd6147ae)             // 000000000064: 5A020200 BD6147AE
v_mul_f32     v6, lit(0x414eb852), v2                 // 00000000006C: 100C04FF 414EB852
v_fmaak_f32   v4, s0, v4, lit(0xbd6147ae)             // 000000000074: 5A080800 BD6147AE
v_mul_f32     v7, lit(0x414eb852), v3                 // 00000000007C: 100E06FF 414EB852
v_cmp_gt_f32  vcc, lit(0x3b4d2e1c), v0                // 000000000084: 7C0800FF 3B4D2E1C
v_fmaak_f32   v5, s0, v5, lit(0xbd6147ae)             // 00000000008C: 5A0A0A00 BD6147AE
v_cmp_lt_f32  s[0:1], v3, lit(0x3b4d2e1c)             // 000000000094: D4010000 0001FF03 3B4D2E1C
v_cndmask_b32  v0, v5, v8, vcc                        // 0000000000A0: 02001105
v_cmp_gt_f32  vcc, lit(0x3b4d2e1c), v2                // 0000000000A4: 7C0804FF 3B4D2E1C
v_cndmask_b32  v1, v1, v6, vcc                        // 0000000000AC: 02020D01
v_mov_b32     v2, 1.0                                 // 0000000000B0: 7E0402F2
v_cndmask_b32  v3, v4, v7, s[0:1]                     // 0000000000B4: D5010003 00020F04
exp           mrt0, v1, v3, v0, v2 done vm            // 0000000000BC: F800180F 02000301
s_endpgm                                              // 0000000000C4: BF810000
...
```

`in_color.rgb` の3コンポーネント分の処理があるため, 見た目ではある程度の量のコードに見える.

## `in_color.r` からレンダーターゲット出力まで
`in_color` から `out_color` への出力のうち, `r` コンポーネントのみに絞って抜粋する.

`vec3` バージョン:
```
0x00000C	v_interp_p1_f32	v2, v0, attr0.x
0x000018	v_interp_p2_f32	v2, v1, attr0.x

0x000024	v_log_f32	v1, v2  
0x000030	v_mul_legacy_f32	v1, lit(0x3ed55555), v1
0x000038	s_mov_b32	s0, lit(0x3f870a3d)
0x000040	v_cmp_gt_f32	vcc, lit(0x3b4d2e1c), v2
0x000050	v_exp_f32	v1, v1
0x000064	v_fmaak_f32	v1, s0, v1, lit(0xbd6147ae)
0x00006C	v_cndmask_b32	v6, 0, 1.0, vcc
0x00007C	v_fma_f32	v7, v2, lit(0x414eb852), -v1
0x0000A8	v_fmac_legacy_f32	v1, v7, v6

0x0000E0	exp	mrt0, v1, v2, v0, v3 done vm
```

`bvec3` バージョン:
```
0x00000C	v_interp_p1_f32	v2, v0, attr0.x
0x000018	v_interp_p2_f32	v2, v1, attr0.x

0x000024	v_log_f32	v1, v2
0x000030	v_mul_legacy_f32	v1, lit(0x3ed55555), v1
0x000038	s_mov_b32	s0, lit(0x3f870a3d)
0x000050	v_exp_f32	v1, v1
0x000064	v_fmaak_f32	v1, s0, v1, lit(0xbd6147ae)
0x00006C	v_mul_f32	v6, lit(0x414eb852), v2
0x0000A4	v_cmp_gt_f32	vcc, lit(0x3b4d2e1c), v2 
0x0000AC	v_cndmask_b32	v1, v1, v6, vcc

0x0000BC	exp	mrt0, v1, v3, v0, v2 done vm
```

`bvec3` バージョンのほうが1命令だけ少ないことがわかる. 全体では3命令だけ少ない.
(差はそれだけか...)

### 登場する実行単位

_work item_:
最小の実行単位.
頂点シェーダの頂点, フラグメントシェーダのフラグメント, コンピュートシェーダの1スレッド...

_wavefront_:
同時に実行される 32個 または 64個の work item.
同じシェーダの1つ1つの命令が全ての work item で同時に(_lock step_ で)実行される.
NVIDIA でいう _warp_.

### 登場するレジスタ

SGPR(_Scalar General Purpose Register_):
各32ビットの汎用レジスタ. wavefront ごとに存在し, その全ての work item で共有される.
`s0`-`s105`.
(レジスタ割当はシェーダコードやGPUの性能による)

VGPR(_Vector General Purpose Register_):
各32ビットの汎用レジスタ. wavefront の work item ごとに個別に存在し, SGPR よりも全体の数は限られている.
`v0`-`v255`.
レジスタ割当はシェーダコードやGPUの性能により,
原則としては割当てる数が少ないほど, 同時に実行する数を増やすことができる(occupancy を上げられる).

VCC(_Vector Condition Code_):
64ビット. wavefront について work item ごとに, ベクタ比較命令の結果(`0` or `1`)を保持する.

### スカラ命令とベクタ命令

`s_` で始まる命令は SALU(Scalar ALU) や SMEM(Scalar Memory) を含むスカラ命令であり,
wavefront ごとに1つの値を扱う.
制御フロー関連の命令もスカラ命令で, これは wavefront の work item が lock-step で実行されることとも整合する.

`v_` で始まる命令は VALU(Vector ALU) や VMEM(Vector Memory) を含むベクタ命令であり,
wavefront の work item ごとに個別の値を扱う.

### `vec3` バージョン 詳説

#### 頂点シェーダの出力 `in_color.r` を補間し `v2` へ

頂点シェーダの出力結果をこのフラグメント用に補間し, `v2` に格納する.
ラスタライゼーションで得られたトライアングル上の重心座標を用いて, 頂点シェーダの出力結果を補間する.
```
0x00000C	v_interp_p1_f32	v2, v0, attr0.x
0x000018	v_interp_p2_f32	v2, v1, attr0.x
```

これらの命令は実際には MAD(積和) と言える (FMA(融合積和) かもしれないがわからない):
<dl>
  <dt><code>v_interp_p1_f32 VDST, VSRC, &lt;ATTR, ATTRCHAN&gt; </code></dt>
  <dd>パラメータ補間し VGPR に代入; 1パス目.</dd>
  <dd><code>VDST = P10 * VSRC + P0</code></dd>
  <dt><code>v_interp_p2_f32 VDST, VSRC, &lt;ATTR, ATTRCHAN&gt; </code></dt>
  <dd>パラメータ補間し VGPR に代入; 2パス目.</dd>
  <dd><code>VDST = P20 * VSRC + VDST</code></dd>
</dl>

今回は `VSRC` としてそれぞれ `v0`, `v1` が渡されているが, これらには現在の work item が処理するフラグメントの, トライアングル上の重心座標(`I`, `J`)が入っている.

`P0`, `P10`, `P20` はプリミティブの3頂点のアトリビュートのうち, `ATTR`, `ATTRCHAN` で選択されたものを指す.
当然プリミティブごとの値であり, LDS から読まれる.

つまり2つの MAD(or FMA) を用いて, `VDST = P0 + I*P10 + J*P20` を計算している.

`ATTR`, `ATTRCHAN` は頂点アトリビュートとそのコンポーネントを選択するものであり, 今回は `attr0.x` (= `in_color.r`) が選択されている.

#### `v2` から `higher.r` を計算し `v1` へ

`vec3 higher = vec3(1.055) * pow(rgb, vec3(1.0 / 2.4)) - vec3(0.055);`
(の第1コンポーネント部分)に相当する計算を行う.

```
// v1 = 1.055 * pow(v2, 1.0 / 2.4) -0.055;

0x000024	v_log_f32	v1, v2     
0x000030	v_mul_legacy_f32	v1, lit(0x3ed55555), v1
0x000038	s_mov_b32	s0, lit(0x3f870a3d)
0x000050	v_exp_f32	v1, v1
0x000064	v_fmaak_f32	v1, s0, v1, lit(0xbd6147ae)
```

###### 1. `pow(v2, 1.0 / 2.4)` を計算し `v1` へ

`pow(v2, 1.0 / 2.4)` を計算するが, `pow` に相当する命令はなく,
`exp2(log2(v2) * (1.0 / 2.4))` として計算する; $a^x = 2^{x \cdot \log_2 a}$

```
0x000024	v_log_f32	v1, v2                          // v1 = log2(v2);
0x000030	v_mul_legacy_f32	v1, lit(0x3ed55555), v1 // v1 *= 1.0 / 2.4;
0x000050	v_exp_f32	v1, v1                          // v1 = exp2(v1);

// 以上で v1 = exp2(log2(v2) * (1.0 / 2.4))
//          = pow(v2, 1.0 / 2.0)
```

ここで `lit(0x3ed55555)` は浮動小数点数 `0.416667` ≒ `1.0 / 2.4`.

<dl>
  <dt><code>v_exp_f32 VDST, SRC0</code></dt>
  <dd>2が底の指数関数, VGPR に代入.</dd>
  <dd><code>VDST = exp2(SRC0)</code></dd>
  <dt><code>v_log_f32 VDST, SRC0</code></dt>
  <dd>2が底の対数関数, VGPR に代入.</dd>
  <dd><code>VDST = log2(SRC0)</code></dd>
  <dt><code>v_mul_legacy_f32 VDST, SRC0, VSRC1</code></dt>
  <dd>乗算, VGPR に代入.</dd>
  <dd><code>VDST = SRC0 * VSRC1 </code></dd>
</dl>


##### 2. `higher.r` を完成させ `v1` へ

```
0x000038	s_mov_b32	s0, lit(0x3f870a3d)          // s0 = 1.055;
0x000064	v_fmaak_f32	v1, s0, v1, lit(0xbd6147ae)  // v1 = s0 * v1 + -0.055;

// 以上で v1 = s0 * v1 + -0.055;
//          = 1.055 * pow(v2, 1.0 / 2.4) -0.055;
//          = `higher.r`;
```

ここで `lit(0x3f870a3d)` は浮動小数点数 `1.055`, `lit(0xbd6147ae)` は `-0.055`.

<dl>
  <dt><code>s_mov_b32 SDST, SSRC0</code></dt>
  <dd>SGPR に値を代入.</dd>
  <dd><code>SDST = SSRC0</code></dd>
  <dt><code>v_fmaak_f32 VDST, SRC0, VSRC1, K</code></dt>
  <dd>乗算し即値を加算(FMA), VGPR に代入.</dd>
  <dd><code>VDST = SRC0 * VSRC1 + K</code></dd>
</dl>

#### 大小比較をし結果を VCC へ

`bvec3 cutoff = lessThan(rgb, vec3(0.0031308));`
(の第1コンポーネント部分)に相当する計算を行う.

```
0x000040	v_cmp_gt_f32	vcc, lit(0x3b4d2e1c), v2 // vcc = 0.0031308 > v2 ? 1 : 0
```

ここで `lit(0x3b4d2e1c)` は浮動小数点数 `0.0031308`.

<dl>
  <dt><code>v_cmp_gt_f32 (VCC,) SRC0, VSRC1</code></dt>
  <dd>SRC0 > VSRC1 ? 1 : 0 を VCC の work item 位置のビットに代入.</dd>
  <dd><code>VCC[work_item_index] = SRC0 > VSRC1 ? 1 : 0</code></dd>
</dl>

#### `higher.r` と `lower.r` を比較結果で線形補間し `v1` へ

`mix(higher.r, lower.r, vcc ? 1.0 : 0.0)` に相当する計算を行う.
つまり `higher.r + (lower.r - higher.r) * (vcc ? 1.0 : 0.0)` のことだが,
このうち `lower.r - higher.r` を一気に求めている.

```
// v7 = v2 * 12.92 + -v1
//    = v2 * 12.92 - `higher.r`
//    = `lower.r` - `higher.r`
0x00007C	v_fma_f32	v7, v2, lit(0x414eb852), -v1

// v6 = vcc ? 1.0 : 0.0; 
0x00006C	v_cndmask_b32	v6, 0, 1.0, vcc

// v1 = v1 + v7 * v6;
//    = `higher.r` + (`lower.r` - `higher.r`) * (vcc ? 1.0 : 0.0);
//    = mix(`higher.r`, `lower.r`, vcc ? 1.0 : 0.0);
0x0000A8	v_fmac_legacy_f32	v1, v7, v6
```

ここで `lit(0x414eb852)` は 浮動小数点数 `12.92`.

<dl>
  <dt><code>v_fma_f32 VDST, SRC0, SRC1, SRC2</code></dt>
  <dd>FMA, VGPR に代入.</dd>
  <dd><code>VDST = SRC0 * SRC1 + SRC2</code></dd>
  <dt><code>v_cndmask_b32 VDST, SRC0, VSRC1</code> (<code>VOP2</code> encoding)</dt>
  <dd>VCC (の work item 位置のビット) の値による conditional move, VGPR に代入.</dd>
  <dd><code>VDST = VCC[work_item_index] ? VSRC1 : SRC0</code></dd>
  <dd>※<code>VOP3</code> encoding の場合は VCC の代わりに SGPR の値で判断可能.</dd>
  <dt><code>v_fmac_legacy_f32 VDST, SRC0, VSRC1</code></dt>
  <dd>FMA, VGPR に加算.</dd>
  <dd><code>VDST += SRC0 * VSRC1</code></dd>
</dl>

#### レンダーターゲットに出力

```
// out_color = vec4(v1, v2, v0, v3);
0x0000E0	exp	mrt0, v1, v2, v0, v3 done vm
```

<dl>
  <dt><code>exp TARGET, VSRC0, VSRC1, VSRC2, VSRC3, DONE, VM</code></dt>
  <dd>頂点/フラグメントシェーダの結果の出力.</dd>
</dl>

`TARGET` として `mrt0` (`location=0` である `out_color`)

### `bvec3` バージョン 詳説

#### 頂点シェーダの出力 `in_color.r` を補間し `v2` へ

`vec3` バージョンに同じ.

```
// v2 = `in_color.x` の補間結果
0x00000C	v_interp_p1_f32	v2, v0, attr0.x
0x000018	v_interp_p2_f32	v2, v1, attr0.x
```

#### `v2` から `higher.r` を計算し `v1` へ

`vec3` バージョンに同じ.

```
0x000024	v_log_f32	v1, v2                          // v1 = log2(v2)
0x000030	v_mul_legacy_f32	v1, lit(0x3ed55555), v1 // v1 *= 1.0 / 2.4;
0x000038	s_mov_b32	s0, lit(0x3f870a3d)             // s0 = 1.055;
0x000050	v_exp_f32	v1, v1                          // v1 = exp2(v1);
0x000064	v_fmaak_f32	v1, s0, v1, lit(0xbd6147ae)     // v1 = s0 * v1 + -0.055;

// 以上で v1 = s0 * v1 + -0.055;
//          = 1.055 * pow(v2, 1.0 / 2.4) - 0.055
//          = `higher.r`
```

#### `v2` から `lower.r` を計算し `v6` へ

まずここが `vec3` バージョンと異なり,
`lower.r` を単独で計算し VGPR に代入している.



```
// v6 = 12.92 * v2
//    = `lower.r`;
0x00006C	v_mul_f32	v6, lit(0x414eb852), v2
```

<dl>
  <dt><code>v_mul_f32 VDST, SRC0, VSRC1</code></dt>
  <dd>乗算, VGPR に代入.</dd>
  <dd><code>VDST = SRC0 * VSRC1</code></dd>
</dl>

#### 大小比較から `higher.r` か `lower.r` を選択し `v1` へ

`bvec3 cutoff = lessThan(rgb, vec3(0.0031308));`
(の第1コンポーネント部分)の計算のあと, 直接 conditional move を用いて結果を選択している.

```
// vcc = 0.0031308 > v2 ? 1 : 0;
0x0000A4	v_cmp_gt_f32	vcc, lit(0x3b4d2e1c), v2 
// v1 = vcc ? v6 : v1;
//    = vcc ? `lower.r` : `higher.r`;
0x0000AC	v_cndmask_b32	v1, v1, v6, vcc
```

#### レンダーターゲットに出力

`vec3` バージョンに同じ.

```
// out_color = vec4(v1, v3, v0, v2);
0x0000BC	exp	mrt0, v1, v3, v0, v2 done vm
```

## 観察

### 頂点シェーダの出力結果の補間 (`v_interp_p1_f32`, `v_interp_p1_f32`)
グラフィクスAPIにおいて頂点入力レイアウトは固定機能であるパイプラインステートに含まれるが,
少なくとも RDNA2 では頂点シェーダの出力結果の補間はフラグメントシェーダの先頭にパッチされるコードで行われることがわかる.
(それを行うためのハードウェアがあるわけではない)

varying (補間される頂点シェーダの出力結果) の数は VGPR の使用数に影響する, と言及されることがあるが,
`v_interp_p1_f32` と `v_interp_p1_f32` の使用を見るとそれがよくわかる.

### ブレンド
ハードウェアによってはブレンド処理が専用のハードウェアでなくマイクロコードで行われる(フラグメントシェーダのISAコードの最後尾にパッチされる)と言われることがある[^pso_shader_patching]が,
どうやら AMD はそうではない.

これはモバイルで顕著, もしくは顕著だったようで, PowerVR では Rogue の次の A-Series アーキテクチャから専用のハードウェアに変更された[^powervr_blending_a_series]ほか,
そもそも Metal の MSL では GLES の拡張の _shader framebuffer fetch_ のように, レンダーターゲットの値をフラグメントシェーダから扱える(_programmable blending_).

[^pso_shader_patching]: [A little clarification on modern shader compile times – Yosoygames](https://www.yosoygames.com.ar/wp/2020/08/a-little-clarification-on-modern-shader-compile-times/)
[^powervr_blending_a_series]: [Fixed Function Changes & Scalability - Imagination Announces A-Series GPU Architecture: "Most Important Launch in 15 Years"](https://www.anandtech.com/show/15156/imagination-announces-a-series-gpu-architecture/3)

### `pow` (任意の底の指数関数)
(他の GPU や CPU で使える SIMD 命令とかでもそうだと思われるが)
`pow` に相当する命令は RDNA2 に存在せず, 底が2の対数関数と指数関数の組み合わせで表現される[^low_level_thinking].

[^low_level_thinking]: [Low-level thinking in high-level shading languages  (Emil Persson, 2013)](https://www.humus.name/index.php?page=Articles&ID=6)

### `mix` の `vec3` vs. `bvec3`
`bvec3` を `vec3` に直接的に変換しているような状況でも, 最終的な ISA コードの段階で単純な conditional move には変換されず FMA を使った線形補間になっていることがわかった.
conditional move で完結させるためには, GLSL の段階で `bvec3` で mix を行う必要がある.

# 参考

[Low-level GPU ISA assembly on RDNA (PDF)](http://cg.elte.hu/~gpgpu/Slides/GPGPU%2004%20Low-level%20GPU%20ISA%20assembly%20on%20RDNA.pdf)

https://interplayoflight.wordpress.com/2021/04/18/how-to-read-shader-assembly/

https://bartwronski.com/2021/01/18/is-this-a-branch/

https://boogy90.github.io/2021/11/24/root-signatures-assembly.html

https://gpuopen.com/learn/occupancy-explained/