第40回 DirectInputはどうなっとんのや!(DirectXパラノイア日記) 99/5/24

記念すべき第40回(16進数なので、ヨンマルカイって読んでね。なんで記念すべきかって?そりゃ2の6乗だからに決まってんでしょ!<阿呆)に、またもや、暴れたくなるような事実が発覚した。ことの発端は、DirectInputである。今日は一日、DirectInputのラッパーを書き直していたのである。ラッパーの意味がわからない人は、ウンジャマラミーのお友達だと思えば...わかりやすないわな(笑)

それはともかく、DirectInputである。僕はずっとDirectInputはDirectX5の機能とばっかり思っていた。なんでかというと、その時持っていた本が、「Windows95ゲームプログラミングDirectX入門」(プレンレティスホール出版/アクロバイト監訳)というものだが、いまや幻である初期のDirectXの入門書を翻訳したものであるため、DirectInputのことについては何も書いていなかったのだ。おまけにこの本、古いだけあって、いろいろ難ありだし、DirectInputなんて無いもんだからキー入力は、ウィンドゥメッセージをハンドルせよみたいなことを書いてある。結局のところ、この本には、相当、騙された。よって、DirectX5のオンラインマニュアル(英文)で、DirectXの勉強をしたわけである。(ただし、当時、他に書籍なんて皆無だったのでこの本の存在意義は大きかったかも知れない。その他の情報と言えば、Bio100%がInsideWindowsで連載していた記事ぐらいだったもんなぁ...)

そんなわけでDirectInput様がいつ御降臨あらせられて、いつからご活躍なのか記憶の遥か片隅にあったのであるが、DirectX3でもキーボードまでならばサポートしているのである。まあ、joystickはまだCOMオブジェクト化されてはいなかったが、フォースフィードバックでも使うのでなければ、マルチメディア関数のjoyなんたらで十分である。そんなわけで、DirectX3に逆戻り。みんなDirectX3使おうぜ!(馬鹿)

どうでもいいが、joystickというのは日本語に何て訳すのだ?「喜びの棒」か?なんか、とっても下品なような気がするのは、やねうらおだけなのか?(相当の阿呆やな>俺)

その問題のDirectInputだが、歯痛、吐いた、あいたたたなんちゅー変換しよんねん排他モードよしよしでオープンできないのである。(なんか日本語ダレとるで>俺) やねうらおには、まったくもって信じられないことだが、フルスクリーンであろうと、排他モードでオープンできないのである。これは、DirectXの七不思議だと思う。ちゅーか、なんでそんな設計にすんねんや馬鹿馬鹿馬鹿マイクロソフト〜!

まあ、排他モードでオープンできないっちゅーことは、ウィンドゥメッセージがビュンビュン飛んでくるわけである。いやー、そんな速くないなー。どっちかっちゅーと、ジャンジャカやね。そう。ジャンジャカ飛んでくるわけである。(笑) つーことで、メッセージループでハンドリングしたやつプロファイルしてみたら、なんとキーボードメッセージしか来てねぇんでやんの!そんなのいらねぇって!こんなのハンドリングするだけで速度低下を招くことは目に見えてるし、そもそもDirectXがDirectに得たはずのキー情報をどないしてパチってきとんねんや、お前!と言いたくなる。(※パチる=盗む) ちゅーか、Directに得とらんのよ!これのどこがDirectInputやねん!ムキー!!それでも、このメッセージかて処理せーへんかったら、メッセージキューにわんさと溜まってしまいにゃ行き倒れてまいよるやんけー。キキキキキとかゆうて、何のキー押しても反応せんよーなりよる。とんでもないやっちゃでホンマ。(特に初期のWindows95)

あああ。しょーもないことゆーてたら、寝る時間が来てしもたがな。ほな、また明日〜。(なんや、これホンマに日記やったんか?)


第41回 Windowメッセージには謎多し(WM_ACTIVATEAPP編) 99/5/25

Windowsのウィンドゥメッセージというのは、どうも理解できない。理解できないっちゅーか、どうも動作がおかしいような気がしてならない。

たとえば、WM_ACTIVATEAPPである。これは他のアプリに制御が移ったときか、他のアプリからこのアプリに制御が移ったときに送られてくる。

