トップ 差分 一覧 ソース 検索 ヘルプ PDF RSS ログイン

独り言日記(2008/11)

独り言日記

GPUでのレイトレース(2008/11/30)

時間があればGPUネタで実験してることを書き連ねていくことにします。このあたりは今までまじめにやってなかったのもあり、試行錯誤兼覚書になりますのでご了承を。

GPUについての分かりやすい論文。

http://gpurt.sourceforge.net/DA07_0405_Ray_Tracing_on_GPU-1.0.5.pdf

この論文ではUniformGridによる空間分割をGPUにて行うようにしてます。結果を見るとCPUとあんまり差はないようですが、当時GeForce6シリーズ、今は9シリーズが一般に普及してるっぽいので、もうちょっと速いのかな。

一度kd-treeによる空間分割をGPUに入れてみようといじってたのですが、スタックってGPUではどう実装するんだろう、ってのがあったので初心に帰ってUGで確認してみることにしました。たしか、kd-treeをGPU実装する実験をされている方はいたと思いますので(海外の方で)、それもいずれはチェックする必要はありそうですね。

こちらは輝度計算(一般で言うシェーダ部分)をCPUにて行いたいので、GPUにすべてを納めない方法を考えていく必要があります。将来のカスタマイズを考えると、これごとGPUに入れると汎用性がなくなるんじゃないかなぁと。そこでのボトルネックの解決策は「いかにまとめてGPUに処理を送って並列化を有効利用するか」にあります。いずれにせよメモリ転送(ハードとのI/O)がボトルネックになるのは分かりきってます。このあたりは昔掘ってたのがあるので、その理論を適用。幸い今はPCI-Expressが一般的でDMA転送のボトルネックはかなりマシになっているはず。

ところで、今はDirectX9で実験してるのですがここでリソースを占有すると、グラフィック(DirectXでの3D描画)との併用は物理的に無理なような・・・。OpenGLとならいけるのだろうか。CUDAは分離して処理できる、みたいなことが書いてましたね。

Larrabeeが発表されるまでに実験結果が出ればいいや、というゆるゆるスケジュールにてこのあたりは進めていきます(ちなみにこれは興味本位の趣味の実装ですので、完全オープンに行きます)。

Macでの画像保存(2008/11/28)

ちょっと間が空きましたが、Mac(Carbon)での画像保存方法です。

読み込み時と同じく、Carbon.framework/QuickTime.frameworkをプロジェクトのビルド時に参照できるようにする必要があります。

#include <Carbon/Carbon.h>
#include <QuickTime/ImageCompression.h>
#include <QuickTime/QuickTimeComponents.h>

