DirectX8のリフレッシュレートな話描き描き
Write: 01/06/24 UpDate:--/--/--

我が家のモニターが新しくなりました。憧れの19インチモニターです。
17インチ:1280x1024x60Hz(フォント大)→19インチ:1280x1024x85Hz(フォント小)にレベルアップ。
目にやさしくていい感じです。やっぱリフレッシュレートが高いと目にやさしいご様子。
そんなわけで今回はリフレッシュレートのお話。
実は私の周りには視覚系の研究をやってる人が結構います。
彼らが実験をやる時に気にするのがVsyncとリフレッシュレートなんですね。
Windowsでこの辺りをいじろうと思うとDirectXがお手軽で良さそうなんですが、
なぜかDirectX7以前のバージョンではろくすっぽリフレッシュレートの設定が出来ません。
SetDisplayModeなる関数の引数で指定できそうな感じなんですが、これで成功した話を聞いたことがありません。
そもそもMSDNには
『新しいモードのリフレッシュレート。この値を 0 に設定すると、ドライバに対するデフォルト リフレッシュレートを要求する。』
と書かれているだけで、いったい何の数字を入れれば良いのかさっぱり分かりません。
多分マイクロさんのいつもの癖で、実装予定な奴をさも出来るかのように書いてあるだけなんでしょう。
にもかかわらず640x480x120Hz+Vsync待ちのプログラムの注文が来ちゃいました、なにやら昼飯一回分で請け負ってしまった様子。
飯がかかっている以上ちょっとは真面目に取り組まなければなりません。
どうしようかな〜、と思案することしばし
『確かDirectX8ではリフレッシュレートの設定法が変わってたよな』
ってなことに思い当たったんですよ。
CreateDeviceするときの引数のD3DPRESENT_PARAMETERS構造体にFullScreen_RefreshRateInHzなるメンバーがいます。
読んで字のごとくなんで120を入れて実行・・・、動きません。
しばらく試行錯誤した結果

d3dpp.BackBufferHeight = 480;
d3dpp.BackBufferWidth = 640;
d3dpp.FullScreen_RefreshRateInHz = 120;
d3dpp.SwapEffect = D3DSWAPEFFECT_COPY;
d3dpp.Windowed = FALSE;

と指定して、モニターを画面のプロパティーをいじってあらかじめ640x480x120Hzに合わせてから実行するとうまくいきました。
どうやらDirectX側は自分でリフレッシュレートを切り替える気がなさそうな感じですね。
あらかじめWin32APIで解像度とリフレッシュレートを切り替えてからCreateDeviceすると良いような気がします。
めんどくさいんで試してませんが・・・、飯一回でそこまでする義理も無いし(^^;


一応サンプルソース
注意:
このプログラムを使用する前に自分のモニタとグラフィックカードが指定するリフレッシュレートをサポートしているか確かめてください。
なお、不幸にもハードが煙を吐いても責任は負いかねます。

 

DirectX8のαな話抜け抜け
Write: 01/06/10 UpDate:--/--/--

つくづく私の脳ミソは抜けているのですが、先ほど就職活動中にその会社のトップとの面接を遅刻していくあたり、
いい加減なにか重要なものが抜け落ちているとしか思えません。つーかこれ去年も同じことやったよなぁ・・・
そんなわけで今回は抜けた脳ミソでDirectX8の抜きであるところのアルファブレンディングについてです。

といっても面白いネタがあるわけでも無いんですけどね、まぁ忘れないうちに。

さてさてDirectXも8になってD3DXライブラリが非常に重要な存在になってきました。
事実上RMが無くなった今、穴埋めはこいつにしてもらわなけりゃならんわけです。
さすがにポリゴンオブジェクトの扱いがRM並に簡単になるわけも無かったんですが、それでも色々便利に拡張されています。
初期化が異常に簡単になった今、D3DXは初期化ライブラリとしての役目を終えて、真に実用的な部分に注力されてる気がします。
で、一番恩恵がでかいのがテクスチャの扱いじゃないかと思うわけです。
BMP、TGA、PNG、JPG、DIB、PPM、DDSなど多種多様なファイルフォーマットに対応しつつ、

D3DXCreateTextureFromFileExの引数にD3DPOOL_MANAGEDを指定しておけば、サーフェースロストの対応なんかや、
VRAM管理なんかを勝手にやってくれる親切さっぷり。
DirectX7以前でそのあたりに苦労した皆さんはご愁傷様って感じです。
ついでに他の引数でカラーキーを指定できたりもするんで、抜き色合成もお手軽にできちゃったりします。
いや〜、便利便利。
こいつと合わせてD3DCreateSpriteでスプライトを作ってあげれば、2Dがらみの描画ルーチンは一瞬で完成ですよ、うひょー。
などとおもってうはうはやっていたのですが、どうやらこのスプライト君α合成がうまく動かないっぽい。
単純な半透明は問題ないんですが、レンダリングステートをいじって加算合成しようとしても普通の半透明にしかならない模様。
爆発with加算合成で萌え萌えな人たちは悲しくなってしまうわけなんですね。
そんなわけでD3DXスプライトがらみはポイして、2Dポリゴンで代用した方が良いようです。
なにやらD3DXスプライトは遅いという噂もあるみたいですし。

αブレンディングのやり方自体は以前のDirectXの方法と変わりありません。

SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );
SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );

