2005.07.30更新

DirectX覚え書き


今までDirectX8を触っていて、気づいたことや初期化の方法などを適当に書いていこうと思います。
詳しい情報が知りたい方は他のWebサイトなどを覗いた方がいいと思います(汗)。
一応実際に使っているソースからコピペしてますので色つけなどで変になってない限り動くと思います。
ただし、これは私の記述法であって各自もっといい方法があると思いますので参考程度に。

DirectX8の初期化
ウィンドウモードとフルスクリーンモードの切り替え
Reset()関数は以下の通り
60fpsのタイミング処理方法(独自方法)
SSE,3D Now!!を使用したルート計算
SSE,3D Now!!を使用したfloatからintへの変換

DirectX8の初期化

グローバル変数などは以下の通り

LPDIRECT3D8 m_pD3D;//Direct3DObject
LPDIRECT3DDEVICE8 m_pd3dDevice;//D3Dデバイス
LPDIRECT3DSURFACE8 m_pBackBuffer;//バックバッファ
LPDIRECT3DSURFACE8 m_pZBuffer;//Zバッファ
DWORD m_dwScreenW,m_dwScreenH;//画面の幅と高さ
D3DPRESENT_PARAMETERS d3dpp;//デバイスへの情報
D3DDISPLAYMODEd 3ddm;//ディスプレイ情報
初期化処理
//D3Dオブジェクトを作成する
if((m_pD3D=::Direct3DCreate8(D3D_SDK_VERSION))==NULL){
    ::OutputDebugString("ErrorCreateD3D():Direct3DCreate8()\n");
    return FALSE;
}
//Windowモードなら、現在のディスプレイモードを取得する
if(FAILED(m_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT,&d3ddm))){
    ::OutputDebugString("ErrorCreateD3D():GetAdapterDisplayMode()\n");
    return FALSE;
}

//デバイスの能力を記述する
ZeroMemory(&d3dpp,sizeof(d3dpp));
d3dpp.BackBufferWidth=m_dwScreenW;
d3dpp.BackBufferHeight=m_dwScreenH;
if(m_bWindow)
    d3dpp.BackBufferFormat=d3ddm.Format;
else
    d3dpp.BackBufferFormat=m_b32?D3DFMT_X8R8G8B8:D3DFMT_R5G6B5;//m_b32はフルスクリーン時32bitならTRUEを;
d3dpp.BackBufferCount=1;
d3dpp.Windowed=m_bWindow;
d3dpp.EnableAutoDepthStencil=TRUE;
d3dpp.AutoDepthStencilFormat=D3DFMT_D16;
d3dpp.SwapEffect=D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow=hWnd;
if(m_bWindow==FALSE){//フルスクリーン時の解像度や使用できる情報を獲得する
    int cc=m_pD3D->GetAdapterModeCount(D3DADAPTER_DEFAULT);
    if(cc){
        D3DDISPLAYMODE adp;
        for(int i=0;i<cc;i++){
            m_pD3D->EnumAdapterModes(D3DADAPTER_DEFAULT,i,&adp);
            if(adp.Format==d3dpp.BackBufferFormat&&adp.RefreshRate==60/*60Hzの場合*/&&adp.Width>=m_dwScreenW&&adp.Height>=m_dwScreenH)break;
        }
        if(cc==i)//一致したものがない(要求した使える解像度と色数がない)
            d3dpp.FullScreen_RefreshRateInHz=D3DPRESENT_RATE_DEFAULT;
        else
            d3dpp.FullScreen_RefreshRateInHz=60;//60Hzの場合
    }
}

