Ở demo này sử dụng code từ file d3dUtil.h, d3dApp.h và d3dApp.cpp. Những tập tin này có thể tải về ở đây. Những file trong thư mục Common sẽ được sử dụng xuyên suốt bài hướng dẫn này nên sau này mình sẽ không up lại nữa. File d3dApp.h và d3dApp.cpp là 2 file cốt lõi ở đây. Nó thiết lập Windows và DirectX3D 10. Xử lý sự kiện cơ bản của ứng dụng.

D3DApp

d3dApp là class cung cấp hàm tạo cửa sổ window, chạy vòng lặp sự kiện, xử lý xự kiện của window và khởi tạo directx.

Class d3dApp được định nghĩa như sau:


class D3DApp
{
public:
 D3DApp(HINSTANCE hInstance);
 virtual ~D3DApp();

HINSTANCE getAppInst();
 HWND getMainWnd();

int run();

// Framework methods. Derived client class overrides these methods
 // to implement specific application requirements.

virtual void initApp();
 virtual void onResize(); // reset projection/etc.
 virtual void updateScene(float dt);
 virtual void drawScene();
 virtual LRESULT msgProc(UINT msg, WPARAM wParam, LPARAM lParam);

protected:
 void initMainWindow();
 void initDirect3D();

protected:
 HINSTANCE mhAppInst; // application instance handle
 HWND mhMainWnd; // main window handle
 bool mAppPaused; // is the application paused?
 bool mMinimized; // is the application minimized?
 bool mMaximized; // is the application maximized?
 bool mResizing; // are the resize bars being dragged?

// Used to keep track of the "delta-time" and game time (§4.3).
 GameTimer mTimer;

// A string to store the frame statistics for output. We display
 // the average frames per second and the average time it takes
 // to render one frame.
 std::wstring mFrameStats;

// The D3D10 device (§4.2.2), the swap chain for page flipping
 // (§4.1.4), the 2D texture for the depth/stencil buffer (§4.1.5),
 // and the render target and depth/stencil views (§4.1.6). We
 // also store a font pointer (§4.4) so that we can render the
 // frame statistics to the screen.
 ID3D10Device* md3dDevice;
 IDXGISwapChain* mSwapChain;
 ID3D10Texture2D* mDepthStencilBuffer;
 ID3D10RenderTargetView* mRenderTargetView;
 ID3D10DepthStencilView* mDepthStencilView;
 ID3DX10Font* mFont;

// The following variables are initialized in the D3DApp constructor
 // to default values. However, you can override the values in the
 // derived class to pick different defaults.

// Hardware device or reference device? D3DApp defaults to
 // D3D10_DRIVER_TYPE_HARDWARE.
 D3D10_DRIVER_TYPE md3dDriverType;

// Window title/caption. D3DApp defaults to "D3D10 Application".
 std::wstring mMainWndCaption;

// Color to clear the background. D3DApp defaults to blue.
 D3DXCOLOR mClearColor;

// Initial size of the window's client area. D3DApp defaults to
 // 800x600. Note, however, that these values change at run time
 // to reflect the current client area size as the window is resized.
 int mClientWidth;
 int mClientHeight;
};

Các biến đã được mô ta trong phần comment. Những hàm sẽ được thảo luận sau.

Hàm Non-Framework

  • D3DApp: Constructor đơn giản là khởi tạo giá trị cho một số biến.
  • ~D3DApp: Destructor Giải phóng COM mà ứng dụng có.
  • getAppInst: trả về instance của ứng dụng.
  • getMainwnd: trả về handle của window
  • run: Chạy vòng lặp chính của ứng dụng sử dụng hàm PeekMessage đê xử lý game logic khi không có tin nhắn nào được gửi tới ứng dụng.
  • initMainWindow: Khởi tạo window. Sử dụng Win32API.
  • initDirectX3D: khởi tạo directx đã được thảo luận ở trước.

Hàm Framework