/**
 * 画像ファイルを保存する
 * @param[in]  pFileName  ファイル名
 * @param[in]  wid        保存する画像の幅
 * @param[in]  hei        保存する画像の高さ
 * @param[in]  pRGBABuff  1ピクセルにてRGBA 4バイトの情報が格納されているバッファ
bool SaveImageToFile(const char *pFileName,
     const int wid, const int hei,
     const unsigned char *pRGBABuff)
{
    int x, y;
    unsigned char *pBuff;
    unsigned char *pSrcPos, *pDstPos;
    char *pPos;
    int fileType;

    if(!pFileName) return false;
    if(!wid || !hei || !pRGBABuff) return false;
    
    // 一度カラファイルを作成。こうしないと、FSPthMakeRefが失敗するため。
    FILE *fp;
    fp = fopen(pFileName, "wb");
    if(!fp) return false;
    fputs("t", fp);
    fclose(fp);
    
    // ファイル名をPOSIX形式からFSRefに変換
    FSSpec spec;
    FSRef ref;
    Boolean isDir;
    if(FSPathMakeRef((const UInt8 *)pFileName, &ref, &isDir) != noErr) return false;
   
    // FSRefからFSSpecの変換
    FSGetCatalogInfo(&ref, kFSCatInfoNone, nil, nil, &spec, nil);

    //--------------------------------------------------//
    // ファイルの拡張子より、保存ファイルの種類を調べる //
    //--------------------------------------------------//
    pPos = strrchr(pFileName, '.');
    if(!pPos) return false;
    pPos++;
    fileType = kQTFileTypeBMP;
    if(strcmp(pPos, "bmp") == 0) {
        fileType = kQTFileTypeBMP;
    } else if(strcmp(pPos, "jpg") == 0 ||  strcmp(pPos, "jpeg") == 0) {
        fileType = kQTFileTypeJPEG;
    } else if(strcmp(pPos, "png") == 0) {
        fileType = kQTFileTypePNG;
    } else if(strcmp(pPos, "tiff") == 0 || strcmp(pPos, "tif") == 0) {
        fileType = kQTFileTypeTIFF;
    } else {
        return false;
    }

    //--------------------------------------------------//
    // 画像をRGBAのバッファに保持                       //
    //--------------------------------------------------//  
    pBuff = (unsigned char *)malloc(wid * (hei + 1) * 4);

    CGContextRef bitmapContext = CGBitmapContextCreate(pBuff,
                        wid, hei, 8,
                        wid * 4,
                        CGColorSpaceCreateDeviceRGB(),
                        kCGImageAlphaPremultipliedLast);

    // ビットマップのRGBAを格納するバッファ
    unsigned char *pBmpDat = (unsigned char *)CGBitmapContextGetData(bitmapContext);
    if(!pBmpDat) {
        CGContextRelease(bitmapContext);
        free(pBuff);
        return false;
    }
   
    // RGBA情報をコピー。
    // memcpy(pBmpDat, pRGBABuff, wid * hei * 4);
    // と同等。
    pSrcPos = (unsigned char *)pRGBABuff;
    pDstPos = pBmpDat;
    for(y = 0; y < hei; y++) {
        for(x = 0; x < wid; x++) {
            *(pDstPos + 0) = *(pSrcPos + 0);    // Red
            *(pDstPos + 1) = *(pSrcPos + 1);    // Green
            *(pDstPos + 2) = *(pSrcPos + 2);    // Blue
            *(pDstPos + 3) = *(pSrcPos + 3);    // Alpha
            
            pDstPos += 4;
            pSrcPos += 4;
        }
    }
    
    //--------------------------------------------------//
    //  画像タイプ別に保存                              //
    //--------------------------------------------------//  
    GraphicsExportComponent exporter;
    OpenADefaultComponent(
            GraphicsExporterComponentType, 
            fileType, 
            &exporter);
    GraphicsExportSetInputCGBitmapContext(exporter, bitmapContext);
    GraphicsExportSetDepth(exporter, 24);
    GraphicsExportSetOutputFile(exporter, &spec);
    GraphicsExportDoExport(exporter, NULL);
    
    CloseComponent(exporter);
    CGContextRelease(bitmapContext);
    free(pBuff);

    return true;
}

注意点としては、FSPathMakeRef関数はファイルが存在していない場合はエラーを返す、という部分です。新規ファイルとして保存する場合は、そのままではエラーになってしまいます。ここでは、エラーにならないようにあらかじめダミーファイルを作成しています。

拡張子を見て、bmp/jpeg/png/tiffなど分けてますが、「kQTFileTypeBMP」などの種類は

 http://homepage.mac.com/vanhoek/MovieGuts%20docs/36.html

のページより、

kQTFileTypePicture ← pict形式
kQTFileTypeGIF
kQTFileTypePNG
kQTFileTypeTIFF
kQTFileTypePhotoShop ← psd形式
kQTFileTypeSGIImage ← sgi形式
kQTFileTypeBMP
kQTFileTypeJPEG
kQTFileTypeJFIF
kQTFileTypeTargaImage ← tga形式?
kQTFileTypeFlash ← swf形式?

が画像としてあるみたいです。WindowsのGDI+と対応フォーマットをあわすなら、gif/png/tiff/bmp/jpeg、かな。

他のFreeImageなどのライブラリを使うのも手ですが、意外とOSネイティブの画像読み込みや保存は、WindowsのGDI+もMacのQuickTimeも速いのでいいですよ。

ヴィルヘルム・ハンマースホイ(2008/11/25)

電車の中の広告にて「ハンマースホイ」という単語を見て、ジャッキーチェンの映画に出てくるような脇役「ホイさん」か!とか一瞬妄想したのですが、、9/30〜12/7に上野にて展覧会をやってるようです。

http://www.shizukanaheya.com/top.html

知らなかった作家さんなんですが、絵として来るもんがありました。GI好きなら好きな作風ですね。フェルメールっぽいなぁと思って調べてみると、やっぱり影響を受けてるみたいです。

もうすぐで展覧会も終わりなので、見に行くかな。しかし、Wikipediaではなんでも情報があるなぁ、すばらしい。

続・GPUでの速度実験(2008/11/24)

GeForce 9600GTで速くなった、と思ったのですがよくよく考えるとGPU計算はバックグラウンド(ハード側)で行われるため、完全に計算が終了まで待つとすると、結局は「IDirect3DDevice9::GetRenderTargetData」の完了まで待たないといけないのかもしれません。

ということで、再度「IDirect3DDevice9::GetRenderTargetData」が終わるまで待つようにして確認しました。後、テクスチャサイズが大きい(1024x1024Pixelとか)とかなり遅いので、256x256pixelで処理するようにしました。

GeForce6600GTのWinXP環境ではCPUに比べて2倍強、GeForce 9600GTのWinVista環境ではCPUに比べて5倍くらい。微妙に速くなりました。う〜ん、でももうちょっと速くならないものかな。CUDAのサンプルではメモリ転送速度を測るものがあったので、そのへんも最適化されてるんでしょう、たぶん・・・。素直にCUDAに移るべきか。ちらっとCUDAのサンプルソース見ましたがややこしそうだ(^_^;;。

その前に、一度、GPUのみで計算する簡易レイトレーシングと、部分的にGPUにて計算する簡易レイレーシングで比較したほうがいいですね。

GPUでの速度実験(2008/11/23)

GPUの実験です。レイトレで使用する三角形交差判定を100万回 x 1000セット、合計10億回の計算を行うものをぶん回してみました。

なお、CUDAじゃなくてDirectX9のHLSLで実験です。CPUでループでまわした場合とGPUの場合で比較してます。

GeForce6600GT + Win XP / Intel Pentium4 2.8GHz / Mem 512MB

CPUGPU
96326 ms64477 ms

結果として、1.49倍。

GeForce 9600GT + Win Vista / Intel Xeon 5110 1.6GHz(Core 2 Duo) / Mem 2GB

CPUGPU
96876 ms32 ms

結果として、3027.37倍。

GeForce6600GTではあんまり効果がないということで放置していたのですが、GeForce9600GTでは如実に速度アップしました。

念のため、計算された結果をCPUの場合とGPUの場合で比較しましたが値としては一致。ということで、ちゃんと計算はされているようです。

ただ、これはあくまでもGPU上での計算のみで、

IDirect3DDevice9::GetRenderTargetData
IDirect3DSurface9::LockRect 〜 IDirect3DSurface9::UnlockRect

でのGPU → CPUに結果転送を行う部分にてボトルネック作ってしまいます。試しに1セットごとにて上記のGPU → CPUの処理を入れると、GeForce 9600GTマシンにて「30110 ms」ということで、CPUに比べて速度アップは3倍程度になってしまいました。う〜ん、やはりメモリ転送がボトルネックか・・・。

ただ、GPU計算自身はたしかにGeForce9シリーズでは速いですね。ちなみに作業用テクスチャは1024x1024pixelのものを3つ(三角形の各頂点を3テクスチャに納めてます)、交差判定の結果格納用のテクスチャを1024x1024pixel、としました。テクスチャサイズを小さくしてみたり、IDirect3DDevice9::CreateOffscreenPlainSurfaceのフラグを調整したりでなんとかできないかなぁ。

ここさえクリアできれば、ハイブリッドにてオフラインレンダラでも高速化させるための方法論が投与できそうではあります。

後、これくらいパフォーマンスがあると空間のトラバースもGPUに入れてしまってもまだ余裕がありそうですね。

Macのヘルプシステムを作ろうかどうしようか(2008/11/21)

Macではヘルプを実装するためにHelpBook(AppleHelpの一機能って立ち居地でしょうか)という仕組みがあるのですが、私としては正直使いにくいです。気になる点は以下。

  • インデックス(ツリー)を表示してほしい
  • 可搬性がない(SDKのマニュアルとしたい場合、単体起動させてほしい)
  • キーワード一覧がほしい
  • 検索入力がデスクトップ上のメニュー部にあるので、目線の移動が必要。同じウィンドウ上で検索させてほしい。

いや、実現できてるよ、ってのがあったらすみません。

Windows/Mac OS Xでマニュアル部を統一しようと思っていろいろ調べはしてるのですが、この使い勝手がドキュメント好きの私としてはどうも受け付けません。WindowsのHTMLヘルプに慣れてしまったからかもしれませんが、、、。かといって、PDFは重いし検索がよろしくないのが気になります。

WindowsのHTMLヘルプは「HTML Help Workshop」にて作成できます。以下のOfficeリソースキットに入ってます。昔っから英語版しかないですが、日本語のヘルプ作成もOKです。

http://www.microsoft.com/japan/office/ork/2003/tools/BoxA02.htm

一応、WindowsのHTMLヘルプはネットと絡めてCGIをHTMLヘルプに入れ込む、なんてことも可能です(実体は単なるHTMLですので)。

昔っからずっとHTMLヘルプは使い続けてるのですが、ヘルプとしての使いやすさはやっぱりHTMLヘルプが一番かなぁ。

wxWidgetsのサンプルでヘルプを作るのもあるのですが、ちょっとごわっとしたのが気になります。でも、wxWidgetsを使うのがマルチプラットフォームのヘルプとして一番実現しやすそうかな。

以下のHelpLogicというのはよさげなんですが、Mac/Winどちらでも動作するものは結局はHTMLとJavaScriptっぽいですね。

http://www.ebutterfly.com/helplogic/

といろいろ調べてもいい案が浮かばないので、作ったほうがいいのかなぁ。次回のレンダラでのヘルプ搭載は現状維持でいきますが、アプリケーションをこれ以外でも作る予定のある都合にて、ヘルプの使い勝手は避けられない問題です。

ユニットバスのレンダリング、リベンジ(2008/11/21)

もう一度シーンを見直して再度サンプリング数を高くしてレンダリングしてみました。

Win Vista / Intel Xeon 5110 1.6GHz(Core 2 Duo) / Mem 2GB

640x480 pixel、20000sampling/pixelにて約33.45時間。

2日マシンを立ち上げっぱなしでCPU酷使です(^_^;;。

以下のような部屋構成になってます。

光は外からのIBLのみ。3つの窓からの光が入ります。

ちょっとサンプリング数を変えて実験してみました。

100 sampling/pixel

1000 sampling/pixel

明らかにサンプリング数により光の入りっぷりが異なってます。拡散反射の場合は半球上にランダムにレイが反射することになりますが、光が極端に偏ってる場合(閉じた部屋の窓から光が入る場合など)はサンプリング数を上げないと最終結果に近づかない、ということなりそうです。

ここで遅いレンダリング時間をあえて出しているのは、「では、この質を保ちつつどこまで時間短縮できるか」というものの記録を行っていきたいから。閉鎖空間でフォトン使用にてどこまでできるのだろうか、これも実験事項です。MLTなどの最適化アルゴリズムを実験する、これも必要。GPU(CUDA)などのハードウェアの恩恵を受ける(将来はLarrabee)、これも実験がいりますね。今のGPUに載せる方法はおそらくあまり効果はないのかなと思ってます(オフラインレンダラの大半にて)。ハードは容量が限られてますので、単純な軽い実験用パストレならいいのですがマテリアルが複雑になるものとかは、良くて数並列程度になってしまいそう。いろいろ誤解がありそうなので近いうちに実験はしてみようとは思ってます(それでも、CPUとGPUの両方を使って2並列、とかも可能でしょうからね)。そう考えるとどうしてもLarrabeeの方に期待が傾くのですが、どうなんでしょうね。

あっと、一応いいわけですが、あくまでもレイトレースベースでのレンダラでのGPU使用でどこまで速くできるのだろう、の探求は行おうとは思ってます。もちろん、レイトレを使わずにつめていく方法もあり、GIがリアルタイムで動作している、というプロジェクトもちらほらあります。ぱっと見はなかなか違いが難しいのですが、、、。室内シーンをより高速に、ができればいいですねぇ。個人的には超リアルなCGでMystみたいなゲームがしたいです(^_^;;。

はずかしながらCUDAはチェックしてなかったので以下のサイトは最近知りました。

http://www.nvidia.co.jp/object/cuda_home_jp.html#

何倍になった、というのがプロジェクトごとに書いてますので、何に強いか弱いか、ってのは把握できますね。単純な計算はそれだけハードリソースが少なくて済むので並列数も増やせる、レンダラみたいにちょっと大きな規模になるとそれだけハードリソースを食うので並列数も少なくしないといけない、ということかもしれませんね。

で、上のユニットバスのレンダリング例だと、サンプリング数を少なくして求める絵を出す、というのは難しいということなのかもしれません(視点からのパストレースでは)。一次レイからの反射は重要なのでそこだけサンプリング数を増やして、それ以上はカットしていく、もしくはそれ以降はフォトンを集める、とか、いろいろ方法はありそうですので絵と比べながら速度アップできれば。今はそのためのリファレンス用レンダラでもあります。

後、これくらいサンプリング数が多ければ右の蛇口が壁に反射してコースティクスを生んでいるのも見えますね。

COLLADA形式(2008/11/20)

http://www.khronos.org/collada/

がCOLLADAの総本山ですかね。単なるXMLですので(バイナリもあるみたいですが)対応しやすそう。

カメラデータやアニメーション、物理関連のパラメータも持てるじゃないですか。でも、レンダリング系のパラメータはないですねぇ。シェーダがあるのでどっちかというとリアルタイム系のフォーマットっぽい。

これも将来対応予定とできれば。シーン保存はこれかfbxで満足できそうだなぁ。

Macでの画像読み込み(2008/11/20)

Macで画像を読み込むにはQuickTimeの機能を使うとWindowsでのGDI+みたいな読み込み・書き込みを行うことができます。確認できたところで、jpeg/png/bmp/gifがOK。たぶんpictもOKか。

ところが、簡単に扱えるというものではありません。Mac OS Xでは、POSIX形式の「/Users/xxx/tmp/test.png」みたいなファイルパス指定を、旧Macでの「:Users:xxx:tmp:test.png」みたいな形式に変換してくる必要があります。ドライブ(とMacで言っていいのか)の指定がある場合があるので、簡単に「/」を「:」に置き換えるだけじゃダメです。そう考えると、UNIX系で使われるファイルパス指定はスマートですね(ドライブという概念はマウントすることになるので特別視する必要はないわけですし)。

さて、Macではファイルパス変換のため、「POSIX → FSRef → FSSpec」なる変換を行います。

ソースを書いたほうがはやいのでそっちで説明します。

なお、Carbon.framework/QuickTime.frameworkをプロジェクトのビルド時に参照できるようにする必要があります。

#include <Carbon/Carbon.h>
#include <QuickTime/ImageCompression.h>
#include <QuickTime/QuickTimeComponents.h>

/**
 * ファイルから画像情報を読み込み
 * @param[in]  pFileName   ファイル名
 * @return 読み込みに成功すればtrueが返る
 */