ところが、こいつが飛んでくるタイミングが、ちょっと遅れるのである。まあ、メッセージっちゅーもんは、ある程度の遅延は仕方ないけれど、遅れるのである。たとえば、ウィンドゥがアニメーションしながら最小化したとする。最小化する段階で、すでにこのアプリから制御が移っているわけで、この瞬間にWM_ACTIVATEAPPが飛んでこなければならないような気がするが、そうではないようである。しかしながら、この瞬間から、DirectInputのキーボードのAcquireに失敗するのである。そして、最小化され終わった後で突如、WM_ACTIVATEAPPが飛んでくるのである。自分、いまごろ来んなって。遅れてきたのに、ごめんの一言もないんかい!「雨で道が混んました」とか「天王寺駅で人身事故がありまして」とか何かありそうなもんやろ!(謎) 

その昔、知り合いのプログラマがWM_PAINTが飛んでくるタイミングがおかしいって言って泣いていたのを思い出した。あれは、こーゆーことやったんか...。あんときゃー、よーわからんかったから、そういうもんなの?とか思ってたけど、こりゃひどいわ。わはははは。(笑っておわりかえ!?)

ちゅーかね。リアルタイム性のあるDirectなんちゃらと、非リアルタイム的な(?)ウインドゥメッセージとは相性がめちゃんこ悪いのではないかとか思わんでもない。個人的には、もうちっとリアルタイム性のある通知手段を考えて欲しかった。それと、WM_PAINT君。きみは、最近わるふざけがすぎるので、廊下に立ってなさい。(意味不明)

しかしやねー、なんか、毎日、こんなんにらめっこしてたらしまいにゃ頭おかしーなってくるんちゃうかなーって、ちょっと心配だ(笑) ともかく。近日中にGAME用のライブラリのプロトタイプ版を公開するんで、首を短くして待っててね。<短くでええのんか??


第42回 DirectInputのAcquire(業の深い男) 99/5/26

二日前の日記、最終的に、DirectInputがなぜにそんなに私をいからしめたのか書くのを忘れていた。(なんや、この英文翻訳調の文章は!!)

DeviceをAcquireしなければならないのだが、こいつ、よく失敗しよるのである。自分のよしなにしとるウィンドゥの入力フォーカスがないと、すぐにソッポ向いてしまうのである。それだけならともかく、その状態でGetDeviceStateしようものなら、その窓が再度初期化されるまで、二度とGetDeviceStateできない体になってしまうのである。こいつにゃまいった。ずっと原因がわからんで悩んでいたのだが、やっとわかった。

要するにDirectXは、エラー処理はしっかりしなさいという前提のもとで作られている。

しかし、プログラムを起動段階で、ウィンドゥはある程度遅れてから構築される。メインスレッドは、ウィンドゥのCreateをするが、それが実際にCreateされるのは、DirectXの初期化およびゲームスレッドの生成が終わって、メッセージループが開始され、一連のウィンドゥ生成メッセージが実行された産物として、ウィンドゥが生成されるからである。ということは、DirectInputの初期化をやっているときに、ウィンドゥが完成していないことは、しょっちゅう有りうるわけで、このときにAcquireしても、DI_OKは返ってこない。それなのに、放っておくと、成田離婚になってしまうのである。(なんのこっちゃ)

よって、正しくは、成功したかどうかのフラグbAcquireを持っておいて、そいつがfalseならば、キー状態の獲得時にAcquireする必要があるわけである。AcquireでDI_OKが返ってきていなければ、決してGetDeviceStateしてはならないというのが鉄則なのである。また、アプリのフォーカスが移ったときも獲得していた入力デバイスをLOSTしてしまうので、WM_ACTIVATEAPPを見て、AcquireとUnacquireしなさいとか書いている本も多々あるが、そんなものは真っ赤な嘘である。そんなことはしなくてよろしい。こいつらみんな、Microsoftのあんちょこそのまま書き写してるだけで、自分で試してもいないのである。

そうこうしているうちに、朝5時になっていた。もちろん、バリバリ会社に出勤日である。悔しさのあまり、30分ほど寝付けない。ちゅーかなー。なんで、やねうらおって、こんなに苦労せんとあかんねや。わしの前世は、殺人鬼かっちゅーの!(謎)


