当て逃げ

スポーツクラブの駐車場で当て逃げされた。修理費 9万円也。
あー、くやしいなあ。

CEATEC

先週の土曜日に CEATEC JAPAN に行ってきた。
個人的に一番面白かったのはやはり東芝ブースの Cellのデモ。
“Cell”の処理能力で奇妙に変身!? “デジタルかがみtype F”を披露――東芝

技術デモとしても見事だけど、「Cellを使ってどれだけ面白いことができるか?」という視点でみても素晴らしいデモだった。
これに髪型や化粧のバリエーション(ヨンさまヘアスタイルとか、きん肉マンメイクとか)を増やして、なんとかPS3で商品化してくれないかな?(ハーフミラーはどうするんだろう?)

SVGの謎

仕事でSVGの簡易レンダラを書いている。
んで、今、アニメーション周りの実装をしているのだが、 SVG仕様書の定義があいまいな部分が多くて困っている。

たとえば モーションパス。モーションパスは直線やベジェ曲線で指定したパスに沿って要素を動かすタグで、たとえば以下のようにすると長方形が直線(0,0)-(100,0)に沿って、右に3秒かけて移動する。
<rect width="10" height="20" >
  <animateMotion path="M 0 0 L 100 0" dur="3s"/>
</rect>
ここで、親要素のrectに transform属性によるアフィン変換がかかっている場合はどうなるか?
<rect transform="rotate(90)" width="10" height="20" >
  <animateMotion path="M 0 0 L 100 0" dur="3s"/>
</rect>
まず、このときの定義が仕様書に書いてない(ような気がする)。 しょうがないので、各種ブラウザの挙動をみると、モーションパス自体も親要素と同じアフィン変換がかかるようだ。てことで、上の場合には直線(0,0)-(0,100)に沿って、真下に矩形が移動するアニメーションになる。
たしかに親要素の影響をモーションパスが受けないと不便そうなので、納得がいく挙動ではある。

では、親要素のアフィン変換自体を動的に変更させるとどうなるか?
たとえば以下は animateTransformというタグによって、親要素のtransform属性を上書きする例である。親要素のrotateの値が子要素のanimateTransformタグによって90から0へと変化していく。
<rect transform="rotate(90)" width="10" height="20" >
  <animateMotion path="M 0 0 L 100 0" dur="3s"/>
  <animateTransform attributeName="transform" type="rotate" from="90" to="0" dur="3s"/>
</rect>
この場合も当然仕様書には書いてない(ような気がする)。
Adobeのビューアの挙動をみると今度は モーションパスは親要素のtransformの影響を全く受けなくなるようだ。 つまり上の例ではモーションパスは一番最初の例と同様に(0,0)-(100,0)の直線として扱われる。

このあたりでちょっと仕様に納得がいかなくなってくるが、さらに、今度は親要素のtransform属性を上書きするのではなく、追記するようにしてみる。
<rect transform="rotate(90)" width="10" height="20" >
  <animateMotion path="M 0 0 L 100 0" dur="3s"/>
  <animateTransform additive="sum" attributeName="transform" type="rotate" from="90" to="0" dur="3s"/>
</rect>
additive属性によって、親要素のrotateの値を上書きするのでなく、加算することになる。つまり親要素のrotateは180から90へ変換するアニメーションとなる。
すると、不思議なことにモーションパスは再度親要素のtransform属性、それもアニメーションによる変形を受ける前の"rotate(90)"の影響だけを受けるようになるようだ。

今回の例以外にも、こういった複数のタグが絡み合ったときの挙動に対する記述が、不思議なことにSVGの仕様書には書いてない(ような気がする)。
現状は仕様書に明記されていない部分は Adobeのビューアを参考に推測しているが、Adobeビューアの挙動が本当に正しいのかもわからないし、そもそもアニメーション周りでもまったく実装されていない機能などが多数存在している。

このままじゃビューアごとに挙動が異なるという混沌とした状況になるんじゃないかと危惧してしまう。
SVG大丈夫なのか!?VRMLみたくなってしまうのは避けてもらいたいのだが...

GDI+のビットマップ転送速度

GDI+ はとても強力な描画命令を持っていて便利だけども、使ってみるとかなり遅い。
かなり単純な図形しか描画しなくても遅い。ということで、描画自体の速度はともかく、描画したGDI+のオフスクリーンサーフェスから、ウインドウへの転送速度が足を引っ張っていそうだ。
webで探してみてもやはりオフスクリーンサーフェスからの転送速度が遅いということを書いてあるページがいくつか見つかった。(このへんとか)
ということで、自分でもいろいろ調査してみた。

まずは一番シンプルで一般的と思われるコードは以下であり、これが遅い。
// オフスクリーンサーフェスの生成
Gdiplus::Bitmap bitmap(rect.Width(), rect.Height(), PixelFormat24bppRGB);