Với mỗi ví dụ mẫu trong hướng dẫn này thì chúng ta override 5 hàm ảo của D3DApp,Lợi ích của việc này là tất cả các thiết lập, bắt giữ sự kiện, vv, được thực hiện trong D3DApp Class. để các lớp được thừa kế chỉ cần tập trung vào các code cụ thể của ứng dụng demo. Dưới đây là một mô tả về các hàm của framework:

  • initApp: Sử dụng hàm này để thiết lập ban đầu cho ứng dụng như là cấp phát tài nguyên, khởi tạo các thực thể. Hàm này gọi 2 hàm initMainWindow và initDirect3D vì thế  bạn nên gọi hàm này đầu tiên:
    
    void AlphaTestApp::initApp()
    {
     D3DApp::initApp();
    
    /* Rest of initialization code goes here */
    }

    Bởi vì những khởi tạo sau này của bạn có thể cần đến ID3D10Device nên chúng ta cần phải khởi tạo nó trước

  • onResize: Hàm này được gọi bởi D3DApp::msgProc() khi nhận được WM_RESIZE. khi window bị thay đổi kích thước thì vài thành phần của directx cần được cập nhật dựa vào kích thước cửa sổ mới. Cụ thể là depth/stencil buffer. back buffer được resize nhờ vào hàm IDGXSwapChain::ResizeBufer() .
    Depth/Stencil buffer cần được giải phóng và cấp phát lại dựa vào kích thức mới. Tiếp theo là render target và depth/stencil view cũng cần được tạo lại.
    Xem trong code để biết chi tiết hơn.
  • updateScene: hàm này được gọi mỗi frame và nên được dùng để update directx  theo thời gian. ở D3DApp thì nó thực hiện việc tính FPS và lưu kết quả ở chuỗi mFrameStat
  • drawScene: Phương pháp được gọi mỗi khung và được sử dụng để vẽ frame hiện tại của khung cảnh 3D của chúng ta. Việc thực hiện D3DApp của method này đơn giản xóa back buffer và depth / stencil buffer để chuẩn bị cho việc vẽ  (người ta thường luôn thiết lập lại các buffer trước khi vẽ một khung mới để các giá trị từ các khung hình trước đó không “còn sót lại”):
    
    void D3DApp::drawScene()
    {
     // Clear the render target to the color specified by mClearColor.
     md3dDevice->ClearRenderTargetView(mRenderTargetView, mClearColor);
    
    // Clear the depth buffer to 1.0, and clear the stencil buffer
     // to 0.
     md3dDevice->ClearDepthStencilView(mDepthStencilView,
     D3D10_CLEAR_DEPTH|D3D10_CLEAR_STENCIL, 1.0f, 0);
    }
  • msgProc: xử lý sự kiện của ứng dụng. Bạn có thể override hàm này để dùng nếu nó không có những sự kiên mà bạn cần.

Frame Statistics

Nó được phổ biến trong các trò chơi và các ứng dụng đồ họa để đo số lượng khung hình được biểu hiện trên giây (FPS). Để làm điều này, chúng ta chỉ đơn giản là đếm số lượng các khung hình xử lý (và lưu trữ nó trong một biến n) trong một khoảng thời gian quy định t. Sau đó, FPS trung bình trong khoảng thời gian t là fpsavg = n / t. Nếu chúng ta đặt t = 1, sau đó fpsavg = n / 1 = n. Trong code của chúng ta, chúng ta dùng t = 1 vì nó tránh được sự phân chia, và hơn thế nữa, nó cho một trung bình khá tốt – nó không phải là quá dài và không quá ngắn. Đoạn code để tính FPS được cung cấp bởi D3DApp của updateScene:


void D3DApp::updateScene(float dt)
{
 // Code computes the average frames per second, and also the
 // average time it takes to render one frame.

static int frameCnt = 0;
 static float t_base = 0.0f;

frameCnt++;

// Compute averages over one second period.
 if( (mTimer.getGameTime() - t_base) >= 1.0f )
 {
 float fps = (float)frameCnt; // fps = frameCnt / 1
 float mspf = 1000.0f / fps;

std::wostringstream outs;
 outs.precision(6);
 outs << L"FPS: "<<  fps<< L"\n"
 << "Milliseconds: Per Frame: "<< mspf;

// Save the stats in a string for output.
 mFrameStats = outs.str();

// Reset for next average.
 frameCnt = 0;
 t_base += 1.0f;
 }
}

Hàm này nên được gọi mỗi frame để đếm số lượng frame.

Ngoài việc tính toán FPS, các code trên cũng tính số mili giây phải mất trung bình để xử lý một khung:

float mspf = 1000.0f / fps;

Lưu ý: Số giây cho mỗi khung chỉ bằng nghịch đảo của FPS, nhưng chúng ta nhân 1000 ms / s 1 để chuyển đổi từ vài giây đến mili giây (nhớ lại có 1.000 ms mỗi giây).

