Your SlideShare is downloading. ×
Low-level Thinking in High-level  Shading Languages  High-level Shading Languages(HLSL) におけるローレベル思考  Emil Persson  Head of...
問題の明確化“最近、業界の著名人は、少しの変更でもっと早くできる  ようなシェーダコードを GDC の講演で使っている”
この講演の目的     “ローレベル思考が今日でもまだ有効であること明らかにする”
背景●   古き良き時代、おじいちゃんがまだ子供だった頃…    ●   シェーダは短かった        ●     SM1: 最大 8 命令, SM2: 最大 64 命令    ●   シェーダはアセンブリで書かれていた        ● ...
背景●   Low-level shading languages は死んだ    ●   シェーダを書くには非生産的    ●   DX10 以降はアセンブリに非対応        ●   とにもかくにも誰も使っていない    ●   コンパ...
どうしてわざわざ気を付けないといけないの?         ●   どうシェーダを書くかは重要!//     float3    float     float   float3       float float    //      flo...
どうしてわざわざ気を付けないといけないの?●   より良いパフォーマンスが得られる    ●   「うちは ALU がボトルネックじゃないんだけど・・・」        ●   消費電力を節約しよう        ●   Texture やメモ...
”コンパイラが最適化してくれるでしょ!”
”コンパイラが最適化してくれるでしょ!”●   コンパイラは狡猾だ!    ●   もう賢すぎてコンパイラ自身をだませちゃう!●   しかし:    ●   コンパイラはあなたの心を読めない    ●   コンパイラは全容が見えていない    ...
”コンパイラが最適化してくれるでしょ!”これは MAD (発狂)しますか?                            (なんちゃって)float main(float x : TEXCOORD) : SV_Target{    re...
”コンパイラが最適化してくれるでしょ!”これは MAD (発狂)しますか?                            (なんちゃって)float main(float x : TEXCOORD) : SV_Target{    re...
”コンパイラが最適化してくれるでしょ!”これは MAD (発狂)しますか?                            (なんちゃって)float main(float x : TEXCOORD) : SV_Target{    re...
どうしてダメだった?●   結果が一致するとは限らないため●   INF や NAN を引き起こす場合がある●   一般的にコンパイラが得意なのは:    ●   使われていないコードの除去    ●   使われていないリソースの除去    ●...
したがって:シェーダはハードウェアに実行してもらいたいように書こう!           そこで:         ローレベル思考
ルール●   D3D10 以降は基本的に IEEE-754-2008 [1] に従う●   例外[2]:    ●   演算精度は 0.5 ULP ではなく 1 ULP    ●   非正規化数は演算でフラッシュされて 0 になる       ...
ハードウェアに関する普遍的*事実●   乗算→加算 は 1 つの命令。加算→乗算 は 2 つの命令●   絶対値(abs), 符号反転(-) , saturate はコストがかからない    ●   MOV が発生する場合を除いて●   スカラ...
MAD ●   一次関数 → mad     ●   さらに clamp する場合 → mad_sat          ●   clamp が [0, 1] の範囲でない場合 → mad_sat + mad     ●   範囲を変える操作 ...
MAD●   その他の式変形     x * (1.0f – x)        →     x–x*x     x * (y + 1.0f)        →     x*y+x     (x + c) * (x - c)     →    ...
除算●   a / b は一般的に a * rcp(b) と実装される    ●   ただし D3D アセンブリは DIV 命令を使うことがある    ●   明示的な rcp() はときどき良いコードを生成する●   式変形        a...
マッドネス●   最初はこんなコード:float AlphaThreshold(float alpha, float threshold, float blendRange){    float halfBlendRange = 0.5f*bl...
マッドネス●   AlphaThreshold() はこうできる!// scale = 1.0f / blendRange// offset = 1.0f - (threshold/blendRange + threshold)float Al...
修飾子●       MOV が発生しなければタダ        ●   入力に対する abs / neg        ●   出力に対する saturate    float main(float2 a : TEXCOORD) : SV_T...
修飾子●       MOV が発生しなければタダ        ●   入力に対する abs / neg        ●   出力に対する saturate    float main(float2 a : TEXCOORD) : SV_T...
修飾子●       MOV が発生しなければタダ        ●   入力に対する abs / neg        ●   出力に対する saturate    float main(float a : TEXCOORD) : SV_Ta...
修飾子●   saturate() はタダ, min() と max() はタダでない    ●   max(x, 0.0f) や min(x, 1.0f) で足りる場合でも saturate(x) を使う        ●   (x > 1....
HLSL コンパイラへの対策●   precise キーワードを使う    ●    コンパイラは NaN を無視しない    ●    saturate(NaN) == 0float main(float3 a : TEXCOORD0) : ...
組み込み関数●   rcp(), rsqrt(), sqrt()* は直接ハードウェアの命令になる●   同等の計算が最適化されるとは限らない…    ●   1.0f / x は rcp(x) になりやすい    ●   1.0f / sqr...
組み込み関数●   sign()    ●   0 ケースに気を付ける        ●   0 を気にしない? それなら (x >= 0)? 1 : -1 を使おう        ●   sign(x) * y → (x >= 0)? y :...
組み込み関数●   mul(v, m)    ●   v.x * m[0] + v.y * m[1] + v.z * m[2] + v.w * m[3]    ●   MUL – MAD – MAD – MAD●   mul(float4(v....
組み込み関数float4 main(float4 v : TEXCOORD0) : SV_Position          float4 main(float4 v : TEXCOORD0) : POSITION{              ...
行列計算●   行列は任意の線形変換を一飲みにできる    ●   CPU 側と GPU 側で!float4 pos =                                   // tex_coord pre-transforms...
スカラー演算●   現代のハードウェアにはスカラー ALU がある    ●   スカラー計算は常にベクトル計算より早い●   昔の VLIW やベクトル ALU アーキテクチャでもメリットがある    ●   シェーダが短くなることがある  ...
スカラー演算とベクトル演算の混在●   ローレベルの計算を考える    ●   ベクトル部分とスカラー部分を分離する    ●   共通の部分式を探す        ●   コンパイラはつねに共通の部分式を再利用できるわけではない!      ...
隠れたスカラー演算●   normalize(vec)    ●   入力も出力もベクトルだが、中間式でスカラー値が出現する    ●   normalize(vec) = vec * rsqrt(dot(vec, vec))        ●...
隠れたスカラー演算●   50.0f * normalize(vec) = 50.0f * (vec * rsqrt(dot(vec, vec)))     ●    不必要にベクトル演算を行っているfloat3 main(float3 vec...
隠れた共通の部分式●   normalize(vec) and length(vec) contain dot(vec, vec)       ●     コンパイラは完全に一致したら再利用する       ●     コンパイラは異なった使い...
隠れた共通の部分式 ●   最適化:ベクトルの長さを 1 にクランプする最初の形   if (length(v) > 1.0f)           v = normalize(v);                              ...
隠れた共通の部分式●   最適化:ベクトルの長さを 1 にクランプするfloat3 main(float3 v : TEXCOORD0) : SV_Target               float3 main(float3 v : TEXC...
隠れた共通の部分式●   最適化:ベクトルの長さを 1 にクランプするfloat3 main(float3 v : TEXCOORD0) : SV_Target{    precise float norm_factor =        sa...
評価の順序    ●        式は左から右に評価される             ●   かっこや演算子の優先順位がある場合を除いて             ●   スカラー計算を式の左に置いたりかっこを使う//     float3   ...
評価の順序     ●     VLIW とベクトルアーキテクチャは依存関係に注意しないとい           けない             ●   特にスコープの始まりと終わりで             ●   a * b * c * d...
実際のテスト●   ケース・スタディ: Clustered deferred shading    ●   品質が混在するコード        ●   オリジナルのライティングコードは完全に最適化        ●   さまざまなプロトタイプ品...
その他の推奨事項●   [branch], [flatten], [loop], [unroll] で意図を伝える    ●   [branch] は “勾配関数” の警告をエラーにする                  【訳注: http:/...
良いローレベルコーダーになるには?●   GPUハードの命令をよく理解する    ●   そして PC の D3D アセンブリを学ぼう●   HLSL からハードウェアコードへの変換を理解する    ●   GPUShaderAnalyzer,...
すべてのシェーダを最適化するのだ!
参考文献[1] IEEE-754http://en.wikipedia.org/wiki/IEEE_floating_point[2] Floating-Point Ruleshttp://msdn.microsoft.com/en-us/li...
Questions?   @_Humus_emil.persson@avalanchestudios.seWe are hiring!New York, Stockholm
翻訳:   @Reputeless訳文改善の指摘は reputeless@gmail.com までお送りください。
Upcoming SlideShare
Loading in...5
×

【日本語訳】"Low-level Thinking in High-level Shading Languages"

6,132

Published on

Emil Persson 氏のGDC 2013 講演 "Low-level Thinking in High-level Shading Languages" を日本語訳しました。
スピーチ文はこちら https://docs.google.com/file/d/0B7A3AKSmQMLeLW1IclZoTG8yOFE/edit?usp=sharing
オリジナルの資料はこちら http://www.humus.name/index.php?page=Articles&ID=6

Published in: Technology

Transcript of "【日本語訳】"Low-level Thinking in High-level Shading Languages""

  1. 1. Low-level Thinking in High-level Shading Languages High-level Shading Languages(HLSL) におけるローレベル思考 Emil Persson Head of Research, Avalanche Studios翻訳: @Reputeless
  2. 2. 問題の明確化“最近、業界の著名人は、少しの変更でもっと早くできる ようなシェーダコードを GDC の講演で使っている”
  3. 3. この講演の目的 “ローレベル思考が今日でもまだ有効であること明らかにする”
  4. 4. 背景● 古き良き時代、おじいちゃんがまだ子供だった頃… ● シェーダは短かった ● SM1: 最大 8 命令, SM2: 最大 64 命令 ● シェーダはアセンブリで書かれていた ● すでに SM2 の時代になって消えた ● D3D の命令は実際のハードウェアに適切にマッピングされていた ● 手動での最適化は当たり前のことだった def c0, 0.3f, 2.5f, 0, 0 def c0, -0.75f, 2.5f, 0, 0 texld sub r0, t0 r0, r0, c0.x ⇨ texld mad r0, t0 r0, r0, c0.y, c0.x mul r0, r0, c0.y
  5. 5. 背景● Low-level shading languages は死んだ ● シェーダを書くには非生産的 ● DX10 以降はアセンブリに非対応 ● とにもかくにも誰も使っていない ● コンパイラとドライバの最適化が良くはたらいてくれる (時々…) ● なんてこった、最近はアーティストがシェーダを作りやがる! ● ビジュアルエディタを使って ● ボックスや矢印を使って ● サイクル数も数えず、アセンブリも調べずに ● テクニカルドキュメントさえ読まずに ● うわぁぁ、あの子供時代、戻ってこい、戻ってこい ・・・● 要するに: ● シェーダの書き手がハードウェアに疎くなった
  6. 6. どうしてわざわざ気を付けないといけないの? ● どうシェーダを書くかは重要!// float3 float float float3 float float // float float float float float3 float3return Diffuse * n_dot_l * atten * LightColor * shadow * ao; return (n_dot_l * atten) * (shadow * ao) * (Diffuse * LightColor);0 x: MUL_e ____, R0.z, R0.w 0 x: MUL_e ____, R2.x, R2.y y: MUL_e ____, R0.y, R0.w y: MUL_e R0.y, R0.y, R1.y VEC_021 z: MUL_e ____, R0.x, R0.w z: MUL_e R0.z, R0.x, R1.x VEC_1201 y: MUL_e ____, R1.w, PV0.x w: MUL_e ____, R0.w, R1.w z: MUL_e ____, R1.w, PV0.y t: MUL_e R0.x, R0.z, R1.z w: MUL_e ____, R1.w, PV0.z 1 w: MUL_e ____, PV0.x, PV0.w2 x: MUL_e ____, R1.x, PV1.w 2 x: MUL_e R0.x, R0.z, PV1.w z: MUL_e ____, R1.z, PV1.y y: MUL_e R0.y, R0.y, PV1.w w: MUL_e ____, R1.y, PV1.z z: MUL_e R0.z, R0.x, PV1.w3 x: MUL_e ____, R2.x, PV2.w y: MUL_e ____, R2.x, PV2.x w: MUL_e ____, R2.x, PV2.z4 x: MUL_e R2.x, R2.y, PV3.y y: MUL_e R2.y, R2.y, PV3.x z: MUL_e R2.z, R2.y, PV3.w
  7. 7. どうしてわざわざ気を付けないといけないの?● より良いパフォーマンスが得られる ● 「うちは ALU がボトルネックじゃないんだけど・・・」 ● 消費電力を節約しよう ● Texture やメモリ帯域の使用をまだ改善できるはず ● 追加機能のために余力を残しておこう ● 「プロジェクトの最後で最適化するつもりなんだけど・・・」 ● 家に帰れなくならないようお祈りします・・・● 一貫性が得られる ● 物事にはしばしば最良の方法がある ● 読みやすさを改善しよう● 楽しい!
  8. 8. ”コンパイラが最適化してくれるでしょ!”
  9. 9. ”コンパイラが最適化してくれるでしょ!”● コンパイラは狡猾だ! ● もう賢すぎてコンパイラ自身をだませちゃう!● しかし: ● コンパイラはあなたの心を読めない ● コンパイラは全容が見えていない ● コンパイラは限られた情報しか使えない ● コンパイラはルールを破れない
  10. 10. ”コンパイラが最適化してくれるでしょ!”これは MAD (発狂)しますか? (なんちゃって)float main(float x : TEXCOORD) : SV_Target{ return (x + 1.0f) * 0.5f;}
  11. 11. ”コンパイラが最適化してくれるでしょ!”これは MAD (発狂)しますか? (なんちゃって)float main(float x : TEXCOORD) : SV_Target{ return (x + 1.0f) * 0.5f;}しなかった! ドライバがしてくれるでしょ?add r0.x, v0.x, l(1.000000)mul o0.x, r0.x, l(0.500000)
  12. 12. ”コンパイラが最適化してくれるでしょ!”これは MAD (発狂)しますか? (なんちゃって)float main(float x : TEXCOORD) : SV_Target{ return (x + 1.0f) * 0.5f;}しなかった! こいつもダメだ! 00 ALU: ADDR(32) CNT(2)add r0.x, v0.x, l(1.000000) 0 y: ADD ____, R0.x, 1.0fmul o0.x, r0.x, l(0.500000) 1 x: MUL_e R0.x, PV0.y, 0.5 01 EXP_DONE: PIX0, R0.x___
  13. 13. どうしてダメだった?● 結果が一致するとは限らないため● INF や NAN を引き起こす場合がある● 一般的にコンパイラが得意なのは: ● 使われていないコードの除去 ● 使われていないリソースの除去 ● 定数の組み立て ● レジスタの割り当て ● コードのスケジューリング● 苦手なことは: ● コードの意味を変えること ● 依存関係を壊すこと ● ルールを破ること
  14. 14. したがって:シェーダはハードウェアに実行してもらいたいように書こう! そこで: ローレベル思考
  15. 15. ルール● D3D10 以降は基本的に IEEE-754-2008 [1] に従う● 例外[2]: ● 演算精度は 0.5 ULP ではなく 1 ULP ● 非正規化数は演算でフラッシュされて 0 になる ● MOV 系の命令を除いて ● min/max は入力をフラッシュするが、出力については決まっていない● HLSL コンパイラが無視すること: ● 特定の条件で NaN や INF になる可能性 ● 例)本当は NaN * 0 = NaN だが、x * 0 = 0 とする ● → precise キーワードか IEEE Strictness を有効にしていない場合 ● 注意: コンパイラは isnan() と isfinite() の呼び出しを最適化で消してしま うかもしれない!
  16. 16. ハードウェアに関する普遍的*事実● 乗算→加算 は 1 つの命令。加算→乗算 は 2 つの命令● 絶対値(abs), 符号反転(-) , saturate はコストがかからない ● MOV が発生する場合を除いて● スカラー演算はベクトル演算より使用するリソースが少ない● 定数だけを使う数学関数はバカげてる● 何もしないことは何かをすることより早い* 我々が知る限りの宇宙において
  17. 17. MAD ● 一次関数 → mad ● さらに clamp する場合 → mad_sat ● clamp が [0, 1] の範囲でない場合 → mad_sat + mad ● 範囲を変える操作 == 一次関数 ● MAD はいつも直感的な形とは限らない ● MAD = x * slope + offset_at_zero ● 簡単なパラメータから slope と offset を作ってみよう【訳注: x 以外は定数】(x – start) * slope → x * slope + (-start * slope)(x – start) / (end – start) → x * (1.0f / (end - start)) + (-start / (end - start))(x – mid_point) / range + 0.5f → x * (1.0f / range) + (0.5f - mid_point / range)clamp(s1 + (x-s0)*(e1-s1)/(e0-s0), s1, e1) → saturate(x * (1.0f/(e0-s0)) + (-s0/(e0-s0))) * (e1-s1) + s1
  18. 18. MAD● その他の式変形 x * (1.0f – x) → x–x*x x * (y + 1.0f) → x*y+x (x + c) * (x - c) → x * x + (-c * c) (x + a) / b → x * (1.0f / b) + (a / b) x += a * b + c * d; → x += a * b; x += c * d;
  19. 19. 除算● a / b は一般的に a * rcp(b) と実装される ● ただし D3D アセンブリは DIV 命令を使うことがある ● 明示的な rcp() はときどき良いコードを生成する● 式変形 a / (x + b) → rcp(x * (1.0f / a) + (b / a)) a / (x * b) → rcp(x) * (a / b) rcp(x * (b / a)) a / (x * b + c) → rcp(x * (b / a) + (c / a)) (x + a) / x → 1.0f + a * rcp(x) (x * a + b) / x → a + b * rcp(x)● どれも中学生の数学レベル!● すべて究極形まで式を導出をしている! [3]
  20. 20. マッドネス● 最初はこんなコード:float AlphaThreshold(float alpha, float threshold, float blendRange){ float halfBlendRange = 0.5f*blendRange; threshold = threshold*(1.0f + blendRange) - halfBlendRange; float opacity = saturate( (alpha - threshold + halfBlendRange)/blendRange ); return opacity;}mul r0.x, cb0[0].y, l(0.500000) 0 y: ADD ____, KC0[0].y, 1.0fadd r0.y, cb0[0].y, l(1.000000) z: MUL_e ____, KC0[0].y, 0.5mad r0.x, cb0[0].x, r0.y, -r0.x t: RCP_e R0.y, KC0[0].yadd r0.x, -r0.x, v0.x 1 x: MULADD_e ____, KC0[0].x, PV0.y, -PV0.zmad r0.x, cb0[0].y, l(0.500000), r0.x 2 w: ADD ____, R0.x, -PV1.xdiv_sat o0.x, r0.x, cb0[0].y 3 z: MULADD_e ____, KC0[0].y, 0.5, PV2.w 4 x: MUL_e R0.x, PV3.z, R0.y CLAMP
  21. 21. マッドネス● AlphaThreshold() はこうできる!// scale = 1.0f / blendRange// offset = 1.0f - (threshold/blendRange + threshold)float AlphaThreshold(float alpha, float scale, float offset){ return saturate( alpha * scale + offset );}mad_sat o0.x, v0.x, cb0[0].x, cb0[0].y 0 x: MULADD_e R0.x, R0.x, KC0[0].x, KC0[0].y CLAMP
  22. 22. 修飾子● MOV が発生しなければタダ ● 入力に対する abs / neg ● 出力に対する saturate float main(float2 a : TEXCOORD) : SV_Target float main(float2 a : TEXCOORD) : SV_Target { { return abs(a.x) * abs(a.y); return abs(a.x * a.y); } } 0 x: MUL_e R0.x, |R0.x|, |R0.y| 0 y: MUL_e ____, R0.x, R0.y 1 x: MOV R0.x, |PV0.y|
  23. 23. 修飾子● MOV が発生しなければタダ ● 入力に対する abs / neg ● 出力に対する saturate float main(float2 a : TEXCOORD) : SV_Target float main(float2 a : TEXCOORD) : SV_Target { { return -a.x * a.y; return -(a.x * a.y); } } 0 x: MUL_e R0.x, -R0.x, R0.y 0 y: MUL_e ____, R0.x, R0.y 1 x: MOV R0.x, -PV0.y
  24. 24. 修飾子● MOV が発生しなければタダ ● 入力に対する abs / neg ● 出力に対する saturate float main(float a : TEXCOORD) : SV_Target float main(float a : TEXCOORD) : SV_Target { { return 1.0f - saturate(a); return saturate(1.0f - a); } } 0 y: MOV ____, R0.x CLAMP 0 x: ADD R0.x, -R0.x, 1.0f CLAMP 1 x: ADD R0.x, -PV0.y, 1.0f
  25. 25. 修飾子● saturate() はタダ, min() と max() はタダでない ● max(x, 0.0f) や min(x, 1.0f) で足りる場合でも saturate(x) を使う ● (x > 1.0f) や (x < 0.0f) にそれぞれ意味がある場合を除いて ● 不幸なことに, HLSL コンパイラは時々逆のことをしてしまう… ● saturate(dot(a, a)) → “わーい、dot(a, a) は常に正だぞ” → min(dot(a, a), 1.0f) ● 回避方法: ● 実際の範囲をコンパイラにわかりづらくさせる ● 例) リテラル値を constants に移動させる ● precise キーワードを使う ● IEEE Strictness を強制できる ● 回避法が回避されていないかチェックしよう ● mad(x, slope, offset) 関数は消えた MAD を復活させられる
  26. 26. HLSL コンパイラへの対策● precise キーワードを使う ● コンパイラは NaN を無視しない ● saturate(NaN) == 0float main(float3 a : TEXCOORD0) : SV_Target float main(float3 a : TEXCOORD0) : SV_Target{ { return saturate(dot(a, a)); return (precise float) saturate(dot(a, a));} }dp3 r0.x, v0.xyzx, v0.xyzx dp3_sat o0.x, v0.xyzx, v0.xyzxmin o0.x, r0.x, l(1.000000)0 x: DOT4_e ____, R0.x, R0.x 0 x: DOT4_e R0.x, R0.x, R0.x CLAMP y: DOT4_e ____, R0.y, R0.y y: DOT4_e ____, R0.y, R0.y CLAMP z: DOT4_e ____, R0.z, R0.z z: DOT4_e ____, R0.z, R0.z CLAMP w: DOT4_e ____, (0x80000000, -0.0f).x, 0.0f w: DOT4_e ____, (0x80000000, -0.0f).x, 0.0f CLAMP1 x: MIN_DX10 R0.x, PV0.x, 1.0f
  27. 27. 組み込み関数● rcp(), rsqrt(), sqrt()* は直接ハードウェアの命令になる● 同等の計算が最適化されるとは限らない… ● 1.0f / x は rcp(x) になりやすい ● 1.0f / sqrt(x) は rsqrt(x) でなく rcp(sqrt(x)) になる!● exp2() と log2() はハードウェア命令, exp() と log() は違う ● exp2(x * 1.442695f) や log2(x * 0.693147f) として実装される● pow(x, y) は exp2(log2(x) * y) として実装される ● リテラル値 y の特殊なケース ● z * pow(x, y) = exp2(log2(x) * y + log2(z)) ● もし log2(z) がコンパイル時に計算できる場合、乗算のコストは 0 ● 例) specular_normalization * pow(n_dot_h, specular_power)
  28. 28. 組み込み関数● sign() ● 0 ケースに気を付ける ● 0 を気にしない? それなら (x >= 0)? 1 : -1 を使おう ● sign(x) * y → (x >= 0)? y : -y● sin(), cos(), sincos() もハードウェア命令 ● ただし一部のハードウェアでは少し時間が必要● asin(), acos(), atan(), atan2(), degrees(), radians() ● そんなのを使うなんて間違ってる! ● とてつもなく長い命令を生成する 【訳注: degrees() と radians() は単に乗算になる】● cosh(), sinh(), log10() ● キミたち誰? シェーダでどんな仕事ができるっていうの?
  29. 29. 組み込み関数● mul(v, m) ● v.x * m[0] + v.y * m[1] + v.z * m[2] + v.w * m[3] ● MUL – MAD – MAD – MAD● mul(float4(v.xyz, 1), m) ● v.x * m[0] + v.y * m[1] + v.z * m[2] + m[3] ● MUL – MAD – MAD – ADD● v.x * m[0] + (v.y * m[1] + (v.z * m[2] + m[3])) ● MAD – MAD – MAD
  30. 30. 組み込み関数float4 main(float4 v : TEXCOORD0) : SV_Position float4 main(float4 v : TEXCOORD0) : POSITION{ { return mul(float4(v.xyz, 1.0f), m); return v.x*m[0] + (v.y*m[1] + (v.z*m[2] + m[3]));} }0 x: MUL_e ____, R1.y, KC0[1].w 0 z: MULADD_e R0.z, R1.z, KC0[2].y, KC0[3].y y: MUL_e ____, R1.y, KC0[1].z w: MULADD_e R0.w, R1.z, KC0[2].x, KC0[3].x z: MUL_e ____, R1.y, KC0[1].y 1 x: MULADD_e ____, R1.z, KC0[2].w, KC0[3].w w: MUL_e ____, R1.y, KC0[1].x y: MULADD_e ____, R1.z, KC0[2].z, KC0[3].z1 x: MULADD_e ____, R1.x, KC0[0].w, PV0.x 2 x: MULADD_e ____, R1.y, KC0[1].w, PV1.x y: MULADD_e ____, R1.x, KC0[0].z, PV0.y y: MULADD_e ____, R1.y, KC0[1].z, PV1.y z: MULADD_e ____, R1.x, KC0[0].y, PV0.z z: MULADD_e ____, R1.y, KC0[1].y, R0.z w: MULADD_e ____, R1.x, KC0[0].x, PV0.w w: MULADD_e ____, R1.y, KC0[1].x, R0.w2 x: MULADD_e ____, R1.z, KC0[2].w, PV1.x 3 x: MULADD_e R1.x, R1.x, KC0[0].x, PV2.w y: MULADD_e ____, R1.z, KC0[2].z, PV1.y y: MULADD_e R1.y, R1.x, KC0[0].y, PV2.z z: MULADD_e ____, R1.z, KC0[2].y, PV1.z z: MULADD_e R1.z, R1.x, KC0[0].z, PV2.y w: MULADD_e ____, R1.z, KC0[2].x, PV1.w w: MULADD_e R1.w, R1.x, KC0[0].w, PV2.x3 x: ADD R1.x, PV2.w, KC0[3].x y: ADD R1.y, PV2.z, KC0[3].y z: ADD R1.z, PV2.y, KC0[3].z w: ADD R1.w, PV2.x, KC0[3].w
  31. 31. 行列計算● 行列は任意の線形変換を一飲みにできる ● CPU 側と GPU 側で!float4 pos = // tex_coord pre-transforms merged into matrix{ float4 pos = { tex_coord.xy, depth, 1.0f }; tex_coord.x * 2.0f - 1.0f, 1.0f - 2.0f * tex_coord.y, depth, 1.0f ⇨ float4 l_pos = mul(pos, new_mat);}; // LightPos translation merged into matrix float3 light_vec = l_pos.xyz / l_pos.w;float4 w_pos = mul(cs, mat);float3 world_pos = w_pos.xyz / w_pos.w;float3 light_vec = world_pos - LightPos; // CPU-side code float4x4 pre_mat = Scale(2, -2, 1) * Translate(-1, 1, 0); float4x4 post_mat = Translate(-LightPos); float4x4 new_mat = pre_mat * mat * post_mat;
  32. 32. スカラー演算● 現代のハードウェアにはスカラー ALU がある ● スカラー計算は常にベクトル計算より早い● 昔の VLIW やベクトル ALU アーキテクチャでもメリットがある ● シェーダが短くなることがある ● あるいは、ほかの処理のためにレーンが空く● スカラーからベクトルへの展開は気付きにくい ● 式の評価順とかっこに依存している ● 時には関数や抽象化で隠される ● 時には関数内で隠される
  33. 33. スカラー演算とベクトル演算の混在● ローレベルの計算を考える ● ベクトル部分とスカラー部分を分離する ● 共通の部分式を探す ● コンパイラはつねに共通の部分式を再利用できるわけではない! ● コンパイラはスカラーを取り出せないこともある! ● dot(), normalize(), reflect(), length(), distance()● スカラー計算とベクトル計算を分けて管理する ● 評価順に気を付けよう ● 式は左から右に評価される ● かっこを使おう
  34. 34. 隠れたスカラー演算● normalize(vec) ● 入力も出力もベクトルだが、中間式でスカラー値が出現する ● normalize(vec) = vec * rsqrt(dot(vec, vec)) ● dot() はスカラー値を返す。 rsqrt() もまだスカラー ● ベクトルと正規化係数を分けて管理する ● 一部のハードウェア (とりわけ PS3) は組み込みの normalize() がある ● その場合 normalize() を使った方が良い● reflect(i, n) = i – 2.0f * dot(i, n) * n● lerp(a, b, c) は (b-a) * c + a と実装されている ● c がスカラー値で、a または b もスカラー値なら, b * c + a * (1-c) が少ない演算でできる
  35. 35. 隠れたスカラー演算● 50.0f * normalize(vec) = 50.0f * (vec * rsqrt(dot(vec, vec))) ● 不必要にベクトル演算を行っているfloat3 main(float3 vec : TEXCOORD0) : SV_Target float3 main(float3 vec: TEXCOORD) : SV_Target{ { return 50.0f * normalize(vec); return vec * (50.0f * rsqrt(dot(vec, vec)));} }0 x: DOT4_e ____, R0.x, R0.x 0 x: DOT4_e ____, R0.x, R0.x y: DOT4_e ____, R0.y, R0.y y: DOT4_e ____, R0.y, R0.y z: DOT4_e ____, R0.z, R0.z z: DOT4_e ____, R0.z, R0.z w: DOT4_e ____, (0x80000000, -0.0f).x, 0.0f w: DOT4_e ____, (0x80000000, -0.0f).x, 0.0f1 t: RSQ_e ____, PV0.x 1 t: RSQ_e ____, PV0.x2 x: MUL_e ____, R0.y, PS1 2 w: MUL_e ____, PS1, (0x42480000, 50.0f).x y: MUL_e ____, R0.x, PS1 3 x: MUL_e R0.x, R0.x, PV2.w w: MUL_e ____, R0.z, PS1 y: MUL_e R0.y, R0.y, PV2.w3 x: MUL_e R0.x, PV2.y, (0x42480000, 50.0f).x z: MUL_e R0.z, R0.z, PV2.w y: MUL_e R0.y, PV2.x, (0x42480000, 50.0f).x z: MUL_e R0.z, PV2.w, (0x42480000, 50.0f).x
  36. 36. 隠れた共通の部分式● normalize(vec) and length(vec) contain dot(vec, vec) ● コンパイラは完全に一致したら再利用する ● コンパイラは異なった使い方には再利用をしない● 例)ベクトルの長さを 1 にクランプするfloat3 main(float3 v : TEXCOORD0) : SV_Target 0 x: DOT4_e ____, R0.x, R0.x{ y: DOT4_e R1.y, R0.y, R0.y if (length(v) > 1.0f) z: DOT4_e ____, R0.z, R0.z v = normalize(v); w: DOT4_e ____, (0x80000000, -0.0f).x, 0.0f return v; 1 t: SQRT_e ____, PV0.x} 2 w: SETGT_DX10 R0.w, PS1, 1.0f t: RSQ_e ____, R1.ydp3 r0.x, v0.xyzx, v0.xyzx 3 x: MUL_e ____, R0.z, PS2sqrt r0.y, r0.x y: MUL_e ____, R0.y, PS2rsq r0.x, r0.x z: MUL_e ____, R0.x, PS2mul r0.xzw, r0.xxxx, v0.xxyz 4 x: CNDE_INT R0.x, R0.w, R0.x, PV3.zlt r0.y, l(1.000000), r0.y y: CNDE_INT R0.y, R0.w, R0.y, PV3.ymovc o0.xyz, r0.yyyy, r0.xzwx, v0.xyzx z: CNDE_INT R0.z, R0.w, R0.z, PV3.x
  37. 37. 隠れた共通の部分式 ● 最適化:ベクトルの長さを 1 にクランプする最初の形 if (length(v) > 1.0f) v = normalize(v); float norm_factor = min(rsqrt(dot(v, v)), 1.0f); 部分式を取り出 return v; v *= norm_factor; す return v;式を展開 if (sqrt(dot(v, v)) > 1.0f) v *= rsqrt(dot(v, v)); float norm_factor = saturate(rsqrt(dot(v, v))); saturate に置換 return v; return v * norm_factor;式を統合 if (rsqrt(dot(v, v)) < 1.0f) v *= rsqrt(dot(v, v)); precise float norm_factor = saturate(rsqrt(dot(v, v))); HLSL return v; return v * norm_factor; コンパイラ対策
  38. 38. 隠れた共通の部分式● 最適化:ベクトルの長さを 1 にクランプするfloat3 main(float3 v : TEXCOORD0) : SV_Target float3 main(float3 v : TEXCOORD0) : SV_Target{ { if (length(v) > 1.0f) if (rsqrt(dot(v, v)) < 1.0f) v = normalize(v); v *= rsqrt(dot(v, v)); return v; return v;} }0 x: DOT4_e ____, R0.x, R0.x 0 x: DOT4_e ____, R0.x, R0.x y: DOT4_e R1.y, R0.y, R0.y y: DOT4_e ____, R0.y, R0.y z: DOT4_e ____, R0.z, R0.z z: DOT4_e ____, R0.z, R0.z w: DOT4_e ____, (0x80000000, -0.0f).x, 0.0f w: DOT4_e ____, (0x80000000, -0.0f).x, 0.0f1 t: SQRT_e ____, PV0.x 1 t: RSQ_e ____, PV0.x2 w: SETGT_DX10 R0.w, PS1, 1.0f 2 x: MUL_e ____, R0.y, PS1 t: RSQ_e ____, R1.y y: MUL_e ____, R0.x, PS13 x: MUL_e ____, R0.z, PS2 z: SETGT_DX10 ____, 1.0f, PS1 y: MUL_e ____, R0.y, PS2 w: MUL_e ____, R0.z, PS1 z: MUL_e ____, R0.x, PS2 3 x: CNDE_INT R0.x, PV2.z, R0.x, PV2.y4 x: CNDE_INT R0.x, R0.w, R0.x, PV3.z y: CNDE_INT R0.y, PV2.z, R0.y, PV2.x y: CNDE_INT R0.y, R0.w, R0.y, PV3.y z: CNDE_INT R0.z, PV2.z, R0.z, PV2.w z: CNDE_INT R0.z, R0.w, R0.z, PV3.x
  39. 39. 隠れた共通の部分式● 最適化:ベクトルの長さを 1 にクランプするfloat3 main(float3 v : TEXCOORD0) : SV_Target{ precise float norm_factor = saturate(rsqrt(dot(v, v))); return v * norm_factor;}0 x: DOT4_e ____, R0.x, R0.x y: DOT4_e ____, R0.y, R0.y z: DOT4_e ____, R0.z, R0.z w: DOT4_e ____, (0x80000000, -0.0f).x, 0.0f1 t: RSQ_e ____, PV0.x CLAMP2 x: MUL_e R0.x, R0.x, PS1 y: MUL_e R0.y, R0.y, PS1 z: MUL_e R0.z, R0.z, PS1● 汎用的なケースに拡張 ● 長さ 5.0f にクランプ → norm_factor = saturate(5.0f * rsqrt(dot(v, v)));
  40. 40. 評価の順序 ● 式は左から右に評価される ● かっこや演算子の優先順位がある場合を除いて ● スカラー計算を式の左に置いたりかっこを使う// float3 float float float3 float float // float3 float3 (float float float float)return Diffuse * n_dot_l * atten * LightColor * shadow * ao; return Diffuse * LightCol * (n_dot_l * atten * shadow * ao);0 x: MUL_e ____, R0.z, R0.w 0 x: MUL_e R0.x, R0.x, R1.x y: MUL_e ____, R0.y, R0.w y: MUL_e ____, R0.w, R1.w z: MUL_e ____, R0.x, R0.w z: MUL_e R0.z, R0.z, R1.z1 y: MUL_e ____, R1.w, PV0.x w: MUL_e R0.w, R0.y, R1.y z: MUL_e ____, R1.w, PV0.y 1 x: MUL_e ____, R2.x, PV0.y w: MUL_e ____, R1.w, PV0.z 2 w: MUL_e ____, R2.y, PV1.x2 x: MUL_e ____, R1.x, PV1.w 3 x: MUL_e R0.x, R0.x, PV2.w z: MUL_e ____, R1.z, PV1.y y: MUL_e R0.y, R0.w, PV2.w w: MUL_e ____, R1.y, PV1.z z: MUL_e R0.z, R0.z, PV2.w3 x: MUL_e ____, R2.x, PV2.w y: MUL_e ____, R2.x, PV2.x w: MUL_e ____, R2.x, PV2.z4 x: MUL_e R2.x, R2.y, PV3.y y: MUL_e R2.y, R2.y, PV3.x z: MUL_e R2.z, R2.y, PV3.w
  41. 41. 評価の順序 ● VLIW とベクトルアーキテクチャは依存関係に注意しないとい けない ● 特にスコープの始まりと終わりで ● a * b * c * d = ((a * b) * c) * d;// float ● Break dependency chains with parentheses: (a*b) * (c*d) float float float float3 //float3floatreturn n_dot_l * atten * shadow * ao * Diffuse * LightColor; float float float float3 return (n_dot_l * atten) * (shadow * ao) * (Diffuse float3 * LightColor);0 x: MUL_e ____, R0.w, R1.w 0 x: MUL_e ____, R2.x, R2.y1 w: MUL_e ____, R2.x, PV0.x y: MUL_e R0.y, R0.y, R1.y VEC_0212 z: MUL_e ____, R2.y, PV1.w z: MUL_e R0.z, R0.x, R1.x VEC_1203 x: MUL_e ____, R0.y, PV2.z w: MUL_e ____, R0.w, R1.w y: MUL_e ____, R0.x, PV2.z t: MUL_e R0.x, R0.z, R1.z w: MUL_e ____, R0.z, PV2.z 1 w: MUL_e ____, PV0.x, PV0.w4 x: MUL_e R1.x, R1.x, PV3.y 2 x: MUL_e R0.x, R0.z, PV1.w y: MUL_e R1.y, R1.y, PV3.x y: MUL_e R0.y, R0.y, PV1.w z: MUL_e R1.z, R1.z, PV3.w z: MUL_e R0.z, R0.x, PV1.w
  42. 42. 実際のテスト● ケース・スタディ: Clustered deferred shading ● 品質が混在するコード ● オリジナルのライティングコードは完全に最適化 ● さまざまなプロトタイプ品質のコードをあとで追加 ● ローレベルの最適化 ● 1-2 時間ほどの作業 ● シェーダは約 7% 短くなった ● 太陽光光源のみ: 0.40ms → 0.38ms (5% 高速化) ● 大量の点光源: 3.56ms → 3.22ms (10% 高速化) ● ハイレベルの最適化 ● 数週間の作業 ● 古典的な deferred shading に比べ -15% ~ +100% の高速化 ● 両方しよう!
  43. 43. その他の推奨事項● [branch], [flatten], [loop], [unroll] で意図を伝える ● [branch] は “勾配関数” の警告をエラーにする 【訳注: http://msdn.microsoft.com/ja-jp/library/bb509610%28v=vs.85%29.aspx 参照】 ● これは素晴らしい! ● さもないと、条件外の時のコードのかたまりを引きずることになる● シェーダの外でできることをシェーダ内に書かない 線形の演算は頂点シェーダに移す float2 ClipSpaceToTexcoord(float3 Cs)● { Cs.xy = Cs.xy / Cs.z; ● もちろん頂点がボトルネックでなければ Cs.xy = Cs.xy * 0.5h + 0.5h; Cs.y = ( 1.h - Cs.y ); return Cs.xy;● 必要以上の出力をしない } ● SM4 以降は float4 の SV_Target が必須でない ● 使われないアルファは書き込まない! float2 tex_coord = Cs.xy / Cs.z;
  44. 44. 良いローレベルコーダーになるには?● GPUハードの命令をよく理解する ● そして PC の D3D アセンブリを学ぼう● HLSL からハードウェアコードへの変換を理解する ● GPUShaderAnalyzer, NVShaderPerf, fxc.exe 等のツール ● あらゆるハードウェアとプラットフォームで結果を比較する● シェーダの編集がコードの長さに与えた影響をチェックする ● 異常な結果だったら? → アセンブリを精査し、原因と結果を調べる ● 実際のベンチマークを行う
  45. 45. すべてのシェーダを最適化するのだ!
  46. 46. 参考文献[1] IEEE-754http://en.wikipedia.org/wiki/IEEE_floating_point[2] Floating-Point Ruleshttp://msdn.microsoft.com/en-us/library/windows/desktop/cc308050(v=vs.85).aspx[3] Fabian Giesen: Finish your derivations, pleasehttp://fgiesen.wordpress.com/2010/10/21/finish-your-derivations-please/
  47. 47. Questions? @_Humus_emil.persson@avalanchestudios.seWe are hiring!New York, Stockholm
  48. 48. 翻訳: @Reputeless訳文改善の指摘は reputeless@gmail.com までお送りください。

×