D3DDEVTYPEdevType=(m_HALREF==0?D3DDEVTYPE_HAL:D3DDEVTYPE_REF);//m_HALREFはHAL使用するならTRUE
//デバイスを作成する
//シェーダが使用可能か調べる
D3DCAPS8 caps;
m_pD3D->GetDeviceCaps(0,D3DDEVTYPE_HAL,&caps);
if(caps.VertexShaderVersion>=D3DVS_VERSION(1,0)){//頂点シェーダーの1.0は存在?
//HARDWARET&L
    if(FAILED(m_pD3D->CreateDevice(D3DADAPTER_DEFAULT,devType,hWnd,D3DCREATE_HARDWARE_VERTEXPROCESSING,&d3dpp,&m_pd3dDevice))){
    //SOFTWARET&L
        if(FAILED(m_pD3D->CreateDevice(D3DADAPTER_DEFAULT,devType,hWnd,D3DCREATE_SOFTWARE_VERTEXPROCESSING,&d3dpp,&m_pd3dDevice))){
            //REFERENCE RASTERIZE
            if(FAILED(m_pD3D->CreateDevice(D3DADAPTER_DEFAULT,D3DDEVTYPE_REF,hWnd,D3DCREATE_SOFTWARE_VERTEXPROCESSING,&d3dpp,&m_pd3dDevice))){
                //対応してません
                MessageBox(NULL,"Direct3Dデバイスの生成に失敗しました","Error",MB_OK|MB_ICONSTOP);
                return false;
            }
        }
    }
}else{
    //HARDWARE&SOFTWARET&L
    if(FAILED(m_pD3D->CreateDevice(D3DADAPTER_DEFAULT,devType,hWnd,D3DCREATE_MIXED_VERTEXPROCESSING,&d3dpp,&m_pd3dDevice))){
        //SOFTWARET&L
        if(FAILED(m_pD3D->CreateDevice(D3DADAPTER_DEFAULT,devType,hWnd,D3DCREATE_SOFTWARE_VERTEXPROCESSING,&d3dpp,&m_pd3dDevice))){
            //REFERENCE RASTERIZE
            if(FAILED(m_pD3D->CreateDevice(D3DADAPTER_DEFAULT,D3DDEVTYPE_REF,hWnd,D3DCREATE_SOFTWARE_VERTEXPROCESSING,&d3dpp,&m_pd3dDevice))){
            //対応してません
                MessageBox(NULL,"Direct3Dデバイスの生成に失敗しました","Error",MB_OK|MB_ICONSTOP);
                return false;
            }
        }
    }
}
//バックバッファとZバッファを取得する
if(FAILED(m_pd3dDevice->GetBackBuffer(0,D3DBACKBUFFER_TYPE_MONO,&m_pBackBuffer))){
    ::OutputDebugString("ErrorCreateD3D():GetBackBuffer()\n");
    returnFALSE;
}
if(FAILED(m_pd3dDevice->GetDepthStencilSurface(&m_pZBuffer))){
    ::OutputDebugString("ErrorCreateD3D():GetDepthStencilSurface()\n");
    returnFALSE;
}
ここまでが初期化となります。初期化は一度書いたらずっと使いますし楽ですよね。
解放はRELEASE(m_pZBuffer);などとしてすべて解放してください。


ウィンドウモードとフルスクリーンモードの切り替え(Alt+Enterなどで)

m_bWindow = !m_bWindow;
if(m_bWindow==0){//フルスクリーンモード
    d3dpp.BackBufferFormat=m_b32?D3DFMT_X8R8G8B8:D3DFMT_R5G6B5;
    d3dpp.Windowed=0;
    int cc=m_pD3D->GetAdapterModeCount(D3DADAPTER_DEFAULT);
    if(cc){
        D3DDISPLAYMODE adp;
        for(int i=0;i<cc;i++){
            m_pD3D->EnumAdapterModes(D3DADAPTER_DEFAULT,i,&adp);
            if(adp.Format==d3dpp.BackBufferFormat&&adp.RefreshRate==60&&adp.Width>=m_dwScreenW&&adp.Height>=m_dwScreenH)break;
        }
        if(cc==i)
            d3dpp.FullScreen_RefreshRateInHz=D3DPRESENT_RATE_DEFAULT;
        else
            d3dpp.FullScreen_RefreshRateInHz=60;
    }
    SetWindowPos(hwnd,HWND_NOTOPMOST,
        0,0,
        m_dwScreenW,
        m_dwScreenH,
        SWP_SHOWWINDOW);
        SetWindowLong(hwnd,GWL_STYLE,WS_POPUP|WS_VISIBLE);
}else{//ウィンドウモード
    d3dpp.BackBufferFormat=d3ddm.Format;
    d3dpp.Windowed=1;
    d3dpp.FullScreen_RefreshRateInHz=0;
    SetWindowLong(hwnd,GWL_STYLE,WS_POPUP|WS_SYSMENU|WS_VISIBLE|WS_CAPTION);
    //maxx=GetSystemMetrics(SM_CXSCREEN);
    //maxy=GetSystemMetrics(SM_CYSCREEN);で設定済みとします
    SetWindowPos(hwnd,HWND_NOTOPMOST,maxx/2-m_dwScreenW/2,maxy/2-m_dwScreenH/2,
        m_dwScreenW+(GetSystemMetrics(SM_CXFIXEDFRAME)+GetSystemMetrics(SM_CXEDGE))*2,
        m_dwScreenH+(GetSystemMetrics(SM_CYCAPTION)+(GetSystemMetrics(SM_CYFIXEDFRAME)+GetSystemMetrics(SM_CYEDGE))*2),
        SWP_SHOWWINDOW);
}
Reset();
Reset()関数は以下の通り

