00/02/13 | フーリエ展開編[★★★◎◎◎◎] |
んで、Wavファイルの分析ということで、フーリエ級数展開をすることにした。 しかしこんなところで大学の振動波動論の授業がダイレクトに利用できるとは・・・ いや、数学の解析の授業ではまだフーリエは扱ってないのね。 どう言うのかって言うと、でたらめな関数(ただし最低限の条件として周期性があるとする)を、SinとCosの式の級数に分解するのね。 たとえば周期がLだとすれば、Sin(x/L*2π)、Cos(x/L*2π)、Sin(x/2L*2π)、Cos(x/2L*2π)、Sin(x/3L*2π)、Cos(x/3L*2π)・・・ の和にするのね。 何の役に立つかというと・・・音域別に分解できるから、高音部だけ音を大きくするとかそう言うことができる。 っていうかイコライザに思いっきり使えるね。 ただし・・・分解するのに積分しないといけないのね・・・ つまり、1つのSinやCosの項にするだけで1回積分をしないといけないとは。 (ところで、一般のオーディオ系ソフトではどうやってるんだろ・・・特定の数秒分についてだけ積分とかするのかな?) んで、16Bit・11025Hz・モノラル・1.10秒(サンプリング数12000程度)のデータを利用。 最初積分を12000データすべてについて行い、振動数が1Hz〜100Hzで1刻み(あとで考えれば、1-50あたりは不可識成分なんだよなぁ・・・)で計算。 かんがえてみれば12000*100*4(積分時と合成時、それぞれSinとCos)回もSinやCosがらみの計算をするわけなんだな。 これはかろうじて数秒。 ただ・・・なぜか合成後は元とは似ても似つかないデータに。 っていうか振幅が全体的に小さすぎるのね。 合成後のデータを再生させると(ここではwaveOutWriteはメンドイので、元ファイルのデータブロックだけを書き換えてsndPlaySoundに送るというかなり強引かつ横着な方法だけど)・・・ほとんど聞こえん。 んで、やっぱ分析をもっと細かくしないとと言うことで1Hz〜20000Hzまで(かなりヤケ)やってみる。 計算に6分以上・・・(無論ネイティブコンパイル時) を、だいぶ近づいた。 でもだいぶ遠い・・・ っていうかその後1Hz〜2000Hzで試したけど・・・あんま変わらん。 まぁゲームの爆発音だしな・・・低音が多くて当たり前(^^;) なお、下がそのとき(2000Hz)のグラフ(上:元データ、下:展開後)(この画像データは次回更新時には消すかも) (削除済み) しかしそれでも遠いとは・・・さらに0.2Hzずつぐらいにしないと行けないのか? せめて積分を12000すべてにやるんではなく1/10ぐらいにしたほうがいいのかな。 せっかくなんで、下のWAVのヘッダーチェッカーにデータ表示機能をつけることにした。 一応ステレオも2色表示だぞ。 さらに、依然作ったNoiseミニデモにノイズ音も入れることにした。 データはRndで作るだけでいいし。 ただ。。。なぜかループができない・・・ dwLoopsやdwFlagsはwaveOutPrepareHeaderの後に設定しないといけないのか? それでも不可。 いろいろいじるが不可。 一端はRIFFチャンクとかも作成してsndPlaySoundで再生しようかと思ったほど。 結局なぜか再起動したらできました。 さらに16Bit・ステレオ・44100Hzのいい音質のノイズです(^^;) |
00/02/11 | 音の作成編[★★★◎◎] |
ついにテスト期間に入りました(^^;) うーむ、大丈夫かなぁ・・・ んで、新しい事にも挑戦したいなぁと言うことで、音がらみのことをやってみることにした。 基本的な手順は、waveOutOpenでデバイスを開き、waveOutPrepareHeaderとwaveOutWriteでデータを送る。 終了したらwaveOutUnprepareHeaderとwaveOutCloseで終了。 そう言えばここらへんは低レベルオーディオ系命令ということでMidiとかと近い点があるなぁ・・・ そう言えばmidiStream・・・まぁ他のリンク先のページに先を越されたということでいいか(^^;) んで、ひとまずデータはRndで適当に作成するとして・・・って再生されてないぞ? ひとまずwaveOutPrepareHeaderとwaveOutWriteの返り値を見ると・・・ エラー値は5で「オーディオハンドルが変」との事。 んで、waveOutOpenを見ると32の「WAVEFORMATが変」とのこと。 しょうがなくwaveOutのドライバ能力チェックのPCMWAVEFORMATのように最後にサンプリングビット数を入れると・・・まぁうまくできた。 んで、今度は正当に正弦波をやろうとすると・・・ ん?いろいろ入れても毎回変な音がなるぞ。。。 やっぱり lpData() as Byte でRedimしただけだとlpDataにはポインタが入ってはないのかな? しょうがないから Dim t() as Integer lpData=VarPtr(t(0)) だとうまくいった。 んで、今度こそ正弦波。 いろいろ試してみる・・・ バッファに入る数字が音の高さを示すわけでもないんだよなぁ・・・ んで、わかったこと。 Sampling = Σ 音量*Sin(周波数) と。いや、定数倍は当然かわるけど。 んで、謎。 Sampling = 定数 + Σ正弦波 としたときにここで定数にはどういう意味があるんだろ? ちなみにここらでフーリエ展開が必要あるっぽいね。 最近振動波動論の授業でちょっと出てきたしなぁ・・・ 珍しく学校の授業がダイレクトに結びついた気がする(^^;) 幸いPCMにおいては8Bitだと0〜255、16bitだと-32768〜32767であらわすため、Unsignedがいらない・・・よかった。 ついでに、ミニソフトとしてWAVファイルのヘッダーを表示するソフトをパパっと作ってみた。 |
00/02/04 | MMXを使おう編[★★★◎◎] |
下のデモスケルトンでMMXを使えるように、MMX機能の有無をチェックするために関数を作成することにした。bool CheckMMX() { //MMX機能のチェック int iMMX; __asm{ mov eax,1 cpuid ;23bit目にMMX情報がある(0bitからだから正確には24つ目) and edx, 800000h shr edx, 23 mov iMMX, edx } return iMMX==1 ? TRUE : FALSE; } ってな感じ。 実際はCPUID命令が使えるのは一部の486とPentium以上のCPUらしいけど・・・ まぁWin95の条件が486DX2って言うぐらいだからDX2にはCPUIDがあるんでしょってことで。 実際はなんかのフラグレジスタをいじってCPUID命令の有無を確かめるらしいけどね。 っていうかこれを実行すると、「and edx,800000h」で「"newline"はインラインアセンブラのトークンエラーだ」みたいなことが出てくる。 ヘルプを見ると・・・ 「Pentium固有の命令を使用しようとすると出るエラー」だと。 コンパイルオプションで「Pentium」を指定してもムダらしい。 VC5の時点でPentium命令を使えないってどういうことだ(^^;) うーむ。こりゃ困った。 こうなれば直接オペコード(アセンブラの実際のバイト)を書けないかなぁと。 突然CPUIDのオペコードを 0F A2 とか書いてもだめだった。 んで、結局インラインアセンブラのへルプ(別にアセンブラの文法が書いてあるわけではない)を見ると・・・ 「固有命令が使いたければ擬似命令 _emit を使え」と。 というわけで書き換えて bool CheckMMX() { //MMX機能のチェック int iMMX; __asm{ mov eax,1 ;cpuidの代わり _emit 0x0F _emit 0xA2 ;23bit目にMMX情報がある(0bitからだから正確には24つ目) and edx, 800000h shr edx, 23 mov iMMX, edx } return iMMX==1 ? TRUE : FALSE; } でOKだった。 これでどうにかMMXも使えるね。 っていうかなんでPentiumの命令がサポートされずにMMX命令はコンパイラを通過できるんだ? |
00/01/23 | ミニデモ作りたいな編[★★◎◎◎] |
センター試験も本試験・追試験ともに終わり、アルバイトでモニター試験を終了してそろそろ大学の試験に備える頃です。 んで、色々と触発されてエフェクトとかをやってみたいなぁと思う今日この頃。 ひとまずVB向けのミニデモスケルトンを作成。 デモ部分に集中できるようにした。 んで、サンプルを作ってみる。 GALAXY−5000個の点が回るだけ。一応3D的になってる。 っていうか点だからラクだしね。 相互に力はかからないため、ラク。 ELECTRON−100個の+電荷、−電荷が相互に力を及ぼしながら移動する。 っていうかGALAXY以上にビジュアル的にはつまらん。 物理とかやるのにいいかも(^^;) NOISE−TVの砂嵐風のエフェクト。 さらに手抜きバージョンなので256色すら使わず、2色。 いや、これだと1回Rnd関数呼ぶだけで8ピクセルが・・・ んで、やっぱりアセンブラとかを使いたいときに毎回DLLにするのもメンドイので、Cのスケルトンも作成することに。 っていうか、RegisterClassとCreateWindowでちゃんとウインドウを作ったCのプログラムを作ったのは初めてだったりする。 いや、リソースでダイアログを作ってメッセージ処理したり、MFCでウインドウを作ったことは有るんだけどさ。 まぁそんな基本的な部分に関してはいくらでも有るのでパッパと作成。 ちょっとスケルトン作成時に8BitDIBSectionのパレット処理に困ったけど。 んで、また2つ作成。 LAND−単なるランドスケープ。 一応フォグも付けたんだけどねぇ・・・VBでもっと速いのがあるので悲しかったりする。 近似でもあんまり速くならないしなぁ・・・ リニアフォグも指数フォグも平方指数フォグも遅い。 まぁDoubleを使わないと行けないしねぇ・・・ HANABI−花火もどきもどき。 単に重力にしたがって落ちる300個の点(でも5*5のサイズ)を加算半透明で合成し、通常のファイアエフェクトで上にずらしてくだけ。 8bitでパレットを作成してるので、結構な速度・・・ 気が向いたら、エフェクトのページを作るかも。 アセンブラ化はする気がしない・・・ |
00/01/14 | MMX16Bitブレンド編[★★★◎◎◎◎] |
んで、ハイカラーでのブレンドに挑戦。 まず必要なDIBSectionのピクセルフォーマットを調べようと思ったんだけど・・・ APIにGetPixelFormatなるものを発見。 ・・・って引数はhDCのみ、返り値もLong1つのみ。 しかも手元にはこの関数の資料が無い(^^;) んで、結局あきらめる。 (返り値がDirectXみたくRGBのマスク値とかならまだいいんだけど) んで、SetPixelしたものをRtlMoveMemoryで取得すると言う手段でチェックすることにした。 (っていうかDirectDrawのMSが出してるサンプルですら色取得はそう言う方法をとってるし・・・) んで、ためしにSetPixelで0xFFFFFFを入れたら0x7FFFが返ってきたのでRGB=555とわかり、一安心。 もしどっかが6で655とかだったらもうチョイ0x808080とかを入れて試さないと行けなかったしね。 っていうか一番(速度的に困るのは)計算途中で0x9546とかみたく0x7FFFよりでかくなっちゃうと、VBだとオーバーフローを起こしちゃう・・・ Cとかならunsigned shortでOKなんだけど・・・ んで最初、もし0x7FFFよりでかかったら毎回0x10000を引いて負値にしないといけないかなぁとか思ってたんだけど。 んで、基本的なやり方はこう。 描画データの透明色チェックをしたあと、2で割り、各色の最上位ビットをはねるために0011110111101111でANDし、描画先のデータにも同様の演算をして足せばハーフブレンドと。 んで、結果。やっぱり32*32で16bit、300個ね。 (32*32*300=640*480なんだよね・・・ってだからなんだ) VB-15FPS C-30FPS ASM-52FPS MMX-110FPS 今回はさらにMMXが勝ってるね。 やっぱり8バイトずつ処理できるのは強みだね。 んで、コード。 //50%ブレンド16bit-MMXアセンブラ extern "C" VOID FAR PASCAL DIBHCHalfBltMMX(byte* DSur,int DPitch, byte* SSur,int SPitch,int LB,int HB,int TC,int PF) { int a; unsigned int TCT[2]; //透明色 unsigned int* pTCT=&TCT[0]; unsigned int FM[2]; //0xFFで反転するため unsigned int* pFM=&FM[0]; unsigned int TB[2]; //ピクセルフォーマット unsigned int* pTB=&TB[0]; TCT[0]=(unsigned int)TC*0x10001;TCT[1]=TCT[0]; FM[0]=0xFFFFFFFF;FM[1]=0xFFFFFFFF; TB[0]=(unsigned int)PF*0x10001;TB[1]=TB[0]; //a-y座標 //ecx-x座標 _asm { mov a,0 ;透明色用Packedデータ準備 mov eax,pTCT movq MM4,[eax] ;MM5は111111・・・にする mov eax,pFM movq MM5,[eax] ;MM3はピクセルフォーマット mov eax,pTB movq MM3,[eax] ;ループ開始 yloop: mov ecx,0 ;ポインタ計算 ;Destラインスタート位置 ebx= DSur + DPitch*a ;Srcラインスタート位置 eax= SSur + SPitch*a mov eax,DPitch mul a mov ebx,eax add ebx,DSur mov eax,SPitch mul a add eax,SSur xloop: movq MM0,[eax] movq MM2,MM0 ;比較用データ ;TC以外の部分はそのまま、TCは0に。 ;TCの部分だけFFに pcmpeqw MM0,MM4 movq MM6,MM5 ;TCの部分以外FFに pxor MM6,MM0 ;TCの部分以外元に戻す pand MM6,MM2 ;TCの部分だけ取りだし、Destにする movq MM1,[ebx] pand MM0,MM1 ;和をとる(TCの部分は描画元と先を同じ色にすることで変化をなくす) por MM6,MM0 ;ブレンドする psrlq MM6,1 pand MM6,MM3 psrlq MM1,1 pand MM1,MM3 paddusw MM6,MM1 ;置く movq [ebx],MM6 ;終了、次のループへ add ecx,8 cmp ecx,LB jae ychk add eax,8 add ebx,8 jmp xloop ychk: inc a mov ecx,HB cmp a,ecx jb yloop emms } } なんか前回のコードとかを思いっきり使いまわししてるのに気づくかも・・・ 欲しい人には8、16、24bitでの半透明のサンプルをあげます(^^;) ふと気づく。 透明処理無しならVBもCもASMも4バイトずつ処理できるっていうかRtlMoveMemoryでOKだね。 |
00/01/13 | MMXでブレンド編[★★★◎◎◎◎] |
さて、一部の方お待ちかね(?)のブレンド処理です。 実はこれ、前回のスプライト処理のものを転用すればすぐ出来るんですね。 1つの色が1バイト使っているとの仮定の元でなので・・・ 今回は24bitでやってます。 ちなみに画像とかは24bitになっただけで一緒。 何がラクかって。。。 前回はぐちゃぐちゃと透明処理をしてからメモリにセットしたんだけど、今回は透明処理をしたものを2で割る(っていうかMMXには割り算処理はないので右に1ビットシフト)したものと描画先メモリの内容を2で割ったものを足せばOK。 一つややこしかったのは、MMXの命令はなぜかバイトを右ビットシフトする命令が無い。 なぜかワードとかダブルワードのみ(^^;) だからヘタすると上位バイトの最下位ビットが1だと下のバイトの最上位ビットが1になってしまって困るんで・・・ しょうがないのであらかじめ最上位ビットを省いた7Fの配列を空いてるMMXレジスタに入れておいて、計算後にANDで最上位を省くと。 なんか昨日からいらないものをANDで省きすぎだな・・・ 同様にCやASMにも処理を施して比較。 一応テーブル利用も含めて結果は・・・ あ、一応32*32で24bitの画像を300個描画した場合ね。 ちなみにテーブル利用ブレンドは以前7・8ごろのもののままです。 (でも描画数が違うけど) テーブル利用ブレンド VB-6FPS C-10FPS ASM-14FPS リアルタイムハーフブレンド VB-10FPS C-14FPS ASM-28FPS MMX-78FPS やはりMMXの圧勝。 気になったのがテーブル利用ブレンドがリアルタイムハーフブレンドに負けてるのね。 まぁテーブルなんてVBとかでは毎回256を掛けなきゃいけないしなぁ・・・ アセンブラとかは一つ上のバイトに移せば256倍だし。 まぁリアルタイムだとMMXの都合でブレンド割合の分母は2の累乗でないと行けないんだけどね。 一応コード。 //50%ブレンド-MMXアセンブラ extern "C" VOID FAR PASCAL DIBHalfBltMMX(byte* DSur,int DPitch,byte* SSur, int SPitch,int LB,int HB,byte TC) { int a; unsigned int TCT[2]; //透明色 unsigned int* pTCT=&TCT[0]; unsigned int FM[2]; //0xFFで反転するため unsigned int* pFM=&FM[0]; unsigned int TB[2]; //0x7Fトップビットを0にするため unsigned int* pTB=&TB[0]; TCT[0]=(unsigned int)TC*0x1010101;TCT[1]=TCT[0]; FM[0]=0xFFFFFFFF;FM[1]=0xFFFFFFFF; TB[0]=0x7F7F7F7F;TB[1]=0x7F7F7F7F; //a-y座標 //ecx-x座標 _asm { mov a,0 ;透明色用Packedデータ準備 mov eax,pTCT movq MM4,[eax] ;MM5は111111・・・にする mov eax,pFM movq MM5,[eax] ;MM3は011111110111111・・・にする mov eax,pTB movq MM3,[eax] ;ループ開始 yloop: mov ecx,0 ;ポインタ計算 ;Destラインスタート位置 ebx= DSur + DPitch*a ;Srcラインスタート位置 eax= SSur + SPitch*a mov eax,DPitch mul a mov ebx,eax add ebx,DSur mov eax,SPitch mul a add eax,SSur xloop: movq MM0,[eax] movq MM2,MM0 ;比較用データ ;TC以外の部分はそのまま、TCは0に。 ;TCの部分だけFFに pcmpeqb MM0,MM4 movq MM6,MM5 ;TCの部分以外FFに pxor MM6,MM0 ;TCの部分以外元に戻す pand MM6,MM2 ;TCの部分だけ取りだし、Destにする movq MM1,[ebx] pand MM0,MM1 ;和をとる(TCの部分は描画元と先を同じ色にすることで変化をなくす) por MM6,MM0 ;ブレンドする psrlq MM6,1 pand MM6,MM3 psrlq MM1,1 pand MM1,MM3 paddusb MM6,MM1 ;置く movq [ebx],MM6 ;終了、次のループへ add ecx,8 cmp ecx,LB jae ychk add eax,8 add ebx,8 jmp xloop ychk: inc a mov ecx,HB cmp a,ecx jb yloop emms } } 16bitも挑戦しようかな。 各色要素の最上位ビットだけはねて2で割っていけば以外と簡単にできそう。 |
00/01/12 | MMXでスプライト編[★★★◎◎◎◎] |
たまには新しいことにも挑戦しないとなということで、MMXで描画ルーチンを書いてみることにした。 MMXの命令をうと、8バイトに対して同時に基本的な演算ができるため、かなり効率がよくなるため、かなりの高速化が見こめると。 さらに飽和演算(たとえばByte型であれば200+200をしてもオーバーフローせずに最大値の255にしてくれる)ができるのでオーバーフローを考えたりする必要もないため、カラー演算にはもってこいなわけで。 前のアセンブラのスプライトとちがい、1バイト単位で透明色の判定ができないため、BitBltのスプライトのようにANDやORの論理演算を使って透明色処理をすることに なるけど・・・ ひとまず透明色抜きの描画ルーチンを作ってみる。 んで、一つのレジスタが余ったので通常のメモリ上の変数を利用していたループ用の値をレジスタで置き換えてみる。 あり? うまくループしない・・・っていうか無限ループしてるのか完全に処理がストップする。 cmp edx,HBで片方がレジスタの場合はedxがHBより小さいときはjaeをつかえばいいのかな?jbeを使えばいいのかな? っていうかどっちも失敗した・・・ 片方は無限ループ、片方は何も描画しない。 うーん、どこでもedxレジスタは使わないようにしてるんだけどなぁ・・・ んで、ニーモニックのマニュアルをみてみる・・・ どうもmul edxに原因があるみたいね。eax*edx=64bitなので、当然eaxには収まらないと。 どうもその分の繰り上がり分はedxにくるらしい・ edxはあまり大きくないからどうせ32bitからはあふれないはずだけど・・・ それでもedxは0になっちゃうみたい。 そういうわけでやっぱり変数を使うことにした。 結局速度がもったいないんだけどね。 っていうかなぜか「C++の描画」で落ちる。 うーむ、スプライトのコードから透明色判定を抜いただけなのに・・・ RtlMoveMemoryでもちゃんとポインタ指定になってるし・・・ ってよくみたら透明色無し描画だから引数を1つ減らすの忘れてた(^^;) (いや、DLLで減らすの忘れると余分にDLLがメモリを読もうとしちゃうから一般保護違反で落ちちゃうのね) んで、透明色無しのコード。 //透明色無しスプライト-アセンブラ extern "C" VOID FAR PASCAL DIBNoTransBlt(byte* DSur,int DPitch,byte* SSur ,int SPitch,int LB,int HB) { int a,b; //a-y座標 //b-x座標 _asm { mov a,0 ;ループ開始 yloop: mov b,0 ;ポインタ計算 ;Destラインスタート位置 ebx= DSur + DPitch*a ;Srcラインスタート位置 eax= SSur + SPitch*a mov eax,DPitch mul a mov ebx,eax add ebx,DSur mov eax,SPitch mul a add eax,SSur xloop: mov cl,[eax] mov [ebx],cl inc b mov ecx,LB cmp b,ecx jae ychk inc eax inc ebx jmp xloop ychk: inc a mov ecx,HB cmp a,ecx jb yloop } } んで注目(?)の速度。 今回は32*32の画像を300個描くことにしました。 ちなみに解像度は640*480*16のときに320*240*8のDIBSectionを作ってます。 (前のアセンブラのときは200ね。) VB-43FPS BitBlt-80FPS Asm-103FPS C-64FPS MMX-240FPS うーむ、MMX圧勝。 透明色無しということで今回はBitBltはSrcCopyのみで描画したんだけど。 ただ・・・幅が8で割り切れないときの処理はしてないしなぁ・・・ んで、今度はスプライト。 透明色の処理が結構メンドウだなぁと思いつつはじめる。 まぁBitBltみたくAndやOrを駆使すればどうにかなるだろうと思い、作る。 んで、テスト。 ありゃ?何も描画しないぞ? ということで色々調査。 最初はmovq MM4,pTCTでは4バイト分しかデータが送られないかなぁなどと勘違いして(っていうか4バイトの転送に関してはmovdという違う命令があるのにもかかわらず)いきなりパック系命令まで持ち出す始末(^^;) パック系命令で実は4バイト分のデータのために4ワードを用意しないと行けないことに引っかかりつつ、と中でmovqはやっぱり8バイトの転送をしてくれることに気づく。 んで、描画・・・ ありゃ?変な風に描画される・・・ 変にすじが入ったりする。 うーん、透明色判定が間違ってるかなぁなどと心配するも痕跡が見つからず。 もとのメモリデータが違うのかなと考える。 問題の場所。 movq MM4,[pTCT] movq MM5,[pFM] うーん、pTCTはちゃんと8バイト分のデータを差すポインタを参照する・・・ ってメモリアドレスを差すはずのポインタを[ ]で囲っていいんだっけ? と思いつつ、今までのものを見てみると・・・ 変数を直接[ ]で囲っていた例が無いことに気づく。 そう言えば[ ]でメモリ指定するのは[レジスタ+定数とかうんちゃら]だけだったなと思い出す。 多分[pTCT]とかの[ ]は無視されてるんだろうなぁと。そうすればMM4=&TCT[0]なわけで当然中途半端な透明色処理だなぁと。 んで、レジスタに変数を代入してから参照するように作りなおし。 んで、結果。 VB-34FPS BitBlt-41FPS Asm-89FPS C-50FPS MMX-220FPS またもやMMX爆速ですなぁ。 んで、コード。 //スプライト-MMXアセンブラ extern "C" VOID FAR PASCAL DIBBltMMX(byte* DSur,int DPitch,byte* SSur ,int SPitch,int LB,int HB,byte TC) { int a; unsigned int TCT[2]; //透明色 unsigned int* pTCT=&TCT[0]; unsigned int FM[2]; //0xFF unsigned int* pFM=&FM[0]; TCT[0]=(unsigned int)TC*0x1010101;TCT[1]=TCT[0]; FM[0]=0xFFFFFFFF;FM[1]=0xFFFFFFFF; //a-y座標 //ecx-x座標 _asm { mov a,0 ;透明色用Packedデータ準備 mov eax,pTCT movq MM4,[eax] ;MM5は111111・・・にする mov eax,pFM movq MM5,[eax] ;ループ開始 yloop: mov ecx,0 ;ポインタ計算 ;Destラインスタート位置 ebx= DSur + DPitch*a ;Srcラインスタート位置 eax= SSur + SPitch*a mov eax,DPitch mul a mov ebx,eax add ebx,DSur mov eax,SPitch mul a add eax,SSur xloop: movq MM0,[eax] movq MM2,MM0 ;比較用データ ;TC以外の部分はそのまま、TCは0に。 ;TCの部分だけFFに pcmpeqb MM0,MM4 movq MM6,MM5 ;TCの部分以外FFに pxor MM6,MM0 ;TCの部分以外元に戻す pand MM6,MM2 ;TCの部分だけ取りだし、Destにする pand MM0,[ebx] ;和をとる por MM6,MM0 ;置く movq [ebx],MM6 ;終了、次のループへ add ecx,8 cmp ecx,LB jae ychk add eax,8 add ebx,8 jmp xloop ychk: inc a mov ecx,HB cmp a,ecx jb yloop emms } } 所詮自分は素人なのでさらに詳しい人から見ればかなり余分な部分があるんだろうけどね。 ひとまずそれなりな結果がでて満足。 っていうか透明色処理でかなり時間を取ると思ったんだけどね。 どうもMMXはテーブル利用の描画にはあまりうまく使えないんで・・・ 今度は24Bitで50%ブレンドに挑戦しよ。 |
00/01/09 | 簡易RLE圧縮編訂正版[★★◎◎◎] |
うーむ。 どうもリプレイがうまく行かない・・・ 最初は調子いいんだけどクリアしたはずのデータでなぜかゲームオーバーになっちゃうんだよね。 計算で1フレーム単位の食い違いがあるだろうと言うことで調査開始。 んで、SaveReplayFileでの圧縮計算とLoadReplayFileの解答計算で食い違いがあることが判明。 今回はLoadReplayFileを修正することにした。 (LoadReplayFile関数の一部) offset = 0 Do Get 1, , ks Get 1, , diff For i = offset + 1 To offset + diff .KeySet(i) = ks Next offset = offset + diff Loop While offset < .Length どこが変わったと言うとFor i =の部分でoffset To offset + diffだったのがoffset + 1 Toになった。 これのせいでキー操作のたびに1フレーム動きがずれちゃったのね。 |
00/01/06 | 簡易RLE圧縮編[★★◎◎◎] |
ひとまず新年あけましておめでとうございます。 M-Warのリプレイデータファイルの形式を考えてるんだよね。 最初は1フレーム1バイトで保存してたんだけど、メチャクチャ無駄が多い。 考えれば1フレームごとに毎回キーを変えることは珍しいし、数フレームはどうしたって同じキー操作がつながるでしょ。 ってなわけで、ちょっとしたRLE(Run Length Encode)形式を利用することに決定。 最近のVBユーザーはRLE形式のBMPファイルなんて知らない人も多いんだろうなぁ・・・ VB2時代は「メモリ使用量を節約する」とかで推奨されてたみたいです(そう言う自分はVB4からだけど) RLEとはなにかというと、簡単に言えば同じ物の繰り返しを最初の物とその繰り返し数で表記するものなんだけど。 「3+3+3+3+3」を「3*5」と表すようなもんだね。 実際BMPファイルのRLE圧縮は結構複雑なんだけどね。 2次元データの圧縮だし。 もちろん縦の部分は横に続いていると考えればいいんだけど・・・ よりよい圧縮はそれだけだと難しいと。 まぁ、今回のような1次元的なデータなら端からやっていけばいいから簡単だね。 んで、ここで繰り返し数を表すデータをByte・Integer・Longのどれにするかが問題。 これで一気に圧縮効率が変るんで。 「最初のデータ(1バイト)+繰り返し数(1・2・4バイト)」の羅列なんでね。 実際はゲームは30FPSで動くため、Byte型の範囲を超えるには8秒以上押しているボタンを変えない状況にしないといけないと。 シューティングゲームで8秒以上同じボタンを押しつづけるのは早々無いでしょということでByteに決定。 もし255を越える様なら、たとえば600=255*2+90であれば、キーのデータがA3のとき、 「A3 FF A3 FF A3 5A」とすれば結果的に「600フレームA3のデータが続く」ということになるんで。 ということで、1つの命令の塊が2バイトになってると。前回のは1フレーム1バイトだったからね。 つまり、1秒30フレーム中平均15回以上キー操作をしない限り圧縮できると。 Integerだと10回、Longだと5回と一気に効率下がるしね。 んで、組んだプログラムはこんなところだね。 (SaveReplayFile関数の一部) ks = .KeySet(0) offset = 0 For i = 0 To .Length - 1 If ks <> .KeySet(i + 1) Then '長さが255以上の場合 If i - offset > 255 Then For j = 1 To (i - offset) \ 255 diff = 255 Put 1, , ks Put 1, , diff Next End If diff = (i - offset) Mod 255 Put 1, , ks Put 1, , diff ks = .KeySet(i + 1) offset = i End If Next '最後 '長さが255以上の場合 i = .Length If i - offset > 255 Then For j = 1 To (i - offset) \ 255 diff = 255 Put 1, , ks Put 1, , diff Next End If diff = (i - offset) Mod 255 Put 1, , ks Put 1, , diff (LoadReplayFile関数の一部) offset = 0 Do Get 1, , ks Get 1, , diff For i = offset To offset + diff .KeySet(i) = ks Next offset = offset + diff Loop While offset < .Length どっちかと言えば読みこみの方が255フレーム以上の処理を特にする必要が無いからラクだね。 このぐらいの圧縮ルーチンなら10分程度で作れるしね(<デバッグにさらに20分) めざせLHA・ZIP・RAR? これで下の「リプレイデータ+Rnd関数編」を見ればあなたのゲームにもリプレイ機能が!(^^;) |
99/12/22 | リプレイ+Rnd関数編[★★◎◎◎] |
Micro Warになんとなくリプレイ機能をつけてみたくなった。 リプレイに必要なのは勿論キー操作の記録だけど・・・ 結構メンドウなのが乱数の再現。 Micro Warは結構敵をランダムに出すように指定しているため、VBのRnd関数を内部で頻繁に呼んでます。 これをどう再現するか。 まぁ「自分で乱数を生成する関数を作る」っていうのがいいんだろうけどね。 乱数の生成は結構単純な方法でできるので(ということは、正確にはかなり分布が均一に散らばっているだけの規則的な数値の羅列という考え方もできるけど、ゲームに使う程度ならそこまで不規則性にこだわらなくてもいいし)それでもいいんだけど・・・ 計算に使う定数値をあんまり変な値にしたりしちゃうと、同じ値から抜けられなくなっちゃう危険性とかもあるんで・・・ ってなわけで、VBのRnd関数でがんばる。 Rnd関数の部分についてのヘルプをみると、Rndの引数について 負−引数に応じた(常に同じ)値を返す。 0−前回と同じ値を返す。 正・省略時−前回の乱数値を利用して新しい乱数を生成する。 んで、さらにRandomizeステートメントについて 省略時−タイマーをシード値とする。 入力時−入力した数値をシード値とする。 これらから導かれる結論。 「最初Rnd値を入れる数値さえ決めればあとは引数無しのRnd関数は同じ順番で数値の羅列を出力して行く」ということです。 ってなわけで。 最初にRnd値に入れる数値が問題。 ここにある程度ランダムな値を入れないと・・・ってことなんだけど、そこにタイマーの値を使えばいいと。 timeGetTimeの値の符号をひっくり返せばOK。 If ReplayMode = 0 Then Seed = timeGetTime Call Rnd(-Seed) Else Call Rnd(-Seed) End If 上はリプレイデータ作成時、下は再生時です。 あとはキー操作を順次記録して行けばOK。 余談だけど、再生直前にSeedの値を変えると当然ながら敵のパターンが変ってしまってすぐ死にます(^^;) |
99/12/16 | リモートホスト+α編補記[★★★◎◎◎] |
結局四則演算で10にするプログラムは改良を加えて掛け算割り算を先に計算できるようにし、さらに分数にも対応しました。 これでますます友人向けでなくなったこと間違いなし(^^;) |
99/12/14 | リモートホスト+α編[★★★◎◎◎] |
ちょくちょくPerlでのCGIを試してる。 で、環境関数を全部表示するプログラムを作ってみたんだけど・・・ ありゃ?リモートホスト名がでない。 掲示板とかで出てるところもあるのになぁ・・・ $ENV{'REMOTE_HOST'}で得られるはずなのに・・・? しょうがないんでどっかで見つけたコードを見てみる。 getaddrbyhost?なんじゃそりゃ? ってなわけで実装。 大学のHPスペースではカウンタやアクセスログとかは色々試してるんだけどね。 今度掲示板も試さないと。 いや、最低限度の機能の掲示板は作ったことあるけどね。 あとは画像連結系カウンタだね。 現在のは5回もIMGタグでCGIよんでるし・・・内部で毎回ファイルのデータを読みこむから良くない。 SSI使えばいいんだけど基本的にはVectorにページを置きたいからムリだし。 大学のHPスペースは家から操作するのはかなり面倒・・・かといってAlpha-NetはCGIは毎回チェックをうけないとアップできないし。 こっそり大学のサーバーからアクセスログとかつけようかなぁ・・・(を 今回の計算機プログラミングの課題が魔方陣のチェックと4桁の数字を四則演算で10にする(よく切符の番号とかでやるやつね。並べ替え・計算順・分数はなしだけど)プログラム(こっちは総当りでいいんだけど) また自分の癖があふれるプログラムになってしまってます(^^;) 四則演算の方なんてnum[0]-num[size-1]の各数字の配列とope[0]-ope[size-2]の演算子用のデータ配列を使って2-10桁対応にしてるしね。 一応友人とか用に大学のHPスペースにはこっそりのっけてあるけど今回の計プロの授業ではじめてプログラムをやった人にとっては無駄に難しいから読みにくいだろうな。 |
99/11/20 | VBでCGI編[★★★◎◎] |
注:やっぱりVBではうまく行ってないです。 PerlでCGIをちょくちょく試す今日この頃。 どうも「標準出力に文字を出力できればCGIが作れる」ということなので、早速VB・VCで挑戦。(^^;)<単純 まずVC。 もちろんIO Streamを使えばよし。 既存のPerl製「掲示板にアクセスできないエラー」を表示するコード(^^;)を移植してみる。 元のPerlのコード。 print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>掲示板</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>掲示板</H1>\n"; print "<HR>\n"; print "只今、掲示板が混雑しております。しばらくお待ちの上、"; print "再度アクセスお願いします。\n"; print "<HR>\n"; print "</BODY>\n"; print "</HTML>\n"; なんかこうしてみるとprint文の感じがBasic風、エスケープシーケンスやセミコロンがC風だな。 んでCのコード。 #include <iostream.h> void main() { cout << "Content-type: text/html" >> endl; cout << "" >> endl; cout << "<HTML>" >> endl; cout << "<HEAD>" >> endl; cout << "<TITLE>掲示板</TITLE>" >> endl; cout << "</HEAD>" >> endl; cout << "<BODY>" >> endl; cout << "<H1>掲示板</H1>" >> endl; cout << "<HR>" >> endl; cout << "只今、掲示板が混雑しております。しばらくお待ちの上、"; cout << "再度アクセスお願いします。" >> endl; cout << "これでOKだね。" >> endl; cout << "<HR>" >> endl; cout << "</BODY>" >> endl; cout << "</HTML>" >> endl; } これはもちろんうまくいきました、ええ。 いや、MS-DOSウインドウだけでなくCGIでもね。 NT系のサーバーでないと実行できないけどね。 っていうか今の状態で利用できる唯一のNT系サーバーのAlpha-NetはCGIはPerlしか認めてないんだけどさ。 んで、VB。 最初はAllocConsoleしてからGetStdHandleしてWriteConsole・ReadConsole・・・・・ うーむ。 エラーとかは起きないけど・・・ 実行時に別コンソールウインドウを開いてしまう・・・ AllocConsoleがマズイのかなと思い、消すともちろん動かない。 MS-DOSウインドウから実行してももう一つウインドウ作っちゃうしなぁ・・・ どうやってそのまま標準出力のハンドルを受け取るんだろう・・・ と、思い、VCで作った際のプロジェクトのプロパティを見てみると・・・ どうやらリンカに「/SUBSYSTEM:CONSOLE」が送られてることが原因らしいのでVBでリンカに送られてるコマンドラインを調べてみると・・・ やっぱり「/SUBSYSTEM:windows,4.0」になってた・・・ これを「/SUBSYSTEM:CONSOLE」にし、さらにコンソールウインドウを作成する必要がないんでAllocConsoleおよびFreeConsoleを外す。 ひとまずMS-DOSウインドウではうまく行った。 ただ、CGIとしてはうまく働かない・・・ 「認識されない応答をサーバーが返しました」だと。 うーん、なんでだろ。 ちゃんと「Content-type: text/html」も返してるのになぁ・・・ ひとまずカウンタもどきなプログラムにもしてこんなんなりました。 だれか完全なCGIにしてくれ(^^;) 'API・定数の宣言 Public Declare Function AllocConsole Lib "kernel32" () As Long Public Declare Function FreeConsole Lib "kernel32" () As Long Public Declare Function WriteConsole Lib "kernel32" Alias "WriteConsoleA" (ByVal hConsoleOutput As Long, ByVal lpBuffer As String, ByVal nNumberOfCharsToWrite As Long, lpNumberOfCharsWritten As Long, ByVal lpReserved As Long) As Long Public Declare Function ReadConsole Lib "kernel32" Alias "ReadConsoleA" (ByVal hConsoleInput As Long, ByVal lpBuffer As String, ByVal nNumberOfCharsToRead As Long, lpNumberOfCharsRead As Long, ByVal lpReserved As Long) As Long Public Declare Function GetStdHandle Lib "kernel32" (ByVal nStdHandle As Long) As Long Public Declare Function SetConsoleMode Lib "kernel32" (ByVal hConsoleHandle As Long, ByVal dwMode As Long) As Long Public Declare Function GetConsoleMode Lib "kernel32" (ByVal hConsoleHandle As Long, lpMode As Long) As Long Public Const STD_INPUT_HANDLE = -10& Public Const STD_OUTPUT_HANDLE = -11& Public Const ENABLE_LINE_INPUT = 2& '書きこみ・読みこみ用ハンドル Dim wHandle As Long, rHandle As Long Dim isNewConsole As Boolean Sub StartConsole(ByVal NewConsole As Boolean) 'コンソール初期化 Dim t As Long 'コンソール作成 isNewConsole = NewConsole If isNewConsole Then Call AllocConsole wHandle = GetStdHandle(STD_OUTPUT_HANDLE) rHandle = GetStdHandle(STD_INPUT_HANDLE) '読みこみをラインモードにする Call GetConsoleMode(rHandle, t) t = t Or ENABLE_LINE_INPUT Call SetConsoleMode(rHandle, t) End Sub Sub EndConsole() 'コンソール終了 If isNewConsole Then Call FreeConsole End Sub Sub DrawConsole(ByVal st As String) '書きこみ Dim t As Long Call WriteConsole(wHandle, st + vbCrLf, LenB(StrConv(st + vbCrLf, vbFromUnicode)), t, 0) End Sub Function GetLineConsole() As String '1行読みこみ Dim s As String, t As Long, a As Long s = String(1024, " ") Call ReadConsole(rHandle, s, 1024, t, 0) a = InStr(s, vbCrLf) If a = 0 Then GetLineConsole = s Else GetLineConsole = Left(s, a - 1) End If End Function Sub Main() 'コンソール開始 '新しいコンソールウインドウは作らない Call StartConsole(False) '順次出力 DrawConsole "Content-type: text/html" DrawConsole "" DrawConsole "<HTML>" DrawConsole "<HEAD>" DrawConsole "<TITLE>CGIのサンプル</TITLE>" DrawConsole "</HEAD>" DrawConsole "<BODY>" DrawConsole "<H1 align=""center"">CGIのサンプル</H1>" DrawConsole "<HR>" DrawConsole "VBで出力しています。<BR>" DrawConsole "あなたは" + CStr(GetCount) + "番目にアクセスしました。<BR>" DrawConsole "上の値は実行するたびに増えて行きます。<BR>" DrawConsole "<HR>" DrawConsole "</BODY>" DrawConsole "</HTML>" '終了 '実際はStartConsoleの引数がFalseの場合はやる必要ないです Call EndConsole End Sub Function GetCount() As Long 'カウンタの値 Dim cnt As Long Dim fn As String Dim temp As String fn = App.Path If Right(fn, 1) <> "\" Then fn = fn + "\" fn = fn + "count.dat" 'ファイルが存在する If Dir(fn) <> "" Then Open fn For Binary As 1 Line Input #1, temp cnt = CLng(temp) Close End If '値を増やす cnt = cnt + 1 'ファイルに書きこむ Open fn For Output As 2 Print #2, cnt Close GetCount = cnt End Function これもMS-DOSウインドウではうまく行くんだけどねぇ・・・ だれか解明してくれ(^^;) |
99/10/30 | 新しい言語編[★★◎◎] |
最近さらに2つの言語をはじめることになった。 一つは「計算機プログラミングT」なる授業で扱うことになったJava。 もう一つはホームページ作成の上でのPerl。 これでチョットでもかじったことのある言語は習得順でVB、C++、Basic(C++とどっちが上かな・・・)、VBScript、Intel系アセンブラ(この位置でいいのかな)、CASLアセンブラ、JavaScript、Java、Perlとなるね。 こんなにやる必要あるのかなぁ・・・・ さすがにFortranやCobolはやらないけどさ(^^;)Pascalはありえるかもね・・・Delphiとか。 んでJava。 まぁ前学期「情報処理」の授業ではプログラミングは扱わなかったので基本から。 しかしJavaは規格がまだまだ変動しそうで大学の授業には向かないと思うんだがなぁ・・・ 実際「計算機プログラミングU」ではC++を使うことになるらしいし。 いくらマルチプラットホームって言ってもねぇ・・・ せっかくなんでVisual J++ v1.0 PEをインストールして自宅でもJavaれる用にしたけど。 もちろんアプレットなんてまだつくらないよ(^^;) 初回の授業は、最後の演習問題が「引数として辺の数をうけとって対角線の数を返す」だった。 んで2回目。 今度はdouble型を使うけど・・・文字列からの変換ってメンドイね。 しかも標準入力から文字列を受け取るのね。 BufferedStream data = new BufferedStream(new InputStreamBuffer(System.in)); とかいうワケワカなオブジェクトを使うし。 doubleへの変換に、 double = new Double(Strings).doubleValue() ってしないと行けない。 VBならdouble = CDbl(String)でいけるのにね。 っていうかPerlならそれすら必要ないけどさ。 まぁオブジェクトやそのメソッドはわからないながらもプリントを見つつ再び即行で宿題を作成し、出す(←授業開始15分) ところが「球の表面積&体積」なプログラムなのにクラス名を間違えて「Cube」にしちゃったし。 これじゃ立方体(^^;) んでPerl。 部活のホームページ係になったのね。 まぁパソコン系のサークルでもなくれっきとした運動部なんでできる人がやるってことなんで。 んで、引継ぎはいつになるかわかんないんだけど、一応CGIはある程度使えるようにした方がいいのかなぁということでPerlに挑戦。 大学のサーバーは外部からのアクセスはなんかメンドウな手続きが要るし、どの道大学のHPスペースは学内からしかFTPできないんでCGIのテストができない(っていうかPerlもない)んで、しょうがなくCGIをローカルで作成する環境作成に取りかかる。 っていうかActivePerlとAN HTTPDをいれるだけなんだけどね。 んで、とあるページから非常にシンプルでわかりやすい掲示板のプログラムを手に入れる。 ふむ、Perlはよくわからなくても見てればだいぶやりたいことはわかる。 ってんわけで改良を加えたりしていじる。 んで、次に取りかかったのがカウンタ。 これがツライ・・・ 大学内ではSSIも使えるみたいだけど、ローカルではうまく作動しない・・・ かといってGIF連結がPerlだけだとうまく行かない。 (2・3個連結系プログラムを見たけどアニメーションGIFを利用してる形式だったし。) んで、しょうがなくテキストで表示する形式を取ってみる。 っていうかこれもSSIならメチャクチャラクっぽいんだけどね。 #HTML内のをカウンタ番号に置換するコード open(CNT,"counter.dat"); $count = close(CNT); $count++; open(CNT,"> counter.dat"); print CNT "$count"; open(IN,"index.html"); @data= close(IN); for($a=0;$a < @data;$a++){ $data[$a] =~ s/<!--COUNTERNUMBER-->/$count/g; } print "Content-type: text/html\n"; print "\n"; print @data; 配列処理に関しては結構融通が利くね。 なんかforeachがよくわからなかったからforを使ったけど。 本サイト用のアクセスログチェックCGIを付けよっかなぁ・・・ ただ、CGIに関しては、 大学−大学内でないと利用できない&FTPできない。 Alpha-net−チェックを受けないと利用不可。 Nifty−利用不可。(掲示板・カウンタ・メールデコードは提供) Vector−利用不可。(カウンタは提供) IidaNet−利用不可。 まぁしばらくはローカルでがんばるか。 http://127.0.0.1/ ばかり入れる日が続く・・・ |
99/10/08 | DirectX for VB編−その1[★★★◎◎] |
いやぁ、帰省してる間に出ちゃいましたね、DirectX7 SDK。 ついにDirectX for VB(以後DX for VB)の正式公開というわけ。 んで、早速DL。 って128MBもある・・・(^^;) しかも1回目DLしたものはファイルに破損があったらしくZIPの解凍が不可能・・・ あきらめて2度目のDL。 そう言えばVB5のSPを1つも入れた記憶が無かったのでSP3をDL。 なんか128MBを2回も落としてると13MBなんてたいしたことが無いように思えてくるな(^^;) んでサンプルを見てみる。 ふむふむ。 どうやらタイプライブラリを使ってDX7VB.DLLを呼び出し、そこからそれぞれのDLLを呼び出してるみたいだね。 まぁそのライブラリもDX7VB.DLL内部にあるらしいけどね。 DX7VB.DLLの内部で何をやってるかわからないけど、マニュアルを見るとVB向けの物はメソッドが少し増えてる。 DirectDrawSurface7::DrawLineとかね。 まぁどうせ内部でGetDC・MoveToEx・LineTo・ReleaseDCしてるだけだろうけどね。 少しAPIの知識がある人なら自分でGetDCを行って一度にすべての線とかを書いて最後にReleaseDCをした方が速いってことはわかるだろうね。 実際GetDC・ReleaseDCは遅いらしいし。 ただ、便利なのはいきなりBMPファイルとかからサーフェスに画像を読みこめるってことかな。 C++だとddutil.cにある関数を使わないといけなかったので。 んで、試しにDToolsをがんばってBASにしようと計画。 ひとまずDTInitだけやりました。 しかしここで問題が・・・ パレット関連で、通常ではDTGetPaletteEntriesとかのようにポインタとPALETTEENTRYの数を送るんですが・・・ DX for VBではこれをVariant型として送るんだよね・・・ しかし、DX for VBはVB5 SP3入またはVB6なら扱えるはずなのに、これだと「ユーザー定義型の配列をバリアント型引数として送ることはできません」だと。 ここらへんは無理してポインタを使ってくれたほうがよっぽど扱いやすいよなぁ・・・ かと思ったらいろいろいじってたらできた(^^;) また何かあったら書くよん。 |