2008年01月16日

SQLite の VFS

いつの間にか SQLite に VFS という仕組みが追加されており、これを使うことで任意のファイルシステムからデータを読み書き出来るようだ。
通常は何もしなくても使えるが、例えばアーカイブの中に入れた DB からデータを読みたい場合などはこれを使う必要があると思われる。
と言うことで、XP3 アーカイブ内に入れた DB ファイルから読み出せるように VFS を実装中。
とりあえず、一通りメソッドを書いた。
24個もあるので面倒だった。
動作確認はまだ。

これが出来ると、読み出し専用の DB をアーカイブ内に含めることができる。
大量にデータがある時は、RDB が使えると便利と言うか、ないと面倒臭すぎる。
書き出しは今のところ必要ないが、必要になった時は外に置く。
その場合、手軽に利用できるように O/Rマップの実装を考え中。
だいたい仕様は考えた。
その仕様どおりに動くとかなり便利なはず。

投稿者 Takenori : 01:23 | コメント (0) | トラックバック

2008年01月19日

xp3_vfs

TVPCreateIStream によって取得した IStream 経由でデータを読む SQLite の VFS はできて動いた。
読み込み専用なのと、sqlite3_open_v2 を使っているので utf-8 専用になっている。
sqlite3_vfs_register でデフォルトに設定すれば、sqlite3_open16 でも開けるかと言うと、その場合はオープン時のフラグをセットできないので、読み込み専用で開けない。
VFS で読み込み専用じゃなくても開けるようにしてもいいが、XP3 に書き込みはできないと思うので、また面倒。
まあ、データベースの内容を一気に読み込まず、少しずつ必要時に読むだけであれば、変換のコストは無視できる程度だろうから、このまま utf-8 で行くことにした。

データの登録は Web 上から出来るように PHP で書いた。
Web 上から登録したデータは SQLite のデータベースファイルに入るので、登録したデータファイルをコピーしてくればそれがそのままゲームに反映される。
数人でデータを登録していくには便利。
後、PHP の PDO で SQLite のデータベースを読み書きする時に utf-16 で保存出来なさそうだったので、プラグイン側で utf-8 を使ったと言うのもある。

プラグインは初め汎用的にしようかと思ったけど、特化してしまった。
公開している API はそれ専用。
外部からはブラックボックスで、中でどのようにデータを取得しているかは不問。
TJS でゴリゴリいろいろ書いてもいいけど、プラグイン書くんだからということでそうした。

投稿者 Takenori : 18:30 | コメント (0) | トラックバック

2008年01月22日

フォースフィードバッグで振動させたい

フォースフィードバッグで振動させたいと言うことで、ゴリゴリプログラミング中。
それで調べた内容などをメモ。

ゲームパッドを使うと言えば、Direct Input だけど、最近は XInput と言うのがある。
これは、Direct Input に比べるとすごくシンプル。
基本的には、初期化不要で Set と Get のメソッドで入出力を行う。
入力系統は、XBox360 のパッドと同じものと想定している。
パッドは4つまでとなっていて、Set や Get で数字を渡せばそのパッドの入出力が返ってくる。
なかなか簡単で便利そう。
ただ、XInput に対応したデバイスでないと使えない (今のところXBox360のパッドをPCにさして使う用?) 。
後、Direct3DX と同じわながありそう。
xinput1_1.dll、xinput1_2.dll、xinput1_3.dll と番号付の DLL がインストールされている。
つまり、これは DirectX がマイナーバージョンアップするごとに数値が増えて増殖するパターンのにおいがする。
で、リファレンスなどにしたがって Xinput.lib とリンクすれば、その開発時の SDK に入っている DLL で固定されると思われる。
カプコンのロストプラネットのFAQにはそのために発生していると思われる問題に関する記載がある Q. 「XINPUT1_3.dllが見つかりません」というエラーが出てゲーム起動できません
Direct3DX の場合は、昔のバージョンだとスタティックリンク版の物が存在しているため、そのバージョンの SDK を手に入れて、その lib とリンクすることで、dll ではまる問題を回避できる。
が、XInput は…… いろいろなバージョンの SDK を入れればあるかもしれないが、比較的新しいもののようだから スタティック版はないかもしれない。
とすれば、xinput1_3.dll に動的リンクして、リンクできなかったら XInput 使用不可とするか。
で、Direct Input を使用して動かす。
でも、XBox360 のパッドを Direct Input 経由で動かすと使えなくなる機能がいろいろあるんだとか。
トラップ絶賛発動中だなぁ。
まあ、昔のパッドならば Direct Input で問題なく動くだろうから、だいたいはそれで何とかなるだろうが。

後、普通に Direct Input を使って、デバイスを列挙すると、その中に XInput に対応したデバイスも引っかかる。
これの回避方法はサンプルに含まれていて、WMI を使って XInput デバイスのメーカーとプロダクトIDを調べて、Direct Input デバイス列挙時に同じのがあったら弾けばいけるようだ。
Windows Me、Windows 2000、Windows XP、および Windows Server 2003 では WMI が標準でインストールされると言うことなので、まあだいたい大丈夫。
それ以前のものは、WMI SDK や IE5 などをインストールしていれば使えると言うことのようなので、何とかなるといえば何とかなる。

Direct Input フォースフィードバッグの指定方法はいろいろあって結構複雑。
それぞれのパラメータの関係などを調べたが、XInput と同じようにただ単純に強さを指定し、コントロールはソフトウェアでする方法が良さそうだ。
ちなみに、Direct Input では、常に一定のフォースを発生する コンスタント フォース。
距離に応じて、フォースを変える コンディション、値の配列を渡してそれに従って働く カスタム フォース、周期的にフォースを発生させる ピリオド、徐々に大きくなってから、また徐々に小さくなるフォースを発生させる 傾斜フォースがある。
で、使うのはこの中の コンスタント フォース。
ソフトでその時に応じて強さを与えてコントロールしようと考えている。

他に Direct Input で問題となるのは、ボタンやキーの数がバラバラなこと。
これを回避するためにアクションマップと言うものが一応存在しているが、ジャンルを固定して云々とかで使いづらそう。
と言うことで、いわゆるキーコンフィグが必要になる。
入力系統は、XBox360 のパッドと同じようになるようにしようとしているので、それに合うようにコンフィグで設定してもらう必要がありそう。
まあ、ある程度は存在するキーやボタンから近いものに割り当てるが、ボタンの配置などはパッドによっててんでバラバラ。
メーカーとプロダクトIDでテーブルを持っていくつかのパッドにはそのままでいけるようにしようと思っているが、それは自分の持っているものか、データが手に入るものしか出来ない。

なんかパッドの対応はいろいろとやっかいだな。

投稿者 Takenori : 20:28 | コメント (0) | トラックバック

2008年01月25日

炎のエフェクトとパーティクル

※ 2008/2/10 認識に誤りがあったので修正。詳細なアルゴリズムは後日アップ予定。

いろいろと燃える演出を入れたいなと炎エフェクトについて調べた。
簡単に見付かったのは以下のページ。
2005/2/2 (水) [炎エフェクト]
このページは Fire をベースにと言うか、そのままソースを少し変えただけのようだ。

エフェクト(2) 炎 については、ゲームエフェクトマニアックス (C MAGAZINE)を元に作っているようだ。

最初の方のアルゴリズムは、炎文字の作り方 プログラマにもできるPhotoshopの使い方のような方法を順にやっているに過ぎない。
昔とあまり変わっていない気がする。
具体的には、画像をぼかしながら上の方にコピーしていく。少しずつ薄くする。完全に上ではなくゆらゆらさせる。
それだけ。いたって普通。

