はじめに
グラフィックス全般 Advent Calendar 2023 13日目の記事になります.
きっかけ
GLSL で mix
を使って2つの値から1つを選択する際に, a
に bvec3
を直接用いずに vec3
に変換して渡しているコードを見かけた.
bvec3
で渡す場合と vec3
で渡す場合の, 最終的にGPUで実行されるコードの違いが気になったので, 簡単に調べてみることにした.
関連する GLSL の mix
のオーバーロード:
// 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が最終的に実行するコードでは, どのような違いが出るだろうか?
#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
(Vulkan), ID3D12Device::CreateGraphicsPipelineState
(DX12), MTLDevice::newRenderPipelineStateWithDescriptor
(Metal)
になる.
つまり, 基本的にはシェーダコードのほかに,
パイプラインステートやレンダーターゲットの構成などが全て定まり描画に必要な情報が全て揃った段階になる1.
なぜ RDNA2 か
AMD の RDNA22 や RDNA33 などのアーキテクチャではその ISA の仕様書が公開されており,
ドライバとは分離したコンパイラも利用できるため今回のような目的には都合がいい.
(他には Intel も情報公開が多い模様 - 「ドライバを一から書けるくらい」とか45)
RDNA2 の ISA コード の逆アセンブリ
RGA (Radeon™ GPU Analyzer) が独立して公開されているが,
ここでは Shader Playground の機能にインテグレーションされたものを用いる.
コンパイル条件:
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(融合積和) かもしれないがわからない):
v_interp_p1_f32 VDST, VSRC, <ATTR, ATTRCHAN>
- パラメータ補間し VGPR に代入; 1パス目.
VDST = P10 * VSRC + P0
v_interp_p2_f32 VDST, VSRC, <ATTR, ATTRCHAN>
- パラメータ補間し VGPR に代入; 2パス目.
VDST = P20 * VSRC + VDST
今回は 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
.
v_exp_f32 VDST, SRC0
- 2が底の指数関数, VGPR に代入.
VDST = exp2(SRC0)
v_log_f32 VDST, SRC0
- 2が底の対数関数, VGPR に代入.
VDST = log2(SRC0)
v_mul_legacy_f32 VDST, SRC0, VSRC1
- 乗算, VGPR に代入.
VDST = SRC0 * VSRC1
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
.
s_mov_b32 SDST, SSRC0
- SGPR に値を代入.
SDST = SSRC0
v_fmaak_f32 VDST, SRC0, VSRC1, K
- 乗算し即値を加算(FMA), VGPR に代入.
VDST = SRC0 * VSRC1 + K
大小比較をし結果を 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
.
v_cmp_gt_f32 (VCC,) SRC0, VSRC1
- SRC0 > VSRC1 ? 1 : 0 を VCC の work item 位置のビットに代入.
VCC[work_item_index] = SRC0 > VSRC1 ? 1 : 0
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
.
v_fma_f32 VDST, SRC0, SRC1, SRC2
- FMA, VGPR に代入.
VDST = SRC0 * SRC1 + SRC2
-
v_cndmask_b32 VDST, SRC0, VSRC1
(VOP2
encoding) - VCC (の work item 位置のビット) の値による conditional move, VGPR に代入.
VDST = VCC[work_item_index] ? VSRC1 : SRC0
- ※
VOP3
encoding の場合は VCC の代わりに SGPR の値で判断可能. v_fmac_legacy_f32 VDST, SRC0, VSRC1
- FMA, VGPR に加算.
VDST += SRC0 * VSRC1
レンダーターゲットに出力
// out_color = vec4(v1, v2, v0, v3);
0x0000E0 exp mrt0, v1, v2, v0, v3 done vm
exp TARGET, VSRC0, VSRC1, VSRC2, VSRC3, DONE, VM
- 頂点/フラグメントシェーダの結果の出力.
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
v_mul_f32 VDST, SRC0, VSRC1
- 乗算, VGPR に代入.
VDST = SRC0 * VSRC1
大小比較から 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コードの最後尾にパッチされる)と言われることがある1が,
どうやら AMD はそうではない.
これはモバイルで顕著, もしくは顕著だったようで, PowerVR では Rogue の次の A-Series アーキテクチャから専用のハードウェアに変更された6ほか,
そもそも Metal の MSL では GLES の拡張の shader framebuffer fetch のように, レンダーターゲットの値をフラグメントシェーダから扱える(programmable blending).
pow
(任意の底の指数関数)
(他の GPU や CPU で使える SIMD 命令とかでもそうだと思われるが)
pow
に相当する命令は RDNA2 に存在せず, 底が2の対数関数と指数関数の組み合わせで表現される7.
mix
の vec3
vs. bvec3
bvec3
を vec3
に直接的に変換しているような状況でも, 最終的な ISA コードの段階で単純な conditional move には変換されず FMA を使った線形補間になっていることがわかった.
conditional move で完結させるためには, GLSL の段階で bvec3
で mix を行う必要がある.
参考
Low-level GPU ISA assembly on RDNA (PDF)
-
A little clarification on modern shader compile times – Yosoygames ↩ ↩2
-
"RDNA 2" Instruction Set Architecture: Reference Guide (PDF) ↩
-
"RDNA3" Instruction Set Architecture: Reference Guide (PDF) ↩
-
You Compiled This, Driver. Trust Me…. – The Burning Basis Vector ↩
-
Fixed Function Changes & Scalability - Imagination Announces A-Series GPU Architecture: "Most Important Launch in 15 Years" ↩
-
Low-level thinking in high-level shading languages (Emil Persson, 2013) ↩