if(m_pd3dDevice){
    RELEASE(g_Font1);//フォントを作っている人はここで解放 テクスチャの方はMANAGEDの場合は解放不要
    RELEASE(m_pZBuffer);
    RELEASE(m_pBackBuffer);
    DWORD rr,rr2=D3DERR_DEVICELOST;
    for(int i=0;i<5;i++){
        DoEvent();Sleep(100);//念のため何回か繰り返す DoEventはメッセージキューを解放する処理
        if(FAILED(rr=m_pd3dDevice->Reset(&d3dpp))){
            continue;
        }else break;
    }
    if(i==5) return;
}
if(FAILED(m_pd3dDevice->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &m_pBackBuffer))){
    ::OutputDebugString("Error CreateD3D() : GetBackBuffer()\n");
    return;
}
if(FAILED(m_pd3dDevice->GetDepthStencilSurface(&m_pZBuffer))){
    ::OutputDebugString("Error CreateD3D() : GetDepthStencilSurface()\n");
    return;
}
InitFont(&g_Font1, "MS ゴシック", 8, 16);//フォントを作っていた人はここで作り直す
//以下、リセットされたためSetRenderStateやSetTextureStateなどを設定し直す
60fpsのタイミング処理方法(独自方法なので参考にはならないかと)

DWORD Timing64(DWORD& Frate,BOOL bl)//QueryPerformanceFrequencyが使える場合はここ、使えなければTiming関数を呼ぶ
{
    LARGE_INTEGER freq,time_before;
    if(QueryPerformanceFrequency(&freq)){
        DWORD ss,FrateL = Frate;
        if(bl==TRUE)DoEvent();
            QueryPerformanceCounter(&time_before);
        Frate=(DWORD)(time_before.QuadPart*1000/freq.QuadPart);
        ss = (DWORD)((int)Frate-(int)FrateL);return ss;
    }else return Timing(Frate,bl);
}
DWORD Timing(DWORD& Frate,BOOL bl)
{
    DWORD ss,FrateL = Frate;
    if(bl ==TRUE)DoEvent();
    timeBeginPeriod(1);
    Frate = timeGetTime();
    timeEndPeriod(1);
    ss = Frate-FrateL;
    return ss;
}

DWORD timetb[3]={16,17,17};
int timec=0;
void timing1(WORD a,BOOL b,BOOL c)//a:1で60fps 2:30fps (16.6ms*a)待機 b:TRUEでこの関数内で指定時間待機 c:終了時にイベント解放処理
{
    DWORD fr2=0;
    if(b) Timing64(fpstiming,FALSE);
    DWORD j=0;
    if(!a)a=1;
    for(WORD i=0;i<a;i++){
        j+=timetb[timec];timec++;if(timec>2)timec=0;
    }
    for(;;){
        Timing64(fr2,FALSE);
        if((int)fr2 >= (int)((int)fpstiming+(int)j+((m_bWindow)==FALSE)*0)) break;
    }
    Timing(fpstiming,FALSE);
    if(c)DoEvent();
}

使用例は、

処理開始時に
DWORD fpstiming,fps;
Timing64(fpstiming);
Timing64(fps);
として処理し、
描画などの処理を行った後、
fps2=Timing64(fps);
とするとfps2に実行にかかったmsが帰ってくるのでそれを元に
フレーム処理を行います。
17ms以下なら60fpsなので、
if(fps2>17) fps=1;
とでも書いて、fpsに1/60描画ですよと与える。
その後、
timing1((WORD)fps,FALSE,FALSE);
とすることにより残りの時間をここで待機します。
内部でfpstiming(DWORD型)を使っているのでそちらを更新します。

まぁ普通はこんな処理はしないと思うんですが私はこの方が作りやすいのでこういう方法をとってます。

SSE,3D Now!!を使用したルート計算

C言語にも平方根関数sqrtなどがありますが、精度や速度の問題で普段はテーブルを作ったりして対処しますよね。
( ここのNo.12参照 )

しかし折角SSEがあるのなら使わない手はないということでここを参考にSSEで書きました

CPUIDにて以下のように獲得しているモノとする

DWORD _ff1,_ff2;
__asm{
    mov eax,1;
    cpuid;
    mov _ff1,edx;
}
__asm{
    mov eax,0x80000001;
    cpuid;
    mov _ff2,edx;
}