Ý tưởng đằng sau dòng này là để tính toán thời gian, trong mili giây, nó cần để thực hiên một khung hình; đây là một đại lượng khác với FPS (nhưng quan sát giá trị này có thể được bắt nguồn từ FPS). Trong thực tế, thời gian cần để render một khung là có ích hơn là FPS, như chúng ta có thể trực tiếp nhìn thấy sự gia tăng / giảm thời gian cần thiết để vẽ một khung để chúng ta sửa đổi cảnh của chúng tôi. Mặt khác, FPS không ngay lập tức cho chúng tôi biết sự gia tăng / giảm thời gian khi chúng ta thay đổi cảnh của chúng tôi. Hơn nữa, như [Dunlop03] chỉ ra trong bài viết của mình FPS so với khung thời gian, do sự phi tuyến tính của đường cong FPS, sử dụng FPS có thể cho kết quả sai lệch. Ví dụ, hãy xem xét tình hình (1): Giả sử ứng dụng của chúng tôi đang chạy ở 1000 FPS, lấy 1 ms (millisecond) để render một khung. Nếu tỷ lệ khung hình giảm xuống 250 FPS, sau đó phải mất 4 ms để render một khung. Bây giờ xem xét tình hình (2): Giả sử rằng ứng dụng của chúng tôi đang chạy ở 100 FPS, lấy 10 ms để render một khung. Nếu tỷ lệ khung hình giảm xuống khoảng 76,9 FPS, sau đó phải mất khoảng 13 ms để render một khung. Trong cả hai trường hợp, việc vẽ trên khung tăng 3 ms, và do đó cả hai đại diện cho sự gia tăng tương tự trong thời gian cần để render một khung. Đọc FPS là không đơn giản. Sự sụt giảm từ 1000 FPS đến 250 FPS dường như nhiều hơn nữa mạnh mẽ so với mức giảm từ 100 FPS 76,9 FPS; Tuy nhiên, như chúng ta vừa thấy, chúng thực sự đại diện cho sự gia tăng tương tự trong thời gian cần để render một khung.

The Message Handler

D3DApp chỉ nắm giữ một số sự kiện quan trọng, cần thiết và tối thiếu.  Mình khuyến khích các bạn tải về các tập tin mã nguồn và dành thời gian làm quen với framework, vì nó là nền tảng của tất cả các mẫu cho hướng dẫn.

Thông điệp đầu tiên chúng ta xử lý là thông điệp WM_ACTIVATE. Tin nhắn này được gửi khi một ứng dụng trở nên kích hoạt hoặc vô hiệu hoá.


case WM_ACTIVATE:
 if( LOWORD(wParam) == WA_INACTIVE )
 {
 mAppPaused = true;
 mTimer.stop();
 }
 else
 {
 mAppPaused = false;
 mTimer.start();
 }
 return 0;

Như bạn có thể thấy, khi ứng dụng của chúng ta trở nên ngừng hoạt động, chúng ta thiết lập biến mAppPaused = true, và khi ứng dụng của chúng ta bắt đầu hoạt động, chúng ta thiết lập biến mAppPaused = false. Ngoài ra, khi ứng dụng bị tạm dừng, chúng tadừng bộ đếm thời gian, và sau đó tiếp tục bộ đếm thời gian một khi ứng dụng hoạt động trở lại. Nếu chúng ta nhìn lại D3DApp :: run , chúng ta thấy rằng nếu ứng dụng của chúng ta tạm dừng, sau đó chúng ta không cập nhật ứng dụng, và giải phóng một số chu kỳ CPU trở lại hệ điều hành; theo cách này, ứng dụng của chúng tôi không chiếm chu kỳ CPU khi nó không hoạt động.

Các tin nhắn tiếp theo chúng ta xử lý là thông điệp WM_SIZE. Nhớ lại rằng tin nhắn này được gọi khi cửa sổ được thay đổi kích cỡ. Lý do chính cho việc xử lý thông điệp này là chúng ta muốn back buffer và depth / stencil buffer để phù hợp với kích thước của cử sổ windows (nếu không thì sự kéo dãn sẽ diễn ra). Vì vậy, mỗi khi cửa sổ được thay đổi kích cỡ, chúng tôi muốn thay đổi kích thước các buffer. Các code để thay đổi kích thước buffer được thực hiện trong D3DApp :: onResize. Như đã nêu, các back buffer có thể thay đổi kích thước bằng cách gọi phương IDXGISwapChain :: ResizeBuffers. Các depth / stencil buffer cần phải được phá hủy và sau đó làm lại dựa trên kích thước mới. Ngoài ra, render target view và depth / stencil views  cần được tái tạo. Nếu người dùng đang kéo những thanh thay đổi kích cỡ, chúng ta phải cẩn thận vì cách kéo các thanh thay đổi kích cỡ sẽ gửi tin nhắn WM_SIZE liên tục, và chúng ta không muốn liên tục thay đổi kích thước bộ đệm. Do đó, nếu chúng ta xác định rằng người sử dụng thay đổi kích thước bằng cách kéo, chúng ta  thực sự không làm gì cả (trừ tạm dừng các ứng dụng) cho đến khi người sử dụng thực hiện kéo những thanh đổi kích thước xong. Chúng ta  có thể làm điều này bằng cách xử lý thông WM_EXITSIZEMOVE. Tin nhắn này được gửi khi người dùng nhả thanh thay đổi kích cỡ.