第43回 WindowsNTでDirectXを動かす(DirectX3の秘密) 99/5/27

NTで動作させるためには、DirectX3に限定しなければならない。なぜにNTはDirectX6(せめて5でも!)に対応させないのか謎である。そもそも、DirectX6のオフィシャルマニュアルは18,000円もするので(DirectX3のは1万円弱)DirectX6の勉強なんかちっともしたくないから良いのだが(笑)

DirectX3だと、MMX最適化の恩恵が受けられないとか書いてあるが、そうでもないんでは?と思う。DirectX3がリリースされた時点では、確かにMMX対応CPU自体が出まわっていなかった。しかし、DirectX5以降では、MMX対応のはずである。ちゅーことは、DirectX5や6をインストールしていれば、DirectX3はMMX対応なんでないの?

おや?と思われた方にちょっと説明しておくが、ActiveXという技術は、下位互換性を常に保つのである(基本的には) すなわち、DirectX5が入っている環境では、それより下位のバージョンであるDirectX3のインターフェースもDirectX5が責任を持って公開するわけである。しかも、その実装はDirectX5のドライバー内によって行なわれるはずである(原則的には) ということは、DirectX5がインストールされている環境で、DirectX3のインターフェースを照会した場合、そやつはMMXに対応してるんでないの?

そんなわけで、NTで、がしがしDirectXのプログラムを書いている。NTは、スレッドを殺ろせるし、デバッグが大変やりやすい。Win95/98のように途中で失踪したまんま帰ってこないということもないし、開発がめちゃはかどっている。

ついでに言えば、先日LinuxをインストールしてXでプログラムの勉強をしようと思っていたFMVも今度はWindows95+DirectX3をインストールして動作テストに使っている。フロッピーの壊れたAKIAのノートパソコンもネットワークカードで接続して動作チェックマシンと化しているし、メール専用マシンの自作機(Win98)も、テストマシンである。おまけに、母親は自分用のパソコンを買うとか言っている。そうすると狭っ苦しい家のなかに5台もWindowsマシンがあるという異様な状態になる。

しかし、なんで俺、人にNT勧めてんのや?しかも、俺、Linux消してなんでWindows入れんのや?マイクロソフトに洗脳されて、頭おかしなってんのちゃうか?やばいぞ?気をつけろ!おまけに、一家になんでWindowsマシンが5台も?俺って、実はマイクロソフトの手先なんとちゃうか(妄想)


第44回 DirectDraw最大の欠陥(なんで誰も騒がへんのかなー) 99/5/28

いまだに知らない人がほとんどだろうが、DirectDrawのflipは使ってはいけない。えー。ウソ〜。とか言われるかも知れないし、ほとんどのDirectDrawの参考書籍では、flipを使ったサンプルが掲載されているが、決して使ってはいけない。

flipは、裏画面を用意しておいて、垂直帰線期間中に表画面と瞬時に入れ替える(ビデオメモリポインタをすり替える)ことによって、チラツキをおさえる技術である。本当は、これ向きのビデオカードであれば、ポインタのすり替えだけであるから、時間なんかまったく取られずにflipできるはずなのである。ところが、そうはなっていないのが現状である。

まー、ビデオメモリが足りないためメインメモリに配置しているだとか、そういう機構自体が備わっていないだとか、まー、いろいろあるが、そうなっていないのである。そのため、flipをかけると、その待ち時間によってCPU占有率が常時100%を達成すると言う、最悪プログラムのお手本のようなものが出来あがる。本当は、前回のflipが終了していないが為に、WASSTILLDRAWINGエラーが帰ってきているのに、それを無理に成功するまで待つようなオプションを付けるからいけないと言う話もあるが、それにしてもCPU占有率常時100%というのは、後ろつっかえてんだから、早くドケよーと言いたくなる。(他のタスクがね)

ということは、フツーにHDCを得て、WIN32のbitblt命令で転送するの?

そう。それでもいいと思う。

そんなことしたら、チラつかないの?

