DirectX でゲームを作ってみよう
プログラム開発者向けのページです。
ここに書いてある内容は自分で試行錯誤した結果です。
決してここに書かれているやり方が正解とは限りませんので、
他のサイトなどもいろいろと調べてプログラムしてください。

第 6 回 サーフェスロックの注意点

§エフェクトをやってみよう
DirectDraw のウリのひとつに「 VRAM ( ビデオメモリ ) に直接アクセスして描画できる」というのがあります。
通常、DirectDraw での描画というのはビットマップを BltFast で画面に描くというやり方です。一方、VRAM に直接アクセスできるというのはメモリ上に数値を書き込むことにより 1 ドットごと描画するようなやり方です。
このやり方では 1 ドット描くのに 1 〜 3 バイトの転送を必要とするのでひとつの絵を描こうものなら相当な処理量になります。

さて、これに何のメリットがあるのでしょうか!? それはエフェクトです。
画面をフェードアウトしたい、画面をうねらせたい、アルファブレンディングをしたい等々。これを実現するには BltFast 関数のような矩形転送ではなく、メモリ上の数値をちくちくといじる( 演算する )しかありません。

が、実際のところ 1 ドットごとの演算などしなくても、Direct3D を使えばちょちょいのちょいです。
と、ここで Direct3D の解説に移れば皆様のお役に立つページとしてアクセス倍増につながるんでしょうが、残念ながら Direct3D はまったく詳しくないので DirectDraw の話を続けます( ごめん )。

§VRAM へ直接アクセスする
VRAM も名前のとおりメモリのひとつなので当然アドレスを持っています。VRAM 上のアドレスを得るのが IDirectDrawSurface::Lock 関数です。たとえば

//  サーフェスをロックする
DDSURFACEDESC2    ddsd;

ZeroMemory(&ddsd, sizeof(DDSURFACEDESC2));
ddsd.dwSize = sizeof(ddsd);
lpSurface->Lock(NULL, &ddsd, 0, NULL);

と書くと ddsd.lpSurface にサーフェス左上のアドレスが得られます。このアドレスに値を書き込むとサーフェスの左上の点が書き換わります。どういう風に描き変わるかは以下のように画面の色数やグラフィックチップによって異なります。

画面モードが 8 ビット( 256 色 )の場合は、書き込んだ値( 0 〜 255 )に対応するパレットの色が 1 ドット表示されます。

画面モードが 16 ビット ( 65536 色 )の場合は少しややこしいです。これは 2 バイトで 1 ドットを表現するのですが、この 2 バイト(= 16 ビット)を RGB で振り分ける割合が 5:6:5 で振り分けるタイプと 5:5:5 で振り分けるタイプが存在します。これはパソコンに搭載しているビデオカードによって決まるため、ソフト側でこの割合を変更する事は出来ません。

16 ビット( 565 ) :■■■■ ■■■■ ■■■■ ■■■■
16 ビット( 555 ) :■■■ ■■■■ ■■■■ ■■■■

画面モードが 16 ビットの時にサーフェスをロックしてエフェクトを掛けようとするなら、5:6:5 用と 5:5:5 用のエフェクト処理を準備する必要があります。それを怠るとエフェクトを掛けた部分の色が想定したものと違う色になります。

画面モードが 24 ビット( 1677 万色 )の場合は 3 バイトで 1 ドットを表現するため 0xff0000 では赤( Red )、0x00ff00 では緑( Green )、0x0000ff では青( Blue )の点が表示されます。この RGB の値を変えてやる事により中間色( って最近言わないよな… )を表現できます。

もうひとつ、画面モードが 32 ビットのものがありますが、これは 1 ドットを 4 バイトにして転送効率を上げようとしたモードなので最上位バイトは使われず、実際に使える色は 24 ビットモードと同じ 1677 万色です。

24 ビット :■■■■ ■■■■ ■■■■ ■■■■ ■■■■ ■■■■
32 ビット :□□□□ □□□□ ■■■■ ■■■■ ■■■■ ■■■■ ■■■■ ■■■■

そうそう、ロックしたサーフェスはロックを解除する必要があります。
ロック解除には IDirectDrawSurface::Unlock 関数です。この Unlock 関数の引数には Lock 関数の第 1 引数で渡した値( ロックするサーフェスの領域 )を渡してください。

//  サーフェスのロックを解除する
lpSurface->Unlock(NULL);

§サーフェスロックは遅い!
さあ、今回の本題。
Lock をしてエフェクトを掛けたプログラムを動かすと判りますが、この Lock 関数は遅いです。高速な DirectDraw の良さをすべてパーにしているかの如く遅いです。BltFast 関数でびゅんびゅん描画していても Lock 関数を呼んだとたん急ブレーキが掛かります。

