SlideShare uma empresa Scribd logo
1 de 48
Low-level Thinking in High-level
  Shading Languages
  High-level Shading Languages(HLSL) におけるローレベル思考

  Emil Persson
  Head of Research, Avalanche Studios


翻訳: @Reputeless
問題の明確化


“最近、業界の著名人は、少しの変更でもっと早くできる
  ようなシェーダコードを GDC の講演で使っている”
この講演の目的



     “ローレベル思考が
今日でもまだ有効であること明らかにする”
背景
●   古き良き時代、おじいちゃんがまだ子供だった頃…
    ●   シェーダは短かった
        ●     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
背景
●   Low-level shading languages は死んだ
    ●   シェーダを書くには非生産的
    ●   DX10 以降はアセンブリに非対応
        ●   とにもかくにも誰も使っていない
    ●   コンパイラとドライバの最適化が良くはたらいてくれる (時々…)
    ●   なんてこった、最近はアーティストがシェーダを作りやがる!
        ●   ビジュアルエディタを使って
            ●   ボックスや矢印を使って
                 ● サイクル数も数えず、アセンブリも調べずに
                 ● テクニカルドキュメントさえ読まずに
                 ● うわぁぁ、あの子供時代、戻ってこい、戻ってこい ・・・
●   要するに:
    ●   シェーダの書き手がハードウェアに疎くなった
どうしてわざわざ気を付けないといけないの?
         ●   どうシェーダを書くかは重要!
//     float3    float     float   float3       float float    //      float     float     float float     float3    float3
return 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_120
1   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.w
2   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.w
3   x:   MUL_e    ____,   R2.x,   PV2.w
    y:   MUL_e    ____,   R2.x,   PV2.x
    w:   MUL_e    ____,   R2.x,   PV2.z
4   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
どうしてわざわざ気を付けないといけないの?
●   より良いパフォーマンスが得られる
    ●   「うちは ALU がボトルネックじゃないんだけど・・・」
        ●   消費電力を節約しよう
        ●   Texture やメモリ帯域の使用をまだ改善できるはず
        ●   追加機能のために余力を残しておこう
    ●   「プロジェクトの最後で最適化するつもりなんだけど・・・」
        ●   家に帰れなくならないようお祈りします・・・
●   一貫性が得られる
    ●   物事にはしばしば最良の方法がある
    ●   読みやすさを改善しよう
●   楽しい!
”コンパイラが最適化してくれるでしょ!”
”コンパイラが最適化してくれるでしょ!”
●   コンパイラは狡猾だ!
    ●   もう賢すぎてコンパイラ自身をだませちゃう!
●   しかし:
    ●   コンパイラはあなたの心を読めない
    ●   コンパイラは全容が見えていない
    ●   コンパイラは限られた情報しか使えない
    ●   コンパイラはルールを破れない
”コンパイラが最適化してくれるでしょ!”
これは MAD (発狂)しますか?                            (なんちゃって)
float main(float x : TEXCOORD) : SV_Target
{
    return (x + 1.0f) * 0.5f;
}
”コンパイラが最適化してくれるでしょ!”
これは 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)
”コンパイラが最適化してくれるでしょ!”
これは 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.0f
mul o0.x, r0.x, l(0.500000)               1 x: MUL_e        R0.x,   PV0.y, 0.5
                                    01 EXP_DONE: PIX0, R0.x___
どうしてダメだった?
●   結果が一致するとは限らないため
●   INF や NAN を引き起こす場合がある
●   一般的にコンパイラが得意なのは:
    ●   使われていないコードの除去
    ●   使われていないリソースの除去
    ●   定数の組み立て
    ●   レジスタの割り当て
    ●   コードのスケジューリング
●   苦手なことは:
    ●   コードの意味を変えること
    ●   依存関係を壊すこと
    ●   ルールを破ること
したがって:

シェーダはハードウェアに実行してもらいたいように書こう!


           そこで:
         ローレベル思考
