第98回 日コン連ってなんや?(ゴキブリの温床?) '00/01/21

とある掲示板で、勝手に私の名前が宣伝に使われていて、カチンと来たんですよ。犯人は、日コン連の理事長、山本隆雄氏。この人、えむびーまんとか言うハンドルで自分の店の宣伝を兼ねて、ゲーム関連の掲示板に棲息しています。先日は「私の弟子のやねうらお24歳が…」とか書いてました。誰がお前の弟子やねーん(笑) 逆ちゃうんかい!いや、あんなおっさんが弟子でも嫌だけど。(笑) ついでに歳を片手分ほど間違えとるぞよ。まあ若く言われる分には構わないんだけど(笑)

そう言えばいままでも何度か不特定多数の人から「山本隆雄が×××って書いてるけど、あれは本当ですか?」ってメールをいただいたことがあります。そういうのも、いちいち答えてると体がいくつあっても足りないんで、無視してたら山本氏と敵対するグループに「やねうらお氏は沈黙を保っている」だの何だの書かれて、それを見て触発された人がその掲示板に「やねうらおって言う人、いくら技術力が高くても倫理感がないとは、まるでオウムだね」とか感想を書いていたんですよ(笑) おいおい、あんた!知らんとはいえ、人んこと、むちゃくちゃ言いよんなー。(笑)

日コン連とは、高校んときに頼まれて英単語ソフト等の教育ソフトを移植したことがあるぐらいですよ。移植と言っても、資料何もなしで見よう見真似で作り、1週間以上かかっているものもあります。報酬は恐ろしく低かったです。支払い段階でゴネられたこと数知れず。時給換算にすると400円とかになるものもあります。私の30年近い人生のなかで、もっとも安い賃金(ほとんどボランティア)で働かされたと言っても過言ではありません。

そうだ。思い出した!シャープのカーナビの仕事のときなんか、漢字クイズの作成を山本氏から依頼されて、私は友人に5万円渡して漢字の問題を入力してもらい、プログラムは私が組んだんですが、結局それはカーナビには採用されず無報酬。はっきり言って、この一件は、いまだに根に持ってますよ。(笑) 山本隆雄氏、これを見てたら、あんときのお金、払ってください!5万でいいから(笑)

そんな、ゴキブリの温床のような日コン連に出入りし、ボランティア活動(?)に励んでいたのも、当時は、そこに優秀なプログラマが出入りしていたからです。具体的には、大学のコンピュータサークルと癒着があって、東大、京大や阪大のコンピュータサークルの人などとも交流がありました。なかには、日本コンピュータチェス協会の馬場氏なんかもおられました。馬場氏も、私と同じような目に逢われたらしく、結局、山本氏とは訣別。馬場氏はほんと気の毒でした。見てるのも辛かったです。そうやって何人もの人が日コン連から離れていきました。

そんな事情も知らず、最近、お人良しの誰かさんが「やねさん、日コン連には関わらないほうがいいよ。あまりいい噂、聞かないし」と忠告してくれました。言っちゃーなんですが、いい噂聞かないどころか、その惨状を私は10年近くに渡って、目の当たりにして来たんですよ!山本氏から直接的な被害を被り、強烈な恨みを抱いている人の名前を実名で2,30人は挙げることが出来ます。なかには、「ゲロブー(山本隆雄)は冷蔵庫に閉じ込めてバルサンたいたろか」と本気で言っていた人がいるぐらいです(笑) 仮に、ゴルゴ13のような人が居て、スイス銀行に3千万振り込めば嫌な奴を誰でも暗殺してくれるというのならば、この人たちは、きっと10万ずつぐらいなら出すでしょう。私も殺人が合法ならば50万ぐらい出します(笑) 

そんなこんなで日コン連のことを書きたくない事情をわかっていただけましたでしょうか。(誰に弁解しとんねん…>俺)


第99回 そしてクロスフェードがはじまる(DirectDrawは、もうええねん) '00/01/27

第8B回で紹介させていただいた、わたやんさんですが、やはり、高校の生徒ではなく、高校の先生だったようで、恐縮です。そんな、わたやんさんが廊下で「先生、やねさんのホームページに出てたね!」とか何とか言って生徒に声をかけられたそうです。うー。このページって、そんなに見てる人があんのか!!そりゃ、1年ちょっとで100万ヒット達成したけど、それって、「やねうらおって馬鹿だからアクセスカウンター増やしといてやれば、調子こいてプログラムに励むだろう」と思った誰かが、毎日ブラウザの更新ボタンを連打してるのかとばっかり思ってたぞ。 ← そんな奴いないって…(笑)

ところで、いま凝ってるのは、クロスフェードですよ。片方の絵がフェードアウトしながら、もう片方の絵がフェードインしてくる、あれですよ。yaneuraoGameScript2000(以下やねげ)では、これが、すっごく遅いんです。遅い原因は、プログラムが手抜きだからってのもあるのですが(笑)、DirectDrawは、サーフェースをlockしてカリカリと書くとやったら遅くなるビデオカードがあるんです。最近のAGP搭載のチップならば、それほどでもないんでしょうけれど、昔のビデオカードは、たいてい遅くなります。システムメモリにサーフェースを持っていれば、遅くはならないのですが、それだとビデオカードのハード転送は使えないわけで、DirectDrawを使う恩恵にはあやかれません。

そんなわけで、フェード系を多用するプログラムでは、DirectDrawをほとんど使っていない(はず)です。αブレンドはDirect3Dの機能を使ってハード的に出来なくはないですが、ハード側で対応していないとHEL(Hardware Emulation Layer)ではこの機能をエミュレートできないので(遅くなりすぎるから。MMX/HighColor限定等の制限つきで可能)、困ったことになるのです。

