サンプルコードのダウンロード

では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、
お持ちの方はVisual C++でプロジェクトファイルを開いてください(「ddraw_05.dsw」をダブルクリックすれば開けます)。
圧縮ファイルに含まれる「ddraw_05.exe」をダブルクリックし、実行してみてください。
どうでしょう?画面が切り替わり、フルスクリーン化し、「フロントバッファ」と「バックバッファ」が交互に表示されると思います。
今回は、バックバッファ(複合サーフェイス)を作成し、ページフリップを行いたいと思います。

バックバッファ作成

バックバッファを作成するには、プライマリサーフェイスを複合プライマリサーフェイスとして作成します。複合プライマリサーフェイスとして作成すると
、プライマリサーフェイス以外に、指定した数のサーフェイス(バックバッファ)が自動作成されます。
その後、バックバッファ(サーフェイス)の情報を取得すれば、
バックバッファへ描画する事ができます。

では、まず複合プライマリサーフェイスを作成します。

	//複合プライマリサーフェイスの作成
	ZeroMemory(&ddsd,sizeof(ddsd));
	ddsd.dwSize=sizeof(ddsd);
	ddsd.dwFlags=DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
	ddsd.ddsCaps.dwCaps=DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX;
	ddsd.dwBackBufferCount=1;
	
	if(lpDD->CreateSurface(&ddsd,&lpFront,NULL)!=DD_OK)
		return FALSE;

第3回でも記述した通り、サーフェイスを作成するにはDDSURFACEDESC構造体のdwFlagに使用するメンバを指定します。
プライマリサーフェイスだけなら、DDSD_CAPSだけでも良いのですが、今回は複合プライマリサーフェイスとしてバックバッファも作成するので
DDSD_BACKBUFFERCOUNTも同時に指定します。そうしたらdwBackBufferCountに1を指定します。
ここは作成するバックバッファの数を指定するのですが、今回は1つだけ作成します。
バックバッファは複数あると、管理しにくくなるという私の独断です(゜゜)☆O(–;)oばこ

さて、ddsCaps.dwCapsは3つ指定しています。DDSCAPS_PRIMARYSURFACEは、作成するサーフェイスがプライマリサーフェイスである事、DDSCAPS_FLIPは、フリッピングを行えるサーフェイスである事、DDSCAPS_COMPLEXは複合サーフェイスである事をそれぞれ表します。
というか、ここらは応用のしようが無いので丸暗記で結構です。

この後、lpDD->CreateSurface(&ddsd,&lpFront,NULL);を実行すると、同時にフロントバッファ、バックバッファを作成し、lpFrontにはフロントバッファ(兼プライマリサーフェイス)が格納されます。
で、これだけではバックバッファへ描画できないので、次にバックバッファを取得します。

	ddscaps.dwCaps=DDSCAPS_BACKBUFFER;           
	if(lpFront->GetAttachedSurface(&ddscaps,&lpBack)!=DD_OK)
		return FALSE;

GetAttachedSurfaceを使用すると、DDSCAPS構造体のメンバdwCapsへ指定した能力を持つサーフェイスを取得する事が出来ます。
ですから、この関数を使用して、さきほど作成したバックバッファを取得します。ちなみに構文は以下の通りです。

書式 HRESULT GetAttachedSurface( LPDDSCAPS lpDDSCaps, LPDIRECTDRAWSURFACE FAR *lplpDDAttachedSurface );
lpDDSCaps 取得したいサーフェイス情報を詰め込んだDDSCAPS構造体へのポインタ。
今回はddscapsのアドレスを引き渡しています。
lplpDDAttachedSurface 取得したサーフェイスを格納する変数へのポインタ。
今回はlpBackのアドレスを引き渡す。
戻り値 成功した場合はDD_OKが返ってくるらしい。

これで、lpBackにサーフェイスを取得出来ました。また、このサーフェイスは、プライマリサーフェイス等と同じような扱いが出来ます。
ですから、GDIを使用して描画すると言った事も、なんなく出来ます。

フリッピング

では、コードを見てみましょう。

void Flip(void){
	lpFront->Flip(NULL,DDFLIP_WAIT);
}

フリップを行うと、フロントバッファとバックバッファに描かれている内容がそっくりそのまま入れ替わります。
ですから、バックバッファにいろいろ書き込んでおき、フリップを行うと、フロントバッファとバックバッファの内容が入れ替わるので書き込んだ内容が画面に瞬時に表示されます。
んで、もう一度フリップすると、また入れ替わり、元の状態に戻ります。では、Flipの構文を見てみましょう。

書式 HRESULT Flip( LPDIRECTDRAWSURFACE3 lpDDSurfaceTargetOverride, DWORD dwFlags );
lpDDSurfaceTargetOverride サーフェイスを示すポインタです。ここは次の順番のバックバッファ以外のバッファにフリップする時に使用するらしいのですが、普段はNULLを指定して順番通りにフリップさせます。
dwFlags フリップオプションを指定するフラグです。ここはDDFLIP_WAIT固定と考えて良いでしょう。これは、何らかの原因によって直ぐにフリップ出来ない場合に、フリップ出来るまで待つという指定です。
戻り値 成功した場合はDD_OKが返ってくるらしい。

このFlipは、複合サーフェイス(フロントバッファ)のメンバとしてのみ呼び出すことが出来ます。
ですからlpBack->Flip・・・と呼び出すことは出来ません。

フリップは、垂直帰線中(走査線が右下から左上に戻る間)に瞬時に行われるため、ちらつく事がありません。
ちなみに、垂直帰線を待ってからフリップを行うという事なので、ディスプレイの構造上、フリップは秒間60回程度行うのが限界です。

後始末について

後始末は、今まで通りです。なんとなく「lpBack->Release();」とやりたくなる人もいるかもしれませんが、必要ありません。
というかコレ実行するとフリーズするので良い子はマネしないでね(謎。
バックバッファは複合サーフェイスとしてフロントバッファと同時に作成されたため、フロントバッファさえ解放してしまえば同時にバックバッファも解放される事になります(たぶん)。

疑似タイマー処理

さて、今回のサンプルプログラムはどうでしょう?1秒間隔でフリップを実行していると思います。
普段、このようなタイマー処理は、APIを使用して一定時間毎にある関数を呼び出すように設定します。
ですが、これが結構面倒なので、今回は擬似的な処理をしてます。
まず、ビルド → 設定 → リンク → オブジェクト/ライブラリモジュールに「winmm.lib」を書き込む → OK をします。
そして、プログラムの最初のほうで「mmsystem.h」をインクルードします。これでマルチメディア系のAPIが使用できるようになります。
その中にはtimeGetTime();という関数が用意されており、これを呼び出した時点での時間がミリ秒単位で取得出来ます(1000ミリ秒=1秒)。
ですから、メッセージループ周辺を改造して、

	DWORD tim;
	while(1){
		//疑似タイマー処理
		tim=timeGetTime();			//①
		//フリッピング
		Flip();
		do{
			//メッセージループ
			while(PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)){
				if(!GetMessage(&msg,NULL,0,0)) 
					Quit();
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}

		}while(timeGetTime()<tim+1000);	//②
	}

とします。こうすると、1番で取得した時間から1000ミリ秒経つまで、ひたすらメッセージループを回す事になります。
ちなみに、こういう一定時間で処理する場合にメッセージループを忘れてしまうと、キー処理がなされなくなり、終了出来ない→電源強制切断という最悪の事態を招くことになります(逝。

記事検索

アーカイブ