ルール
●   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() の呼び出しを最適化で消してしま
            うかもしれない!
ハードウェアに関する普遍的*事実
●   乗算→加算 は 1 つの命令。加算→乗算 は 2 つの命令
●   絶対値(abs), 符号反転(-) , saturate はコストがかからない
    ●   MOV が発生する場合を除いて
●   スカラー演算はベクトル演算より使用するリソースが少ない


●   定数だけを使う数学関数はバカげてる
●   何もしないことは何かをすることより早い

* 我々が知る限りの宇宙において
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
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;
除算
●   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]
マッドネス
●   最初はこんなコード:
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.0f
add r0.y, cb0[0].y, l(1.000000)              z:   MUL_e      ____,   KC0[0].y, 0.5
mad r0.x, cb0[0].x, r0.y, -r0.x              t:   RCP_e      R0.y,   KC0[0].y
add r0.x, -r0.x, v0.x                    1   x:   MULADD_e   ____,   KC0[0].x, PV0.y, -PV0.z
mad r0.x, cb0[0].y, l(0.500000), r0.x    2   w:   ADD        ____,   R0.x, -PV1.x
div_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
マッドネス
●   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
修飾子
●       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|
修飾子
●       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
修飾子
●       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
修飾子
●   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 を復活させられる
HLSL コンパイラへの対策
●   precise キーワードを使う
    ●    コンパイラは NaN を無視しない
    ●    saturate(NaN) == 0
float 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.xyzx
min 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 CLAMP
1 x:    MIN_DX10   R0.x,   PV0.x, 1.0f
組み込み関数
●   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)
組み込み関数
●   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()
    ●   キミたち誰? シェーダでどんな仕事ができるっていうの?
組み込み関数
●   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
組み込み関数
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].z
1   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.w
2   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.x
3   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
行列計算
●   行列は任意の線形変換を一飲みにできる
    ●   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;
スカラー演算
●   現代のハードウェアにはスカラー ALU がある
    ●   スカラー計算は常にベクトル計算より早い
●   昔の VLIW やベクトル ALU アーキテクチャでもメリットがある
    ●   シェーダが短くなることがある
    ●   あるいは、ほかの処理のためにレーンが空く
●   スカラーからベクトルへの展開は気付きにくい
    ●   式の評価順とかっこに依存している
    ●   時には関数や抽象化で隠される
    ●   時には関数内で隠される
スカラー演算とベクトル演算の混在
●   ローレベルの計算を考える
    ●   ベクトル部分とスカラー部分を分離する
    ●   共通の部分式を探す
        ●   コンパイラはつねに共通の部分式を再利用できるわけではない!
        ●   コンパイラはスカラーを取り出せないこともある!
        ●   dot(), normalize(), reflect(), length(), distance()
●   スカラー計算とベクトル計算を分けて管理する
    ●   評価順に気を付けよう
        ●   式は左から右に評価される
        ●   かっこを使おう
隠れたスカラー演算
●   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)
        が少ない演算でできる
隠れたスカラー演算
●   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.0f
1   t:   RSQ_e    ____,   PV0.x                          1   t:   RSQ_e    ____,   PV0.x
2   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.w
3   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
隠れた共通の部分式
●   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.y
dp3        r0.x, v0.xyzx, v0.xyzx               3   x:   MUL_e        ____,   R0.z, PS2
sqrt       r0.y, r0.x                               y:   MUL_e        ____,   R0.y, PS2
rsq        r0.x, r0.x                               z:   MUL_e        ____,   R0.x, PS2
mul        r0.xzw, r0.xxxx, v0.xxyz             4   x:   CNDE_INT     R0.x,   R0.w, R0.x, PV3.z
lt         r0.y, l(1.000000), r0.y                  y:   CNDE_INT     R0.y,   R0.w, R0.y, PV3.y
movc       o0.xyz, r0.yyyy, r0.xzwx, v0.xyzx        z:   CNDE_INT     R0.z,   R0.w, R0.z, PV3.x
隠れた共通の部分式
 ●   最適化:ベクトルの長さを 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;            コンパイラ対策