それならば、画面(サーフェース)はあらかじめすべてシステムメモリに持っておいて、そこでMMX系の命令を使ってブレンドしたほうが速そうです。もちろん、メモリはQWORD境界にあわせておくべきです。具体的には、

// QWORD境界に整合する
m_pPtr = new BYTE[4 * WIDTH * HEIGHT + 7];
m_pBits = (void*)( ((int)m_pPtr + 7) & ~7 );

ですね。そのあと、MMX系の命令を使ってカリカリと書けば、やねげの10倍ぐらい速いクロスフェードが出来ます。ただ、各スクリーンモードごとにフェードルーチンを用意してやらなければならないのが、考えるだけでもうっとーしいのです。システムメモリ内に持たせた仮想スクリーンは、Full ColorやTrue Colorで管理して、それを実際にプライマリサーフェースに転送するときに32bit→16bitのような変換をしてやるほうがシンプルな設計と言えます。ただ、このとき

1.メモリを食いすぎる
2.変換に時間がかかる(MMX系ならば少しまし)

という問題はあります。しかし、逆にメリットとして、32bit→8bitもサポートすれば、256色モードでもαブレンドが可能になります。ただ、32bit→8bit変換は、遅そうなので、仮想スクリーンはHigh Colorで持って、16bit→8bitは、変換テーブル(BYTE byTable16to8[65536])を使います。もちろんその初期化は、パレット設定時に行ないます。

仮想画面をTrueColorで持つ場合32bit→8bit変換を行なう必要がありますが、そのときは、RGBそれぞれの下位ビットを間引いて16bit化したのちに前述の変換テーブルを用いて8bitに落とすと良いでしょう。

ちなみに私が新しく買ったビデオカード(RIVA128ZX)では、24bit color(FullColor)というのは存在しません。3バイト単位のアクセスはめちゃ遅いので、すたれていく方向にあるのかも知れません。

逆に、特定用途向けのアプリ(ゲーム等)ならば、HighColor限定にしてしまうのもひとつの手です。MMX限定+HighColor限定ならば、擬似パレットも出来ますし、αブレンド、回転・拡大・縮小・三角テクスチャー貼付、ぼかし等やりたい放題できます。(やねげは、いまさら仕様変更する気はありませんが、そういうアプリも機会があれば組んでみたいと思っています)


第9A回 ラスタースキャンについて考える(MMXプログラミング) '00/01/28

IDE(ATAPI)接続のCD-RW買いました。6倍速で2万ちょいでした。SCSI接続は、WindowsNT4.0/2000RC2ではASPIのドライバがうまく動作せずに困っていたのですが、IDE接続のほうは、なんなく動作。動作が不安定では?とまわりから言われたのですが、6倍速で、100枚焼き続けて焼きミスはひとつも無し。(ただしon the flyではないが…)

MOもIDE接続のものが出回ってきてますし、今後、SCSI接続っちゅーのは、相当数の機器を接続する場合か、よほど高速転送を必要とするデバイスのみの使用に限られてくるんではないでしょうか。

そういや、先週、あるゲームプログラマの書いたソースのデバッグを頼まれたんですよ。それが、MMX系の命令を使って、ラスタースキャンするルーチンなのですが、そのプログラムときたら、Direct3Dを使わずに、完全に自前で3D計算をやってるんです。なかなかのつわものです。まあ、機能を限定すれば、3Dなんてたいした処理ではないのですが…。

そのなかで、テクスチャを、三角形に貼り付けるルーチンがありました。いわゆるラスタースキャンですね。三角形のテクスチャを、三角形に貼り付けるわけですが、テクスチャ貼り付け先の座標系は(x,y)として、貼り付け元の座標系はその三角形の左上の頂点から伸びる2辺の方向に軸u,vをとって(u,v)座標系を考えてます。その後、(u,v)→(x,y)の逆変換を考えて、(x,y)のピクセルに貼り付けるテクスチャは、(u,v)のどの座標にあたるかを求めていきます。とは言っても転送先の三角形の1ラスター(三角形を構成する水平ライン1本)を描画するのは、最初に、その貼り付け元の座標(u,v)を確定させたあとは、(u,v)座標系において、そのスキャン方向のベクトルを加算していくだけという単純なものです。

そのプログラムは、スキャン部分、特にこのベクトル加算にMMX系の命令を使うことによって、1命令で済ませています。具体的には、加算するベクトルの2要素をそれぞれDWORDだと考えます。HIGH-WORDは整数部、LOW-WORDは小数部と考えます。その2要素をMMXのレジスタにQWORD(DWORDの2倍=64bits)として持っておけば(パックドワード)、一命令でベクトル加算が出来るという仕組みです。そのあと、それに対応するテクスチャーのメモリ位置をpack命令(インターリーブパック)を利用して確定するというものです。

実際、このアルゴリズムならば、うまくやれば相当速いような気はします。…そうは言っても、なんでDirect3D使わへんかなー。こんなプログラム書くぐらいなら、Direct3D使うほうがよっぽど楽でしょー。もしかして、Direct3Dより速い3Dルーチンを目指しているとか?まさかなぁ…いや、ありそうで怖い。(笑)


第9B回 propertyをtemplateで実装する(三浦さんからのメール) 00/01/29

プログラミングを習得するためにどれくらいの期間が必要なのかは定かではありませんが、理系の大学教養レベルの知識があるのならば、1ヶ月もあればC++を習得できるでしょうし、3ヶ月ほどやればWindowsのプログラムが組めるはずです。そのあとアセンブラをやったとしても、せいぜい1ヶ月。半年もあれば、(一応は)プロとして通用するかも知れません。

