DirectXで遊ぼう 上のページ

IDirectDrawSurface その4(復帰)
  1. 概要

    サーフェイスの復帰についてを解説しています。

    以下の内容について解説します。

  2. サーフェイスが無くなる?

    フルスクリーンモードで表示しているとき、アイコン化を行ったりするケースがあります。 これを行うと、DirectDrawはいったん生成していたサーフェイスを開放してしまう事があるようです。 そうしないと他のアプリケーションを表示する為に必要なVRAMを確保する事ができないので、こうしてしまうそうです。
    この現象は、描画関数を呼び出した時に確認できます。
    具体的には以下コード中にある、※1の部分です。
    RECT srect = {....};
    RECT drect = {....};
    HRESULT r;
    
    r = ddsurface->Blt(&drect,dd_image,&srect,DDBLT_WAIT,NULL);
    if(r != DD_OK){
        switch(r){
        case DDERR_SURFACELOST:     // ※1
            //
            // ※2
            //
            break;
        case ....
             ....
        }
    }
    
    アイコン化を防ぐために、ウィンドゥ生成時にWS_MINIMIZEBOXフラグを外したり、 DirectDrawの制御モード設定(SetCoopertiveLevel)時にCTRL+ESCキー動作を禁止させたりという方法もあるにはあるのですが、 あまりクールな方法では無いです。
    これにはちゃんと回避方法(※2に記述するコード)があるので、これからそれを説明します。

  3. Restore関数

    DDERR_SURFACELOSTエラーが発生すると、以降の描画関数は必ずエラーが発生します。
    サーフェイスが失われてしまった為に起こるエラーですが、Restore関数を呼び出す事によってサーフェイスを再構築する事ができます。
    HRESULT r;
    
    r = ddsurface->Restore();
    if(r != DD_OK){
        //
        // Restore失敗(r:エラーコード)
        //
    }
    
    これでプライマリサーフェイスに関してはエラーを回避できます。 再度描画関数を呼び出してやると、正しく処理されるに違いありません。
    Restore関数でも失敗してしまったら?
    まずそういった事は無いと思います。もし起きたとしても、回避方法は分かりません (^^)

    さて、上記の文章のアンダーラインで強調してある部分です。
    実はこの方法ではオフスクリーンバッファとして構築したサーフェイスに関しては、完全に復帰した事にはなりません。 ただ単に領域が再確保されただけですので、描画を繰り返してもナゾの空白が画面を埋め尽くすだったりします。

  4. オフスクリーンバッファを再構築する

    次にオフスクリーンバッファの復帰方法です。
    やり方ですが、サーフェイス生成時に行った画像展開をここでも行ってやるのです。具体的なコードは以下の通り。
    HRESULT r;
    
    r = ddsurface->Restore();
    if(r != DD_OK){
        //
        // Restore失敗(r:エラーコード)
        //
    }else{
        //
        // 画像を再度展開する
        //
        DDSURFACEDESC dds;
        ZeroMemory(&dds,sizeof(DDSURFACEDESC));
        dds.dwSize = sizeof(DDSURFACEDESC);
        r = ddsurface->Lock(NULL,&dds,DDLOCK_WAIT,NULL);
        if(r != DD_OK){
            //
            // Lock失敗(r:エラーコード)
            //
        }else{
            //
            // ※ここに展開するコードを記述します
            //
            ddsurface->UnLock(NULL);
        }
    }
    
    の部分に、Lock関数で取得したDDSURFACEDESC構造体の情報を用いて展開を行ってやります。
    これで、本当にサーフェイスの復帰は完了しました。

  5. クラス化しちゃえ!

    とりあえず、以上のような方法でサーフェイスの復帰はできます。
    しかし! これで納得できますか? 私は納得できません……だって、面倒だもん。
    復帰の作業くらい、DirectDrawが内部でやってくれてもいいじゃないですか。 でも無いって言い張っているのが、現状のDirectDrawの仕様です。 だったら、自分で自動復帰できるようにを作ってしまいましょう。
    やり方としては、クラスを用います。
    以下、クラスヘッダー案。
    class CDDrawSurface{
    protected:
        LPDIRECTDRAW m_ddraw;           // DirectDrawのポインタ(引渡し)
        LPDIRECTDRAWSURFACE m_dds;      // サーフェイスのポインタ
        CHAR m_loadfile[MAX_PATH];      // ロードした画像のパス
    public:
        CDDrawSurface();
        ~CDDrawSurface();
        BOOL Create(LONG width,LONG height,LPDIRECTDRAW ddraw);
        BOOL LoadFile(LPSTR filename,LPDIRECTDRAW ddraw);
        BOOL Release();
        BOOL GetSurface(){return m_dds;}
        BOOL IsLost(){return m_dds->IsLost() == DDERR_SURFACELOST;}
        BOOL Restore();
    };
    
    上のクラスの要点としては、LoadFileとRestoreです。 他のメンバ関数は、他の章で説明した物が入ると思ってください。
    LoadFileメンバ関数は、こんな感じ。
    詳しく書くと、このページが異常にでかくなるのであえて書きません。流れだけ書いておきます。
    BOOL CDDrawSurface::LoadFile(LPSTR filename,LPDIRECTDRAW ddraw)
    {
        // ファイル名を覚えておく
        lstrcpy(m_loadfile,filename);
    
        // ファイルのロード
    
        // DirectDrawSurfaceの生成
    
        // サーフェイスに画像を貼りつける
    
        return TRUE;
    }
    
    さて、問題のRestoreメンバ関数です。
    前節では何だか面倒な事やってましたけど、クラスならこんな感じにするだけで事足ります。
    こっちは、全て書いておきます。
    BOOL CDDrawSurface::Restore()
    {
        if(m_dds->Restore() != DD_OK)return FALSE;
    
        return LoadFile(m_loadfile,m_ddraw);
    }
    
    以上。マジです。これだけあれば、やりたい事は全て事足りてます。
    使用は IsLostメンバ関数でサーフェイスのロストをチェックして、ロストしてればRestoreするってな具合。 これでもう、サーフェイス復帰はもう怖くありませんね。



上のページ