BREWアプリを Windows へ移植(8)

IDisplay の実装について。


IDisplay はテキスト表示とかフォントとかありますが、その辺はとりあえず無視して、IDISPLAY_Update() や IDISPLAY_SetDestination(), IDISPLAY_GetDestination(), それから IDISPLAY_CreateDIBitmap() を実装していきます。


まずはヘッダの中身から。

class WinDisplay : public IDisplay{
public:
    WinDisplay();
    ~WinDisplay();
private:
    static uint32   AddRef(IDisplay*);
    static uint32   Release(IDisplay*);

    static int      GetFontMetrics(IDisplay * po,AEEFont nFont,int * pnAscent,int * pnDescent);
    static int      MeasureTextEx(IDisplay * po, AEEFont nFont, const AECHAR * pcText,int nChars,int nMaxWidth, int * pnFits);
    static int      DrawText(IDisplay * po,AEEFont nFont, const AECHAR * pcText,int nChars,int x,int y,const AEERect * prcBackground,uint32 dwFlags);
    static void     DrawRect(IDisplay * po,const AEERect * pRect,RGBVAL clrFrame, RGBVAL clrFill, uint32 dwFlags);
    static void     BitBlt(IDisplay * po,int xDest,int yDest,int cxDest,int cyDest,const void * pbmSource,int xSrc,int ySrc,AEERasterOp dwRopCode);
    static void     Update(IDisplay * po, boolean bDefer);
    static void     SetAnnunciators(IDisplay * pd, uint16 wVal, uint16 wMask);
    static void     Backlight(IDisplay * po,boolean bOn);
    static RGBVAL   SetColor(IDisplay * po, AEEClrItem clr, RGBVAL rgb);
    static AECHAR   GetSymbol(IDisplay * po,AEESymbol sym, AEEFont nFont);

    // Helper routines - buttons, frames

    static int      DrawFrame(IDisplay * po, AEERect * prc,AEEFrameType ft, RGBVAL rgbFill);

    static int      CreateDIBitmap(IDisplay *po, IDIB **ppIDIB, uint8 colorDepth, uint16 w, uint16 h);
    static int      SetDestination(IDisplay *po, IBitmap *pDst);
    static IBitmap* GetDestination(IDisplay *po);
    static int      GetDeviceBitmap(IDisplay *po, IBitmap **ppIBitmap);
    static IFont*   SetFont(IDisplay *piDisplay, AEEFont nFont, IFont *piFont);
    static void     SetClipRect(IDisplay *piDisplay, const AEERect * pRect);
    static void     GetClipRect(IDisplay *piDisplay, AEERect * pRect);
    static int      Clone(IDisplay *po, IDisplay **ppIDisplayNew);
    static void     MakeDefault(IDisplay *po);
    static boolean  IsEnabled(IDisplay *po);
    static int      NotifyEnable(IDisplay *po, AEECallback *pcb);
private:
    IDisplayVtbl        _vtbl;
    uint32              _ref;

    IBitmap*            _dest;
};

_dest は、このディスプレイにセットされている IBitmap です。
デフォルトではデバイスのビットマップがセットされています。
バイスのビットマップはアプレット内で単一なので、WinApplication が保持しています。
なので、SetDestination(), GetDestination(), GetDeviceBitmap() は次のようになります。

int      WinDisplay::SetDestination(IDisplay *po, IBitmap *pDst){
    if( ((WinDisplay*)po)->_dest == pDst ){
        return SUCCESS;
    }
    if( pDst == NULL ){
        IDISPLAY_GetDeviceBitmap( po , &((WinDisplay*)po)->_dest );
    }else{
        IBITMAP_Release( ((WinDisplay*)po)->_dest );
        ((WinDisplay*)po)->_dest = pDst;
        IBITMAP_AddRef( pDst );
    }
    return SUCCESS;
}
IBitmap* WinDisplay::GetDestination(IDisplay *po){
    IBITMAP_AddRef( ((WinDisplay*)po)->_dest );
    return ((WinDisplay*)po)->_dest;
}
int      WinDisplay::GetDeviceBitmap(IDisplay *po, IBitmap **ppIBitmap){
    if( WinAppFrame::Get()->GetActiveAppllication()->GetDeviceBitmap() == (*ppIBitmap) ){
        return SUCCESS;
    }
    (*ppIBitmap) = WinAppFrame::Get()->GetActiveAppllication()->GetDeviceBitmap();
    IBITMAP_AddRef( (*ppIBitmap) );
    return SUCCESS;
}

AddRef() や Release() を忘れてはいけません。


IDISPLAY_CreateDIBitmap() については、内部的に WinBitmap を生成し、それを IDIB にキャストして返してやることにします。

int      WinDisplay::CreateDIBitmap(IDisplay *po, IDIB **ppIDIB, uint8 colorDepth, uint16 w, uint16 h){
    WinBitmap* pBitmap = new WinBitmap();
    pBitmap->cx = w;
    pBitmap->cy = h;
    pBitmap->nDepth = colorDepth;
    // pitch を求める(4byteでアラインされている)
    pBitmap->nPitch = (((w * colorDepth + 7) >> 3) + 3) & ~3;
    if( h > 0 ){
        pBitmap->pBmp = new byte[ pBitmap->nPitch * h ];
        pBitmap->GetDIB()->pBmp = pBitmap->pBmp;
    }
    (*ppIDIB) = pBitmap;
    return SUCCESS;
}


で、あとは IDISPLAY_Update() ですが、これは yaneGameSDK3rd を使って、ウインドウのセカンダリに転送するだけだと思うかもしれないですが、その場合、複数のアプレットをウインドウに表示して、それぞれの表示が重なっていて、Update() のタイミングが異なっていた場合、どちらが上になるか明確にならないため、表示がちらつくことになります。
これに対処するために、IDISPLAY_Update() では、デバイスビットマップと同じ大きさの画面(デバイスプライマリ)画面に反映し、ウインドウのアップデートフラグを立てておきます。
そして、全てのアプレットの処理が終わった後にプライマリに反映させることにします。
こうすれば全てのアプレットについて一定の重なりで画面を表示することが可能になります。

void     WinDisplay::Update(IDisplay * po, boolean bDefer){
    WinDisplay* display = (WinDisplay*)po;
    // デバイスビットマップ以外は反映させなくて良い
    if( (WinBitmap*)display->_dest != WinAppFrame::Get()->GetActiveAppllication()->GetDeviceBitmap() ){
        return;
    }
    // デバイスビットマップをデバイスプライマリに描画
    // 内部的には IBITMAP_BltIn() を使ってるだけ
    WinAppFrame::Get()->GetActiveAppllication()->UpdatePrimary();
    // ウインドウのアップデートフラグを立てる
    WinAppFrame::Get()->UpdateDelayDisplay();
}

で、メインループでデバイスプライマリを描画すれば正常に描画出来ます。


これで IDisplay の実装は終わりです。次は……サウンド