そんな風に考えると、自分が20年近くやってきたプログラミングっちゅーのは、一体、なんやったんや?と思わざるを得ません。いま思い返してみて、ずいぶんと回り道したなーとか思わずにはいられません。しかし、プログラムで人生を狂わされた(?)ひとりの人間として、プログラミング理論を極めてみたいというのも人情です。

最近は、趣味でレイノルズの2階のラムダ計算による高階の型理論や、スコット領域を使った表示的意味論などを勉強してますが、お金の心配さえなくなれば、DirectXとか、しょーもない技術を追いかけるのはやめて、そういった研究に残りの人生を捧げたいと思っています。

ええっと。今回は、少し古いトピックになりますが、第84回のpropertyについて、C++のtemplateで実装された方がおられるので、その方からのメールを紹介したいと思います。

プロパティの是非については随分昔から議論がなされているようなので、
多分、標準C++でもそれなりのワザがあるのかもしれませんが、私も
自分なりに拡張キーワード無しの解決策を考えてみました。

なにかコメントを頂ければ幸いです。
あ、それから私はVC++もBCもしらないので、ひょっとしたら全然
違うシロモノなのかも知れません。

ちなみに、自己採点ではこのやり方は50点ぐらいです(笑)
減点内容は

・エレガントでない(笑)
(でも、C++自体エレガントでは無いと思いますが。)

・テンプレートを使っているのでコード量が増える。
(わりと減点が大きいかも)

・関数呼び出しも入っているので、コストが多少かかる。

・本物のプロパティでは、クラスの宣言部に、プロパティをつかう旨を書くだけで
よいが、このやり方では、クラスのコンストラクタにもプロパティを使う旨を
書かなければならない。
(これが致命的で大減点ですね。暗黙のコンストラクタが使えなくなります。
コンストラクタが複数あると面倒ですし。)

・getでは、constな参照ではなく値が返る。
(アクセスしたいデータが大きなオブジェクトだった時は痛いです。
キャスト演算子と暗黙の型変換を使っている以上どうしようもない。)

・アクセスしたい内部メンバがメンバ関数を持っていても、直接メンバ
関数を呼ぶことができない。
(もともとの意図からすれば、あまり問題はないかもしれませんが、
たとえば、
classA a, b, c;
c.value = a.value + b.value
(valueがclassBを指すPropery)
などとしたくても、operator+(const classB&)を呼ぶことができません。
operator+(const classB&, const classB&) なら問題ないですが。)

あと、constなオブジェクトに関するgetを許可すべきか禁止すべきかは
難しい問題です。
この実装ではgetによってオブジェクトの内部状態が変わる可能性を考えて、
constなオブジェクトに対するgetは使えないようにしてあります。
getによってオブジェクトの内部状態が変わらない。あるいは変わっても、
外から見たら不変と考えられるような状況ならば、
許可すべきですね。

ソースは、

#include <iostream>

template<class T1, class T2>
class Property {
public:
typedef T2 (T1::*const GetPtr)();
typedef void (T1::*const SetPtr)(const T2&);
Property(T1* p, GetPtr g, SetPtr s);
Property<T1, T2>& operator=(const T2& rhs);
Property& operator=(Property&);
operator T2();

private:
Property(const Property&);
T1 *const container;
GetPtr get;
SetPtr set;

friend T1;
};

template<class T1, class T2>
Property<T1, T2>::Property(T1* p, GetPtr g, SetPtr s)
: container(p), get(g), set(s)
{}

template<class T1, class T2>
Property<T1, T2>& Property<T1, T2>::operator=(const T2& rhs)
{
(container->*set)(rhs);
return *this;
}

template<class T1, class T2>
Property<T1, T2>::operator T2()
{
return (container->*get)();
}

template<class T1, class T2>
Property<T1, T2>& Property<T1, T2>::operator=(Property& rhs)
{
(container->*set)(rhs);
return *this;
}

// サンプル

class A {
public:
    A::A();
    bool getFlag();
   void setFlag(const bool&);

   Property<A, bool> flag;

private:
   bool _flag;
};

A::A()
: flag(this, &A::getFlag, &A::setFlag)
{
}

bool A::getFlag()
{
    cout << "get value" << endl;
    return _flag;
}

void A::setFlag(const bool &x)
{
    cout << "set value" << endl;
    _flag = x;
}

int main()
{
    A a, b;
    a.flag = true;
    b.flag = false;
    bool x = a.flag;
    cout << "x = " << x << endl;
    cout << "a.flag = " << a.flag << endl;
    cout << "b.flag = " << b.flag << endl;
    x = a.flag = b.flag;
    cout << "x = " << x << endl;
    cout << "a.flag = " << a.flag << endl;
    cout << "b.flag = " << b.flag << endl;
}

使う価値があるかについては疑問もありますが、実に面白いと思います。スマートポインタも、こんな感じでテンプレートで実装できそうですね。

これに似た例として、あるクラス内で、なんたら型のポインタを持っている場合、そのクラスのコンストラクタで、そのポインタをNULLに初期化し、そのクラスのイニシャライザで、なんたら型をnewし、終了時に、そのなんたら型のポインタがNULLでなければ、それをdeleteするような定型的な処理を行なうことがよくありますが、こういうのもテンプレートを使って、なんとかならないもんでしょうか…?


第9C回 Windows2000RC2(いまのところ...) 00/01/31

ここのところ、知り合いに頼まれてパソコンを組み立てつづけています。会社をやめてから既に4台組み立てました。昨日も、やね宅で、その受け渡しのための説明をしていたんですが、