// オフスクリーンサーフェスに Graphicsクラスを使って描画
Gdiplus::Graphics g(&bitmap);
...

// オフスクリーンサーフェスからウィンドウへの転送。pDCはウインドウのデバイスコンテキスト
Gdiplus::Graphics g2(pDC->GetSafeHdc());
g2.DrawImage(&bitmap, 0, 0);

真っ先に疑ったのはDrawImage関数の効率が悪いのではないかということだ。以前にもDirectXのD3DXLoadSurfaceFromMemory関数がとても遅くて、自前で書いたほうが遥かに速かったことがあり、かなり疑わしい。
ということで、オフスクリーンサーフェスからHBITMAPを取得して、BitBltでウインドウへ流し込んでみた。
// オフスクリーンサーフェスからウィンドウへの転送。pDCはウインドウのデバイスコンテキスト
HBITMAP hBitmap = NULL;
bitmap.GetHBITMAP(0, &hBitmap);
{
  CDC dcMemory;
  dcMemory.CreateCompatibleDC(NULL);
  dcMemory.SelectObject(hBitmap);
  pDC->BitBlt(0, 0, bitmap.GetWidth(), bitmap.GetHeight(), &dcMemory, 0, 0, SRCCOPY);
}
DeleteObject(hBitmap);
残念ながら速度に違いは見られなかった。

つづいて、オフスクリーンサーフェスのバッファのポインタを直接取得して、StretchDIBitsで転送してみた。
// オフスクリーンサーフェスからウィンドウへの転送。pDCはウインドウのデバイスコンテキスト
Gdiplus::BitmapData bitmapData;
bitmap.LockBits(NULL, Gdiplus::ImageLockModeRead, PixelFormat24bppRGB, &bitmapData);

BITMAPINFOHEADER bmpInfoHeader;
memset(&bmpInfoHeader, 0, sizeof(BITMAPINFOHEADER));
bmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpInfoHeader.biWidth = bitmapData.Width;
bmpInfoHeader.biHeight = bitmapData.Height;
bmpInfoHeader.biPlanes = 1;
bmpInfoHeader.biBitCount = 24;

StretchDIBits(pDC->GetSafeHdc(),
    0, 0, bmpInfoHeader.biWidth, bmpInfoHeader.biHeight,
    0, 0, bmpInfoHeader.biWidth, bmpInfoHeader.biHeight,
    bitmapData.Scan0, (BITMAPINFO*)&bmpInfoHeader,
    DIB_RGB_COLORS, SRCCOPY);

bitmap.UnlockBits(&bitmapData);

おおっ、速い。でも、画像の上下がさかさまになってしまった...GDI+のオフスクリーンサーフェスはWindows従来のDIBとは上下が反転しているようだ。
まあ、GDI+にはSetTransform()という便利なメソッドがあり、ここでY座標をミラーする行列を仕込んでおけばあまりコードを汚さずに上下さかさまに描画できそうだ。
あとオフスクリーンサーフェスのピクセルフォーマットは当然24bitRGBのときに効率が一番よさそうだが、それ以外のピクセルフォーマットのときはもしかしたら一番最初のシンプルなコードでも問題ないかもしれない(実験してない)。

ということで結局、GDI+のオフスクリーンサーフェスには上下さかさまに描画して、StretchDIBitsで転送するという方法にしたら、 1600x1200程度のウインドウを描画するのに、もともと40fps程度だったのが80fps程度まで向上した。

と、ここまでやったところで気づいたんだが、GDI+のGraphicsはHDCからコンストラクトすることもできるので、実はDIB section ビットマップから作れば上下さかさまにせずとも速いのではなかろうか。
まずDIBSectionビットマップとGraphicsの生成。DIB Sectionビットマップにはオリジナルのラッパクラスを使ってもいいけど、ATLのCImageを使うと便利。
// DIB section と Graphicsの生成
CImage image;
image.Create(rect.Width(), rect.Height(), 32);
HDC hDC = image.GetDC();
Gdiplus::Graphics g(hDC);
image.ReleaseDC();
オフスクリーンサーフェスからウインドウへの転送はとてもシンプル。
image.BitBlt(pDC->GetSafeHdc(), 0, 0);

速度自体は上下さかさまに描画したものとそんなに差はないようだけど、これが最善っぽいね。あとDIBSectionの色深度を24ビットにすると遅かった。これはアライメントが影響してるんだろうか。でもBitmapクラスに上下さかさまに描画したときは24ビットでも速かったんだよなあ。ちょっと不思議だな。暇なときにまた調査してみよう。


とても長いエントリになってしまったが、結論。

オフスクリーンサーフェスに GDI+の Bitmapクラスを使うと遅い。DIBセクションビットマップを使うべし。

1/1