こんな感じでレンダリングステートを設定して描画してやればいいんですが、
気をつけなければならないのが、不透明物体を先に描画しておいてあげること。
これもいつもどおりです。

Zバッファへの書き込みは不透明物体のときはON、透明物体のときはOFFにしときましょう。
SetRenderState( D3DRS_ZWRITEENABLE, TRUE );
不透明物体->Render();
SetRenderState( D3DRS_ZWRITEENABLE, FALSE );
透明物体->Render()

透明物体の描画は奥にある奴から順にやってあげます。

α値の扱いはこんな感じで、テクスチャとマテリアルをちゃんぽんに使って
SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE );
SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE );
SetTextureStageState( 0, D3DTSS_ALPHAARG2 ,D3DTA_DIFFUSE );

色の扱いはこんな感じで、こっちもテクスチャとマテリアルをちゃんぽんに使って
SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE );
SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );

抜き色を使うにはαテストもONにしておかないと駄目だったかも。
SetRenderState( D3DRS_ALPHATESTENABLE, TRUE )

そもそもαテストってなんだったっけという気もしてきたので、こちら

こんなもんでしょうか、なんでこれしきのことに半日も悪戦苦闘していたのでしょうか(笑)
半分はPhotoShopのPNGファイルの扱いが馬鹿だったことに起因するんですが・・・(日記参照)
まぁ、うろ覚えな知識の再確認ができただけでも良かったということで。

 

美麗フォントな話アンチエイリアスをかけましょ
Write: 01/03/10 UpDate:--/--/--