「ハードディスクは、いくら載ってんの?」と聞くK氏。えっ。ハードディスクですか...28GBです。(パソコン工房にて23,800円だった)「28ギガ!」えっ。なんで、そんな驚いてんのよ。「だって、ネオジオが100メガショックゆうてたやろ。28ギガゆうたら、その280倍やんけ。こちとら、280倍驚いたわ!」なんやねんそれ…。

「だって、だって、280倍なんだってば!!」280倍は、もう、ええっちゅーねん!わかっとるっちゅーねん!

「28ギガショーーック!!」いちいち驚かすなっちゅーに!!

そんなK氏、もっと容量が少ないのでいいから、安くなんないかと言ってきた。もっと安くなんないかって言われても、この28GBのんどうすんのよ。しゃーないなぁ、それなら、僕ちんが使ってた8GBと交換してやるよ。

「8ギガショーーック!!」いまどき8ギガぐらいで驚かんでええっちゅーに!!

K氏の言われるがままに、彼のマシンをダウングレードし続けると、なぜか、私のハードディスクは8GB→28GB,ビデオメモリが2MB→8MB,システムメモリは128MB→256MBにグレードアップ。ありがとう、K氏!ありがとう、星の王子様!(誰が星の王子様や...)

ところで、あれからまだWindows2000Professional RC2を使いつづけているやねうらおですが、そろそろ善し悪しがわかってきたので、ここにレポートを書きます。

まず、Windows2000は、Windows95/98とWindowsNTを融合したようなOSですが、どちらかと言えばWindowsNTにDirectX7を載せて、Plug&Play、USB対応にしたものだと言ったほうが良さそうです。ソフト開発と言えばWindowsNTでやるのが常識となっていますが(デバッグがやりやすいため)、WindowsNT4.0ではDirectX3までしか動作しないがために、仕方なくリモートデバッグすることもありました。Windows2000ならば、そんなことをしなくて良くなるので、これは大きなメリットです。

逆にパラレルポートの入出力にI/Oを直に叩いているようなソフトは保護違反で落ちてしまいます。そういうドライバも使えません。mcc(メモリーカードキャプチャーさくら),DirectPad Proは使えなかったです。今後、Windows2000対応ソフトが増えてくるでしょうけれど、いまのところWindowsNTで動かないドライバ類はWindows2000で動かない可能性が高いです。パラレルポートの状態を読み出すサンプルプログラムが確か、Microsoft Win32 Device Development Kit (DDK) for Windows NTに付いていたと思いますが、パラレルポートは自作の工作等でよく使うんで、まどろっこしいことなしに直接outpで書き込みたいです…。

あと、Windows2000RC2(以下RC2)では、VisualStudio6.0(SP3)(以下VS6)が、いろいろバグります。混合モード表示できなくなったり、デバッグしていて、デバッグの中止を選ぶとそのアプリが解放されずに残ったりすることがあります。涙出てきます。おかげでWindows98マシンにVS6を入れて、ネットワークごしにWindows2000のパソコンに入っているプログラムをコンパイル&デバッグしてます。何やってんだかわかんない状態です。

RC2では、CD-R焼きソフトによってはASPIのドライバがうまく動かなかったです。NT4.0対応と書いてあるものでもインストールすると起動時に止まってしまうものもありました。仕方ないのでSCSI接続のCD-RWを使うのはやめました。IDE接続のものならば、B's Recorder Gold Win1.55が動作し、安定して焼けています。

Windows2000β3では私のモデムは、うまくダイアルしてくれないので使えませんでした。RC2ではどうだかわかりません。さすがに製品版ではなおっているでしょうけれど…。

あと、シャットダウン時にうまく電源が切れないことが多いです。何かのソフトをインストールすると、以降、電源が切れなくなります。どのソフトかは特定出来ませんでした。

DirectDrawの動作に関しては、描画に際してマウスカーソルがちらつくようになりました(笑) WindowsNT4.0ではちらつかなかったので、この部分、Windows95/98に近くなったと言えると思います。

余談ですが、WindowsNTのサービスパックでDirectX5に対応するという計画はないんでしょうか。DirectX3までのDirect3Dは、いちいち描画に際してExecuteBufferに放り込まないといけないので、非常に手間がかかるんです。そのへんの方向性と、世間様の移行具合を見極めないと、おちおちDirectX5の機能を使うわけには参りません。(とは言っても、最近のソフトはすべてDirectX5以降を使用してるんだよなぁ…)

まあ、そうは言うものの、(いまから買うなら)最低でもCerelon433ぐらいが常識になりつつあるので、DirectX6+VRAM8MB以上+システムメモリ64MB以上+HighColor+MMX専用というようなソフトを作っても顰蹙は買わないのかも知れません。というかそういうソフト猛烈に作りたいんですけど(笑)

なんちゅーか、リアルタイムの縮小右回転フェードイン残像残し+拡大左回転フェードアウト残像残しをやりながらのサテュレーションブレンドとかが猛烈にやりたいのです。この最後のサテュレーションブレンドってのは、やねうらおの造語です。

普通、αブレンドは、転送先とブレンドするときの転送方式で、転送元の画像のRGB値をそれぞれα倍(0≦α≦1)し、転送先の画像を1−α倍して合成します。あるいは、単純に加算します。(加算ブレンド) α倍(0≦α,1以上もありえる)した後に加算し、上限値を越えていたら飽和させる(上限値にしてしまう)タイプをサテュレーションブレンドと呼びます。ブレンドしてきた画面が徐々に明るくなってきて真っ白になる画面効果などは、この部類だと考えます。MMX系の命令には便利なことにpacked byteに対するサテュレーション加算があるため、本来ならば加算しオーバーフローのチェックを行なって、オーバーフローしていれば上限値に戻すような作業をしなければならないところを、一命令で、しかもpacked byteならば8バイト同時にその加算できてしまうというのだから、非常に有利と言えます。MMX専用最適化は今後必須でしょう。ちゅーか、MMX用ルーチンと非MMX用ルーチンとを作るのが非常に面倒なんですけど(笑) かと言って、しょーもないビジュアルノベルがMMX専用っちゅーのは顰蹙買いそう…はやく非MMXマシンって無くなってくんないかなぁ…。


