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; }ここまでが初期化となります。初期化は一度書いたらずっと使いますし楽ですよね。
ウィンドウモードとフルスクリーンモードの切り替え(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; }