ちょっと待って欲しい。あなたは、フルスクリーンではなく、ウィンドゥタイプのゲームで、チラつきの発生するゲームを見たことがあるのか?そもそも、ウィンドゥには、flipという機構は存在しない。直にbltしている。それなのに、チラつきなんて無いはずである。ビデオカードがよっぽど古いタイプでもない限り、画面のスキャンより遅い転送なんて考えられないからして、よって、チラつきは発生しないのである。

そんなことしたら、画面の途中でズレが発生したりしないの?

うーん。厳しい質問だね。それは、確かに発生します(笑) しかし、ウィンドゥタイプのゲームで、画面の途中で1ラインズレが30フレームに1回発生するんですけど、というクレームは見たことがない(笑) そこまで神経質になるのなら、垂直帰線を調べて転送すれば良い。ただし、ビデオカードによって、フレームレート(1秒間の更新回数=周波数)は違うのである。すべてが60FPSとは限らない。よって、ビデオカードのフレームレートにタイミングを合わせるようなゲームは、御法度なのである。結局のところ、60FPSと決めうちして、70Hzのディスプレイならば、10回コマ落ちさせるしかないのである。それをどこでさせるかだけの問題である。

もう一つ。flipなのだが、やけに重いのである。while(true) ;という永久ループを作ったとしても、CPU占有率は100%になったりしないのに、flipだけは簡単に100%を達成するのである。垂直帰線を捕まえるように待つのがそんなにウェイトのかかる動作とは思えないのだが、どうも、そのへんの処理が相当、ダサイのか、100%になってしまうのである。

以前にも書いたが、WMメッセージというのは、リアルタイム性のあるメッセージの通知には向いていない。こういうのは、強いて言えばCALLBACK関数である。垂直帰線期間に差し掛かったらCALLBACKしてくれればそれで良いのに、どうして、そういう機構にしなかったのかゲイツ君のハナクソ度もかなりのもんだと思うのだが、ともかく責任者出てこーい!である。


第45回 これってDirectDrawの仕様なんか?(困るなぁ) 99/5/29

DirectDrawの知られざる制限に、プライマリサーフェース以上の大きさのバックサーフェースを作ってはいけないというのがある。正確には、DirectX5では対応しているはず(技術資料の仕様上は)であるのだが、なんかそのへんお馬鹿なドライバーがあって、対応していないこともしばしばある。まして、DirectX3なんかでは、対応していなかったり、対応していたり実にややこしい。バックサーフェースの横幅がプライマリのそれより大きくなるといけないだとか、縦幅に関して、そういう制限があるだとか、やっぱりそれはいろんなソフトでバグの原因となったので新しいドライバではそのへんが改善されているだとか、様々である。

いずれにせよ、最新のドライバにすれば、たいていは、プライマリより大きなサーフェースをバックサーフェースとして作成出来ることは確認しているのだが、それでも安全の為にはプライマリより大きなサーフェースは作らないというのが原則である。そもそも、サーフェースというものを作らないほうが良いという話もある。DirectDrawの真価とは、決して、透過機能付きbltやFlipなどではなく(それらは、僕に言わせればまったく価値のないものである)、画面をLockして、リニアなメモリポインタが得られるというところである。こいつが一番大きい。(と思うのは、やねうらおだけかも知れないが)

ビデオメモリがノンリニアであっても、Lockするだけで、仮想的にでもリニアメモリを作り出す(ビデオメモリ上に構築できなければシステムメモリ上に作成する)部分にある。そのオーバーヘッドを考えるとLockするならばシステムメモリに最初から置いておいたほうが、場合によっては速いのであるが、そうなってくると、わざわざビデオメモリなんてものの上で操作しなくても、システムメモリ上でサーフェース構築して、画面の(プライマリ)サーフェースだけ用意しといて、そいつをLockしたあと、どかーんとメモリからラスター単位でコピーしてしまうだとか(もち、CopyMemory一本ではなく、MMX用にはラスターコピーを最適化したルーチンを用意したりすること)も考えられる。

ウィンドゥモードでなら、DirectDrawCliperを使うより、bitbltで転送したほうが、お手軽かつ完璧な協調動作が期待できるし(あまり速度面では誉められたプログラムではないかも知れない)、そう考えていくと、DirectDrawが一番役に立つシーンというのは、本体が旧世代のマシン(Pentium133MHzぐらいまで)でビデオメモリ間の転送のほうが、システムメモリ間の転送より圧倒的に速く、かつ透過機能等をハードでサポートされるような場合においてのみ、意味があるのであって、それ以上のスペックを持っているのならば、リニアなビデオメモリポインタさえあれば、そいつにがしがし書くほうが、断然スマートである。ちゅーかなー、なんでそないしてくれへんかったんや!