第9D回 DirectDrawを捨てる(とどうよ?) 00/02/03

ここ数日、寝ても覚めても残像つき回転Bltのことが頭から離れません。ほとんど病気です。知り合いが持ってきたmpegファイル(自主制作)を見ては溜息ついてます。

表現効果がかっこ良くって、かつ音楽とマッチしてたら、シナリオが駄目駄目でもなんかそれなりに見えてくるんだから不思議なもんです。このデモ、字幕が消えるときとか、下方向へ拡大しながらフェードアウトしてかつモザイクがかかるんです。それだけでも、すっごくカッコイイ!きっと、いい音楽に合わせてそんな演出があったら、それだけで物語に引き込まれそう!

あるいは、なめらかにセピア色に変わるシーンとかあるんですが(おそらくAdobeのPremiereで作ってるんでしょう)なかなか考えさせられるものがあります。

しかし、描画部分をまるっきりリライトしたとしても、そのような表現効果がリアルタイムに出せるのかという疑問もあります。また、サーフェースの矩形転送部分でDirectDrawのBltを使わないような設計にしてしまうと、今度は通常のゲームの速度ダウンにつながってしまいます。すべてシステムメモリ内で描画を済ませ、最終的にそれを転送するタイプのアプリは、やはり遅いです。そのオーバーヘッドは無視できないでしょう。ただ、画面解像度切替等に対するリストア(復元)処理を省けることと、サーフェースがロストしないことを前提としてプログラミングできるのは、ずいぶんと有利で(転送領域も更新差分だけで済む。トランジション等を行なうときに非常に有利)、ゲームの性質によっては、そういうコーディングスタイルのほうが有利です。(となれば、DirectDrawはフルスクリーン時にサーフェースのロックを行なって転送するだけの機能しか使わないということになるんでしょう)

折衷案としては、拡大・縮小・ミラー・ブレンドを猛烈にやりたい時だけ、システムメモリ上に設けた仮想画面上で処理を行ない、それをプライマリサーフェースへ転送するという手もあります。たとえば、仮想画面はTrueColorで持つとします。そこのスクリーンに対してきちきちにMMXアセンブラで最適化したルーチンで処理し、描画の際には、そこからプライマリサーフェースに転送します。(ただし、二つの経路[DirectDrawを使うほうと、使わないほう]があるのはいかにもやらしいです)

その仮想画面を使う限りは回転拡大縮小はもとより、ラスタースキャンであろうが、サテュレーションであろうが色相変換であろうが油絵変換であろうがぼかしであろうが残像残しであろうが燃え上がる炎であろうが三角テクスチャであろうが円筒テクスチャであろうがやりたい放題できます。出来るはずです。いや、やります!:p

仮想画面で処理をすると本当にそんなに遅いのでしょうか?拡大縮小に関しては、ビデオカードがハードウェアアクセラレーションに対応していないと、ビデオメモリをぐりぐりいじくり倒すことになるので、やったら遅いビデオカードが多いですが、そういったこともなくなります。描画のときに一定のオーバーヘッドが加わるだけで、それ以外の点においては、相当有利な気もします。

最大の難点は、仮想画面に対してだとDirect3Dが利用できなくなってしまうので、自前で3Dルーチンを作成しないといけない点です。まあ、複雑な画面効果を行なわないならば、3Dエンジンを自作することぐらいわけないので、自前で3Dルーチンを作ってしまったほうがよっぽど融通が利くという気はしますが、ハードウェアを使わないので、ポリゴン数の増大に対してはずいぶんと不利です。他のゲーム会社のプログラマが書いた、Direct3Dを使わずに3D(擬似3Dも含めて)を行なっているプログラムを何度か見たことがありますが、面倒な割には報われないという気がしています。

あと、画面モードごとに描画ルーチンを分けずに済むかわりに、MMXと非MMXと二つのルーチンは少なくとも用意する必要はあると思います。しかし、MMXって本当にそんなに速いんでしょうか?

以前にMMXアセンブラを少し使った感触では、MMX命令はそれほど速くなかったようなことを覚えています。浮動小数レジスタと兼用であるあたり設計上の無理があるのかも知れません。あと、うまくパイプライン処理できるコーディングスタイルにするだとか、そういうところに難しさがあるような気がします。

ペンティアムのパイプライン処理は、Uパイプ,Vパイプの2本で行なわれ、Uパイプでは全命令が実行できますが、Vパイプは一部の単純な命令しか実行できなかったはずです。つまり連続した2命令に対し、どちらか一方がVパイプで実行できるならば、それらはペアリングされ、同時実行されます。これと同じような制限がMMXでも存在し、Vパイプは算術論理演算しかできなかったはずです。よって、MMXに大きなアドバンテージがあるのは、パック演算系、特に、パックド乗算とか、パックド飽和加算、パックド飽和減算ぐらいで、単純なビット転送は、レジスタが64ビット幅であるにも関わらず、ストリング命令を使ったほうが速いです。複数回のループならば、命令にキャッシュが掛かるので、さらに差がつくはずです。

