読者です 読者をやめる 読者になる 読者になる

graphics.hatenablog.com

テクニカルアーティストの技術を書き殴るためのメモ帳

High-Performance Software Rasterization on GPUs (2)

(続き)


パイプラインの設計
f:id:hal1932:20120522220036j:plain

  • triangle setup
    1. ビューポートカリング
    2. 三角形のタイルへの割り付け
    3. ビューフラスタムカリング
    4. リッピング
  • bin/coarse rasterizer
    1. 頂点をタイルごとにまとめる
  • fine rasterizer
    1. フラグメントシェーダ
    2. デプステスト
    3. スタオペレーション
    4. フレームバッファ書き込み

この 4 つのステージがそれぞれ CUDA カーネル関数として用意されてて、CPU 側から順番に呼び出すかんじ。ソースコードでいうと src/framework/CudaRaster.cpp の 231 行目から、関数でいうと CudaRaster::launchStages(void) のところ。こうすると各ステージ内では処理順と依存関係を気にせず全力で処理できる。bin → coarse のとこでマージ(全タイル走査して自分とこで処理するやつを拾ってきてソート)のコストはかかるけど、bin 内で同期をとるよりはずっと速い。


各ステージの詳細

 ものすごくざくっと書くけど、まぁ、ソースコード公開されてるし。これでいっか。読むのは src/curaraster/cuda の中身。

1. triangle setup
 トライアングルごとにスレッドを起動。出力は配列に記録するけど、入力三角形のインデクスが1だったら配列のインデクス1の位置に記録する、みたいなことやってるから、入出力間で順序がズレるとかは気にしなくていい。in_arr[idx] = { i0, i1, i2 } みたいな三角形が入力されてきたら、out_arr[idx] の位置にその三角形の処理結果をいれとく、でいいのかな。
 クリッピングで三角形が増えたら、その増えた三角形への参照を out_arr[idx] にいれとく。こうしとけば out_arr を動的確保しなくて済む。その参照先は動的確保しなきゃだけど、そもそもそんなん全体からみればレアケースだし。
 あと、カリングのロジックがよくわからない。なんでこれでビューフラスタムの内外判定になってるんだろう。。

    if (v0.w < fabsf(v0.x) | v0.w < fabsf(v0.y) | v0.w < fabsf(v0.z))
    {
        if ((v0.w < +v0.x & v1.w < +v1.x & v2.w < +v2.x) |
            (v0.w < -v0.x & v1.w < -v1.x & v2.w < -v2.x) |
            (v0.w < +v0.y & v1.w < +v1.y & v2.w < +v2.y) |
            (v0.w < -v0.y & v1.w < -v1.y & v2.w < -v2.y) |
            (v0.w < +v0.z & v1.w < +v1.z & v2.w < +v2.z) |
            (v0.w < -v0.z & v1.w < -v1.z & v2.w < -v2.z))
        {
            triSubtris[taskIdx] = 0;
            return;
        }
    }

2. bin rasterizer
 SM ごとにいっこの CTA を起動して、そこで 512 個の三角形を処理する。前段の出力配列には 1 要素(input batch)あたり 0 から 7 個の三角形がはいってるから、まずはそこから、512 個取り出す。全部の CTA がこの処理を終えるまでが第一段階。
f:id:hal1932:20120527114134j:plain
 次に、どの三角形(の AABB)がどの Bin を覆うのかを計算する。図を見るとわかりやすいけど、各三角形ごとに 0~4 個。ここで Bin 単位の各三角形の描画位置が決まる、つまり、描画位置を基準にして三角形をソートしてることになる。この計算は各 CTA 内で完結してるから、同期を取る必要はない。

3. coarse rasterizer
 前段と同じく、ここでも 512 個ずつ三角形を処理する。前段で計算した「どの三角形がどの Bin に書き込まれるか」を頼りに、自分が担当する Bin にかかる三角形を入力にする。
 次に 1 スレッドあたり 1 三角形の割り振りで、どの三角形がどの Tile を覆うかを計算する。で、この処理も各 CTA ごとに独立してるから同期は取らない。

4. fine rasterizer
 いわゆるラスタライズと呼ばれる処理。1 タイルあたり 20 個の warp を起動して、各 warp が 32 個単位で三角形を処理してく。
 第一段階では、三角形単位でのアーリーデプステストをやって、そっからピクセルカバレッジを計算、結果をルックアップテーブルに書き込んでく。フラグメントが 32 個たまったら第二段階へ。
 第二段階では、1 フラグメント 1 スレッドでラスタライズ処理をする。デプステストしてシェーダ動かしてから、最後に ROP。シェーダまではブロックの共有メモリ上でできるんだけど、それだけじゃメモリ足りないからグローバルメモリ上で直接 ROP しなきゃなんないのが悩みどころなんだってさ。


(続く)