カラーキーかってなー、上限と下限と設定できるけど、一つの数字をはさみ込むように(例:上限=121,下限=121のように)しとかんと(=複数の透過キーがあると)ハードでbltができんかって、目をそむけたくなるほど遅くなるビデオカードがほとんどやし、拡大縮小をした途端、なんじゃこりゃーと言うほど遅くなるハードもまだ市場には多く出回っているのである。(特にノートパソコン)

そんなわけで、結局のところ仮想メモリも自分で用意させてくれて、Blt(lpVirtualVideoMemory);とメモリを一発転送する部分だけをあればそれで良かったのである。いまのようなゴテゴテした機能は全部不要なのである。もちろん、垂直帰線期間中に、勝手に転送するところまで面倒を見てくれるようでなければならないが、その命令ひとつであるほうが、DirectXの自由度とプログラミングのしやすさは、何千倍にもなるような気がする。使いもしないハードウェアの機能をサポートするのは、いい加減にやめて欲しい。どのみち、市場に出すゲームのためには、最大公約的な機能しか利用できないわけだし、それを考えるとBlt(lpVirtualVideoMemory);のありがたさを理解してもらえるかも知れない。


第46回 DirectInputの性能テスト(たまには役に立つ?) 99/5/30

なんか、最近、DirectXについて役に立つことしか書いていないので、これでええんかなーと思っている。なんか本連載の趣旨に反しているような気がしないでもない。このまま突っ走ると、下手すると日本で一番DirectXについて詳しく書いているページになってしまう。まあ、それならそれでいいんだけど...

さて。キーを同時に入力するだけならば、GetASyncKeyStateというAPIもあり、こいつでもキーのリアルタイム入力が可能である。こいつで事足りるならば、DirectInputなんて使う必要ないのである。

そこで、ちょっと、自分の開発マシン(Celeron366MHz)でテストしてみた。簡単なループでの呼び出しであって、関数呼出しのオーバーヘッドなんかもあるかも知れないし、他の実装系ではまったく違う数字になるかも知れないが、そのへんは目をつぶることにしよう。

☆ DirectInput 10,000,000回テスト

  処理時間14秒(ただし、すべてのキーの入力判定が出来る)

☆ GetASyncKeyState 10,000,000回テスト

  処理時間31秒(ただし、1つのキーに対してしか入力判定が出来ない)

仮に10個のKeyを使うゲームならば、GetASyncKeyStateの場合10回呼び出す必要があるわけで、DirectInputで処理する場合にと比べると約20倍の差が生じる。このとき、Pentium 100MHzでなら、だいたい秒間10,000回判定が出来ると言ったところか

60FPSだとして毎フレームごとにKey受付をするから、秒間60回この処理を行なうことになる。これに60/10000秒必要だから、プロセス時間の0.6%を費やすことになる。(DirectInputの場合、この1/20だから、0.03%)

そもそも、この手の処理では、20倍速いという倍率ではなく、0.6-0.03%=0.57%速いという差で考えるほうが妥当であって、CPU時間に換算して1%以下の処理はどうだっていいとぐらいに考えるべきだろう。

よって、GetASyncKeyStateを必要回数使うプログラムでも全然OKなわけである。糞なDirectInputをわざわざKeyBoardのためだけにCOMオブジェクトを呼び出して、(そもそも、なんでKeyBoard定数c_dfDIKeyBoardをDinput.libの中で定義するかなー。dinput.hのなかに書いたらええやん!こんなことしてくれるから、DInput.dllをLoadLibraryして、なおかつDinput.libをリンク指定せなあかんことになってきよんねんや!なんちゅーダサイ設計や)いつKeyBoardをLostしてまうか腫れ物に触るかのようにひやひやしながら、GetKeyStateするプログラムを書いて、それでしかも、アプリ切り変わる瞬間にすぐにKeyBoardをロストしてまうドタコなDirectInput。