そんなわけで、ygs2Kのほうはネイティブコンパイラ化が終わったんで、BMS読み込みDLL作って、mpeg再生とmp3読み込み、susie plug-inぐらいに対応させて、これはこれでひとまず開発終了(今月末予定)とし、次はビジュアルノベル向きにyaneGameSDKを作りなおし、余力があれば同時にC++やJavaをも凌ぐオブジェクト指向スクリプト(当然、ネイティブコンパイラ)の開発に向かいたいと思っています。あいにく、今度は仕事で作るつもりなので、すべてを一般公開出来るかどうかはわかりませんが…。


第9E回 人真似をするのはやめよ!(とそのお方はおっしゃった) 00/02/05

インターネットには、奇特なお方がいらっしゃる。よその掲示板で、いろいろ僕に忌憚ない意見を投げかけてくれる人がいる。(それを世間では陰口とも言うが…) たとえば、その人は、僕が第99回で書いた、

// QWORD境界に整合する
m_pPtr = new BYTE[4 * WIDTH * HEIGHT + 7];
m_pBits = (void*)( ((int)m_pPtr + 7) & ~7 );

この部分が、どこそこのソースのパクリではないかというのだ。そして、お前(やねうらおのことね)は、色々な人がフリーで公開しているソースのなかにある、人が苦労して発明したテクや、人が苦労して発明した知恵を、さも自分が発案したかのように思い込み、書いているではないか。それは、ひとりのクリエイターとして恥ずべき行為ではないのか?とおっしゃる。(うろ覚え。謝々)

この部分のソースは、ご指摘の通り、不精してそのまま拝借させてもらっています。だから、言われることがわからないでもない。僕には反省するところがあるだろうし、それは自分自身の課題として受け止める所存です。

しかしやねぇ、QWORD境界にアラインすることがパクリだなんて言われるのは、もの凄く心外なのよ!!

あなた、QWORD境界にアラインすること自体が凄いテクか何かと勘違いしてるんだと思うんですけど、QWORD境界にアラインしなさいなんてことは、intelがPDFファイルとして公開している「インテルアーキテクチャMMXテクノロジ ディベロッパーズガイド」に嫌と言うほど書いてあるし、アセンブラをやるんならば知ってて当然でしょう?7足して~7でandを取る部分のテクをパクっていると言うのならば、このテクは、僕がこの連載の第5D回で先に書いていると言いたい。(いや、発表時期を考えると先でもないか…) そして、仮想画面をシステムメモリ上に持つのが他の人のアイデアを盗んでいると言うのならば、それはこの連載の第5B回で僕が自分なりに実験して得た結論だと言いたい。

もちろん、(学習の過程として)他人のソースをいろいろ見る以上、すべて自分で発見しただとか、産み出しただとか言うつもりは毛頭ありません。そう感じられる文面があったのならばこの場をお借りしてお詫びしますが、今回のに関しては、(不精してソースはそのまま引用していても)アイデア/テクのパクリだと言われる覚えはないです。常套的かつ一般的なテクとして紹介させていただいただけのつもりです。(第99回のような行為がそのような誤解を産むのなら、以降、気をつけますが)

ついでに、その人が感化されるに至ったそのホームページの主のひと!「(やねうらおと)関わりたくない」と思うなら、あなた何かにかこつけて、そういう書き込みすんの、いい加減にやめたらどうなのよー?「互いに干渉しない」とちごたのー?約束違うじゃない(笑)あなたも発言に自分で思っている以上の影響力があるんだから、もっと気をつけてよね!(と自分にも言い聞かせたやねうらおであった)


第9F回 Saturation & Morphingその1(ぼわー&うにゃー) 00/02/07

Keyboard Maniaが近くのゲーセンに入ったんですよ(笑) 引越しで忙しいのに、入りびたってます(笑) 木曜から東京だっちゅーのに、まだ住むとこ見つかってません。大丈夫かー。大丈夫なのかー?>俺。しかし、あのゲーム、ダブルでやってるんですが、1Pと2Pとの間がオクターブでないんで、ピアノの延長として考えてるやねうらおには、ちょっとやりにくいです。あんましやってるとピアノ下手になりそうなんでダブルはもうやらないかも知れません。(すんません。オクターブでした。嘘ついてました。訂正00/02/10)ちゅーか、ダブルやり続けるんなら練習用にパソコン版作るかも知れません。そんときはよろしく(笑)>みなさま

ところで、第9D回で書いた飽和加算というのは、使えるんでしょうか…。フェードアウトして白になるような画面効果は、白で画面をクリアしておいて、そこにブレンド比率を最初は100%から、0%になるように画像を転送したほうが良いような気もします。これなら、飽和加算はいりません。

しかし非MMXで、飽和加算をしたくなった場合はどうすれば良いのでしょうか?加算して、それがオーバーフローしているかをチェックして、オーバーフローしていれば、限界値に戻すような処理ですが、これはうまくやらないと相当遅いような気がします。

仮想画面はTrueColor(32bit per pixel)で持っていて、RGBA各8ビットだとしましょう。その8bitに加算するのは

        if (pixel+r>=256) pixel=255; else pixel+=r;

ですね。この部分が非常に遅くなりそうです。アセンブラでなら、バイト加算してキャリーフラグが立っていればFFに戻すような処理です。キャリーフラグが立っていればFFに戻すというのは、ノンキャリー側を条件ジャンプで飛ばしたあと、キャリーフラグが立っていることを利用してsubtract with borrow(sbb)を使って、sbb al,alなんてやれば即値は不要ですが…。