bool LoadImageFromFile(const char *pFileName)
{
   CGImageRef image;
   FSSpec spec;
   OSErr err;
   GraphicsImportComponent gi;
   int wid, hei;
   unsigned char *pBuff;
   int x, y;
   unsigned char *pSrcPos, *pDstPos;
   bool ret;
   
   if(!pFileName) return false;

   // ファイル名をPOSIX形式からFSRefに変換
   FSRef ref;
   Boolean isDir;
   if(FSPathMakeRef((const UInt8 *)pFileName, &ref, &isDir) != noErr) return false;
   
   // FSRefからFSSpecの変換
   FSGetCatalogInfo(&ref, kFSCatInfoNone, nil, nil, &spec, nil);

   // グラフィックのインポート処理開始
   err = GetGraphicsImporterForFile((const FSSpec *)&spec, &gi);
   if(err != noErr) return false;

   // グラフィックスを得る
   ret = false;
   if(!GraphicsImportCreateCGImage(gi, &image, 
      kGraphicsImportCreateCGImageUsingCurrentSettings)) {
       // 画像の幅と高さを取得
       wid = (int)CGImageGetWidth(image);
       hei = (int)CGImageGetHeight(image);
       
       // 1ピクセルでのビット数
       //int bpp = CGImageGetBitsPerPixel(image);
       
       // 1ラインでのバイト数
       //int scanBytes = CGImageGetBytesPerRow(image);
       
       // ビットデータの取得(RGBAを取得するとする)
       pBuff = (unsigned char *)malloc(wid * (hei + 1) * 4);

       CGContextRef bitmapContext = CGBitmapContextCreate(pBuff,
                          wid, hei, 8,
                          wid * 4,
                          CGColorSpaceCreateDeviceRGB(),
                          kCGImageAlphaPremultipliedLast);

       // ビットマップを描画
       CGContextDrawImage(bitmapContext, CGRectMake(0,0,wid, hei), image);

       // ビットマップのRGBAバッファ
       unsigned char *pBmpDat = (unsigned char *)CGBitmapContextGetData(bitmapContext);
       if(pBmpDat) {
           pSrcPos = pBmpDat;
           for(y = 0; y < hei; y++) {
               for(x = 0; x < wid; x++) {
                   // *(pSrcPos + 0) がRed
                   // *(pSrcPos + 1) がGreen
                   // *(pSrcPos + 2) がBlue
                   // *(pSrcPos + 3) がAlpha
                   
                   pSrcPos += 4;
               }
           }
           ret = true;
       }

       CGContextRelease(bitmapContext);
       free(pBuff);
       CGImageRelease(image);
   }
   
   CloseComponent(gi); 
   
   return ret;
}