// WM_ENTERSIZEMOVE is sent when the user grabs the resize bars.
case WM_ENTERSIZEMOVE:
 mAppPaused = true;
 mResizing = true;
 mTimer.stop();
 return 0;

// WM_EXITSIZEMOVE is sent when the user releases the resize bars.
// Here we reset everything based on the new window dimensions.
case WM_EXITSIZEMOVE:
 mAppPaused = false;
 mResizing = false;
 mTimer.start();
 onResize();
 return 0;

Cuối cùng, ba thông điệp cuối cùng chúng ta xử lý được thực hiện bình thường:


// WM_DESTROY is sent when the window is being destroyed.
case WM_DESTROY:
PostQuitMessage(0);
return 0;

// The WM_MENUCHAR message is sent when a menu is active and the user
// presses a key that does not correspond to any mnemonic or accelerator
// key.
case WM_MENUCHAR:
// Don't beep when we alt-enter.
return MAKELRESULT(0, MNC_CLOSE);

// Catch this message to prevent the window from becoming too small.
case WM_GETMINMAXINFO:
((MINMAXINFO*)lParam)->ptMinTrackSize.x = 200;
((MINMAXINFO*)lParam)->ptMinTrackSize.y = 200;
return 0;

Going Full Screen

IDXGISwapChain tạo ra với D3D10CreateDeviceAndSwapChain tự động bắt tổ hợp phím Alt + Enter  và sẽ chuyển đổi ứng dụng sang chế độ toàn màn hình. Nhấn Alt + Enter trong khi ở chế độ toàn màn hình sẽ chuyển về chế độ cửa sổ. Trong chế độ chuyển đổi, các cửa sổ ứng dụng sẽ được thay đổi kích cỡ, mà sẽ gửi một thông điệp WM_SIZE đến các ứng dụng; điều này cho phép các ứng dụng một cơ hội để thay đổi kích thước back buffer và depth / stencil buffer để phù hợp với kích thước màn hình mới. Ngoài ra, nếu chuyển sang chế độ toàn màn hình, style cửa sổ sẽ thay đổi nếu cần thiết. Bạn có thể sử dụng công cụ Studio Spy ++  trực quan để xem các thông điệp của Windows được tạo ra bằng cách nhấn Alt + Enter cho ứng dụng demo mà sử dụng framework mẫu.

Một trong những bài tập ở phần cuối của chương này tìm hiểu làm thế nào bạn có thể vô hiệu hóa mặc định Alt + Enter.

 The Init Direct3D Demo

Bây giờ chúng ta đã thảo luận về framework, chúng ta hãy làm một ứng dụng nhỏ sử dụng nó. Chương trình yêu cầu hầu như không có công việc thực tế vì một phần của chúng ta từ các lớp cha mẹ D3DApp làm hầu hết các công việc cần thiết cho bản demo này. Nhưng điều cần lưu ý là cách chúng ta thừa kế một lớp từ D3DApp và thực hiện các chức năng của framework, Tất cả các chương trình trong hướng dẫn này sẽ theo cùng một mẫu.


#include "d3dApp.h"

class InitDirect3DApp : public D3DApp
{
public:
InitDirect3DApp(HINSTANCE hInstance);
~InitDirect3DApp();

void initApp();
void onResize();
void updateScene(float dt);
void drawScene();
};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance,
PSTR cmdLine, int showCmd)
{
// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif
InitDirect3DApp theApp(hInstance);

theApp.initApp();

return theApp.run();
}

InitDirect3DApp::InitDirect3DApp(HINSTANCE hInstance)
: D3DApp(hInstance)
{
}

InitDirect3DApp::~InitDirect3DApp()
{
if( md3dDevice )
md3dDevice->ClearState();
}

void InitDirect3DApp::initApp()
{
D3DApp::initApp();
}

void InitDirect3DApp::onResize()
{
D3DApp::onResize();
}

void InitDirect3DApp::updateScene(float dt)
{
D3DApp::updateScene(dt);
}

void InitDirect3DApp::drawScene()
{
D3DApp::drawScene();

// We specify DT_NOCLIP, so we do not care about width/height
// of the rect.
RECT R = {5, 5, 0, 0};
mFont->DrawText(0, mFrameStats.c_str(), -1, &R, DT_NOCLIP, BLACK);

mSwapChain->Present(0, 0);
}

ScreenShot_20160606235318.png
Ví dụ minh họa.