それより、1ピクセル分(DWORD)をeaxに転送したあと、どうやってパックド加算を行なうかと言うのがまた難しいような気がします。単純に考えると、8ビットずつローテイトしていくわけですが、加算する値を保持するための8ビットレジスタが4本欲しいところです。とは言っても余っているのは、既にblとcl,dlだけ。blは、ebxをアドレスを指すのに使うような気がするので、実質使えそうなのはcl,dlだけ。そうなってくるとWin32のVirtualProtectを使って実行領域に書き込み属性を与え、関数の先頭でパッチを当てに行く(即値を書き込む)手もありそうですが、それより、加算する値を保持しているレジスタも(edx)を一緒にローテイトさせるほうが自然だと思います。edxを毎回潰して良いのならば、

    LOOP(4){        //    4回展開する
        add    al,dl
        sbb    dl,dl    //    if (cy==1) dl=0xff; else dl=0;
        ror    eax,8
        ror    edx,8
    }
        or    eax,edx    //    こうやれば条件分岐は不要

こんな感じでしょうか?条件分岐をさせずsbbでマスクビットを生成し、最後でorを取っているところらへん、我ながら面白いコーディングだと思いますが、想像を絶するほど遅いような気もします。少なく考えても17命令=17clock(?) al,dlを使用直後にeax,edxを使っているので、P6ファミリ以降ではパーシャル・レジスタ・ストールでさらに遅くなる気もします。もっとも、MMX対応の場合、MMX用の専用ルーチンで処理することになるので、それは考えなくても良いのですが…。

画像処理、しかも動きがあるものなので、もっとごまかしてでも、速くは出来ないもんでしょうか?たとえば、RGBAの各下位1ビットを捨てても良い(無視しても良い)としたらどうでしょうか?

    and    eax,0xfefefeff    //    R,G,Bの各下位1ビットを捨てる
    // (同様にedxもfefefeffでandされているとする)
    add    eax,edx         //    そして、加算してしまう。carry,bit24,bit16,bit8が立っていれば、
                                //    サチュレートしたものとして、それぞれに対応するバイトをFFにすれば良い。
    IF(cy)               or eax,0xff000000    //    2命令
    IF(bit(eax,24))    or eax,0xff0000        //    3命令
    IF(bit(eax,16))    or ax,0xff00            //    3命令
    IF(bit(eax,8))      or al,0xff                //    3命令

えっと…、これで13命令でしょうか。見やすくするために勝手なマクロ使ってますが、意味はわかっていただけるでしょう。余談ですがbit24,bit16,bit8に、下位バイトからの繰り上がりが残ったままになると薄気味悪いので、ビットテストにはbtではなくbtr(bit test and reset)を使います。

しかし、これだと条件分岐でずいぶんと損をしそうです。それでは、最後の部分で、テーブル処理をするというのはどうでしょうか。

    xor    ebx,ebx        //    ebx  = 0
    and    eax,0xfefefeff
    add    eax,edx
    adc    ebx,ebx        //    if (cy) ebx++;
    btr     eax,24         //    ebx<<1; if(bit(eax,24)) ebx++;
    adc    ebx,ebx        //
    btr     eax,16         //    ebx<<1; if(bit(eax,16)) ebx++;
    adc    ebx,ebx        //
    btr     eax,8           //    ebx<<1; if(bit(eax,8)) ebx++;
    adc    ebx,ebx        //
    or      eax,[ebx*4+BIT_MASK_TABLE]

こちらは11命令ですね。しかも、条件分岐を使わないので速い気もします。adc ebx,ebxで、シフトさせながらキャリー加算するあたりが、アセンブラ特有のなかなか奥深いコーディングだと思うのですが、いかがなもんでしょうか。(自画自賛とも言う)

しかし、MMXならばQWORD(8バイト)まとめて1命令で飽和加算できるのに対し、これだとDWORD(4バイト)に対して行なうだけで11命令必要です…。。ただ、レジスタに転送する時間とかもあるので、そんな単純なもんでもないですが、これではほんと辛いです。しかもこれ下位1ビット落としているという…(言わなきゃ、わかんないと言う話もある…)

何かいい案がありましたら、ご教授ください。


第A0回 Saturation & Morphingその2(ぼわー&うにゃー) 00/02/09

明日から、いよいよ東京ですよ。東京!思えば、ここ大阪府八尾市には、いろいろ思い出があります。特に思い出深いのが、ドムドムを潰したことです。(ごめん!>ドムドム関係者) ドムドムをご存知ない方もおられるでしょうが、ドムドムというのは、マクドナルドのようなファーストフードのチェーン店です。メニューのラインナップは「森のきのこバーガー」だとか謎なものが多かったですが、八尾のドムドムが潰れたのは、そんな理由ではありません。

思えば、あれは、8年前。やねうらおは、パソ通をやってまして、パソ通で知り合った女装趣味のおっさん(確か、30数歳だったと思う)の、み〜なさん(仮)とツーショットで会うことになったのでした。この人、公務員だし(笑)、まさか女装しては来ないと思ったんですけど、なんとこの人、女装してきたんですよ!!(半泣き)

しかも、僕より背が20cmほど高い。ラグビーの選手のような体格で、ぱっと見た印象は「ごつい人やなあ」でした。その人が、白地に黒の水玉のワンピース着てるんですよ。しかもハイヒールですよ。もちろん、あんた、なんでハイヒールやねん!という突っ込みをしようものならば頭つかまれて握り潰されそうな体格差ですよ。目の悪いやねうらおでも、待ち合わせ場所のすげー遠くから変態のおっさんがそこに待っていることがわかりました。もう知らん顔して帰ろうかとも思いましたが、パソ通では世話になっていたんで、そういうわけにもいかなかったんです。