皆さんエロゲーやってますか〜、いえー。
萌えてますか〜、いえー。
ドットは正義ですか〜、いえー。
かつてWindowsなんてもんが無かった時代、グラフィッカーは己の魂の全てをかけてジャギー(斜めの直線のギザギザ)を
手作業で消していました。要はギザギザ部分にそれと似た色を並べてぼかしただけなんですけどね。
フルカラーなWindows時代になって、ここら辺の技術はかなり廃れてしまいました。
例外的にエロゲーで背景とキャラクタを表示するときキャラクターの輪郭部分をきれいに表示する為に
手作業でやっていた節がありますが(ToHeartとか)、最近ではアルファチャンネルを利用する方が主流です。
で、表示されるフォントもいつのまにかジャギーの無いもの(アンチエイリアスフォント)になっています。
考えてみるとノベルタイプのエロゲーでは自分の好きなフォントが選べたりするんで、
あらかじめアンチエイリアスフォントを用意しているとは思えません。
(ただし画面のプロパティーで『スクリーンフォントの縁を滑らかにする』を指定してある場合は
Windowsによって自動的にアンチエイリアスがかけられる)
信号処理的には、でかく描画したフォントを縮小して表示するとアンチエイリアスがかかるんで、
普通はそういった技法が使われるんでしょうが、Windowsにはアンチエイリアスフォントを作ってくれる親切なAPIがあります。
その名もGetGlyphOutline、こいつを使うとフォントのアウトライン曲線またはビットマップを取得できます。
曲線もらっても困るんで、今回はビットマップを頂くことにします。
ここで取得できるフォントは引数の指定によって5〜65階調のアンチエイリアスがかかったものになります。
ちなみにどの階調を選んでも、1ピクセルあたりの1バイトのデータになります。
この際せこい事言わずにGGO_GRAY8_BITMAPを指定して最高階調の奴をゲットしましょう。
このAPIでビットマップの配列を取得できるんですが、ドット単位でどうこうするのではなく、
あらかじめ必要サイズのDIBを作っておいてそこに流し込むのがミソです。
DIBは256色にしておくとぴったりのサイズになります。色もパレットをいじれば何色にでもできます。
ただし単にDIBに入れても上下が反転してしまう(DIBの癖)んで、このAPIがxy座標に2x2行列をかけることが出来るのを利用して、
あらかじめフォント自体を上下逆にしておきます。
なぜかDCにあらかじめフォントを設定しておかないとAPIがこけるので、CreateFont、SelectObjectを使って指定しておきましょう。
デフォルト設定は無いんでしょうか…
後はビットマップ配列がDWORDにアライメントされるのさえ気をつければOKです。
それではDIBをBitBltしてあげましょう。どうですか、表示されたでしょ。ただし文字の背景が塗りつぶされてしまってますが。
これはTextOutでSetBkMode( hDC, TRANSPARENT )を指定しなかったのと同じ状態です。
ちょっと困りもんです、エロCGが文字でげしげし塗りつぶされたらユーザーに殺されてしまいます。
DirextXを使っているときには抜き色指定が出来るので一発解決です。
問題はノーマルなWindowsを使っている場合ですが、うってつけのAPIがあります。
その名もTransparentBlt、なぜか全然知られていませんが抜き色指定付のBltです。
いやぁ、便利、つーか便利すぎ。
使うしかないっしょ、うはうは。
で、何で使われていないのかというと、このAPIが通常のBltと違うライブラリ(msimg32.lib)なのと
メモリーリーク
疑惑のせいでしょう。WebでTransparentBlt & Leak で検索をかけるとわらわらヒットします。
どうやらWin95系ではリソースを食いつぶすらしいです。なんで便利なものに限ってこうなんでしょうね。
でも、Win2000上でBoundcheckerをかけても漏れてなさそうだし、VAIO(Win98)でも大丈夫そうです。
こっそりと直したのかもしれないですね。
気になる方はここでほぼ互換なTransparentBltが公開されているので、そちらを使うのが吉でしょう。
これは昔ながらのマスクを作って抜き色を実現するタイプです。
というわけでサンプルソース