かたやDirectXの存在なんてまったく無視して使える、これを呼び出せば良いだけのバカチョン(差別用語?)GetASyncKeyStateとでは、てんで出来が違うような気がしてならない。

これだけわかっていても、自分のライブラリでは速度を考慮してDirectInputで実装してしまうんだよなー。俺ってセコバッタだよなぁ...


第47回 DirectDrawClipperの性能テスト(&どうでもええ話) 99/5/31

やねうらおは、開発マシンがWindowsNTに変わったのである。なんかユーザー管理されていて、普段使うときは、Administrator(管理者)ではなく、一個人としてログインするわけである。そうすると、なんと、カレンダーが見れない!のである。カレンダーってなんや?とお思いの方もおられるであろうが、マウスをタスクバーのコーナーにある時刻表示のところに持って行って、ダブルクリックしていただきたい。

えっ?それって、時刻調整でないの?違う。違うったら違う!!これは、やねうらおの、とってもとっても大切なカレンダーなのであって、電話しながら、これを見て待ち合わせの日を決めたりするのである。これがないと仕事になんないのである。時刻調整なんかでは断じてないのである。NTよ。日付を変更する権限がないのはわかったから、せめてカレンダーだけでも見せてくれよー。(だからそれは、カレンダーちゃうねんて>俺)

そんなにカレンダーが欲しけりゃ、カレンダーのソフトを常駐させとけばいいじゃんと言われそうだが、開発マシンだからして、EXEファイルは決して実行しないのである。このままだと勢い余って、カレンダーのスクリーンセーバーとかを作るかも知れない。(スクリーンセーバーの意味ないじゃん!)

まあ、そんなことはどうでもいいのだが、今回はクリッパの話である。クリッパ(DirectDrawClipper)についてご存知ない人は、ちょっとDirectDrawのオンラインヘルプでも参照していただこう。

いきなりだが、ウィンドゥモードでは、クリッパは必要不可欠である。(こないだとゆうてることちゃうやんけ...)

使用方法はいたって簡単。

if (lpDirectDraw->CreateClipper(0,&lpClipper,NULL)!=DD_OK){ // クリッパ作成

if (lpClipper->SetHWnd(0,hWND)!=DD_OK){ // クリッパをWindowと結びつける

if (lpPrimary->SetClipper(lpClipper)!=DD_OK){ // サーフェースにクリッパを取り付ける

この3つだけである。あとは、フツーに他のサーフェースからプライマリサーフェースへの転送すれば、勝手にはみ出している部分をクリップしてくれる賢い奴である。

DirectDrawClipperを使った640*480のウィンドゥへの5000回bltの結果は23秒であった。これに対し、DirectDrawClipperを使わない場合、すなわち、以下のように、GDIを介した場合、

HDC hDDSDC; // DirectDrawSurface's DC
lpSecondary->GetDC(&hDDSDC);

PAINTSTRUCT ps;
BeginPaint(hWND,&ps);
HDC hDC = ps.hdc;

BitBlt(hDC,0,0,ScreenXSize,ScreenYSize,hDDSDC,0,0,SRCCOPY);
EndPaint(hWND,&ps);

lpSecondary->ReleaseDC(hDDSDC);

5000回で47秒であった。つまり、転送に限って言えばだいたい半分ぐらいの速度しか出ていないわけである。Clipperは遅いとよく言われるが、それはフルスクリーンモードに比べての話であって、やはりGDIなんかとは比べ物にならないわけである。あまり、Clipperを使ったサンプルプログラムを見かけないが、それは使いかたが簡単だからであろう。IMEもうまくクリップされるし、こいつは、なかなかお利口さんである。クリッパちゃんは、いい子いい子したげるかんね。(馬鹿)

しかしなー、あのIMEやけど、あらゆるウィンドゥよりしゃしゃり出てくんの、ええかげんやめーよ、と言いたくなる。なんかセンスないなーとか思ってしまう。アイコントレイにでも入ってて「あ」と「A」だけわかったら、それでええんとちゃうのん?邪魔でしゃーないんやけど。そんなこんなで、今日の合言葉は、IMEは死刑!(なんや今日の合言葉って?葉書に書いて送ったら抽選でなんかもらえるんけ?)


戻る