そのみ〜なさんですが、会うなり「まいど〜」ですよ。すんげードスの利いた低い声ですよ。まいど〜と言っているのか、おいど〜と言っているのか聞き分けれないほど低い声です(笑) ほとんどヤクザの親分ですよ。女子高生が黄色の歓声だとしたら、このおっさんの場合、黄色は黄色でも、う○こ色(食事中の人、ごめん)のうめき声です。ちょっとは裏声使ったりするのかと思ったら、思いっきり地声です。しかも、めちゃ低い声です。「どこいきまひょか〜」聴いてるだけで雀が落ちてきそうな声です。そもそも、この人を連れて入れる行き先なんて思いつきません。もう二度と行けなくなってもいいところと言えば…そうや!ええこと思いついた!ドムドムや!あそこならどうなってもええわ!やねうらおの脳裏に電光石火の如く閃いた名案がそれでした。(どこが名案なんじゃい…)

ドムドムに入るなり、その人は「何しまっか?」と僕に聞きました。いや、シェイクだけでいいんですけどね…「ああシェイク。奢りますわ」と言って、カウンターのほうに一人で歩いていったかと思うと、ピタっと立ち止まり、低いうめき声で「シェイグふだぁづううう」顔面硬直する女店員、店のなかが一瞬にして静まりかえる。女子高生も、カップルの視線も一身に受け止めたみ〜なさん。僕の、いままでの人生のなかで一番恥ずかしかった瞬間です。

5分ほどすると、店のなかの客は誰もいなくなりました。日曜の昼間にです!しかし、我々はその後、他に行くところもないし、そのまま店の入り口付近の席でシェイク2つで延々6時間ぐらい居座りました。ドムドムは、外から店の中が見えるんで、客が来ても外からみ〜なさんを見ると逃げていくんです!おかげで、我々が店を出るまで、客が誰も入りませんでした。一説によると、その後、そのときの店員が全員、バイトをやめたそうです。この日から数えて2週間後にドムドム八尾店は潰れました。ドムドム八尾店には、ホント悪いことしました。ごめんね>ドムドム(笑)

さて、今回は、前回の続き。さ〜さんから、面白いメールをいただいたので、それを紹介したいと思います。さ〜さんは、

http://www.csl.sony.co.jp/person/fnami/

を参考にされていて、こちらのページによれば、btr命令はクロック数が少々かかるのと、ペアリングができないように書いてあり、そのあたりを重点的にとのことです。


#include <stdio.h>
void main(void) {
unsigned long tmpa = 0x7f7f7f7f;
unsigned long tmpb = 0x7f7f827f;

_asm{
mov eax,tmpa
mov edx,tmpb

and eax,0xfefefeff
and edx,0xfefefeff
add eax,edx

mov edx,eax
rcr edx,1
shr edx,7
and edx, 0x01010101
xor ecx,ecx
sub ecx,edx
shl edx,8
add ecx,edx
sub eax,edx
or eax,ecx

mov tmpa,eax
mov tmpb,ecx
}
printf("%lx, %lx\n", tmpa, tmpb);
}

基本思想としては、各Byteの上のbitが1なら ff 、0なら 00のマスクを作ります。最上位はcarryを使うのはやねさんの
コードのままです。

基本思想のその2として、0/1に応じたマスクの生成ですが、1byteを考えたとき、a=0となっていれば、a=a-1とすれば1byteのマスクを作れます。これを4byte同じにやると、下のほうで 1を引くと上のほうが0であっても ff のマスクが作られてしまいます。

たとえば、飽和情報tが0 0 0 1(0:飽和していないByte)のように最下位byteが飽和したときには、マスクは a=0x000000ff としたいのですが、a=0; a=a-t とすると a=0xffffffff になります。このときは、a=a+0x100 とすれば a=0x000000ff になります。

つまり、1を引いた場合はひとつ上のbyteに1を足してやることになります。(やね注釈/よって、飽和バイト情報tを、8回左シフトしたものを加算する処理が必要になる。それが上のソースのshl edx,8  add exc,edxの部分)

というところで、コードの説明ですが、まず、演算結果を利用してマスクを作るので
mov edx,eax
で、コピーしておきます。次に、飽和情報tが欲しいので、
rcr edx,1
shr edx,7
として、飽和を示すbitを各byteの最下位bitに移動します。2命令に分けているのは、クロック数の関係でです。(^^;

で、マスクして飽和情報tを作ります。
and edx, 0x01010101    ;    これで飽和情報tが求まった

xor ecx,ecx        ;    ecx = - t
sub ecx,edx

引いたbyteの上位byteの最下位に足すためにシフト+加算します。
shl edx,8            ;    ecx += edx<<8 
add ecx,edx

これで、ecxに飽和したbyteが ff であるマスクが作られました。飽和したbyteの上位バイトの最下位は1になっているので、
それをクリアします
sub eax,edx

最後にorをとってやって終了です。
or eax,ecx

前述のページの最後のほうにクロック数のカウントをするためのコード例があったので試してみたのですが、正しい結果が
得られなかったので、実際に何クロックで実行されるのかがわかりませんが、割とペアリングできる命令があるので、
お徳かもしれません(^^;命令数で考えると・・・、増えてますね。増えた分は、将来への投資ってことで。(謎)

実は、このコード、8bitカラーで動的にスプライトマスクを生成する
ときに使ったコードを元にしました。宣伝。

http://www1.webnik.ne.jp/%7Esanami/tuatmcc/freeware.html

ここのスクリーンセイバーがそうです(^^;

▼佐波 晶
- 東京農工大学工学研究科
- mailto:asurada@sugo.vip.co.jp


マスクビットを生成するために、0からの引き算を使うところらへん、凄いと思います。なんちゅーか、天才ですね(笑) はっきり言って完敗です。やられました。今後ともよろしくお願いします。>さ〜さん


戻る