で、どのようにゆらゆらさせるかだけど、バネの動きを模しているようだ。
最初はランダムに動きを決めるが、その後は対象画素頂点の周囲の数画素頂点の動きを使って動き方を変えている。
図がないとわかり辛いので、以下に図を貼る。
fire_spring_20080124.PNG
A が対象画素頂点、B が比較画素頂点。それぞれ A'、B' に移動する。
ここで、その画素頂点間の距離を求め、それぞれ L と L' とする。
で、( L - L' ) * 係数 として求めたものを L' にかけて、それを速度に加算している。
つまり、位置関係が離れる時は近づき、近づく時は離れるようになる。
その結果ゆらゆらゆれる。
単純に正弦波でもいいんじゃないのと思ったりもするけど、こちらの方がリアルになるのかな?

2個目の方はパーティクルの考え方とだいたい同じ。
HP ではなく、本のほうの解説をまとめると、ある位置からビルボードをランダムな速度で上の方に移動させ、徐々に薄くして、一定時間経つと消えるようにする。
単純なアルファ合成ではなく、加算合成を使う。

ここでふと昔書いたパーティクルのソースをあさってみた。
Cで書いていて、起動すると途中で落ちた。
確か、いろいろとリアルに見えるようにいじっていた記憶があるけど、なんでまともに動かない状態で止めているのかはなぞ。
だいぶ昔のなのでソースを見るといろいろとダメな点が見えるが、それなりに考えられている。
まず、パーティクルは出現時間、存続期間、現在位置、初期位置、速度、エネルギー、重力加速度、質量を持っている。
そもそも、重力加速度は個別に持つ必要はなくて、全体で1つでいいんだけど、なぜか個別になっていた。後、質量もいらないはず。
で、このパーティクルの初期値を乱数である程度の範囲で決めて、大量に作って動かすだけ。
ただ、描画するのは画像ではなくてエネルギー場(?)に対してで、コピーではなく加算になっている。
これは加算合成と等価な効果があるはず。
後、Z軸にしたがって、エネルギーが及ぶ範囲が変わるようにしている。
手前ほど大きくなるようだ。
後、特異点というものも持ち込んでいる。
パーティクルの中に、速い物が混じるようになっている。
これによって、爆発時に高速に遠くまで飛ぶものが混じる。
エネルギーから色への変換は単純なテーブルによって行われている。
このテーブルは赤→黄→白とRGB値が変化するようになっており、256+256+256の768段階になっている。
当時記憶では、PenIII 800MHzで、パーティクルを10万個ぐらいばら撒いたら 2、3秒に1回しか画面が更新されなかった気がする。
荒削り&適当っぷりが伺えるプログラムだな。
それなりに見えるものだったが、とにかく遅かった記憶がある。

と言うことで、以上を元にある程度使えるものを作ろうと考えている。
使えなかったら、その演出はカット。
初めは PhotoShop で作ったのをそのまま出せばいいと思ったけど、動いてなかったらつまらないと言うことで作ろうとしている。

投稿者 Takenori : 18:10 | コメント (0) | トラックバック

2008年01月26日

ゲームパッド - デッドゾーンと飽和点

XInputの仕様と同じようにパッドは4つまでにしようかと思ったけど、制限を加える意味はないと気付いた。
と言うか、複数のパッドが接続されている場合、ユーザーに使用するパッドを選択してもらうことになると思うが、この時は見付かった全てのパッドが列挙される。
つまり、この段階では制限がない。
で、この後使うパッドが選択されて、決定されるわけだけど、ここで制限がある意味はないなと。
パッド数の制限は、ゲームのシステムによって決まるはずなので、同時利用可能なパッドの数はゲームのシステムに決定させればいい。

意外と知らせていないと言うか、自分が知らなかっただけだけど、コントロールパネル - ゲームコントローラで優先デバイスと言うものが設定できる。
優先デバイスが設定されていると言うことは、利用される可能性が高いかもしれないと言うことで、優先デバイスがあったらそのデバイスをリストの先頭に持ってくることにした。
その次にXInputデバイスが入り、後は見付かった順。

DirectInput ではデッドゾーンと飽和点が取得できるが、これはあまり意味がなさそう。
デッドゾーンとは、このアナログ軸の値がこの範囲にある時は、値を 0 とみなす範囲のこと。
アナログ軸の値をスティックを動かしたりして取得してみるとわかるが、手を離しても 0 にならず、小さい不定値になる。
で、この時動いていると見てしまってはいけないので、デッドゾーンを使って判定する。
飽和点は、反対にこの値以上になったら最大値とみなす値。
ただ、XInput では飽和点がないようなので、これはいらないのかもしれない。
で、これらの値を Logitech RamblePad 2 で取得してみるとともにないことになっている。
つまり、中心は 0 のみ、飽和点は最大値 となる (厳密には初期値は0~65535なので、中心は32768)。
でも、動かしてから手を離すと小さい不定値になる。
DirectInput の使い方を間違っているのだろうか?
検索してヒットしたソースを見たら、このデッドゾーンや飽和点を設定していたりするのがあって良くわからない。
性質からしてハードウェアの固有値だと思うのだが……
と言うことで、これらは XInput で定義されている固定値を使ってしまうことにした。
この値よりも大きい値で止まるパッドの場合、手を離しているのに少しずつ動いてしまったりするかもしれないが、そこは諦める。
まあ、一応デッドゾーンと飽和点をログに書き出して、キチンと値を設定しているパッドがあるかどうかは見てみようと思うが。

アナログ軸の値の範囲は、DirectInput に合わせて、-32768~32767に設定するようにした。
Logitech RamblePad 2 の初期値は、0~65535なので範囲的には同じ。
この範囲を扱えないパッドがあると、設定に失敗してしまうかもしれない。

DirectInput で対応デバイスを増やそうとしたら結構面倒かもしれない。
まあ、いろいろなデバイスで試してすんなり動いてくれれば、問題ないのだが。

投稿者 Takenori : 21:37 | コメント (0) | トラックバック

2008年01月30日

HID のフィジカル デスクリプター

HID ( ヒューマン インターフェイス デバイス ) には、Physical Descriptors と言うものがあり、これで物理的な位置を取得できる。
位置と言っても、左手の親指で操作するボタンで、操作のしやすさもしくは距離は、いくつと言うような情報が得られる。
これによってゲームパッドのボタンがどの位置にあるかある程度わかると思ったんだけど、うまく取得出来ない。

まず、ゲームパッドから HID デバイスのパスを得るには、IDirectInputDevice8 の GetProperty で DIPROP_GUIDANDPATH を指定する。
これで得られたパスを CreateFile でオープンし、このハンドルを用いて HidD_GetPhysicalDescriptor をコールしてやることでデータが得られると思ったんだけど、うまく行かない。
普通にやると HidD_GetPhysicalDescriptor をコールするには、WDK が必要だけど、hid.dll を自分でロードしてやり、HidD_GetPhysicalDescriptor のアドレスを得れば、WDK なしでもコールできる。
HID 系のメソッドは、だいたいこの方法でアドレスを得られてコールできるようだ。
HidD_GetAttributes や HidD_GetPreparsedData、HidP_GetCaps、HidP_GetButtonCaps は普通にコールできて、値も得られた。
ただ、これらのなかには構造体のポインタを引数にとるものがあるので、WDK を使わないのなら、これらも自前で定義してやる必要がある。

HidD_GetPhysicalDescriptor で得られるのは RAW データとなっているので、これは自分で解析する必要がある。
Device Class Definition for Human Interface Devices (HID) ( PDF ) の 6.2.3 Physical Descriptors に記載されている。
また、HID Usage Tables ( PDF ) の Appendix C: Physical Descriptor Example には、その例があるので、こちらも見るとわかりやすい。
ってことで、このパーサは先に書いていた。

が、HidD_GetPhysicalDescriptor をコールしてもうまくいかない。
後、パッドによって動作が異なる。
何事もなく終了するが中身がからか、デバイスが機能していませんとエラーが返ってくるか、引数が不正で返ってくるか。
たぶん、使い方が悪いんだと思うが、良くわからない。
HID と直接通信するには、CreateFile で開いたハンドルを用いて、ReadFile や WriteFile で非同期で読み書きすればいいようだ。
つまるところ、自分でHID と通信してやれば、データを得られるのではないかと思う。
上に貼った HID の資料や MSDN2 のページを読んで、ひたすらトライアンドエラーを繰り返せば動かせると思うが、時間がかかりそう。
今は Physical Descriptors は置いておくことにした。
あっても少しだけ初期のボタン割り当てが、期待したものに近くなるだけだし。

投稿者 Takenori : 14:05 | コメント (0) | トラックバック

2008年02月09日

VCのプロジェクトファイルを自動生成する

何かを作るとき、最初にソースファイルを作って、エディタで組んだ後、VCを立ち上げて、プロジェクト作って、ファイル追加して、オプションを変更して…… とするわけだけど、この「VCを立ち上げて云々」がすごく面倒臭い。
たいした作業ではないのだが、なんか気が重いのだ。
オプションもデフォルトから毎回同じ設定に変更している。
本来はカスタムウィザードを作って、それで何とかするんだろうけど、そこも面倒臭い。

ということで、スクリプトを組むことにした。
とりあえずは、吉里吉里のプラグイン専用で、kirikiri2/src/plugins/win32 以下に新しいフォルダを作ってその中にソース入れて作る用。
実行したフォルダ以下のソースとヘッダー、リソースを全てプロジェクトファイルに追加する。
また、ヘッダーが存在するパスも全部インクルードディレクトリに追加する。
これでフォルダ分けしている場合も、インクルードディレクトリのことを大して気にせず作業できる。( VC の GUI でやるとデバッグとリリース両方変更しないといけなくて面倒 )

スクリプト類は mkpj_kr_plugin.zip に置いておく。
設定などは私用になっているので変えたい場合は、project.pji を設定変更した *.vcproj に合わせて変更する必要がある。
プラグイン名は project.ini に書く。
mkpk.pl を見てもらえばわかるが、VCのプロジェクトファイル類を消してから作るので、取り扱いには注意すること。
*.vcproj を変えて mkpk.pl を実行してデグレードとかは注意しないとやってしまうと思う。
後、Win32-Guidgen モジュールを使っているので、ppm などで入れる必要がある。
他の注意事項は…… mkpk.pl を読んでください。

これは吉里吉里のプラグイン専用でVC2005用だけど、少し変更すれば exe などにも転用できる。
GUI でちまちまするのが嫌いな人は、自分用の設定で数種類作っておくといいかも。

投稿者 Takenori : 20:55 | コメント (0) | トラックバック

2008年02月10日

パーティクルで炎は失敗

20080210_fire_particle.png
点描みたい。
これで約1秒間に1万個だったかな。
動いていても粒状感が消えないというか、まんまパーティクル。
ぼかしてもイマイチ。
数を増やせばある程度改善するが、どんどん重くなっていく。
いろいろといじってみるも、このアプローチでリアルに見せるのは難しそうだ。
ただ、パーティクル自体は何かに使えそう。
粒子が一方向に流れていると砂が飛んでいるように見える。
単純なパーティクルとして、パラメータを増やして実装するのはいいかも。

ということで、次はWrapマップの方で実装することにした。
元のままでは汎用性がないし、見た目も少しおかしくなる時がある。
アルゴリズムは理解したので、汎用性がありもう少し高精度な実装にしている。

投稿者 Takenori : 15:20 | コメント (0) | トラックバック

2008年02月11日

Warp Map で炎

20080211_fire_red.png20080211_fire_blue.png
パーティクルはイマイチだったので Warp Map を使う方法で実装した。
静止画像でもだいぶリアルに見えていると思う。
動いているともっと炎っぽい。
ただ、現状だいぶ重い。
MMX 化することでかなり改善されると思う。
ただ、描画する領域が大きいとそれに従って重くなっていくので、あまり画面サイズが大きいと辛いかも。

ということで、ここから MMX 化による速度改善とテーブルの改善によってよりリアルにしていく予定。

投稿者 Takenori : 00:37 | コメント (0) | トラックバック

2008年02月18日

炎エフェクトのクオリティアップと高速化

20080217_fire.png

テクスチャマッピングの実装でミスっていたというか、以前使っていた用途に限定して高速化していたのを忘れていて、そのまま使っていた部分を直した。
後、カラーテーブルも少し改良。
他に風が吹いているように見せたり、最後消火するなど出来るようにした。
で、右側から風が吹いているようにして描画したのが、上の画像。

最初の実装はかなり重く、Core 2 Duo E6750 で 480 x 480 の範囲に炎を描いても処理落ちしていた ( 60 FPS )。
で、これを改善するべく高速化に取り組んだ。
Warp Map のアルゴリズムはまとめると以下のような処理になる。

1. Heat Mapへ火種を描画 (加算合成)
2. Wrap Mapを動かす (バネを模倣した動き)
3. Heat MapへWrap Mapを適用する (テクスチャマッピング)
4. Heat MapへCooling Mapを適用する (減算合成)
5. Heat Mapをぼかす (ガウスブラー)
6. Heat Mapを1ピクセル上へ移動する
7. 描画対象へHeat Mapを適用 (強度に応じた色で塗る)
8. Cooling Mapを1ピクセル上へ移動する
1 ~ 8 を繰り返す。Heat Map はクリアせずに使いまわす。

Heat Map は呼び名がないと不便なので適当に名付けた。仮想的な温度というか濃度というかそういうものを表している。
で、最初はほぼこの通りに実装していた( テクスチャマッピング時に減算合成を同時にしていたが )。
そこで、この 4 ~ 7 のプロセスをまとめ、キャッシュ効率やレジスタ効率を上げることにした。

なお、ガウスブラーは 3x3 で以下の比率でブラーをかけている。

1/161/81/16
1/81/41/8
1/161/81/16

この比率がガウス関数に沿ったものかどうかは知らない。
ガウスブラーは、横方向にブラーをかけたものに縦方向にブラーをかけると、結果的に矩形範囲でブラーをかけたのと同じになるので、1方向で見てみると 1/4、1/2、1/4 を適用すればいいことになるため、効率的なので、上のマトリックスを使っている ( つまり、( 右 + 2 * 中心 + 左 ) / 4 となるため、足し算とシフトだけで計算できる )。

で、いわゆる縦横の for 二重ループ内で 4 ~ 7 を全てやってしまおうとするわけだけど、そのままだとうまく行かない。
横方向ブラーは、1ピクセル遅れ、縦方向ブラーは1ライン遅れで処理していく必要がある。
ただ、縦方向は後でスクロールするから、一番上のラインを処理しても意味はないので、3ライン目以降になる。
ということで、最初の2ラインは減算合成して、1ピクセル遅れで横方向ブラーをかけて書き込んでいく。
3ライン目からは、減算合成して、1ピクセル遅れで横方向ブラーをかけて、上の2ライン分横方向ブラー済みのデータを読み込んで、直前に処理した横方向ブラー後のデータを使って縦方向ブラーをかけて、その結果を使って色テーブルから色を取って来て最終描画対象の1ライン上に書くのとHeat Mapの1ライン上に書き込む。
このようにして出来るだけデータを使いまわして順に処理していくように書き換えた。
これで、MMX など使わなくても 40% ぐらい速くなった。
で、ここから MMX や MMX2、SSE、SSE2 を使って高速化していった。

最初は、この 4 ~ 7 の処理を SIMD 化して高速化した。
横方向ブラーは、単純に直前の8ピクセルと直後の8ピクセルから1ピクセルを得られるようにシフトして、 or を取ったものと、現在の8ピクセルで処理すれば実現できる。
縦方向ブラーは、特に深く考えずに処理できる。
で、SSE2 まで実装してみたところ、MMX2 よりも SSE2 の方が Athlon 64 X2 でも速くなった。
以前のアルファブレンドでは大差なかったが、ある程度の処理を行う場合は、SSE2 の方が効率が良いようだ。
後、シフトと or をとる部分を SSSE3 のアライメント調整命令で実装してみたが、ほんの少ししか速くならなかったので、結局使わないことにした。

次に Wrap Map を動かす部分の高速化に取り掛かった ( 加算合成の部分の SIMD 化はさくっとやった ) 。
プロファイル結果を見るとそれほど処理時間を使っていないが、他の高速化は大変そうなのでやることにした。
ここは 5 x 5 の範囲で 元の位置関係の距離と移動後の位置関係の距離の差に係数をかけた結果を速度に加算して、最後にその速度を位置に加算する処理をする。
元の位置関係の距離は定数となることから元はテーブルで求めていたが、そこを毎回計算するようにしてみた。
つまり、元は len[abs(y-y1)][abs(x-x1)] のようになっていたのを、sqrt( (y-y1)*(y-y1) + (x-x1)*(x-x1) ) とした。
で、計測すると毎回計算する方が速い。
sqrt は比較的重い処理だと良く書いているのだが、abs やテーブルを引く処理の方が遅いのか……
で、次にこの処理の部分をインライン関数化して、24個並べた。
24個並べると、距離は計算する必要がないので、事前に計算した直値をいれる方法に。テーブルを引くのではなく、直接コードに埋め込まれるはず。
これで思っていた以上に速くなった。
速くなりそうだったので、ここを SSE 化した。
SSE 化した時、最初アライメントを気にして処理を分けたのだが、アライメント関係なく読み込む遅い方のを使って分岐しないようにした方が速かった。
やはり、ループ中で分岐するのは遅くなるようだ。

次にテクスチャマッピングの高速化に取り掛かることにした。
テクスチャマッピングの処理が全体の50%を占めているのでここを速く出来れば、かなり高速化できると思うが大幅な高速化は難しい。
続く……

投稿者 Takenori : 00:20 | コメント (0) | トラックバック

2008年02月19日

テクスチャマッピングの高速化 その1

現在、テクスチャマッピングは、各スキャンラインで各辺を順に交差判定し、交差したX座標間のUV値を補間しながら描画している。
他のアルゴリズムも試したが、炎エフェクトの描画では使えないことがわかった。
それは、炎エフェクトで使われるテクスチャマッピングでは、凹ポリゴンが含まれているからだ。
この制限がなければ、より高速なアルゴリズムが使える。

とりあえず、今回は使えないことがわかったアルゴリズムは……
最初に試そうとしたのは、各辺のXとUV座標値をY軸値の変化に沿って変化させる方法。
が、初めうまく行かなかった。
で、この方法の考え方を用いて、各スキャンラインで交差判定するが、判定対象の辺を限定する方法を思い付いた。
現在、各頂点は右回りで格納されている。
そのため、頂点番号を-1すると左側へ、+1すると右側へ進む。
また、頂点数は4個なので、 & 0x03 とすれば、判定なしで 0 ~ 4 の範囲を循環する。
このことを利用して、一番上の頂点から、左右の辺を求め、その辺に対して交差判定することで、交差する辺が限定できる。
Y軸値が、辺の下端の値を超えたら次の辺に進めばいい。
また、左右の位置関係もわかっているので、交差判定後にX座標値の大小値を比較してスワップする必要もない。
が、これもうまく行かなくてなぜだろうと思っていて気付いた。
Y軸値は、整数値で変化させているが、この開始値は浮動小数点値からキャストしたものだ。
つまり、切捨てとなっている。
そのため、この整数のY軸値と交差判定しても、交差する辺はない ( 小数点以下が 0 となるまれなケースでは交差するが ) 。
ということで、この整数のY軸値は最初に1加算することにした。
これで、Yの範囲は常に交差するようになり、右方向と左方向へ辺を見て行って判定する方法でうまく行くようになったと思ったのだが…… しばらく動かしているとアクセス違反で落ちた。
なぜ? と思って調べたら、凹ポリゴンだった。
うーん…… この方法で 2% 程度高速化したのだが、この方法は使えないようだ。
( 整数のY軸値を1加算する方法は、1回ループが減るので元のアルゴリズムでも使うことにした )

この+1する必要があるということがわかったので、最初に試そうとしたアルゴリズムを試してみた。
凹ポリゴンが描けないので使えないのはわかっているが、どの程度速くなるか見てみようということで。
アルゴリズムは上述の通り、左右の辺を求めて、その辺でY軸値が1増加するごとにX座標値とUV座標値がいくら増加するか求め、Y軸値が1進むごとにX座標値とUV座標値をその増加値分進めるという方法。
この方法では、交差判定が必要ないので、高速であることが期待出来る。
事実、20% 近く速かった。
でも、今回は使えないんだけど。

ちなみに、最大最小法 + ブレゼンハム でやる方法は、最近の CPU では遅いようだ。
以前、テクスチャマッピングではなく、単純なポリゴン描画で試したことがあるのだが、かなり遅くなったので使わなかった。
これらのアルゴリズムはループ中に分岐を多く含んでいるため、速度が出ないとか。
ライン描画もブレゼンハムではなく、素直に固定小数点でやった方が速いという話も ( 今回のことから考えると浮動小数点でもブレゼンハムより速そう ) 。
このライン描画については一度試してみようと思う。
ライン描画の前にブレゼンハムの考え方を使った拡大縮小を別ので使っているので、そちらでまず試すか。

これを書いていて気付いたけど、凹ポリゴンかどうか判定してアルゴリズムを切り替えると速くなるかもしれないと思った。
ただ、その判定コストに見合うだけ速いのかどうかはわからないが。

後、試したことは……
全て固定小数点で処理するようにしてみたが、逆に遅くなった。
ということで、現在X軸方向が増加する時にUV座標値を増減させる部分のみ固定小数点で処理している。
他に固定小数点にしてMMXやSSE2の倍精度浮動小数点でUV値を同時に処理するなど試してみたが、変換コストの方が大きいのか、遅くなった。
ポリゴン4個同時処理のような並列処理でなければ、速度は出し辛いのかもしれない。

いろいろ試して感じたのは、多少計算量が増えようともループ中の分岐を出来るだけ減らした方が速いということ。
このことを元に、ループ中の分岐を出来るだけ削るなどして、今使っているアルゴリズムでも、4~5%ぐらいは高速化出来た ( ループ中での分岐が1,2個減ることで数%高速になったりする。ループ数が多いところではたぶんもっと効く ) 。
ただ、今のアルゴリズムでこれ以上高速化するのは少し辛い。
もっと別のアプローチで高速化しなければ、速くなったと思うほど高速化することは難しいように思う。

今考えているのは、上に書いたような複数ポリゴンの同時描画。
SSE を使って 4個同時描画に近づける。
炎エフェクトでは、複数ポリゴンで1枚のテクスチャを使う形なので、各スキャンラインである程度絞り込まれた辺と交差判定し、その辺間を補間しながら描画すればいいのではないかと考えている。
各ポリゴンは辺を共有しているので、交差判定後のX座標値やUV座標値の計算量は 1/2+1に減る。
絞り込む処理やまとめて処理することによる速度低下が懸念されるが、計算量ほぼ1/2 + SSE による4個同時処理の恩恵の方が上回ってくれたらいいなぁと思う。
まあ、実際はやってみないとどうなるかわからない。

ちなみに、現在のソースだと 480 x 480 の領域に炎を 60 FPS で描くとしたら、1 ~ 1.5 GHzぐらい必要だと思う。
30 FPS でいいのなら、800 MHz程度でも動くのではないかと思う。
後、領域が狭ければ、劇的に軽くなる。
実際炎を描画するのは画面の一部だろうから、現在でも実用的な範囲に入っていると考えている。
そのうち、Athlon XP 1600 と Pen III 800 MHz でどの程度動くか試してみたい。

投稿者 Takenori : 00:55 | コメント (0) | トラックバック

2008年02月21日

テクスチャマッピングの高速化 その2

前回書いたようにポリゴン1個1個描画するのではなく、各スキャンラインでそのスキャンライン上にある全ポリゴンの辺と交差判定するようにしてみた。
その際、8ラインごとにポリゴンの辺と交差するかどうかを保持することにした。
これは頂点が上か下か判定する時に、上から下まで配列にそのポリゴンの辺のインデックスを追加して実現した。
つまり、配列は高さ / 8 個になる。
そして、画像の上から下までスキャンラインを移動させ、辺のインデックスを保持した配列から交差する可能性のある辺を取得して、交差判定後、交差位置を割り出し、1ライン分終わったら、X軸値でソートして、左から順にUV値を補間しつつ描画した。
ポリゴンの辺は、左端にあるものほど先に入りようにしており、X軸値はほぼソート済みとなっている。
そのため、交差する辺の数も少ないことから挿入ソートで実装した ( ソートの有り無しで速度はそれほど変わらなかった ) 。
で、時間を計ってみたのだが、28% も遅い。
交差する辺の絞込み周りがネックになっているようだ。
これを4ラインごとにすると少し速くなるが、1ラインごとにすると遅くなった。
そもそも、交差する辺を判定しなくても良いようにならないものか…… と考えていて気付いた。
スキャンライン基準ではなく、ポリゴンの辺基準でやればいい。
つまり、全ての辺の上端から下端までの範囲に辺のインデックスを格納してしていけばいい。
こうすれば判定は不要になり、そのスキャンラインを通る辺のみが各Y座標値にリストとして得られる。
また、補間に使うY軸値の比も毎スキャンライン計算するのではなく、1.0 / (y2 - y1) で得られた値を加算していけばよくなる。
そして、再度時間を計ってみたのだが、18% 遅い。先ほどと比べると 10% 短縮されているが遅い。
ただ、この方法だと凹ポリゴンをより正確に描ける。
1個1個描画するバージョンは、各辺を順に見ていく関係上凹ポリゴンの時、最初に交差した2辺間を塗ってしまう。
つまり、凹ポリゴンの時、以下の図のように塗られることになる。
20080220_poly.png
炎の動きが激しくない場合は、それほど凹ポリゴンがないので、気にならないレベルだが、激しくすると破綻しているところが見える。
まあ、普通に使う分にはわからないのだが。

よくよく考えれば、交差位置の計算は必要ないことに気付いた。
前回、右方向、左方向で進めていく方法について書いたが、それと同じように辺ごとに上から少しずつ値を加算していけばいい。
ただ、前回 20% 程度速くなっていたということは、今回の方法でこれを使っても大差ないレベルに出来るというだけの話か。
実際は、テンポラリの変数が増えるのでもっと遅くなりそうか。

この辺りで手を打つかな。
画面全体でやろうとした場合、かなり速い CPU でないと無理そうで、そうなるともう GPU に任せるだろうから。
部分的に適用するものと言う事で使うか。
もっと根本的にリアルタイムで炎を生成せずに、アルファチャンネル付きムービーを実装してしまうと言う手もあるが (笑) 。

投稿者 Takenori : 00:05 | コメント (0) | トラックバック

2008年02月25日

テクスチャマッピングの高速化 その3

テクスチャマッピングの高速化を書きつつ、そのアルゴリズムを知らない人にとっては意味不明のエントリーだろうなぁと思って、アルゴリズムについて解説するエントリーでも書こうかなと考えた。
そして、まずは最大最小法だろうなどと考えている時に気付いた。
全ての辺をまとめてやって遅いのなら、1個1個のポリゴンでやればいいのではないか?と思った。
まとめてやると速度が出ないのは、メモリ使用量の増加と間接参照の増加が原因ではないかと思われる。
1個1個やると計算量は 1/2+1 にはならないが、それでも速くできるかもしれない。

基本的なアプローチは一度にやる方法に近い。
まず、左右の X 座標と UV 座標を最初に全て求める。
次に、この間を補間しつつ描画する。
左右の座標を求めるのは、辺が右か左で判断し、2辺目に移った時に、Y座標が上になっていたら、凹ポリゴンか辺が逆に移った可能性がある。
この時、辺が外側に向いていたら凹ポリゴン、内側に向いていたら辺が移ったことになる。
これは下図を参照してもらうとよくわかると思う。
20080225_side_order.png
内側か外側かは単純にX軸の値を見ればわかる。

また、間を補間するための値の計算は4つまとめて SSE を使って出来る。
この方法で全スキャンラインで交差判定する方法よりも少し速くなったんだけれど、少し不具合があって時々アクセス違反で落ちる。
Y 軸も X 軸も変化量を加算して求めているために誤差が出ているのか、アルゴリズムに見落としがあるのかわからないが、時々テクスチャ画像の範囲外にアクセスしてしまう。
テクスチャ画像へアクセスする時に範囲チェックをすれば解決するが、それをやると 25% 程度速度が低下するので、かなり遅くなってしまう。
アルゴリズム的な問題は何度か見直したが、解決しない。
どうするかと考え、全スキャンラインで交差判定する方法でも同様に SSE で高速化できるのでは? と思った。
全スキャンラインで交差判定する方法の SSE 化は何度かやろうとしたのだが速度が出なかった。
それは垂直演算に近いことを単に2並列でやろうとしていたからだったのではないかと思う ( U と V を同時に計算しようとするなど ) 。
そうではなく、4 ライン同時に処理すればいいのではないかと思った。
つまり、最初に 4 ライン分の交差判定を済ませ、その後 4 ライン同時に処理する。
また、SSE を使ってスワップやクリッピングを行うので分岐が消える。

実装してみると 9% 程度速度が向上した。
ここからさらに交差判定も SSE 化することでさらに速度向上できるのではないかと思う。
また、横方向の補間も SSE2 を使うことで 4 ライン同時処理出来るので、もしかしたら速くなるかもしれない ( 横方向ラインによって幅が異なるので、終了位置がバラけるのと、テクスチャ画像の参照位置と書き込み位置がバラバラなので、速度が出るかどうかは不透明 ) 。

まだもう少し高速化できそうだ。

投稿者 Takenori : 17:27 | コメント (0) | トラックバック

2008年03月01日

炎エフェクトリリースへ向けて……へのはずが

高速化もあまり成果が上がらなくなってきたのと、そろそろ一度リリースしておくかと言うのがあって、公開に向けて整備を開始した。
使う場合は、目標スペックを考えて、描画領域を絞り込んで負荷を抑えることにした。

それでまずは、火種となる画像を複数指定可能にした。
これで、独立に動く火の玉とか、文字を1文字ずつ出して燃やすとかが出来るようになる。
後、描画対象レイヤーのサイズとかバッファが変わっていないか毎サイクルチェックするようにした。
これらを入れると少し遅くなったがそれは仕方ない。

次にパラメータをいろいろ変えても落ちないようにしようとしたのだが……
炎の揺れる速さを上げるとすぐに落ちた。
前々からある程度は認識していたんだけれども……
さらに、あんまり値を上げなくても、かなり長い間動かしていると落ちてしまう。
これはまずいと言うことで調査。
値を上げすぎると、頂点の座標に78億とか入っていた。
これは明らかにまずいので、あまりに大きすぎるか小さすぎる頂点を含む場合は弾くようにした。
後、除数が小さすぎて値が大きくなりすぎることがあるようなので、除数が 0.01未満の時は 0.01 にするようにした。
最終的には、1ピクセルの単位で描画されるので、それ未満の値かどうかによる誤差は気にしないことにする。
これで問題ないかと思ったんだけど、Release 版だけなぜか落ちる。
Debug 版では落ちない。
なんだろう? と思いつつ、Release 版でテクスチャの座標を拾ってくるところで範囲チェックを入れたら、落ちない。
うーん…… やはりここは範囲チェックいるのか。
仕方なく、範囲チェックを入れる。
20% 程度速度が低下した。
困った。
範囲チェックを高速化する方法を考える。
マスクを使おうと思ったが、よく考えたらマスクは二の累乗値幅の時しか使えない。
困った。
二の累乗値幅の時だけ、マスクでチェックするようにした。
このため幅と高さが二の累乗値の時、20% ぐらい速い。
このせいで二の累乗値幅以外では使い辛いかも。
で、範囲チェックがあるんだったら、テクスチャマッピングは毎ライン交差判定する方法ではなくて、稜線に沿って補間していく方法でも問題ない。
確かこっちの方が速かったはず。
と言うことで、次は値のチェックの強化をこちらにも入れて、差し替えてみることにする。

投稿者 Takenori : 19:08 | コメント (0) | トラックバック

炎エフェクトプラグイン

吉里吉里2プラグインとして実装した炎エフェクトを吉里吉里2プラグインページに置いた。
サンプルスクリプトと簡単なリファレンス付き。
今のところ動作確認レベルのテストしかしていない。
これから実際にこれを使った演出を組み込んだり、自分が使うパラメータでのテストを行う予定。
ライセンスは、吉里吉里のライセンスと同じ扱いとして、このプラグインを使ってもらってもかまわない。

テクスチャマッピングは結局安全性を優先した方になってる。
自分が使って、パラメータが決まっていて、エフェクトの持続時間が固定なら、結果は同じなのでそれでテストすれば、チェックをそんなに行わなくて速度を優先してもいいんだけど。

投稿者 Takenori : 23:42 | コメント (0) | トラックバック

2008年03月03日

炎のをリビルド

Athlon XP の Win2k マシンで確認すると、 C000001D (不正な命令) が発生。
調べると SSE を使っている辺り。
PenIII 866MHz WinXP のマシンでも発生する。
あれかなと思って、コンパイラの拡張命令セットを有効にするでSSE2を使わないようにしたら直った。
このオプションは、使えるか判定せずにSSE2を使うコードを吐いていたのか。
と言うことで、これを外してリビルドしてバイナリを差し替えた。

で、Athlon XP 1600 のマシンで動かすとやはり重い。
60 FPS は出ていない。
でも、なぜか CPU 負荷は 70% 程度。
-contfreq 60 指定が原因の様子。
処理が間に合わない場合は、繰り越しになる作りのようだ。
でも、制限しないと常に CPU を使い切ってしまう。
これは -contfreq 240 などにして回避する手があるとか。なるほど。
速度は ContinuousHandler でコントロールしているので、速いマシンで 240 FPSになることはない。
と言うことで、240 で試すと CPU 負荷は 85% ぐらいで、速くなった。
でも、ContinuousHandler のみで速度制限した物には及ばない。
まあ、その辺りは諦めるかな。
しかし、Athlon XP 1600 では、480 x 480 に 60 FPS で描くのは間に合わなかったか。

Core2Duo の方で制限をなくしてフルスピードでしばらく動かしていたらアクセス違反で落ちた。
あれ?
まだ落ちる可能性があるようだ。

後、現在のはマルチスレッドを切ったのにしているけど、有効にした方がいいかな。
ただ、今のマルチスレッド対応ソースはテクスチャマッピングのところで、上半分、下半分の2つにしているけど、テクスチャマッピングはポリゴン単位でも問題ないので、ポリゴンごとで処理すれば、理論的にはコア数に応じてかなりスケーラブルに速度が上がるはず。
他はマルチスレッド化できなくはないけど、面倒なのでテクスチャマッピングだけしようと思う。
まあ、処理時間の50%以上がテクスチャマッピング関係なので、そこだけでもかなり速くなる。
デュアルコアで今の上半分と下半分で分けたものでは 1.44 倍速ぐらい。
でも、デュアルコアで速くなるとかよりも、もっと低いスペックのマシンで速くならないことにはなぁ。

投稿者 Takenori : 01:32 | コメント (0) | トラックバック

炎エフェクトを使えるレベルに

炎のクオリティは問題ない。
思っていたよりも、かなりリアルになってびっくりしたぐらい。
そして、これを組み込んでみるとかっこいい。
でも、重いのを何とかしないと辛い。
最近の CPU なら特に問題ないけど……
Athlon XP 1600 だと、少しゆっくりになっているぐらいで、見れないと言うことはないが……

パラメータの調整で、45FPS ぐらいでも普通に見られるように出来るか。
これぐらいなら要求スペック 1.5 GHzでもいけるかな。
480 x 480 より領域を小さくすれば、だいぶ軽くなるから、範囲をギリギリまで削れば使えるかな。
領域の大きさは、負荷にかなり響く。
小さくすれば劇的に速くなる。
512x512 を 256x256 にするとほぼ4倍速になるので、だいたい大きさに比例している。
でも、大きい領域でも使いたいよなぁ。
それですぐに思いつく方法は2つ。
1. GPU で処理する
2. プリレンダリングする

最終的には GPU で処理するようにしたいが、完全に対応するにはそれなりに時間がかかる。
この炎だけならそれほどでもないと思うが……
GPU で処理して結果を書き戻す手もあるが、それもなぁ。
プリレンダリングして、ムービーでアルファを扱えるようにするのが、比較的早い解決策かな。
他にも融通が利くし。
でも、何かちょっと負けた気がするのが難点か。

投稿者 Takenori : 02:56 | コメント (0) | トラックバック

2008年03月06日

炎を入れてあれこれ

Athlon XP 1600 でも 256x256 程度なら余裕なんだな。
常に大きい領域で炎を出すのではなく、表示したい領域を絞り込めば普通に使える。
横幅は火種画像の幅 +32 ぐらいの大きさにして、高さは炎が上に伸びる関係上見ながらある程度調整するか、画面の一番上までとして出す領域を狭めるのが良さそう。
いきなり大きい領域ではじめから処理落ちして遅いと不自然だけど、処理落ちしない小さい領域で炎が出てその後大きく広がるような場合、大きくなった時に急にゆっくりになるが、処理落ちしているのかなとは思うけど、そんなに変ではない。
800x608 ぐらいのサイズだと、2GHzぐらいは必要になりそうだけど、みんなそれぐらいは満たしてないかな? まあ、800x608で炎を出したいがためにそんなスペック要求するのはどうかと言う話だが。
後、倍ぐらい速くなれば、使いやすいのだけれど……
とりあえず、あれからまた 5% 程度高速化できた。
それでもまだまだ重いのだが、実際に組み込んで見た感じでは、別にプリレンダリングにしてしまうほどではないかなと言う感触。
まあ、燃やすもののパターンが結構あるので、プリレンダリングにすると容量食いそうだと言うのもある。
このエフェクトであれば、元々ある絵を火種画像として渡してやれば、その領域を元にして燃えるのでデータ量は増えない ( エフェクト用に特別に火種画像を準備すればその分増えるが ) 。
例えば、立ち絵を火種画像として 0.5 秒ぐらいの間設定しておいて、その後火種画像をクリアすると、一瞬燃え上がるようなエフェクトが出来る。
動いている絵を毎フレーム設定してやれば、それに炎も追従する ( 少し重くなると思うけど ) 。
アルファ付きムービーが使えたら、それを火種画像として設定することで、動いているものに炎を重ねられる ( アルファ付きムービーが使えるのなら、燃えてるムービーをはじめから入れておけと言う話だが )。

で、結局どうするかと言う話だが、単純にオプションでエフェクトのON/OFFを入れればいいやと言う考え。
まあ、できるだけ高速化しようと思っているが。
でも、800x608 の範囲ぐらいなら 1GHz 程度でいけても良さそうなんだけどなぁ。
根拠はないんだけど。

ポリゴン1個ずつやるのではなく、全ポリゴンの辺で一気にやる方法を再び検討する。
凹ポリゴンが少し気になるのと、前やった時に追いつきそうだったこと。
前回はデータ構造がまずかったのではないかと思ったこと。
SSE でスワップが出来るのなら、4つ同時ソートもできるのではないかと言うこと。
きっかけは、プリレンダリングするならできるだけクオリティを高くと思って、凹ポリゴンも綺麗に描画できるアルゴリズムに変えようと考えたことだけど、あれこれ考えてより高速化出来そうだとも思った。
なんとか倍ぐらい速くならないものかなぁ。

炎ではないけど、違うエフェクトを単なる連番静止画で入れてみた。
完全透明領域はカットして、オフセットと静止画像の組で出すように。
で、当然だけれど見た目は普通。
容量はほんの数秒なので、そんなでもない。
アルファムービー入れなくても、今回はこれで十分かと思いつつ、フレーム間差分ぐらい使った方がいいかな? そうすればもっと容量削れる。さらに単純な差分じゃなくて、ある程度動き補償的なものも加えた方が…… と考えていて気付く。
普通に一般的な動画圧縮方法に近づいていることに。
それならはじめから、アルファ付きムービー入れればいいと。
でも、いろいろ工夫して圧縮率を上げるのはそれで楽しいから、惹かれてしまうのだが。
それと、元が自分の作ったスプライトアニメツールで作られているので、アニメ機能入れれば済む話とも思う。
そうすれば、可逆圧縮で最も圧縮率が優れたものになる ( ムービーの圧縮は動画像からアニメーションの元データに近付けようとしているので、アニメのデータが初めからあるのならそれが最も効率的。ただ、効率を考えずに組まれているとその限りではないが )。
先にアニメーション再生機能も入れるかな。

投稿者 Takenori : 03:06 | コメント (0) | トラックバック

2008年03月08日

PhotoShop のファイルからレイアウトを取り込む

レイアウトはアニメーションでも表現できるので、全てアニメーションデータとして扱ってしまおうと考えていたが、レイアウトはレイアウトだけで扱えた方が便利なこともある。
と言うか、PhotoShop で組まれたデータをさくさく反映させる方法として、アニメーションデータとは別にレイアウトデータを作ることにした ( 後々考え直して、フォーマットを統一してしまう可能性もあるかも ) 。

それで、レイアウトデータフォーマットをどうするかだけど、いろいろ検討した結果 SVG を使うことにした。
SVG はベクター画像の印象が強いが、単なるラスター画像のレイアウトとしても使える。
ただ、原始フィルタ 'feBlend' では、PhotoShop のサポートする描画モード ( ブレンド方法 ) 全てはカバーできない ( SVG は、normal と multiply、screen、darken、lighten しかない ) 。
たぶん、feColorMatrix や feComponentTransfer、feComposite などを組み合わせたりすることで同じ効果が作り出せるのだとは思うけど、とりあえずはそんな面倒なことはしたくない。
だからと言って、SVG とは違う形にしてしまうのも…… XML なので、拡張して違うネームスペースで定義してしまうと言う方法もあるが、それもなぁと考えていて気付いた。
フィルタは ID を与えて定義し、画像などに対してその ID でフィルタを適用することが出来る。つまり、ID は PhotoShop の描画モードの名前で定義しておいて、SVG にない効果は normal とかにしておけばいいんじゃないか?
これだと SVG 対応のビューアで見た時、描画モードが異なっているので見た目が変わるが、位置などはわかる。
そして、この SVG ファイルを読み込む側では、フィルタがどう定義されているかは気にせず、ID によって識別すれば、本来の描画モードがわかる。
また、将来的に feColorMatrix や feComponentTransfer などを使って SVG ビューアで見た時も同じ見た目になるようにしようとしたときも、フィルタの定義を変えれば済む。
読み込む側は ID でしか見ていないので、そのままでかまわない。
レイアウトデータとして使う側は、SVG の仕様を忠実に守っているわけではないが、忠実に守ろうとしたらかなり面倒な上に、重くなるだろうからそこは気にしないことにする ( なので厳密には SVG フォーマットではない ) 。

と言うことで、このような仕様でツールを作った。
ツールで PSD ファイルを変換すると、レイヤーを PNG ファイルにばらして、配置情報と描画モードをもった SVG ファイルを作ってくれる。
そして、この SVG ファイルは SVG 対応のソフトで見て確認できる ( Firefox に D&D するのが手軽 ) 。
ラスタ化されていないテキストや、調整レイヤー、レイヤー効果などは反映されないので、そのような情報があると明らかに見た目が異なるため、変換してすぐに確認できるのは楽 ( 描画モード全対応でないので、そこでも見た目が変わってしまうが ) 。

これで PhotoShop からレイアウト情報を取り出すことが出来た。
次は、この SVG ファイルを取り込んで、レイアウトデータを反映する方法。
SVG ファイル自体は、XML なので読むのは難しいことではない。
後、上述のように SVG に忠実に対応するのではなく、上の変換ツールで吐き出した SVG のみ読めればいいので、読んだ情報を解釈するのも難しくない。
問題はレイアウトを反映する方法。
SVG ファイル内のレイアウト全てを一度に反映すると言う方法が最初に思い浮かぶが、それでは扱い辛い。
なぜなら、同じレイアウト内に、同時に表示されないものを含むことがあるから。
レイアウトは、他に表示されているものと比べながら行うと思うが、一部分だけ挿し変わるような場合、一度に反映する形だと、ほとんど同じものを2つ作らないといけない。
別にそれでもかまわないが、それは冗長すぎる。
それよりもシンプルで柔軟な形が望ましい。
で、気付いたのが、単純に PhotoShop のレイヤー情報を指定した吉里吉里のレイヤーに反映するだけというもの。
PhotoShop のどのレイヤーかは、SVG にレイヤー名と同じ名前で ID を振ってあるので、それで特定できる。
後、全て反映したい時のために、ID リストを取得できればそれで十分。
つまり、指定レイヤーにレイアウト情報と画像を反映するメソッドと ID リストを取得するメソッドの2つがあれば、それで事足りる。
後はスクリプト側でいろいろやればいい。
それと、反映するメソッドは、オフセットや画像読み込みの有無も指定できた方が良さそうだ ( 他にもいろいろあったほうが便利そう ) 。
レイアウトの情報のみが欲しい時は、一度レイヤーに反映してから取得すれば得られる。
少し回りくどいが、レイアウトの情報のみを欲しいというケースはあまりないと思う。
と言うことで、これからそのようなプラグインを作る。
その後は使いながらイマイチな部分を改善していく予定。

投稿者 Takenori : 16:23 | コメント (0) | トラックバック

2008年03月26日

ブラーをもう少し綺麗に

吉里吉里の標準の矩形ブラーは、何か線のようなものが見えるのが少し気になっていたので、もう少し綺麗に見えるものを作ることにした。
ガウシアンブラーだとかなり重いだろうから、1,3,7,3,1 のように2の累乗値ごとに重み付けが変わるようにして作ったが、範囲を大きくしてもなかなかぼやけない。
そこで、もっと単純に 1,2,3,2,1 のように1ずつ大きくなるようにしてみて実装した。
こうするとよりぼけるようになった。
また、矩形ブラーの時のような線は見えない。
今回のを上に標準のを下に描画した時の実行結果は以下のような感じ。

blur_test_20080326.png

まずまず良好。
が、8 ~ 9 倍遅い。
アルゴリズム的には矩形ブラーと同じような方法を取って速くなるようにしているが、他は特に何も高速化を意識せず書いた。
ここから MMX 化や SSE2 化と、高速化を意識してかけば、倍かそれ以上いけるかな。
すると…… 4倍遅いぐらい?
我慢できるぐらいの速度だろうか?

で、速度計るために複数回ブラーをかけたら、矩形ブラーの変な感じが消えることに気付いた。
なんだ、2回かければよかったのか。
それでも2倍遅い程度だろうからこちらの方が速いだろう。
と言うことで、以下に2回かけた時の比較。

blur_test_x2_20080326.png

2回かける方法で十分かな。

投稿者 Takenori : 23:07 | コメント (0) | トラックバック

2008年03月28日

ピラミッドブラーの SSE2 化

ここで作ったブラーを SSE2 化してみることにした。

実装して計って、吉里吉里標準のボックスブラーと比較してみると……
Core2 Duo E6750 で 2.6 ~ 2.7 倍遅い
Athlon64 X2 3800+ で 2.2 ~ 2.3 倍遅い

思ったよりも速い。
Core2 は、SSE2 速いから Core2 の方が差が縮まると予想していたけど、そうはならなかった。
と言うことは、演算速度よりもメモリアクセスの方が問題となっているのだろうか?
MMX 版はまだ作っていないが、この程度の速度差ならボックスブラーを2回かけるのとあまり差はない。

ただ、ピラミッドブラーはボックスブラーと比べて、範囲が広がった時の速度差がボックスブラーよりも大きい。
そのため、範囲を大きくすると差が大きくなる。
上のものは、2, 4, 8, 16 の4パターンを100回かけて計測してた時の速度差。

Core2 Duo E6750 で 範囲を変えて計測すると以下のようになった。
幅 1 の時、2.43
幅 2 の時、2.61
幅 4 の時、2.81
幅 8 の時、2.25
幅 16 の時、2.83
なぜか幅が8の時妙に速い。

品質的には、ボックスブラー2回は2段のピラミッドのようなものと考えられるので、範囲が広がるにつれて差は大きくなると思われる。
でも、あんまり気になるほどではないような。

割る数をループを回さずに計算できないかと調べていて、以下のものを見つけた。
1 = 1 = 1x1
1+2+1 = 4 = 2x2
1+2+3+2+1 = 9 = 3x3
1+2+3+4+3+2+1 = 16 = 4x4
1+2+3+4+5+4+3+2+1 = 25 = 5x5
1+2+3+4+5+6+5+4+3+2+1 = 36 = 6x6
確かピタゴラスの三角形……と思っていたけど、パスカルの三角形だった。しかも、これはパスカルの三角形ではない。
それはともかく、これを使えばループを使わずに計算で求められる。
これで少しだけ速くなった ( 上の計測値はこれが入ったもの ) 。

この程度の速度差なら使ってもいいと思うけど、やっぱりボックスブラー2回で十分。
はじめから2段のピラミッドになるようなものにするのが、それなりに綺麗で速いと言うことになりそうな気がする。
ま、ボックスブラー2回でいいや。

投稿者 Takenori : 00:19 | コメント (0) | トラックバック

2008年03月30日

ピラミッドブラーの MMX 化

息抜きに作ってみた。
で、MMX 化する時に気付いたが、幅が15までの時は、16bit 精度で計算できるから、もっと速くなりそう。
と言うか、MMX の場合掛け算が 15bit 精度 (+符号) になってしまうので気付いた。
MMX2 なら符号なしが使えるので 16bit 。
つまり、MMX 版は幅 10 まで ( 掛け算の時に int に戻してきてやればもっといけるが ) 。
使おうと思っている用途では、そんなにぼやけさせないので、10 もあれば十分。
たぶん、3 か 4 程度で使う。
まあ、それはいいとして、16bit で MMX2 で実装し、2, 4, 8, 15 の4パターンを100回で計測したところ、1.93 倍程度になった。
それと、幅が小さい時だけ計ってみた。
幅 1 の時、1.42 倍
幅 2 の時、1.60 倍
幅 3 の時、1.78 倍
幅 4 の時、1.92 倍

Athlon64 X2 3800+ で 2, 4, 8, 15 の4パターンを100回は 1.55倍程度。

幅が10までなら、ボックスブラー2回かけるよりも速くなった ( 計ってみると幅が11で2倍を超えた ) 。
と言うことは、SSE2 版で 16bit 精度のを作れば、もっと速くなりそうだな。
使えそうになったな。

追記:
と思ったら、アルファ考慮していなかった。
不透明なら今の方法でもいいが、違う場合はアルファをかけないといけない。
だから、もう少し遅くなりそう。

投稿者 Takenori : 03:53 | コメント (0) | トラックバック

2008年04月15日

SQLite3 の XP3 用 VFS

SQLite の VFSxp3_vfs で書いた、SQLite3 の XP3 用 VFS をコミットした。
これを使えばアーカイブの中に SQLite の DB を入れておいて、クエリー発行してそのデータベースファイルからデータが読める。
アーカイブの外にあってもいいんだけど、やっぱり中に入れておきたい。

これを使うにはプラグインを作る必要がある。と言うか、プラグインを作るためのソース。
汎用的な SQLite 3 のプラグインは作っていない。
特化して作ったものは、その用途であれば使えるので、これもついでに入れておこうかと思ったが、よく見たら、テーブル名などが汎用的な名前じゃなかったので止めた。

O/Rマップで使える汎用的な SQLite 3 プラグインは気が向いたら作るかもしれない。
TJS2 のクラスや Dictionary で SQL をあまり意識せずデータを出し入れできれば、データ保存などで便利なはず。

投稿者 Takenori : 01:25 | コメント (0) | トラックバック

framemove タグ

KAG の move タグは、一定時間間隔ごとの位置と不透明度を指定してアニメーションさせるタグだけど、これはどうも使い辛い。
指定した時間に指定した位置と不透明度を設定して、アニメーションさせる方がやりやすいはずなので、そういうのを作ろうかなぁと前々から思っていて、作ったのでコミットした。
次のリリースには framemove タグが入っているのではないかと思う。

具体的な使い方や説明は以下のような感じ。

使用例
[image storage="bg" page=fore layer=0 opacity=0 left=0 top=0 visible=true]
[image storage="uni" page=fore layer=1 opacity=255 left=50 top=50 visible=true key=adapt]
[framemove path="(30,0,0,255) (60,0,0,255) (120,0,0,0) (130,0,0,255)" layer=0 page=fore fps=60]
[framemove path="(10,0,0,255) (100,400,400,255) (130,0,0,255)" layer=1 page=fore fps=60 ox=50 oy=50]
[wm][wm]

layer, path 必須

path
(frame, left, top, opacity) の順で指定する
frameは、1以上の値を指定すること
0フレームは現在のレイヤー位置と不透明度になる

fps はフレームの再生速度
1/fps 秒が 1フレーム辺りの表示時間。
描画周期は、ContinuousHandler の呼び出し頻度に依存する。
省略時 30fps になる

oxは、X軸のオフセット値。
oyは、Y軸のオフセット値。
基点は異なるが、同じ動きをさせたい時など、マクロ化してオフセット値のみを変える事で同じ動きをさせることが出来る。
省略時 0になる。


シンプルな動作であればこれで十分なはず。
やろうと思えば、ある程度複雑な動きも出来る。
拡大縮小・反転・回転などはないので出来ない。

複雑な動きをさせるためには、拡大縮小・反転・回転・ブラーなどの現在の状態と現在の画像、元データ画像を持ったものを作ったほうが良さそう。
で、状態が変更された時に現在の画像を変更するような。

投稿者 Takenori : 01:36 | コメント (0) | トラックバック

炎エフェクトの高速化 全辺一気版

炎を入れてあれこれに書いたように、再検討した全ポリゴンの辺で一気にやる方法を試してみることにした。
まずは SSE を使用せずに実装して、測ってみる。
SSE を使わない版の前のと比べると 8.5% 程度速い。
やはり、アルゴリズム的にはこちらの方が速いのかな。
交差判定がなくなっている代わりに、保持する途中のデータが増えているので、速いかどうかわからなかったが。
でも、問題は SSE 版。
SSE を使って速くないと意味がない。
今、SSE を使うバージョンを実装中。

それにして、SSE 版を作るのとか飽きてきた。
基本的には同じ処理をする関数を別に作ることになるので余計に疲れる。
もうちょっとうまい具合に出来ればいいんだけどなぁ。

投稿者 Takenori : 01:40 | コメント (0) | トラックバック

炎エフェクトの高速化 全辺一気SSE版

全辺一気にやる方法をSSE化してみた。
結果、1.5% 速くなった。
うーん、大幅に変えた割には大して速くならなかった。
と言うか、そもそもテクスチャマッピングの補間部分はあまり処理時間を使っていないのかもしれないと、いくつか処理をコメントアウトして時間を計ってみた。

前にテクスチャマッピング処理自体をコメントアウトして計ったら、75% ぐらいの時間がテクスチャマッピングに使われているようだった。
今回、元データを読み込んで、書き込む部分をコメントアウトしてみたら…… 全体の 58% の時間がここで使われている。
さらに、書き込み値を固定値にして、読み込み部分をコメントアウトしたら、全体の 46% の時間がここで使われている。
つまり、データを読み込む部分に 46% の処理時間がかかっていることになる。
やっぱりテクスチャマッピングの補間部分はあんまり時間使っていないようだ。
処理時間でどこでどれだけ使われているかまとめてみると……

12% ピクセル書き込み ( テクスチャマッピング )
46% ピクセル読み込み ( テクスチャマッピン グ)
17% テクスチャマッピング残り
25% そのほかの処理 ( ブラーとかWarp Map動かしたりとか最終的な色を書き込んだりとか )

そのほかの処理はかなりいろいろやっている割に速い。
補間処理部分はそれなりか。
と言うか、ピクセル読み込み ( メモリ位置を求めたりするのも含む ) 遅い。
ここを攻めるべきだな。
今、最初に画像サイズのメモリを確保した後、縦方向の開始アドレスの配列を別に作って、2次元配列としてアクセスできるようにしている。
これを補間の段階で1次元配列としてアクセスすれば、速くなるのではないかということで試してみた。
ΔU、ΔV をまとめして処理できるようにと考えたが、これはうまくいかないようだ。
よく考えたら当然か。
そこで、x と y でアクセスしていたのを y * stride + x としてアクセスするようにした。
その際、範囲チェックは index < range として、配列の範囲ないなら値を読み出し、範囲外の時は0にすることにした。
で、動かしてみると…… 19% 程度高速化された。
速い。
後、前はしばらく動かしていたら落ちていたけど、この処理にしたら落ちなくなった。
バイリニア補間の時はまた別に関数を作らないといけないけど、この方法で行くかな。
ただ、横端の方で範囲外になると折り返されて表示されてしまうはずなので、端の方にも炎を表示する時は気を付けないと変になると思う。

Core 2 Duo E6750 ( 2.66GHz ) で 512 x 512 の範囲に描いたとして、CPU 負荷はコア1個で 14.3% 程度。
Athlon 64 X2 3800+ ( 2GHz ) で 38%程度。
Athlon XP 1600+ ( 1.4GHz ) で 71%程度。
なお、ここでの値は炎の処理のみで、この後吉里吉里の合成処理分の負荷が追加される。
で、見た目では Athlon XP 1600+ は少し遅いように感じるが、それほどではない。
何とか使えるぐらいかな。

凹ポリゴンもきちんと描画して、落ちなくなって、2の累乗値の制限もなくなって、20%程度高速化されたので、前よりもだいぶ使えるようになったはず。
後はソースコードを整理して、SSE版と非SSE版を分離して、もう少し最適化出来そうならしてみる。
その後、公開しているのを差し替えよう。
そしたら、炎は完成として、しばらくいじらないかな。

投稿者 Takenori : 23:22 | コメント (0) | トラックバック

2008年04月16日

炎エフェクトプラグイン リリース2

前回 のを更新。

1個だけメソッド名が変わってます。
updateWrapMap を updateWarpMap に変更しました。
WrapMap ( ラップマップ ) だと思っていたら、WarpMap ( ワープマップ ) が正解だったので、それに合わせて名前変更しました。
最初に見間違いをしていた様子。

他の変更点は、
20% 程度高速化された。
2の累乗値の時速いとかはなくなり、サイズが小さいほど速い。
ぐらい。
領域の幅と高さが16の倍数にするのは残ってる。

1.5 GHz 程度で 512x512 の範囲ならそれなりに動く。
もっと範囲が小さくなれば余裕。

そう言えば、SSE のない CPU 持ってないから、非 SSE 版が SSE の付いてないCPUで意図したとおりに動くかどうかはテストしていない。
SSE を使わないバージョンが動くことは確認したけど。
まあ、そもそも Pentium III や Athlon XP以降でないとまともに動かないと思うが。

投稿者 Takenori : 18:05 | コメント (0) | トラックバック

2008年05月12日

ゲームパッド プラグインがとりあえず動くように

DirectInput と XInput に対応し、複数パッドを扱えて、フォースフィードバックのバイブレーション対応、アナログスティック対応したゲームパッド プラグインがとりあえず動くようになった。
XInput デバイス ( XBox360 コントローラ) と DirectInput デバイスをつなげて、両方のパッドからアナログスティックの情報を得られることを確認。
アナログスティックは、-1.0 ~ 1.0 の値を返すようにしている。
細かい部分のデバッグとボタン設定用のインターフェイスはまだ。
DirectInput デバイスはパッドによってボタン配置が違うので、設定用のメソッドは必須だろう。
アクション マッパーは使わない予定。

XInput デバイスはボタン固定なので、ボタン設定用のメソッドは持たない。
このプラグインでは、あくまでパッド間の差異を吸収するための設定と考えている。
でも、そしたらゲーム個別の設定が出来るようにしようとしたら、2回も設定が必要か……
その辺りは少し考えないとダメかな。

投稿者 Takenori : 23:33 | コメント (0) | トラックバック

2008年05月13日

ボタンエッジ検出と長押し判定

今作っているゲームパッドプラグインでは、単純に現在のボタンやキーの状態を取得するのみになっている( 厳密には、パッドの update メソッドをコールした時の状態 )。
この機能のみあれば、スクリプトでエッジ検出や長押し判定することが出来る。
ただ、エッジ検出や長押し判定は、よく使うと思うので、プラグイン内に入れてしまってもいいかもしれないと考え中。

update がコールされた時、対象のボタンが押されていれば 1加算、離された時 ( 押されておらず値が 0 より大きい時 ) -1 に、押されていない時 ( 値が 0 以下の時 ) は 0 にする。
このようにすれば、0 より大きい時は押されていて、0 以下の時は離されているのがわかる。
また、マイナスの時は離された瞬間、1 の時は押された瞬間とわかる。
長押しは、どれぐらいの長さにするかによるが、値がいくつ以上かと判定することでわかる。
仕組み的にはこんな感じで。
もっといい方法があれば…… と考えていた気付いたけど、長い間押されていないのも判定できた方が良いだろうか?
単純に 負の値の時に 1 減算していくことで、どれくらい長い間押されていないかはわかる。
しばらく操作されていないとキャラクターがあくびするなどの時に使う…… ことはないか。
操作されていないのであれば、全ボタンで判定した方が効率的。
まあ、長押されてない判定もあってもいいかな。
あって不都合はないし。

ボタンエッジ検出や長押し判定は、状態取得のクラスに入れるのではなく、そのクラスをラップして実現するのがいいかな。
ゲームパッドの状態取得クラスは、IInputDevice インターフェイスを継承して作っている。
これの派生クラスは、XInput 用、DirectInput 用、DirectInput フォースフィードバック対応用の3つある。
継承関係の間に入れるという手もあるけれど、必ずしもボタンエッジ検出や長押し判定が必要とは限らないので、包含して拡張するのが良いかな。

ま、ボタンエッジ検出と長押し判定は入れよう。
いずれかのボタンが押されている離されているも。

投稿者 Takenori : 15:58 | コメント (0) | トラックバック

2008年05月28日

プレイ画面の動画書き出し

前々から動画の書き出し機能があると便利かなぁと思いつつも、需要があるのかどうかわからなかったので、特に手をつけていなかったんだけど、ある程度は使う人がいそうなのと、自分も使いそうなので作ることにした。

AVI で書き出すものはごうさんが既に作っていたけれど、AVI 2.0未対応で無圧縮で保存されるので、あっという間に2GB超え&HDDへの書き出しが間に合ってなさげで、扱いづらかった。
そこで、AVI 2.0 対応のために DirectShow を使用して書き出すことを考えていたが面倒くさい。
あれこれ考えていて、ふと、WMV で書き出すものなら、さくっと出来る気がした。
という事で、コーディング開始。
さくっと…… と思ったけど、4時間ぐらいコーディングにかかった。
動画系の API は手続きが多いので、いろいろと面倒だった。
改めて考えると、DirectShow を使っても大差なかった気がする。

で、動かすと IWMWriter::BeginWriting でエラーが返ってくる。
内容は、Video codec エラー ( An unexpected error occurred with the video codec ) 。
さっぱり理由がわからない。
サンプルのソースといろいろ見比べたり、サンプルをデバッガで動かして入っている値を見たりして修正するも改善せず。
もしかして、スレッドを分離しているのが原因? と思って、IWMWriter::BeginWriting を初期化側のスレッドとあわせるとエラーにならなくなった。
別スレッドにしたらだめだったのか。
でも、実際の書き出し部分はスレッドが分かれていても問題ない様子。
バッググラウンドでひたすらエンコードしてもらう必要があるので、書き出し部分は別スレッドに出来ないと少し面倒なので、そこは良かった。

という事で、吉里吉里でゲーム画面の動画を書き出すプラグインを作った (結局1日かかった)。
が、ここではまだ音に対応していない。
吉里吉里の本体から直接音を得る方法はない様子。
本体をいじるか、ループバックで録音するか。
結局、ループバックで録音する方法で行くことにした。
エラー音やクリック音などが鳴ると、一緒に録音されてしまうという難点があるが、まあそこは諦めるという事で。
音のキャプチャー自体は、DirectShow を用いて行った。
ここは特に問題なかったのだが、WMV 書き出しでまたうまく行かない。
エンコードしていたら途中で落ちてしまう。
エラーは The writer has received samples whose presentation times differ by an amount greater than the maximum synchronization tolerance. You can set the synchronization tolerance by calling IWMWriterAdvanced::SetSyncTolerance. とか。
IWMWriterAdvanced::SetSyncTolerance で時間を長くすると、どんどんメモリに溜めていってなかなかエンコードしない様子。
で、終わったものをみてもうまく撮れていない。
なんなんだろう? といろいろと調べていたら、書き出し時に指定するストリーム番号が設定されていなかった……
絵だけや音だけの場合、そこがおかしくても問題ないけど、絵と音のように2つ以上ストリームがある場合はまずいようだ。
というか、設定する部分を見逃していた。
これで音も撮れるようになった。
結局2日かかった。

ただ、これには問題がある。
WMV をリアルタイムでエンコードする関係上、すごくCPUパワーが必要。
640x480 30FPS では、Core 2 Duo E6750 (2.66GHz)、メモリ 4G、HDD RAID 0 でも間に合わない。
動きの少ないシーンでは大丈夫のようだが、動画を再生しながらとなるとだめ。
エンコードが間に合わない場合、メモリ上にひたすら画像を溜めていくのんだけど、どうも吉里吉里側の描画の方が処理落ちして、それであんまりメモリには溜まらずに進んでいく様子。
プライオリティーをいじれば解決するかもしれないけど、4Gあっても1分程度しか撮れないのであまり効果はない。
これはクアッドコア必須か。
ただ、Video Codec を Windows Media Video V7 や V8 にすると負荷が下がり撮れる。
というか、マルチスレッド化されておらず片方のコアをめいっぱい使っているだけのように見えるが、撮れることは撮れている様子。
まあ、これでいいかなぁと思ったが、もっと速い CPU が欲しいと自分も周りもつぶやいていて、少し考える。

連番 JPEG 書き出しバージョンを作ろう!
JPEG エンコードは、SIMD 拡張版 IJG JPEG library を使う。
で、組む。
1時間ぐらいで出来た。
WAV でもいいから音入らない? と言われる。
で、組む。
さらに1時間ぐらいで出来た。
いい感じ。
Core 2 Duo E6750 (2.66GHz) で CPU 負荷が 20 ~ 30% 程度。
比較的軽い。
WMV よりも容量が大きくなってしまうのは仕方ないが、バラバラのファイルになるので HDD 容量のみの問題だから、容量が大きくなるのはそれほど問題ではない。

実際のゲーム画面がキャプチャーされたものを見る。
普通に見れる。
というか、ゲームじゃなくてこっちでもいいとか思ったり。
それはどうか…… と言う話なんだけど、字幕付きのアニメを見ている感覚で楽。
これはちょっと考えさせられた。

このプラグインは少し使い方が厄介なので、マニュアルを少し書かないといけない。
その辺り書けたら公開するかも。

投稿者 Takenori : 13:31 | コメント (4) | トラックバック

吉里吉里2 + キャプチャープラグイン = 動画作成環境

吉里吉里2 と キャプチャープラグイン の組み合わせが、動画作成環境としてかなり高機能な気がする。
という事で、ゲーム画面動画キャプチャープラグインをリリース。

このページに置いておきます。

投稿者 Takenori : 16:10 | コメント (0) | トラックバック

 
Total : Today : Yesterday :