DirectDrawによるビデオメモリアクセス

DirectDrawは、ビデオカードのビデオメモリに直接アクセスするバッファ(サーフェイス)を提供します。今表示されている画面を記憶したビデオメモリ領域は「プライマリサーフェイス」と呼ばれ、ここに直接ピクセルの情報を書きこむ事でその結果はただちに画面に反映されるわけです。さらにDirectDrawには、プライマリサーフェイスと同形式のバックバッファを複数とっておきそのバッファを一瞬のうちにプライマリサーフェイスに「フリップ」(切り替え)する機能もあるので、Windowsでも高速かつちらつきのない「ダブルバッファ」を実現できるようになりました(もっとも、これはDIBSectionでもある程度可能でしたが)。このような特長を活かして、最近では多くのゲームがDirectDrawベースになっていますね。
 今回は、このDirectDrawを使ってビデオメモリに書きこんでみます。

DirectDrawの使い方

 DirectDrawを使うには、まずDirectDrawオブジェクトを作成し初期化します。作成にはDirectDrawCreateという関数を使い、この関数にLPDIRECTDRAW型ポインタのアドレスを渡すと、DirectDrawオブジェクトが生成されそのアドレスが関数に渡したLPDIRECTDRAW型ポインタに入るようです。
 次に、DirectDrawオブジェクトがどんな環境で動くかを設定します。DirectDrawオブジェクトには、通常のアプリケーションと同様に「ウインドウ」の中で動くウインドウモードと全画面を占有する全画面モードがあるので、ここでどちらで動くかを設定するわけです。動作モードが全画面なら、他のアプリケーションとは排他的に実行するDDSCL_EXCLUSIVEも同時に指定する事になるでしょう。この指定をしておけば、DirectDrawオブジェクトの関数SetDisplayModeを使って画面の大きさと色数を設定できるようになります。この関数は、3つの引数をとり順に幅・高さ・色数(ビット数)を指定するようになっています。

  DirectDrawCreate(NULL,&lpDD,NULL ); // DIRECTDRAWオブジェクト作成
  lpDD->SetCooperativeLevel(hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN ); // 動作モード設定
  lpDD->SetDisplayMode(640,480,8); // 画面を640*480、256色モードに設定

 DirectDrawオブジェクトを作成したら、次はビデオメモリにアクセスるためのプライマリサーフェイスを作りましょう。サーフェイスの作成には、DirectDrawオブジェクトのCreateSurface関数を使います。この関数は、引数にDDSURFACEDESC型構造体のアドレスやプライマリサーフェイスへのポインタをとるので、呼び出す前にDDSURFACEDESC型構造体を作成しておいてください。

  FillMemory(&ddsd,0,sizeof(ddsd)); // DDSURFACEDESC構造体初期化
  ddsd.dwSize=sizeof(ddsd); 
  ddsd.dwFlags=DDSD_CAPS; 
  ddsd.ddsCaps.dwCaps=DDSCAPS_PRIMARYSURFACE;
  lpDD->CreateSurface(&ddsd,&lpDDSPrimary,NULL ); // プライマリサーフェイス作成

 今回は、画面の色数が256色でしたので、パレットの設定も行います。 パレットの設定はPALETTEENTRY型配列を作成してDirectDrawオブジェクトのCreatePalette関数でパレットを作り、そのパレットをサーフェイスのSetPalette関数でパレットとして設定するだけです。

  lpDD->CreatePalette(DDPCAPS_8BIT,pePal,&lpDPalette,NULL); // パレット作成
  lpDPalette->SetEntries(0,0,256,pePal); // パレットの設定範囲指定
  lpDDSPrimary->SetPalette(lpDPalette); // サーフェイスにパレット設定

プライマリサーフェイス(ビデオメモリ)へのアクセス

 プライマリサーフェイスにアクセスするには、まずサーフェイスを「ロック」します。これで、サーフェイスへのポインタをロックする時に渡したDDSURFACEDESC構造体のメンバ変数lpSurfaceから取得できるようになるので、後は読み書きするだけです。例えば、1ピクセル1バイト(8ビット)で640×480のサーフェイスなら、先頭からx+y×640を足したアドレスに書きこめば(x、y)のピクセルを書きかえられる事になります。なお、y方向の座標は、GDI同様上から下に増えていくようです。DIBとは逆なので、DIBファイルをそのまま転送する時などは、要注意ですね。
 アクセスし終わったら、サーフェイスを「アンロック」して処理を終えましょう。

  lpDDSPrimary->Lock(NULL,&ddsd,DDLOCK_WAIT,NULL); // サーフェイスをロック
  BYTE* lpVideoMemory=(BYTE*)ddsd.lpSurface; // サーフェイスへのポインタ取得

  lpVideoMemory[x+y*640]=0; //(x,y)に0を書きこむ

  lpDDSPrimary->Unlock(NULL); // サーフェイスをアンロック

 アプリケーション終了時には、サーフェイス・DirectDrawオブジェクトともRelease関数で解放するようにしてください。

  case WM_DESTROY : // 終了処理

      lpDD->RestoreDisplayMode(); // 画面モードを元に戻す

      if (lpDDSPrimary) // サーフェイス解放
          lpDDSPrimary->Release();

      if (lpDD) // DirectDrawオブジェクト解放
          lpDD->Release();

プログラム

 今回のプログラムは、640×480・8ビットのプライマリサーフェイスにランダムに点を打つ関数drawをメインループから呼び出すものです。実行すると、アプリケーションが全画面を覆いランダムに点を打っていきます。終了する時には、エスケープキーを押してください。
 なお、drawの中のWaitForVerticalBlankは、垂直帰線期間の開始や終了を待つ関数で、これを使うと処理の周期を一定にしたりちらつきのないスクロールを実現する事が出来ます。

  void draw(void) {

      lpDDSPrimary->Lock(NULL,&ddsd,DDLOCK_WAIT,NULL );
      BYTE* lpVideoMemory=(BYTE*)ddsd.lpSurface;

      lpDD->WaitForVerticalBlank(DDWAITVB_BLOCKBEGIN,0);
      lpVideoMemory[rand() % 640+(rand() % 480)*640]=rand() % 256;

      lpDDSPrimary->Unlock(NULL);

  }

 プログラムをコンパイルする時には、DirectX5以上のSDKをインストールする必要があります。ソースをダウンロードしたら、拡張子をcppにしてプロジェクトの設定でddraw.libをリンクしてからビルドしてください。

プログラムソース表示


Windowsプログラミング実験室 > プログラミング資料庫