隠れた共通の部分式
●   最適化:ベクトルの長さを 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.0f
1   t:   SQRT_e       ____,   PV0.x                         1   t:   RSQ_e        ____,   PV0.x
2   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, PS1
3   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.y
4   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
隠れた共通の部分式
●   最適化:ベクトルの長さを 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.0f
1   t:   RSQ_e    ____,   PV0.x      CLAMP
2   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)));
評価の順序
    ●        式は左から右に評価される
             ●   かっこや演算子の優先順位がある場合を除いて
             ●   スカラー計算を式の左に置いたりかっこを使う
//     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.z
1       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.x
2       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.w
3       x:   MUL_e   ____,   R2.x,   PV2.w
        y:   MUL_e   ____,   R2.x,   PV2.x
        w:   MUL_e   ____,   R2.x,   PV2.z
4       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
評価の順序
     ●     VLIW とベクトルアーキテクチャは依存関係に注意しないとい
           けない
             ●   特にスコープの始まりと終わりで
             ●   a * b * c * d = ((a * b) * c) * d;
//     float
                ●   Break dependency chains with parentheses: (a*b) * (c*d)
                  float  float float float3    //float3float
return 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.y
1   w:   MUL_e     ____,   R2.x,   PV0.x                           y:   MUL_e   R0.y,   R0.y,    R1.y     VEC_021
2   z:   MUL_e     ____,   R2.y,   PV1.w                           z:   MUL_e   R0.z,   R0.x,    R1.x     VEC_120
3   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.w
4   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
実際のテスト
●   ケース・スタディ: Clustered deferred shading
    ●   品質が混在するコード
        ●   オリジナルのライティングコードは完全に最適化
        ●   さまざまなプロトタイプ品質のコードをあとで追加
    ●   ローレベルの最適化
        ●   1-2 時間ほどの作業
        ●   シェーダは約 7% 短くなった
        ●   太陽光光源のみ: 0.40ms → 0.38ms (5% 高速化)
        ●   大量の点光源: 3.56ms → 3.22ms (10% 高速化)
    ●   ハイレベルの最適化
        ●   数週間の作業
        ●   古典的な deferred shading に比べ -15% ~ +100% の高速化
        ●   両方しよう!
