2018年5月28日 星期一

Direct3d與OpenGL的Viewport差異

Direct3D與OpenGL的Viewport差異

前言

  Direct3D與OpenGL的Viewport雖然在程式上使用差不多的參數,但實際上卻有相當多的差異,這裡做個紀錄。

內容

   Direct3D與OpenGL的Viewport用來的差異有單個/多個Viewport、坐標系的不同與視窗的解析度認定不同。
  單個/多個Viewport的部分,Direct3D透過RSSetViewports()來決定Viewport的數量,但OpenGL就比較麻煩,舊有的glViewport()只能有一個Viewport,如果要多個Viewport的話,要透過glViewportArray(),但這是OpenGL4才有的。
  坐標系的不同的部分,如下圖
坐標系的差異
Direct3D的基準點在左上,而OpenGL的基準點則是在左下。
  視窗的解析度認定不同的部分比較不好懂,如果用的是RenderTarget來繪圖的話,用起來沒差,但如果是用在輸出到視窗的時候就有差別,Direct3D的IDXGISwapChain在建立時會需要解析度,這解析度的指的是原生的解析度,而不是視窗的解析度,當視窗被縮放時,Viewport所認定解析度會是原生的解析度!但在OpenGL則不一樣,當視窗被縮放時,Viewport的解析度會跟隨著目前視窗大小改變。這裡舉個例子,假設視窗原本為1280*720,且初始化時也依據這個解析度做初始化,所以Viewport的參數就是(0,0,1280,720),不論是Direct3D或OpenGL皆是如此,但當視窗按下放大最大被縮放成1920*1080時,這個時候Direct3D的Viewport參數依舊是(0,0,1280,720),但OpenGL的Viewport參數則變成(0,0,1920,1080)。

參考內容

glViewport
glViewportArray
RSSetViewports

2018年5月21日 星期一

Win32的Gameloop的滑順問題(2)

Win32的Gameloop的滑順問題(2)

前言

  還記得前一篇裡Win32的Gameloop的滑順問題(1)有個沒解決的問題嗎?就是會有一個20mSecs的MSG的問題,這裡找到答案了,在此做個紀錄。

內容

  在Win32的Gameloop裡的接收視窗訊息的程式碼如下
while( true ) {
    if ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) {
        if ( WM_QUIT == msg.message ) {
            break;
        } else {
            TranslateMessage( &msg);
            DispatchMessage( &msg);
        }
    } else {
        gameloop();
    }
}

裡面的PeekMessage()的第二個參數是NULL,接著看PeekMessage的說明,會發現當第二個參數是NULL時,會接收所有的訊息,所以將第二個參數改成主要的視窗後,發現那一個20mSecs的MSG遽然不見了!本以為事情這樣就解決了,但當按下視窗右上角的叉叉後,程式會變得無法關閉,原因是WM_QUIT這個MSG再也收不到了,所以當按下叉叉後,迴圈並能正常跳出,解決的程式碼如下
bool isAppRun=true;//global var...
LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    PAINTSTRUCT ps;
    HDC hdc;
    switch( message )
    {
        case WM_PAINT:
            hdc = BeginPaint( hWnd, &ps );
            EndPaint( hWnd, &ps );
            break;

        case WM_DESTROY:
            PostQuitMessage( 0 );
            isAppRun= false;
            break;

        default:
            return DefWindowProc( hWnd, message, wParam, lParam );
    }

    return 0;
}
//...
int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow )
{
  //...
  //
  while( WM_QUIT != msg.message && isAppRun)
  {
    if( PeekMessage( &msg, hWnd, 0, 0, PM_REMOVE ) )
    {
      TranslateMessage( &msg );
      DispatchMessage( &msg );
    }
    else
    {
      gameloop();
    }
  }
}

新增一個isAppRun,在WndProc裡處理WM_DESTROY裡設為false,接著按下叉叉後就可以順利跳出迴圈了。

參考資料

PeekMessage

2018年5月14日 星期一

Win32的Gameloop的滑順問題(1)

Win32的Gameloop的滑順問題(1)

前言

  最近發現win32寫出來的Gameloop有不穩定的現象,像是FPS有偶而卡頓、FPS不滑順等問題,這裡把研究過程記錄下來。

內容

  一般的Gameloop如以下程式碼

while( true ) {
    if ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) {
        if ( WM_QUIT == msg.message ) {
            break;
        } else {
            TranslateMessage( &msg);
            DispatchMessage( &msg);
        }
    } else {
        gameloop();
    }
}