システムメモリ上のサーフェスロックはともかく、ビデオメモリ上のサーフェスをロックした時は壊滅的に遅くなります。
試しに、ひたすら円を描く処理と、円を描く処理+ VRAM 直接描画をするプログラムを動かしてみました。結果はこうなりました。

円描画のみ
ビデオメモリ635 fps
システムメモリ280 fps
円描画 + Lock + Unlock
ビデオメモリ595 fps
システムメモリ260 fps
円描画 + Lock + ゼロ書込み + Unlock
ビデオメモリ520 fps
システムメモリ250 fps
円描画 + Lock + ゼロ書込み(10回) + Unlock
ビデオメモリ380 fps
システムメモリ210 fps

※96x156のサーフェスにLock/Unlockを掛けた場合。
 ゼロ書込みは各ピクセルごとに *p = 0;を実行。
 マシンはPentiumIII 800MHz / Millennium G400

一見、ビデオメモリの方が速く見えますがロックをした時のスピード低下がビデオメモリの方が激しい……いや、激しすぎます。
ロックを掛けると Win16Mutex を保持して排他状態になり、別のスレッド、他のアプリ、OS までもが停止しているらしいです( この辺は詳しく知りません )。
しかもビデオメモリへのアクセスというのはそれ自体が相当遅いそうです。書き込む回数が増えるほど比例してスピードがぐんぐん下がります。ビデオカードが速いのはビデオメモリ⇔ビデオメモリ間で転送する時のみのようです。

このように同じ処理をさせても、ビデオメモリをロックするのとシステムメモリをロックするのではスピードが大きく異なる事がわかります。
というわけでサーフェスにエフェクトを掛けようとしてビデオメモリをロックするのは間違いです。

エフェクトを掛ける場合、どうするのがいいのでしょう。もちろん一番の正解は「 Direct3D を使ってハードウェアで描画」だと思いますがここでは除外。
結論からすれば、サーフェスはすべてシステムメモリ上に作る。これです。これで鈍足 VRAM ロックから解放されます。

バックサーフェスもオフスクリーンサーフェスもシステムメモリ上に作ることです。すべてのサーフェスと言っても、ロックを掛けないサーフェスはどちらでも構いませんし、プライマリーサーフェスはビデオメモリ上に作る必要があります。
システムメモリ上のバックサーフェスに BltFast で描画したり、ロックしてエフェクトを掛けたりして、最後にバックサーフェスの内容をプライマリーサーフェスへ転送します。

もちろん、これでは DirectDraw のウリのひとつであるハードウェア描画の恩恵にあやかれません。が、ロックで急ブレーキが掛かるよりいいと思います。それに今のコンピューターは普通に描画( Blt 関数や BitBlt 関数 )でも十分に速いのでこの方法でも問題はないかと思います。

§DirectDrawの存在意義とは!?
さて、こうなると DirectDraw を使う意味があるのでしょうか?
前回の講座で説明したように画面フリップは使わない、サーフェスもシステムメモリ上に置く。こうなれば通常の GDI 描画でいいのでは?という気にもなってきます。

実際に 2D ゲームを作るのならどちらでもいいと思います。むしろ、GDI 描画の方が楽かもしれません。
DirectDraw に残された利点と言えば簡単に解像度を変えたりフルスクリーン表示にできるくらいでしょうか。透過色つき矩形転送は強力ですが、GDI でもそれほど重たい処理ではありません。

しかし、GDI では 3D 描画はまず不可能です。それを考えるとやはり DirectX に馴染んでおくためにも DirectDraw を使うのが結局は正解なのかもしれません。

§サンプルの解説
今回はサーフェスにロックを掛けて 2 つの絵を合成をするサンプルです。

画面上に幽霊くんが浮遊しています。この幽霊くんを描画する際にロックを掛けて背景(洋館)との合成演算を行っています。処理がいい加減なため、描画速度は劇的に遅いです。
この合成処理は、アルファブレンディングでもないし、加算合成でもありません。2 つの絵の明るい方の色を描画する合成です。幽霊くんは暗い場所だと見えますが、明るい部分に移動すると幽霊くんが見えなくなります。

また、画面モードは 24 ビット/ 32 ビット/ 16 ビット( 565 )/ 16 ビット( 555 )で動作するように作りましたが、24 ビットと 16 ビット( 555 )は動作確認できるマシンがなかったため、本当に動くか不明です( たぶん大丈夫と思うけど… )。

この sample5.lzh には実行ファイル( sample5.exe )とソースとヘッダー、画像ファイルが格納されています。コンパイルをする際には dxguid.lib と ddraw.lib をリンクしてください。
なお、このサンプルは ESC キーで終了します。

( 376.2KB )

§おわりに
今回をもってこの連載は終了します。今後は何かネタを思いついたら載せようと思っています。

なるべく近いうちに作成中のシューティングゲームをソースコードとともに公開したいですが、いつになる事やら……