その他の推奨事項
●   [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;
良いローレベルコーダーになるには?
●   GPUハードの命令をよく理解する
    ●   そして PC の D3D アセンブリを学ぼう
●   HLSL からハードウェアコードへの変換を理解する
    ●   GPUShaderAnalyzer, NVShaderPerf, fxc.exe 等のツール
    ●   あらゆるハードウェアとプラットフォームで結果を比較する
●   シェーダの編集がコードの長さに与えた影響をチェックする
    ●   異常な結果だったら? → アセンブリを精査し、原因と結果を調べる
    ●   実際のベンチマークを行う
すべてのシェーダを最適化するのだ!
参考文献
[1] IEEE-754
http://en.wikipedia.org/wiki/IEEE_floating_point

[2] Floating-Point Rules
http://msdn.microsoft.com/en-us/library/windows/desktop/cc308050(v=vs.85).aspx

[3] Fabian Giesen: Finish your derivations, please
http://fgiesen.wordpress.com/2010/10/21/finish-your-derivations-please/
Questions?
   @_Humus_
emil.persson@avalanchestudios.se

We are hiring!
New York, Stockholm
翻訳:   @Reputeless

訳文改善の指摘は reputeless@gmail.com までお送りください。

Mais conteúdo relacionado

Mais de Ryo Suzuki

ゲーム開発者のための C++11/C++14
ゲーム開発者のための C++11/C++14ゲーム開発者のための C++11/C++14
ゲーム開発者のための C++11/C++14Ryo Suzuki
 
Processingによるプログラミング入門 第6回
Processingによるプログラミング入門 第6回Processingによるプログラミング入門 第6回
Processingによるプログラミング入門 第6回Ryo Suzuki
 
Processingによるプログラミング入門 第5回
Processingによるプログラミング入門 第5回Processingによるプログラミング入門 第5回
Processingによるプログラミング入門 第5回Ryo Suzuki
 
Processingによるプログラミング入門 第4回
Processingによるプログラミング入門 第4回Processingによるプログラミング入門 第4回
Processingによるプログラミング入門 第4回Ryo Suzuki
 
Processingによるプログラミング入門 第3回
Processingによるプログラミング入門 第3回Processingによるプログラミング入門 第3回
Processingによるプログラミング入門 第3回Ryo Suzuki
 
Processing によるプログラミング入門 第1回
Processing によるプログラミング入門 第1回Processing によるプログラミング入門 第1回
Processing によるプログラミング入門 第1回Ryo Suzuki
 

Mais de Ryo Suzuki (6)

ゲーム開発者のための C++11/C++14
ゲーム開発者のための C++11/C++14ゲーム開発者のための C++11/C++14
ゲーム開発者のための C++11/C++14
 
Processingによるプログラミング入門 第6回
Processingによるプログラミング入門 第6回Processingによるプログラミング入門 第6回
Processingによるプログラミング入門 第6回
 
Processingによるプログラミング入門 第5回
Processingによるプログラミング入門 第5回Processingによるプログラミング入門 第5回
Processingによるプログラミング入門 第5回
 
Processingによるプログラミング入門 第4回
Processingによるプログラミング入門 第4回Processingによるプログラミング入門 第4回
Processingによるプログラミング入門 第4回
 
Processingによるプログラミング入門 第3回
Processingによるプログラミング入門 第3回Processingによるプログラミング入門 第3回
Processingによるプログラミング入門 第3回
 
Processing によるプログラミング入門 第1回
Processing によるプログラミング入門 第1回Processing によるプログラミング入門 第1回
Processing によるプログラミング入門 第1回
 

Último

20240412_HCCJP での Windows Server 2025 Active Directory
20240412_HCCJP での Windows Server 2025 Active Directory20240412_HCCJP での Windows Server 2025 Active Directory
20240412_HCCJP での Windows Server 2025 Active Directoryosamut
 
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略Ryo Sasaki
 
Amazon SES を勉強してみる その12024/04/12の勉強会で発表されたものです。
Amazon SES を勉強してみる その12024/04/12の勉強会で発表されたものです。Amazon SES を勉強してみる その12024/04/12の勉強会で発表されたものです。
Amazon SES を勉強してみる その12024/04/12の勉強会で発表されたものです。iPride Co., Ltd.
 
PHP-Conference-Odawara-2024-04-000000000
PHP-Conference-Odawara-2024-04-000000000PHP-Conference-Odawara-2024-04-000000000
PHP-Conference-Odawara-2024-04-000000000Shota Ito
 
UPWARD_share_company_information_20240415.pdf
UPWARD_share_company_information_20240415.pdfUPWARD_share_company_information_20240415.pdf
UPWARD_share_company_information_20240415.pdffurutsuka
 
Postman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By DanielPostman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By Danieldanielhu54
 
スマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システムスマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システムsugiuralab
 
新人研修のまとめ 2024/04/12の勉強会で発表されたものです。
新人研修のまとめ       2024/04/12の勉強会で発表されたものです。新人研修のまとめ       2024/04/12の勉強会で発表されたものです。
新人研修のまとめ 2024/04/12の勉強会で発表されたものです。iPride Co., Ltd.
 
IoT in the era of generative AI, Thanks IoT ALGYAN.pptx
IoT in the era of generative AI, Thanks IoT ALGYAN.pptxIoT in the era of generative AI, Thanks IoT ALGYAN.pptx
IoT in the era of generative AI, Thanks IoT ALGYAN.pptxAtomu Hidaka
 

Último (9)

20240412_HCCJP での Windows Server 2025 Active Directory
20240412_HCCJP での Windows Server 2025 Active Directory20240412_HCCJP での Windows Server 2025 Active Directory
20240412_HCCJP での Windows Server 2025 Active Directory
 
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
 
Amazon SES を勉強してみる その12024/04/12の勉強会で発表されたものです。
Amazon SES を勉強してみる その12024/04/12の勉強会で発表されたものです。Amazon SES を勉強してみる その12024/04/12の勉強会で発表されたものです。
Amazon SES を勉強してみる その12024/04/12の勉強会で発表されたものです。
 
PHP-Conference-Odawara-2024-04-000000000
PHP-Conference-Odawara-2024-04-000000000PHP-Conference-Odawara-2024-04-000000000
PHP-Conference-Odawara-2024-04-000000000
 
UPWARD_share_company_information_20240415.pdf
UPWARD_share_company_information_20240415.pdfUPWARD_share_company_information_20240415.pdf
UPWARD_share_company_information_20240415.pdf
 
Postman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By DanielPostman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By Daniel
 
スマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システムスマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システム
 
新人研修のまとめ 2024/04/12の勉強会で発表されたものです。
新人研修のまとめ       2024/04/12の勉強会で発表されたものです。新人研修のまとめ       2024/04/12の勉強会で発表されたものです。
新人研修のまとめ 2024/04/12の勉強会で発表されたものです。
 
IoT in the era of generative AI, Thanks IoT ALGYAN.pptx
IoT in the era of generative AI, Thanks IoT ALGYAN.pptxIoT in the era of generative AI, Thanks IoT ALGYAN.pptx
IoT in the era of generative AI, Thanks IoT ALGYAN.pptx
 

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

  • 1. Low-level Thinking in High-level Shading Languages High-level Shading Languages(HLSL) におけるローレベル思考 Emil Persson Head of Research, Avalanche Studios 翻訳: @Reputeless
  • 3. この講演の目的 “ローレベル思考が 今日でもまだ有効であること明らかにする”
  • 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. 背景 ● Low-level shading languages は死んだ ● シェーダを書くには非生産的 ● DX10 以降はアセンブリに非対応 ● とにもかくにも誰も使っていない ● コンパイラとドライバの最適化が良くはたらいてくれる (時々…) ● なんてこった、最近はアーティストがシェーダを作りやがる! ● ビジュアルエディタを使って ● ボックスや矢印を使って ● サイクル数も数えず、アセンブリも調べずに ● テクニカルドキュメントさえ読まずに ● うわぁぁ、あの子供時代、戻ってこい、戻ってこい ・・・ ● 要するに: ● シェーダの書き手がハードウェアに疎くなった
  • 6. どうしてわざわざ気を付けないといけないの? ● どうシェーダを書くかは重要! // float3 float float float3 float float // float float float float float3 float3 return 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_120 1 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.w 2 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.w 3 x: MUL_e ____, R2.x, PV2.w y: MUL_e ____, R2.x, PV2.x w: MUL_e ____, R2.x, PV2.z 4 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. どうしてわざわざ気を付けないといけないの? ● より良いパフォーマンスが得られる ● 「うちは ALU がボトルネックじゃないんだけど・・・」 ● 消費電力を節約しよう ● Texture やメモリ帯域の使用をまだ改善できるはず ● 追加機能のために余力を残しておこう ● 「プロジェクトの最後で最適化するつもりなんだけど・・・」 ● 家に帰れなくならないようお祈りします・・・ ● 一貫性が得られる ● 物事にはしばしば最良の方法がある ● 読みやすさを改善しよう ● 楽しい!
  • 9. ”コンパイラが最適化してくれるでしょ!” ● コンパイラは狡猾だ! ● もう賢すぎてコンパイラ自身をだませちゃう! ● しかし: ● コンパイラはあなたの心を読めない ● コンパイラは全容が見えていない ● コンパイラは限られた情報しか使えない ● コンパイラはルールを破れない
  • 10. ”コンパイラが最適化してくれるでしょ!” これは MAD (発狂)しますか? (なんちゃって) float main(float x : TEXCOORD) : SV_Target { return (x + 1.0f) * 0.5f; }
  • 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. ”コンパイラが最適化してくれるでしょ!” これは 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.0f mul o0.x, r0.x, l(0.500000) 1 x: MUL_e R0.x, PV0.y, 0.5 01 EXP_DONE: PIX0, R0.x___
  • 13. どうしてダメだった? ● 結果が一致するとは限らないため ● INF や NAN を引き起こす場合がある ● 一般的にコンパイラが得意なのは: ● 使われていないコードの除去 ● 使われていないリソースの除去 ● 定数の組み立て ● レジスタの割り当て ● コードのスケジューリング ● 苦手なことは: ● コードの意味を変えること ● 依存関係を壊すこと ● ルールを破ること
  • 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. ハードウェアに関する普遍的*事実 ● 乗算→加算 は 1 つの命令。加算→乗算 は 2 つの命令 ● 絶対値(abs), 符号反転(-) , saturate はコストがかからない ● MOV が発生する場合を除いて ● スカラー演算はベクトル演算より使用するリソースが少ない ● 定数だけを使う数学関数はバカげてる ● 何もしないことは何かをすることより早い * 我々が知る限りの宇宙において
  • 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. 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. 除算 ● 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. マッドネス ● 最初はこんなコード: 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.0f add r0.y, cb0[0].y, l(1.000000) z: MUL_e ____, KC0[0].y, 0.5 mad r0.x, cb0[0].x, r0.y, -r0.x t: RCP_e R0.y, KC0[0].y add r0.x, -r0.x, v0.x 1 x: MULADD_e ____, KC0[0].x, PV0.y, -PV0.z mad r0.x, cb0[0].y, l(0.500000), r0.x 2 w: ADD ____, R0.x, -PV1.x div_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. マッドネス ● 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. 修飾子 ● 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. 修飾子 ● 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. 修飾子 ● 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. 修飾子 ● 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. HLSL コンパイラへの対策 ● precise キーワードを使う ● コンパイラは NaN を無視しない ● saturate(NaN) == 0 float 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.xyzx min 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 CLAMP 1 x: MIN_DX10 R0.x, PV0.x, 1.0f
  • 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. 組み込み関数 ● 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. 組み込み関数 ● 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. 組み込み関数 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].z 1 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.w 2 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.x 3 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. 行列計算 ● 行列は任意の線形変換を一飲みにできる ● 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. スカラー演算 ● 現代のハードウェアにはスカラー ALU がある ● スカラー計算は常にベクトル計算より早い ● 昔の VLIW やベクトル ALU アーキテクチャでもメリットがある ● シェーダが短くなることがある ● あるいは、ほかの処理のためにレーンが空く ● スカラーからベクトルへの展開は気付きにくい ● 式の評価順とかっこに依存している ● 時には関数や抽象化で隠される ● 時には関数内で隠される
  • 33. スカラー演算とベクトル演算の混在 ● ローレベルの計算を考える ● ベクトル部分とスカラー部分を分離する ● 共通の部分式を探す ● コンパイラはつねに共通の部分式を再利用できるわけではない! ● コンパイラはスカラーを取り出せないこともある! ● dot(), normalize(), reflect(), length(), distance() ● スカラー計算とベクトル計算を分けて管理する ● 評価順に気を付けよう ● 式は左から右に評価される ● かっこを使おう
  • 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. 隠れたスカラー演算 ● 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.0f 1 t: RSQ_e ____, PV0.x 1 t: RSQ_e ____, PV0.x 2 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.w 3 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. 隠れた共通の部分式 ● 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.y dp3 r0.x, v0.xyzx, v0.xyzx 3 x: MUL_e ____, R0.z, PS2 sqrt r0.y, r0.x y: MUL_e ____, R0.y, PS2 rsq r0.x, r0.x z: MUL_e ____, R0.x, PS2 mul r0.xzw, r0.xxxx, v0.xxyz 4 x: CNDE_INT R0.x, R0.w, R0.x, PV3.z lt r0.y, l(1.000000), r0.y y: CNDE_INT R0.y, R0.w, R0.y, PV3.y movc o0.xyz, r0.yyyy, r0.xzwx, v0.xyzx z: CNDE_INT R0.z, R0.w, R0.z, PV3.x
  • 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. 隠れた共通の部分式 ● 最適化:ベクトルの長さを 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.0f 1 t: SQRT_e ____, PV0.x 1 t: RSQ_e ____, PV0.x 2 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, PS1 3 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.y 4 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. 隠れた共通の部分式 ● 最適化:ベクトルの長さを 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.0f 1 t: RSQ_e ____, PV0.x CLAMP 2 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. 評価の順序 ● 式は左から右に評価される ● かっこや演算子の優先順位がある場合を除いて ● スカラー計算を式の左に置いたりかっこを使う // 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.z 1 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.x 2 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.w 3 x: MUL_e ____, R2.x, PV2.w y: MUL_e ____, R2.x, PV2.x w: MUL_e ____, R2.x, PV2.z 4 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. 評価の順序 ● VLIW とベクトルアーキテクチャは依存関係に注意しないとい けない ● 特にスコープの始まりと終わりで ● a * b * c * d = ((a * b) * c) * d; // float ● Break dependency chains with parentheses: (a*b) * (c*d) float float float float3 //float3float return 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.y 1 w: MUL_e ____, R2.x, PV0.x y: MUL_e R0.y, R0.y, R1.y VEC_021 2 z: MUL_e ____, R2.y, PV1.w z: MUL_e R0.z, R0.x, R1.x VEC_120 3 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.w 4 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. 実際のテスト ● ケース・スタディ: Clustered deferred shading ● 品質が混在するコード ● オリジナルのライティングコードは完全に最適化 ● さまざまなプロトタイプ品質のコードをあとで追加 ● ローレベルの最適化 ● 1-2 時間ほどの作業 ● シェーダは約 7% 短くなった ● 太陽光光源のみ: 0.40ms → 0.38ms (5% 高速化) ● 大量の点光源: 3.56ms → 3.22ms (10% 高速化) ● ハイレベルの最適化 ● 数週間の作業 ● 古典的な deferred shading に比べ -15% ~ +100% の高速化 ● 両方しよう!
  • 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. 良いローレベルコーダーになるには? ● GPUハードの命令をよく理解する ● そして PC の D3D アセンブリを学ぼう ● HLSL からハードウェアコードへの変換を理解する ● GPUShaderAnalyzer, NVShaderPerf, fxc.exe 等のツール ● あらゆるハードウェアとプラットフォームで結果を比較する ● シェーダの編集がコードの長さに与えた影響をチェックする ● 異常な結果だったら? → アセンブリを精査し、原因と結果を調べる ● 実際のベンチマークを行う
  • 46. 参考文献 [1] IEEE-754 http://en.wikipedia.org/wiki/IEEE_floating_point [2] Floating-Point Rules http://msdn.microsoft.com/en-us/library/windows/desktop/cc308050(v=vs.85).aspx [3] Fabian Giesen: Finish your derivations, please http://fgiesen.wordpress.com/2010/10/21/finish-your-derivations-please/
  • 47. Questions? @_Humus_ emil.persson@avalanchestudios.se We are hiring! New York, Stockholm
  • 48. 翻訳: @Reputeless 訳文改善の指摘は reputeless@gmail.com までお送りください。