在我利用Timer測執行時間後發現PeekMessage()的部分(含TranslateMessage() ),有的時候執行時間高達20mSecs,比60FPS(16.67mSecs)還要長,而在Game loop on windows裡,找到一個回答是用下列程式碼

while( !finished ) {
    DWORD currentTick = GetTickCount();
    DWORD endTick = currentTick + 1000/FRAMES_PER_SECOND;

    while (currentTick < endTick) {
        if ( PeekMessage( &m_msg, m_hWnd, 0, 0, PM_REMOVE ) ) {
            if ( WM_QUIT == m_msg.message ) {
                finished = true;
                break;
            } else {
                TranslateMessage( &m_msg );
                DispatchMessage( &m_msg );
            }
            currentTick = GetTickCount();
        } else {
            break;
        }
    }

    processFrame();
}

簡單的說他給PeekMessage()的部分一個執行的時間,如果超過就直接執行gameloop,這個方法我實際實驗是沒效果的,那個20mSecs似乎不是多個MSG造成的,而是單一一個MSG就有這樣的時間!這個目前沒找到解決的方案。
  本來以為FPS不滑順也是上述的問題造成的,但後來發現不是,以下是gameloop的程式碼

  //do something
  //...
  double updateTime=m_cTimer.Elapsed();
  double frameTime=(1.0f/60.0f);
  if(updateTime <  frameTime )
  {
    double delTime = frameTime - updateTime;
    ::Sleep( (size_t)(delTime*1000.0f) );
  }
  m_cTimer.Reset();

這樣的gameloop會非常不滑順!發現主要的問題是Sleep(),睡眠的時間並不是相當的準,解決的方法是將多的睡眠時間給下一個frame,並且過低的睡眠時間直接放棄,完成的程式碼如下

  //do something
  //...
  double updateTime=m_cTimer.Elapsed()+m_flFPSModefiyTime;
  double frameTime=(1.0f/60.0f);
  if(updateTime <  frameTime )
  {
    double delTime = frameTime - updateTime;
    if(delTime > 0.001f)
      ::Sleep( (size_t)(delTime*1000.0f) );
    m_flFPSModefiyTime=m_cTimer.Elapsed()+m_flFPSModefiyTime-frameTime;
    //Set max m_flFPSModefiyTime was half frame time!
    m_flFPSModefiyTime=fmin(frameTime*0.5,m_flFPSModefiyTime);
  }
  else
  {
    //Frame not on time!
    m_flFPSModefiyTime=0.0f;

  }
  m_cTimer.Reset();

這樣出來的FPS就相當滑順。

後記

  這次還有問題沒解決,就是20mSecs得MSG的問題,以後有機會再來解決!

參考資料

Game loop on windows
Sleep function

2018年5月7日 星期一

XInput的使用

XInput的使用

前言

  最近需要偵測XBox的手把,所以就研究了一下XInput,在此做個紀錄。

內容

  XInput用起來相當簡單,首先先看以下的程式碼

DWORD dwResult;    
for (DWORD i=0; i< XUSER_MAX_COUNT; i++ )
{
  XINPUT_STATE state;
  ZeroMemory( &state, sizeof(XINPUT_STATE) );

        // Simply get the state of the controller from XInput.
        dwResult = XInputGetState( i, &state );

        if( dwResult == ERROR_SUCCESS )
  {
      // Controller is connected 
  }
        else
  {
            // Controller is not connected 
  }
}

  可以看到一個"XUSER_MAX_COUNT",這個Marco會定義最多可以用的手把數量,接著透過XInputGetState()來偵測目前的輸入,第1個參數就是指第幾隻手把,而第二個參數是一個"XINPUT_STATE",裡面會儲存所有的輸入資訊。
  "XINPUT_STATE"裡的"XINPUT_GAMEPAD"會儲存手把的按鍵資訊,以下示範取得按鍵的範例

  XINPUT_STATE state;
  if(state.Gamepad.wButtons&XINPUT_GAMEPAD_DPAD_LEFT)
  {
    //Left button was pressed...
  }

接著是取得類比輸入的範例

  XINPUT_STATE state;
  float leftThumbX = fminf(1.0f,( (float)state.Gamepad.sThumbLX) / 32767);
  float leftThumbY = fminf(1.0f,( (float)state.Gamepad.sThumbLY) / 32767);

這樣就可以得到類比輸入的X與Y值,但要注意的是當類比歸位的時候(就是不動的時候),類比的數值並回會剛好是0.0f,他會有一個微小的誤差,這範圍大概設在-0.1f~0.1f,要記得過濾掉。


參考資料

XInput Game Controller APIs
XINPUT_GAMEPAD structure