ファイル名はPOSIX形式(「/Users/xxx/test.png」みたいにスラッシュ指定)で指定します。日本語など全角がある場合はUTF-8で与えるようにしてください。

読み込まれると、上記のコメント化している「*(pSrcPos + 0)〜*(pSrcPos + 3)」にてピクセルのRed/Green/Blue/Alpha値が0〜255の値にて取得できます。

Carbonの「GraphicsImporter」というもので画像を読み込むのですが、それに渡すために「FSPathMakeRef」関数でPOSIXからFSRefに変換、「FSGetCatalogInfo」関数でFSSpecに変換、という手順を踏む必要があります。

その後、GraphicsImporterよりCGImageを取得、「CGContextDrawImage」関数にてビットマップに描画、でようやくピクセル値にアクセスできます。

・・・正直、こんだけややこしいのならカプセル化してくれよ、と思うのは私だけでしょうか(^_^;;?Carbonのオンライン情報見てもなかなかたどりつけない部分ですので(私は断片的な情報を寄せ集めてようやくたどり着きました)、参考になれば幸いです。読み込み速度に関しては、遅くはないです、それなりの速度といったところ。

しかし、OSの持つ命令群は古い仕様と新しいものが混在してますね。パスカル文字列なんてひさしぶりに見ましたし。Cocoaだともっと簡単なのかな。でも、覚える気ゼロです(苦笑)。だって、ObjectiveCは他OSに生かせる汎用性がないんだもの。なんでJavaなりCをネイティブに選択しなかったのか、まずい対応だよなと今でも思ってます。政治的なことではありそうですがNeXTの血なんで、分からなくはないのですが・・・。

で、画像あたりとQuickTime命令をいじっていてInside Macintoshを思い出しました。昔のMacプログラマなら持っていたであろう分厚いプログラム本です。今は情報がAppleのサイトにWeb展開されてますが、どうも探しにくいです(少し前まではもうちょっとアクセスしやすかったのですが、、)。

次回は、画像ファイル保存について説明していきます。

wxWidgets(wxAUI)でのズレ対策(2008/11/19)

タイミングによって、wxAUIでのフローティングウィンドウを移動するとマウスリリース時に他のウィンドウも移動することがある、という現象に出くわしてまして、wxWidgets内のソースをチェック中です。WindowsでもMac OS Xでも再現(こちらの環境ではMacのほうが再現率は低いです)。平行してレンダリングを走らせている場合など、負荷が高いときに起こりやすい感じでした。

aui/framemanager.cppの「wxAuiManager::OnFloatingPaneMoved」関数内にて

if (pane.IsFloating())
{
    pane.floating_pos = pane.frame->GetPosition();
    if (m_flags & wxAUI_MGR_TRANSPARENT_DRAG)
        pane.frame->SetTransparent(255);
}

のところにて、

if (pane.IsFloating())
{
    // 以下を追加
    {
        int cou, loop;
        cou = m_panes.GetCount();
        for(loop = 0; loop < cou; loop++) {
            wxAuiPaneInfo& p = m_panes.Item(loop);
            if(!p.IsFloating()) continue;
            if(p.name == pane.name) continue;
            p.floating_pos.x = p.frame->GetPosition().x;
            p.floating_pos.y = p.frame->GetPosition().y;
        }
    }
    
    pane.floating_pos = pane.frame->GetPosition();

    if (m_flags & wxAUI_MGR_TRANSPARENT_DRAG)
        pane.frame->SetTransparent(255);
}

のようにすることで再現しなくなりました。この関数では、キャプション部をドラッグしてひとつのウィンドウを移動させた場合、移動が完了した(マウスリリースになった)タイミングで呼ばれています。

どうも、ドラッグ対象のフローティングウィンドウ以外でもfloating_posの値が書き換えられる場合があり、この「wxAuiManager::OnFloatingPaneMoved」関数の最後で呼んでいるUpdate関数内でfloating_posとframe->GetPositionで取得できる位置が違う場合に「frame->SetSize(floating_pos.x, floating_pos.y...)」としてる部分でウィンドウがあらぬ位置に移動してしまうようでした。

みくよん(2008/11/15)

みくよん書籍版を買ってきました。

ニコニコ動画でこれを見て感動して、当時同人誌でのなぎみそSYSさんの「みくよん」を探してたのですが売り切れてて手に入らなかったのです。で、11月に書籍化するというのが紹介されてましてようやく手に入りました。

こういう文字の少ないものでも内容が伝わるのは絵の表現力としてすごいなぁと思ったりしてます。

後は来月出るうろたんだーのCDを買う予定です。ニコ房、ミク房(うろたんだーはKAITOですが、VOCALOID全体にはまってます)まっしぐらなんですが気にしない!!

みくよんを買ったのは秋葉原のとらのあなで、行ったときは同人ゲーム売り場も見てるのですが、東方とミク関連が増えてますね。音楽CDはほしいのがあるなぁ。今年はMacBookで散財したので自重しなくては、、、。

続続続・多角形とレイとの交差判定(2008/11/15)

話が飛びましたが、凹凸多角形とレイの交差判定の最後です。2D上に投影した凸多角形は11/11にやった内容で、外積にて内包されているか判定、というところまでやりました。

凹凸多角形の場合、交点のある位置を赤丸としたときの図です。

それぞれの交点の横方向にラインを引きます。ここでは3つの交点を例に挙げてます。

LineA

多角形の稜線より、LineAと交差する位置を求めます。赤丸よりも左または右にある(上図では青四角の)交点の数をカウントしましょう。左は1個、右は1個です。このときは赤丸は多角形に内包しています。

LineB

多角形の稜線より、LineBと交差する位置を求めます。赤丸よりも左または右にある(上図では青四角の)交点の数をカウントしましょう。左は1個、右は1個です。このときは赤丸は多角形に内包しています。

LineC

多角形の稜線より、LineCと交差する位置を求めます。赤丸よりも左または右にある(上図では青四角の)交点の数をカウントしましょう。左は2個、右は2個です。このときは赤丸は多角形の外にあります。

こんな感じで見てみると、ある法則が見えてきます。「左にある横方向のラインとの交差数が奇数の場合は内包、偶数の場合は外」、同様に「右にある横方向のラインとの交差数が奇数の場合は内包、偶数の場合は外」、ということでどっちかひとつを見ればOK。

プログラムで説明します。2D上に投影されたときの頂点をvector2 posA[]の配列であらわす、頂点数vCou、平面上での交差位置をvector2 cPosとします。

float x1, y1, x2, y2, tmp, xx;
int cou;

cou = 0;
for(loop = 0; loop < vCou; loop++) {
    x1 = posA[loop].x;
    y1 = posA[loop].y;
    if(loop + 1 < vCou) {
        x2 = posA[loop + 1].x;
        y2 = posA[loop + 1].y;
    } else {
        x2 = posA[0].x;
        y2 = posA[0].y;
    }

    if(y1 > y2) {
        // y1 < y2となるように入れ替え
        tmp = x1; x1 = x2; x2 = tmp;
        tmp = y1; y1 = y2; y2 = tmp;
    }
    if(y2 - y1 < 1.0f) {
        if(x1 <= cPos.x) cou++;
        continue;
    } 

    if(cPos.y >= y1 && cPos.y <= y2) {
        // y = cPos.y の位置でのスキャンライン上の交差位置を求める
        xx = (x2 - x1) * (cPos.y - y1) / (y2 - y1) + x1;
        if(xx <= cPos.x) cou++;
    }
}
if(cou & 1) {
    交点cPosは多角形posA[]に内包される
} else {
    交点cPosは多角形posA[]の外にある
}

ということになります。思ったよりも簡単になりますね。

ということで、このように一連の処理を行うことで、凹凸多角形がある場合のレイとの交差判定も可能になります。ただ、まだまだ最適化できる点は残してますので(ifで分岐してるところとか)詰めてみるものいいかもしれません。

多角形の法線の計算や、凸多角形かどうかの判定などは前処理でやってしまってもいいですね。三角形の場合は、Tomas Mollerのほうがコストが低いのでそっちに分けてしまうとか。

まだまだネタはあるのですが小出しにしていければと思ってます。Macでの画像読み込みやFSRefからFSSpec変換などはまだでしたね。これも徐々に。

SPEEDフォーマット(2008/11/12)

「ジャパンホームショー2008」というイベントが東京ビッグサイトにて行われてまして、本日は一日行ってきました。目が疲れた・・・。

私の目的はCAD系システムってどんなもん、というのを見るためと「SPEEDフォーマット」に関しての情報収集です。SPEEDとは建築CAD向けのオープンフォーマットの規格、で建築関連CADメーカー各社が集まって考えたフォーマットとのこと。これのお披露目会をやってましたので見てきました。オープンフォーマットなのでライセンス料はかからないようですね。

個人的にはSPEEDフォーマット自身の説明を期待していたのですが、アプリケーションに組み込んだ実例、といったメーカーさんの製品での操作の紹介が主ではありました。

本日サイトが新しくオープンしてたのでURLを。

http://www.speed-net.jp/

「SPEEDとは」のページから仕様書をダウンロードできるのですが、最後のページ「面の表裏」は右手系だと「逆周りじゃね?」と思うのは私だけでしょうか(^_^;;。反時計周りが表のような。

レンダラの絡みで最近はいろんなフォーマットを見てるのですが、これといった決め手のあるものはなかなかないのかなと思ってます。仕様書から判断できるSPEEDフォーマットは形状のみ(マテリアルはあります)ですね。カメラやレンダリング、アニメーション系の機能はないような感じです。ユーザが拡張できる、という話はしていたのですが、fbxなんかそうですが個々に拡張してしまうと結局方言を作ることになるので初期仕様がある程度完成していないとなかなか使う側は難しいのかも。まだオープンにされていない仕様や今後機能追加していく部分があるとは思いますので、どのように進化するのかは注目しております。

後、某メーカー(SPEEDフォーマット対応してます)のブースにてCADソフト見せてもらったのですが、窓の開閉・ドアの開閉ができてました。Shadeホームデザインで開閉してほしいなぁと思ったりしてます(半開きの窓をShade上で作らないといけない、なんてなんか無駄な気がするので)。

CADソフトは専門外なのであんまり何ができるのか理解できてないのですが、なるほど、3DCGとの間にはたしかに溝がありますね。基本は図面なのか。マテリアル設定から先のレンダリングまでの作業は3DCG寄りの分野といった感じではありました。

続続・多角形とレイとの交差判定(2008/11/11)

続きです。

面が凸多角形か判定

2D上に投影されたときの頂点をvector2 posA[]の配列であらわす、頂点数vCou、平面上での交差位置をvector2 cPosとします。

まずは、多角形が凸型かどうかの判別です。

上図のように、頂点がn個ある多角形の頂点をはさむ辺の外積を取り、0.0以下かそれ以外か、でカウントします。プログラム書いたほうがはやいので、プログラムを。

cou = 0;
for(loop = 0; loop < vCou; loop++) {
    if(loop == 0) {
        v1 = posA[loop] - posA[vCou - 1];
    } else {
        v1 = posA[loop] - posA[loop - 1];
    }
    if(loop + 1 < vCou) {
        v2 = posA[loop + 1] - posA[loop];
    } else {
        v2 = posA[0] - posA[loop];
    }
    if((v1.x * v2.y - v1.y * v2.x) < 0.0f) cou--;
    else cou++;
}

頂点をはさむ辺の外積を計算して、変数couにてカウントしてます。このcouがvCouまたは-vCouの場合は、凸多角形と判断できます。外積で求まるのは平面に垂直な直交軸が手前に伸びているか奥に伸びているか、です。これがすべて同じ向きかを調べるわけです。

凸多角形の場合は外積により内包判定。凸多角形の場合の判定はここで終了。

凸多角形と分かった場合は、その凸の中に交点があるか外にあるか、を判定します。

今度も外積を使用します。交点位置と個々の辺の両端を結ぶベクトルを求めます。それを元に先ほどと同じように外積で求めた値が0.0以下かそれ以外かでカウントします。

cou = 0;
for(loop = 0; loop < vCou; loop++) {
    v1 = posA[loop];
    if(loop + 1 < vCou) {
        v2 = posA[loop + 1];
    } else {
        v2 = posA[0];
    }
    e1.x = cPos.x - v1.x;
    e1.y = cPos.y - v1.y;
    e2.x = v2.x - cPos.x;
    e2.y = v2.y - cPos.y;

    if((e1.x * e2.y - e1.y * e2.x) < 0.0f) cou--;
    else cou++;
}

ここでのcouがvCouまたは-vCouの場合は凸多角形でかつ交点cPosが内包されている、と判断できます。couがそれ以外でかつ凸多角形の場合は交差しない、と判断できます。

後は、凹凸多角形ですね。これはスキャンライン的に判断します。といっても、交点位置は分かってるので稜線との交点を求めて判断するだけではあります。よく塗りのアルゴリズムで使われるのですがキーワードは偶数・奇数です。これは次回にて。

続・多角形とレイとの交差判定(2008/11/11)

先日の続きです。

上記のように、凹凸多角形がありそれにレイが交差するとします。計算する流れとしては

  • 多角形から面法線を求める(平面式との交差計算で使用)
  • 平面とレイの交差距離を求める。これで交差位置も求まることになる。
  • 面の頂点および交差位置は同一平面上にあると仮定して、2D上に投影
  • 面が凸多角形か判定
  • 凸多角形の場合は外積により内包判定。凸多角形の場合の判定はここで終了。
  • それ以外の凹凸多角形の場合の内包判定。スキャンラインにて判定を行う。

のような感じです。

多角形から面法線を求める

面を構成する頂点は同一平面上にあると仮定します。モデラーなどで同一平面にそろえない場合もあるので極力平均を取った法線を求めます。

以下のvector3は(x,y,z)要素を持つものとします。

// vCouを面を構成する頂点数、
// pWPosが頂点座標の格納された先頭ポインタとする
int loop;
double dx, dy, dz;
vector3 v1, v2;
vector3 n = vector3(0, 0, 0);
vector3 *pPos = (vector3 *)pWPos;       // 頂点の配列先頭
for(loop = 0; loop < vCou; loop++) {
    if(loop == 0) {
        v1 = (*pPos) - (*(pWPos + vCou - 1));
    } else {
        v1 = (*pPos) - (*(pPos - 1));
    }
    if(loop + 1 < vCou) {
        v2 = (*(pPos + 1)) - (*pPos);
    } else {
        v2 = (*pWPos) - (*pPos);
    }

    // 外積より面法線を求める
    dx = (double)(v1.y * v2.z - v1.z * v2.y);
    dy = (double)(v1.z * v2.x - v1.x * v2.z);
    dz = (double)(v1.x * v2.y - v1.y * v2.x);

    if(fabs(dx) < (1e-5) && fabs(dy) < (1e-5) && fabs(dz) < (1e-5)) {
        pPos++;
        continue;
    }
    n.x += (float)dx;
    n.y += (float)dy;
    n.z += (float)dz;
    pPos++;
}
normalize(&n, &n);      // 法線の正規化

法線は、1つの頂点をはさむ2辺の外積にて求まります。念のため、すべての頂点の法線を求めてそれを加算、最後に正規化して長さが1.0となるようにしてます。面の向きはどちらでもOKです。求めるのは平面式でのレイからの距離を求めるのに使用しますので。

平面とレイの交差距離を求める

法線nが求まりましたので、平面式は

nx(X - x1) + ny(Y - y1) + nz(Z - z1) = 0

となります。(nx, ny, nz)が面の法線、(x1, y1, z1)が面を構成する頂点。ここでは、頂点座標の一番はじめのものを使うとします。

レイの開始位置をrayPos、レイの方向ベクトル(正規化済みとする)をrayDirとし、仮に以下の式を用意します。

c.x = rayDir.x * t + rayPos.x;
c.y = rayDir.y * t + rayPos.y;
c.z = rayDir.z * t + rayPos.z;

で求めたcが平面との交点位置、tがレイの開始点からの距離とします。このcを(X, Y, Z) = (c.x, c.y, c.z)として平面の式に代入します。t以外は定数ですので、これにてtが求まります。計算は以下な感じ。

double dx, dy, dz, A, B;
dx = (double)(rayPos.x - (pWPos->x));
dy = (double)(rayPos.y - (pWPos->y));
dz = (double)(rayPos.z - (pWPos->z));
B = (double)(n.x * dx + n.y * dy + n.z * dz);
A = (double)(n.x * rayDir.x + n.y * rayDir.y + n.z * rayDir.z);
if(fabs(A) < 1e-5) return false;
t = (-B / A);
if(t < 0.0f) return false;

ここで計算したtがレイの開始位置からの距離です。計算途中のAが限りなく0に近い場合は平面の法線とレイが垂直に近い場合(面とレイが平行で交差せず)、tが0より小さい場合はレイより手前にあることになります。

ここで求めたtを上で書いたcの式に当てはめると、交差位置が求まりますね。理想としては、多角形の頂点と交点位置は同一平面上にあるとよいことになります。

ここで交点は求まったのですが、まだ「平面上にあるけど、多角形内に内包されるか」は判断できてません。ということで、内包判定を行います。

面の頂点および交差位置は同一平面上にあると仮定して、2D上に投影

とある一点が内包されているかどうか、は2D上に投影することで計算しやすくなります。三次元上ではXYZの3要素があるのですが、これから1つの要素を消してXYだけで考えると分かりやすいですよね。で、上の説明で「多角形の頂点と交点位置は同一平面上にあるとよい」というのを入れました。こういう条件だと、平面から見ると2Dで判別できそうです。

で、投影方法なんですが、平面式の計算で法線nを求めました。これがZ軸にイコールになるように変換し、Zを相殺しましょうか。

そこで出てくるのが「正規直交基底」です。まずは計算式から。

vector3 vX, vY, vZ;
vZ = n;
if(fabs(vZ.y) < 0.9f) {
    vY = srVector::SVector3(0, 1, 0);
} else {
    vY = srVector::SVector3(1, 0, 0);
}
vec3Cross(&vX, &vY, &vZ);   // 外積計算。vXにvY x vZの結果が入る
vec3Cross(&vY, &vX, &vZ);   // 外積計算。vYにvX x vZの結果が入る
vec3Normalize(&vX, &vX);    // 正規化
vec3Normalize(&vY, &vY);    // 正規化

法線ベクトルnが(0, 1, 0)に近くなければもうひとつの軸を(0, 1, 0)とする、(0, 1, 0)に近ければもうひとつの軸を(1, 0, 0)とする、で外積計算をすることで3つめの軸が求まります。そして求まったベクトルとvZ(法線ベクトル)を再度外積計算することで、完全に直交する3つのベクトルが求まります。これを正規化して(vX, vY, vZ)として保持。

この正規直交基底は異なる座標間の変換ではよく使われますので覚えておいて損は無いです。

(fx, fy, fz)を変換前の頂点座標とすると、

dx = (double)(vX.x * fx + vX.y * fy + vX.z * fz);
dy = (double)(vY.x * fx + vY.y * fy + vY.z * fz);
dz = (double)(vZ.x * fx + vZ.y * fy + vZ.z * fz);

の計算にて、元の頂点座標が同一平面にある場合はdzはすべて同じ値になります。ということで、Z成分を除去できます。同様に、交点cも当てはめてZ成分を除去します。

概念としては、以下のようなXY座標で交点位置が投影される状態となります。

後は2D処理です。

と、ここでいったん区切ります。

多角形とレイとの交差判定(2008/11/10)

本日はネタフリだけ。ひさびさに実用的(?)な話題です。まず、レンダラ(とビュワー)にてobjファイル形式のインポータと、多角形ポリゴン対応をしてみました。objの仕様上は1つの面に対して何頂点でも指定可能な感じでしたので、これを機に。レンダラ内部では多角形対応していましたが、ビュワーにて対応中です。

レンダラ内部では多角形を三角形分割して保持し、それで三角形とレイとの交差判定を行ってます。

多角形との交差判定では以下のような方法が代表的でしょうか。

  • 多角形を三角形分割して扱う
  • 多角形のまま扱う

大部分は三角形分解が可能だと思うのですが、なにぶん余計なメモリを確保しておかないといけないというのもあるので、後者の場合を考えていきます(前者は昔日記に書きましたのでスルーです)。レンダラで扱う場合はどれが適切なんでしょうね、処理時間が大事なので三角形分解して扱うほうがいいのかなと考えてますが、たしかにメモリに無駄は生じるかも。

以下のような関数で、凹凸多角形の交差判定を行うとします。

/**
 * 多角形とレイとの交差判定
 * @param[in]  rayPos  レイの開始位置
 * @param[in]  rayDir  レイの方向ベクトル(正規化済み)
 * @param[in]  vCou    面を構成する頂点数
 * @param[in]  pWPos   面のワールド座標での頂点座標の配列
 * @param[out] pRetT   レイの開始位置から交差位置までの距離
 * @return 交差する場合はtrue
 */
bool PolygonIntersect(const vector3 &rayPos, const vector3 &rayDir,
       const int vCou, 
       vector3 *pWPos, float *pRetT);

この機能、ビュワーにてマウスクリックされたときに多角形ポリゴンが選択されているか、の判別に使いたいんです。手順としては、平面とレイとの交差判定で交差位置を求め、平面上に頂点および交差位置を投影、で2次元上での内包判定を行うとします。スクリーンに投影した2D上の多角形とスクリーン上のマウスクリック点で判別してもいいのですが、一応レンダラでも使えるようにレイとの交差判定で考えることにします。

なお、凸多角形の場合は外積計算だけで内包されているかは判定できます。問題となるのは凹が存在する多角形の場合。ということで、しばらくはこのネタで引っ張ります。

Shadeのフォーラムページ(2008/11/07)

私はてっきり削除されたものかと思ってたのですが、どうもまだ生きてるらしい、という情報をいただきました。

Shade onlineのページから「コミュニティ&ギャラリー」のトップメニューを選択。左に表示されているメニュー「過去の投稿作品(閲覧のみ)」の「画像投稿機」を選択。これで、Shade onlineフォーラムのメニューが左に出てきて「最近の投稿」を選択するとフォーラムが出てきます。

なんという隠し通路。

artist sideには登録・投稿したいかなぁと思うこともあるのですが、個人的にはどうも点数をつけられるのが受け付けられなくって今はROMってます。競争原理が働くのでクリエイティブな方には有効かもしれませんが、正直素人な私が嬉々としてアップして点数つけられてへこむのもイヤだしなぁと。

objファイルフォーマット(2008/11/06)

3Dではよく使われるであろうobjファイルフォーマットについて。後できちんと調べて実装しようと思ってShadeのフォーラムに情報として書き込んだのですが、なんだかフォーラム自体が消えてますのでもう一度こっちに貼っておきます。

http://local.wasp.uwa.edu.au/~pbourke/dataformats/obj/
http://local.wasp.uwa.edu.au/~pbourke/dataformats/mtl/

お気に入りにいれときゃいいですね(^_^;;。面倒なので後で調べようと思って忘れてしまうことが多いです<自分。

しかし、フォーラムがなくなると(で、artist sideが出来たのだと思うけど)作品メインじゃない自分は活躍の場がないなぁと。まあいいや、自分のサイトで自作自演で盛り上がっておきます。

さて、objはいろいろ機能があるようですが頂点のリストが1つしかないです。個々のオブジェクトはグループという概念でまとめることができるのですが、グループごとに再度頂点リストを再配置しないといかんですねぇ。

ただ、構成としては単純なので実装はしやすいです。マテリアルのほうは細かい機能が多いのですが、ツールのインポータでは対応してない機能が多いのかもしれませんね。とりあえず、Shade/メタセコ/3Dアトリエにてobjを出して試してみました。マテリアルに関してはほんとに基本部分のみといった感じ。

そういえば、ご存知の方もいるとは思いますがShadeホームデザインのコンテンツが増えてます。最近、なぜかイーフロからの案内メールがこない状態でしてはぶられた感があるのですが、artist sideの書き込みでアップデートされてるのを知りました。今は1.0.2.2です。私としては、ささっと配置したいというのもありこれはありがたいです。今まで気づいてなかったのですが「琉球畳」素材が!!前からあったかどうかは記憶にございません(^_^;;。

で、Shadeホームデザインは個人的によく使ってます。Shadeのshdに吐き出したときのにマテリアルがまとまってないのがもったいないですが、よく考えればプラグイン作ってまとめちゃえばいいかもしれないですね。

後、Autodeskからのメールを見てfbx 2009.03なるものが出てることを知りました。これも何が変わったのか試してみたいなぁ。ちなみにfbx SDKにtarga形式の読み込み・書き込み関数がありまして、そのソースを元にレンダラにてtarga形式のインポータを作らせてもらいました。フォーマットとしては割と簡単です。

fbx SDKにて知ったのですが「COLLADA」ってのがあるのですね。3Dのモデルフォーマットらしい。fbxもちょっと過去の仕様が点在して分かりにくいところがあるので、なんかもうちょっと整然としたかつ拡張性のある統一フォーマットはないかなぁと思ったりしてます。

スレッドを最適化(2008/11/04)

サンプルレンダリングは、以下の400x300pixel、100 sampling/pixelのもの。

Mac OS X 10.5.5 / 2.1GHz Intel Core 2 Duo / Mem 1GB

最大スレッド数Ver 0.5.1.3Ver 0.5.1.4備考
1 Thread135秒135秒---
2 Thread99秒74秒旧来から見て1.34倍の速度アップ
16 Thread74秒74秒---

Win XP / Pentium4 2.8GHz(HyperThread環境) / Mem 512MB

最大スレッド数Ver 0.5.1.3Ver 0.5.1.4備考
1 Thread161秒161秒---
2 Thread148秒137秒旧来から見て1.08倍の速度アップ
16 Thread153秒159秒---

Win Vista / Intel Xeon 5110 1.6GHz(Core 2 Duo) / Mem 2GB

最大スレッド数Ver 0.5.1.3Ver 0.5.1.4備考
1 Thread132秒132秒---
2 Thread70秒70秒---
16 Thread70秒72秒---

Winでは一部で速度アップですが、マルチコア環境ではほぼ変わらず。Win環境のマルチコア以外では2スレッド時に若干速度アップしました。ただ、スレッド数を上げても変わらないことから(HyperThreadで若干速いですが)、コア数にあわせたスレッドを使うほうが効率がよさそうですね。

Mac環境で速度アップ。スレッドのスケジューリングを見直しました。Win環境ではCoreDuoにてスレッドを1から2の使用に変えることで約1.88倍、Mac環境ではCoreDuoにてスレッドを1から2の使用に変えることで約1.82倍、ということでマルチコアの恩恵としてはこんなところかな(以前完全2倍になっていたのですが、シーンによるのかも)。

一応、無駄なくスレッドのスケジューリングしたつもりですが、スレッドに関して分かったことが数点あります。

無駄なSleepを置かない

これは当たり前ですね。Sleepを使うんじゃなくて、Windowsの場合はWaitForSingleObject/WaitForMultipleObjectsで待たせること。Macはsem_wait(POSIXの命令。指示があるまではこの場で待機)で待つように。

WindowsのWaitForXXXでは常にINFINITE

タイムアウトによる待ちからの脱出はスレッドプログラミングでは必要ないのかなと思いました。別スレッドからちょっかいを出したい場合(または一定の時間で抜け出たい場合)はシグナルを送って「立たせる」ようにすれば、待ってる間は他のスレッドにて時間は配分されます。後、MacのPOSIXスレッドではタイムアウトの概念が使えないようですので、OS依存をしないためにINFINITEで。

Macでは定期的にsched_yieldを呼ぶ

「sched_yield」は、別のスレッドに対して制御を譲る命令です。GUI系と並列で動かしている場合は、イベントの息抜きがいる場合があります。定期的に呼ぶ必要はないですが、sem_waitの後くらいに一度呼んであげるのがいいのかもしれません。

MacでのWaitForMultipleObjectsの代わり

Winでの「WaitForMultipleObjects」は、複数のシグナルを監視してどれかひとつでもイベントが起これば次にいく、といったものです。残念ながらPOSIXスレッドではこの機能はないです。で、どうすれば実現できるかということですが、、、。1つの「まとめ役のセマフォ」を作成し、複数シグナル(セマフォ)の1つがおきたらついでに「まとめ役のセマフォ」も起こしてあげます。

sem_t *parentSem;   // まとめ役のセマフォ
sem_t *semA[16];    // 個々のセマフォ。これで16スレッド管理するとする
... sem_openにて名前付きセマフォの作成。 ...

// n番目のスレッドでの処理。
// 以下の処理が0〜15まで別々のスレッドとして存在するとします。
while(1) {
    // 何か処理
    ...
    // n番目のスレッドのイベントを立ち上げ
    sem_post(semA[n]);
 
    // まとめ役のセマフォでのイベントを立ち上げ
    sem_post(parentSem);
}

これをチェックする親のスレッド関数にて以下のようにチェックできます。

// 個々のスレッドのイベントを感知
while(1) {
    // 何か処理
    ...
    
    // まとめ役のセマフォでのイベントが来るまでずっと待機。
    sem_wait(parentSem);

    int ret;
    for(int i = 0; i < 16; i++) {
        // i番目のスレッドのイベントが立っているかチェック
        ret = sem_trywait(semA[i]);
        if(ret < 0 && errno == EAGAIN) continue; 
        if(ret != 0) continue;
        break;
    }
    // 他のスレッドに処理を渡す
    sched_yield();

    // i番目のスレッドのフラグが立った!!
} 

これで擬似的にWindowsで言う

WaitForMultipleObjects(16, semA, false, INFINITE);

を実現していることになります(semAに格納された16個のシグナルの1つが立ち上がったら抜ける。INFINITEなので、シグナルがおきるまでずっと待つ)。

ここで、

while(1) {
    // 何か処理
    ...
    
    int ret;
    for(int i = 0; i < 16; i++) {
        // i番目のスレッドのイベントが立っているかチェック
        ret = sem_trywait(semA[i]);
        if(ret < 0 && errno == EAGAIN) continue; 
        if(ret != 0) continue;
        break;
    }
    // 他のスレッドに処理を渡す
    sched_yield();

    if(i < 16) {
        // i番目のスレッドのフラグが立った!!
    }
} 

のようにすればこんなことしなくてもいけるんじゃないか、と思ってはいけません。上記はご法度です。イベントがこなければwhile(1)にて延々と回り続けるのでCPUリソースを消費することになりますので。じゃあ、Sleepをwhileの最後に置こう、とかするとこれもCPUの空白時間ができるのでアウトです(Sleepの間は他のスレッドは有意義に使えるのですが、このwhile自身のループにて待ちが発生し、結果的に遅くなります)。そういった意味でもSleepは使わずにスケジューリングできることが大事かなと思います。

後、Macでのセマフォについて役に立つかどうか、覚書がありますのでまた別途日記にて書いておくようにします。

ちなみに、現行ベクターで公開しているレンダラのバージョン(Ver 0.4.1.3)よりもすでに手を加えていってますので、年末くらいに上記の最適化も加味したものが公開できるかと思います。

後、上画像を見て、まだ最適化できる点があるのに気づきましたでしょうか?「レイトレと変わらないじゃないか」と思った方、正解です。1ピクセルに対して100サンプリングする必要もない箇所がありますよね。拡散反射しない場所はわざわざサンプリングする必要もないわけで、このあたりはがさっと最適化できそうではあります。ただし、二次三次で拡散反射の面にぶちあたるとやっぱりサンプリングは分岐するので、「局所的な偏り」をなんとか検出する必要がありそうです。

MacでのPOSIXにおけるセマフォの上限(2008/11/03)

Macにて、あるスレッド数以上を処理しようとするとアプリが不安定になることがありまして、調べて見ると「sem_open」による名前付きセマフォのオープン数の上限に引っかかっていたようでした。1つの処理にて3つのセマフォを使っていたのですが、これの16スレッド分(16x3=48セマフォのオープン)では不安定に。処理を最適化して1つの処理にて2つのセマフォを使う(16x2=32セマフォのオープン)ことで安定。

この考えでいくと、たとえば将来32コアなんてCPUが出た場合はアウトになりますね。それとも、セマフォ数の上限自身もあがるのかな。と、これはOSでの設定になるかも。

いずれにせよ、CPUだけじゃなくてOSとしてどれくらいのスレッド数の作成(+セマフォなどの機能)に耐えうることができるのか、両方から攻めていく必要がありそうです。

セマフォの上限はOSがどこかで握ってそうですね。これも調査せねば。

Future's Laboratory 技術格納庫 2004-2013 Yutaka Yoshisaka.