#define ALIGNED __declspec(align(16)); として定義してあるものとする

float sqrt_fast(float x){
    if(_ff2&0x80000000){//3DNow!!
        ALIGNED float k,x1=x;
        __asm{//某サイトの情報をかなりそのまま引用しております
            femms
            movd     mm0,x1
            pfrsqrt  mm1,mm0 ;mm1     ???        | 1/sqrt(b) 15ビット精度
            movq     mm2,mm1
            pfmul    mm1,mm1
            pfrsqit1 mm1,mm0
            pfrcpit2 mm1,mm2 ;mm1     ???        | 1/sqrt(b)     24ビット精度    
            pfmul    mm0,mm1 ;mm0     ???        | sqrt(b)
            movd     k,mm0
            femms
        }
        return k;
    }else if(_ff1&0x02000000){//SSE
        ALIGNED float k,x1=x;
        __asm {
            // 1/√x1 = 3/2 * y - 1/2 * x1 * y3; y=1/√x1 の近似値
            //SSE = a2*xmm3 + (-0.5*xmm0*xmm1*xmm1*xmm1) で逆数平方根の近似値
            //平方根に戻すにはx1を解に掛ける √x1 = x1 * 1/√x1
            //平方根ならsqrtpsもありますが56サイクルかかりますそのため逆数から求めます
            movaps  xmm0, x1  //入力したx1をxmm0へ :2:xmm0=x1
            movaps  xmm2, xmm0//                   :1:xmm2=x1
            rsqrtps xmm1, xmm0//x1を逆数平方根する :2:xmm1=1/√x1(11bit)
            movaps  xmm3, xmm1//                   :1:xmm3=1/√x1(11bit)
            mulps   xmm1, xmm1// y3乗              :4:xmm1=xmm1*xmm1 2乗
            mulps   xmm1, xmm3//                   :4:xmm1=xmm1*xmm3 3乗
            mulps   xmm1, xmm0//aを掛ける          :4:xmm1=xmm1*xmm0(x1)
            mulps   xmm1, a1  //-0.5               :4:xmm1=xmm1*(-0.5)
            mulps   xmm3, a2  //1.5                :4:xmm3=xmm3*1.5
            addps   xmm3, xmm1//                   :3:xmm3=xmm3+xmm1
            mulps   xmm3, xmm2//逆数平方根から戻す :4:xmm3=xmm3*xmm2
            movaps  k   , xmm3//答え 22bit程度     :3: 36サイクル
        }
        return k;
    }
    //某サイトの情報をかなりそのまま引用しております
    int s, t, u;
    s = t = u = *(int *)&x;
    s >>= 23;
    t &= ((1<<23)-1);
    u &= ((1<<(23-BITLEN))-1);
    t >>= (23-BITLEN);
    u >>= (23-BITLEN*2);
    return idxTbl[s] * (sqrtTbl1[t] + sqrtTbl2[t] * u);
}


多少は速くなるのかもしれませんね。sqrt関数の速度がどれくらいなのかはっきりしないの速くなったかどうかはすぐには確認できませんが。
ルート計算を多用する場面では多少は効果が出るのかもしれません。

SSE,3D Now!!を使用したfloatからintへの変換

float(浮動小数点)からint(整数)へのキャストはVCなどで a=(int)b; などとするときかなり遅くなります。
これは昔の古い命令などを使用しているためでその部分を取っ払い独自少し高速な関数を作りました。
もっと効率のいいソースもあると思いますがいろいろ考えるのも面倒でしたので以下のような形になりました。 なお、ソースとしては a=(int)b; としているところを a=ftol(b); と変換してください。 _ff1と_ff2にはCPUIDの値が入ります。SSEのルートの方を参照してください。

WORD SaveCW,WorkCW;
int r;
float r2=0.5f;
int ftol(float f)
{
    if(_ff1&0x02000000){//sse
        ALIGNED float k=f;
        __asm{
            movapsx   mm0,k
            cvttss2si eax,xmm0
            mov       r,eax
        }
        return r;
    }
    if(_ff2&0x80000000){//3d now
        ALIGNED float k=f;
        __asm{
            femms
            movd  mm0,k
            pf2id mm1,mm0
            movd  eax,mm1
            mov   r,eax;
            femms
        }
        return r;
    }
    //SSE,3D Now!!がないときはFPUを使う
    __asm{
        fld    f
        fnstcw SaveCW
        fnstcw WorkCW
        or     WorkCW,0x0c00 //切り捨てモードに
        fldcw  WorkCW
        fistp  r
        fldcw  SaveCW
     }
     return r;
}