『スクリーンフォントの縁を滑らかにする』のチェックが入っているとありがたみが分かりません。切っておきましょう。
このサンプルの使用によるいかなる不幸も当方では責任を負いかねます(^^;
微妙にTextOutを使ったときとレイアウトが異なるんですが(上下にずれる)、その真相をご存知の方はご一報!

 

フルスクリーンな話大きいことはいいことだ
Write: 01/02/23 UpDate:01/11/17

プログラマーにとって命の次に大事なものは、過去のプログラムソースではないでしょうか。
まぁ過去の自分が書いたソースなんてろくなもんが無くて、速攻新しく書き起こすことの方が多いんですが。
さて、そんなわけで今回はフルスクリーンの話です。
ゲーマーは100人中120人くらいは『フルスクリーン萌え〜』であるという統計があります。
もちろんWindowsプログラマーも『萌え萌え〜』といいながら対応せざるを得ません。
DirectXの場合はSetCooperativeLevelでDDSCL_FULLSCREENを指定すれば簡単にフルスクリーンになります。
一方、通常のWinプログラムでもフルスクリーン化したいニーズが多々あります。
今回の場合、VGAをヘッドマウントディスプレイに映して遊びたいとの欲望から発生しました。
別ウインドウが眼前で巨大に見えては面白くないですからね。
で、昔書いたフルスクリーン化のソースを掘り出してきました。
てなわけでソースのクリーニングついでに紹介しましょう。
やることは

1:EnumDisplaySettingsでフルスクリーン時の画面モードを探す
2:ChangeDisplaySettingsでフルスクリーンに
3:満足したらもう一度ChangeDisplaySettingsして元の画面モードに戻す。

いや、簡単ですね。つーかこの手のソースはネットでいくらでも公開されてます。
しかし鵜呑みにしてはいけません。グラフィックカードの挙動なんてモノは彼女の気持ちよりも怪しいのです。
まず、Win95では現在の色数から別の色数に変更できないという噂があります。
OSR2以降ならOKとか2000なんかでは余裕とか色々ありますが、怖いんで私は色数変更はしないことにしています。
色数を気にする時はDirectX使ってます。
さらにリフレッシュレートの問題があります。
ChangeDisplaySettingsでリフレッシュレートを渡してもその値にならなかったり、いきなりハングったりすることがあります。
ついでに、Win95系では現在のリフレッシュレートを取得できません(NT系はOK)。
レジストリの
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Class\Display\000?\MODES\??\各解像度
を参照すると書いてあることもありますが、少なくとも家のVAIOC1はなんも書いてありませんでした。
他にも特定の機械では関数の戻り値が当てにならなかったり、リフレッシュレートの違いだけで再起動を要求したりします。
そこらへんはここが詳しいのでそちらを参照してください。
私は仕事で作るわけじゃないんでそこそこ動けばいいや〜ということで、
『640 x 480 x その時の色数 x サーチで最初に引っかかったリフレッシュレート』
にしています。
そのココロは
Windowsを使う時間違いなく640 x 480以上の解像度を使っているはずであり、
その解像度での色数と同じ色数の640 x 480のモードを持っている可能性が非常に高い。
リフレッシュレートについては神を信じる。
てなところです。
以上で、フルスクリーン化の話は終わりなんですが、実はまだ解決しなければならないことが残っています。
それは、
『フルスクリーン(低解像度)にすると元の画面モード(高解像度)に戻った時、
ウインドウ位置がおかしくなる』ってことです。
これはかなりうっとうしいです。開発中でもプログラムを実行するたびにVCのウインドウが画面の左隅に追いやられてしまうんですね。
で、毎回ウインドウをずりずり引っ張ることになります。
考えてみれば当たり前な話で、解像度が変わった影響で画面外にウインドウが存在する状況の方がOSにしてみれば何かと不自然なわけです。
画面外に出たウインドウを無理に変更後の解像度に押し込もうとしてウインドウ位置が狂ってしまうわけです。
逆に元の解像度に戻る時(低->高解像度)にはウインドウは画面内に収まっているので変更したウインドウ位置がそのまま残るって寸法です。
そんな訳で解像度を切り替える前に元のウインドウ位置を覚えておいて、解像度を戻すときに元の位置に戻してあげなければなりません。
これも実は簡単で

1:フルスクリーン化する前にEnumWindowsで画面上のウインドウに対してコールバック関数を呼び出す
2:コールバック関数内でGetWindowPlacementしてウインドウ位置を取得、保存する
3:元の解像度に戻すときに再度EnumWindowsで画面上のウインドウに対してコールバック関数を呼び出す
3:コールバック関数内でSetWindowPlacementしてウインドウ位置を元に戻す

といった感じです。意外なことにこのソースはあまり見かけないんですよね。
フルスクリーン化するときには必須だと思うんですが…。
以上2つのフィーチャを突っ込んだサンプルソースです。
なおエラーチェックは最低限であり、
このサンプルの使用によるいかなる不幸も当方では責任を負いかねます(^^;

01/11/14 追記 WindowsXPでは画面のプロパティで指定できる最低解像度が800x600になってしまいましたが、
古式ゆかしい640x480はもちろん320x240やら320x200なんかもプログラムからは指定可能なようです。よかったよかった。

 

タイマーな話Windowsのタイマーはいまいち信じられないのよ〜
Write: 01/01/11 UpDate:01/11/14

実は私は大変時間に正確なのです。必ず約束の時間を5分過ぎたころに到着します。
いいかげん反省しなきゃならんような気もしますが、体の芯まで染み付いたこの習慣は死ぬまで抜けきらねーとか諦めてます。
某社の面接も遅刻した挙句筆記用具も忘れたという素敵さっぷり。いやぁ、脳味噌が溶けてるとしか思えませんな。
さて、ゲーム作ってるときにどうしても必要になってくるのがこの時間管理のお話。
昔から秒間60フレームがどうとかこうとか言って、VSYNC(垂直帰線)基準でカウントすることが多かったんですが、
まぁテレビで動くゲームの場合VSYNCにあわせて描画するとちらつきのないきれいな描画が出来るという至極まっとうな理由があったわけです。
そんなわけでコンシューマーやら昔のPCなんかはVSYNC待ちをかけてやるのが一番です。
そういえば気合の入った人々はHSYNC(水平帰線)使ってラスタースクロールかけたりしてましたね。
ところが世の中例外があって、液晶ゲーム機とWindowsの世界ではVSYNCは使いません。
液晶には端からVSYNCなんてないし(もちろん何らかの更新間隔はありますが)、Windowsの窓で動いてるプログラムにもVSYNCなんて関係ありません。
つーかWindowsなんてグラフィックカードのリフレッシュレートがまちまちなんで当てになりません。
DirectXにもVSYNC絡みの命令がいくつかありますがそんなわけであまり使いたくないです。
裏技的にWindowsのリフレッシュレートをプログラムから変更するって方法もありますがちょっとやりたくないですね。
で、どうすんだよ〜ってな話がDirectXBBSやらBBXやらに定期的に登場し、すでに風物詩と化している訳です。
結論から言うと仮想VRAMを用意してあらかじめそこに描画しておいて、タイマー割り込みを使って一気にVRAMに転送するってのが基本です。
これで一定周期でそこそこ美しく描画が出来ます。そこで問題なのがタイマーの精度。
もちろん私は『適当』をモットーに生きているので細かいことは気にしません。
ポケステでえここを作ってたときは『めんどっちーからfor文まわしちゃえ〜』とかやっていました。
下手すると最適化で削除されかねない適当さっぷりです。
『人が見てなけりゃいいんじゃ』っつー了見なんですが、もちろんハードの種類が一つしかないから出来る技なわけです。
将来高速なポケステ2とか出ると大変なことになるでしょう(出ないか(^^;)
じゃあWindowsの場合はどーすんだよというわけでやっと本題です。
真っ先に思いつくのがWM_TIMERメッセージですがこんなもんはポイです。
精度がせいぜい50msなんで実現可能なフレームレートは20FPS程度になります。
60フレーム萌え〜とか言ってる皆さんから刺し殺されかねない駄目さっぷりです。
現実的なレベルではメッセージループから自分のルーチンに飛んでGetTickCount, timeGetTimeなんかを使って自前で経過時間を見守ってあげることになります。
WebでもGetTickCountを使ってる記事を良く見かけます。ところがこいつが曲者で妙に精度が低いんですよ。
大体15ms程度の誤差ってところでしょうか。60FPSってのが16.6ms間隔なんでさすがに誤差が大きすぎます。
そこでtimeGetTimeを使うのが日曜ゲームプログラマーのお約束になっています。
MSDNには1msの精度があるんだぜ〜などと書いてありますが実際には誤差は5ms程度、まぁ必要十分なところでしょうか。
もちろん今まで私もこいつを使っていました。が、どうもWindows2000にしてから調子が悪いんですよ。
なんでかな〜と思ってもう一度MSDN読み返してみると

Windows NT: The default precision of the timeGetTime function can be five milliseconds or more, depending on the machine.

いやぁNTなんて使わないんですっかり見落としてました。MSDN様のおっしゃることには95系で1msの精度、NT系では5msより低い精度だとのこと。
サバ読まれてることを考えるとNTではかなり素敵な誤差が出てそうです。
調べてみるとWindows2000では約10msの誤差が出ています。

You can use the timeBeginPeriod and timeEndPeriod functions to increase the precision of timeGetTime.

という言葉を信じて精度を1msに設定してみてもどうも改善されない気が…。
そこで登場するのがQueryPerformanceCounterであります。
このあまり知られていないタイマーは高分解能パフォーマンスタイマーと呼ばれるだけあってかなりいい感じです。
家の環境ではカウンタ周波数3579545Hz(CPU:Athlon1G@1.1G,MB:ABIT KT7)、1193182Hz(CPU:Celeron300@450,MB:ABIT BH6)なる値が出ています。
ってことは1msの間に1000回位はカウンターが増えるはず、十分過ぎです。
皆さんもQueryPerformanceCounter使いましょーね。でんこのお願い。
しかし、こいつを使ってもいまいち精度が良くなった気がしません。
なんでじゃ〜っと暴れることしばし、改めてソースを見るとsleep君がいるじゃないですか。
そうです奴です。フレーム維持を単純に書くとwhile( Time - PreviousTime < WaitTime) ;こんな感じですが、これは『めんどっちーからfor文まわしちゃえ〜』精神にのっとっていると言えます。
私的には全然OKなのですが、世間では大変嫌われます。
まがりなりにもマルチタスクOSであるWindowsで空ループを回すとそこで全CPUパワーを食ってしまって同時に起動している別プログラムが死ぬほど遅くなっていしまいます。
何より芹香(我が家の旧メインマシン)が許してくれません。
何でかと言いますと、彼女はCeleronのオーバークロック450Mhz駆動マシンなので下手に長時間フルパワーで稼動させると熱出して旅立ってしまうという事情があるんです(笑)
そこでまずsleep( Time - PreviousTime - 3 )させてからループに突入させていました。
しばらく寝て別タスクに実行権を進呈するわけですね。
ちなみにこの-3というのはsleepの精度がそれくらいだと、どこかで伝え聞いて信じているという大変アバウトな理由。
今回改めて計測してみるとWindows2000上でのsleepの精度は10ms程度・・・、駄目じゃん。
16.6ms間隔でループまわすのに10msの誤差があるもんは使えません。(もちろん30FPS以下でループを実行するときは十分使えますが)
妥協案としてsleep(0)してからループに入ることにします。面白いことに『0ms寝ててね』という命令でそれなりにCPUに空き時間を作れます。
それでも無駄にCPUパワーを食っていますが熱暴走しないくらいにはなるかと。

以上を踏まえたフレームレート管理のサンプルソース(まだ作ってない)。

適当にいじってお使い下さい。
もっと真面目に知りたい方は、こことかここ辺りを参照してください。
なんかQueryPerformanceCounterも当てにならないみたいなことが書いてありますね(^^;;;

01/11/14 追記 こちらの日記によると『fpsの調節をするためにビジーループでQueryPerformanceCounterを呼び出していると、環境によっては数秒に一度関数からしばらく戻って来ないでフリーズする現象が発生します。どのくらいフリーズするのか実際に計ったわけではありませんが、全体の速度から計算すると300ms以上戻らないようです。これはアセンブリ言語のRDTSC命令でも同じ症状が発生しました。』(引用)とのことです。いやいや、ためになります。

 


戻る