Bài này mình sẽ hướng dẫn khởi tạo ban đầu cho ứng dụng DirectX. Tiến trình của chúng ta sẽ được thực hiện theo những bước sau: 

  • Mô tả các đặc tính của swap chain, chúng ta sẽ tạo ra bằng cách điền vào cấu trúc DXGI_SWAP_CHAIN_DESC.
  • Tạo ID3D10SwapChain và ID3D10Device sử dụng hàm D3D10CreateDeviceAndSwapChain.
  • Tạo Render Target View và back buffer cho swapchain.
  • Tạo Depth/Stencil Buffer và tầm nhìn của Depth/Stencil buffer.
  • Chỉ định Depth/Stencil view và Render Target View cho tiến trình Merger của Rendering Pipline để mà ta có thể dùng DirectX3D.
  • Thiết lập viewport.

Mô tả Swap Chain

Khởi tạo Direct3D bắt đầu bằng cách điền vào của cấu trúc DXGI_SWAP_CHAIN_DESC, trong đó mô tả các đặc tính của swap chain mà chúng ta sẽ tạo ra. Cấu trúc này được định nghĩa như sau:


typedef struct DXGI_SWAP_CHAIN_DESC {
 DXGI_MODE_DESC BufferDesc;
 DXGI_SAMPLE_DESC SampleDesc;
 DXGI_USAGE BufferUsage;
 UINT BufferCount;
 HWND OutputWindow;
 BOOL Windowed;
 DXGI_SWAP_EFFECT SwapEffect;
 UINT Flags;
} DXGI_SWAP_CHAIN_DESC;

DXGI_MODE_DESC là một cấu trúc khác được định nghĩa như sau:


typedef struct DXGI_MODE_DESC
{
 UINT Width; //chiều rộng của back buffer
 UINT Height; //chiều cao của back buffer.
 DXGI_RATIONAL RefreshRate; // tần số làm tươi
 DXGI_FORMAT Format; // định dạng của back buffer
 DXGI_MODE_SCANLINE_ORDER ScanlineOrdering; // display scanline mode
 DXGI_MODE_SCALING Scaling; // display scaling mode
} DXGI_MODE_DESC;

Ghi chú: Trong các mô tả dữ liệu thành viên sau đây, chúng tôi chỉ bao gồm những flag chung và các tùy chọn quan trọng nhất đối với một người mới bắt đầu vào thời điểm này. Đối với một mô tả về flag và các tùy chọn khác, hãy tham khảo tài liệu SDK.

  • BufferDesc: Cấu trúc này mô tả tính chất của back buffer mà chúng ta muốn tạo. Tính chất chính của chúng ta đề cập bao gồm width (chiều rộng), height (chiều cao) và định dạng cho pixel. xem tài liệu SDK để thêm thông tin chi tiết của các tính chất khác.
  • SampleDesc: Số lượng mẫu và chất lượng. Ở bài này chúng ta sẽ không dùng multisampling. Để làm điều này thì ta sẽ thiết lập Cuont = 1 và Quality = 0
  • BufferUsage: Chỉ định DXGI_USAGE_RENDER_TARGET_OUTPUT vì chúng ta sẽ được render ở back buffer (nghĩa là , sử dụng nó như là render target ).
  • BufferCount: Số lượng của back buffer trong swap chain. Chúng ta chỉ dùng một  back buffer cho double buffer. Nếu không bạn sẽ có triple buffer.
  • OutputWindow: Một handle đến Windows chúng ta đang rendering.
  • Windowed: true nếu chúng ta chạy trong chế độ cửa sổ, false nếu chúng ta chạy đầy màn hình.
  • SwapEffect: Chỉ định DXGI_SWAP_EFFECT_DISCARD để trình hiển thị lựa chọn phương pháp hiển thị hiệu quả nhất.
  • Flags: Tùy chọn flag, Nếu bạn chỉ định DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH, khi ứng dụng được chuyển sang chế độ toàn màn hình nó sẽ chọn một chế độ hiển thị phù hợp nhất với các thiết back buffer hiện tại. Nếu flag này không được xác định, khi ứng dụng được chuyển sang chế độ toàn màn hình nó sẽ sử dụng các chế độ hiển thị máy tính để bàn hiện nay. Trong framework mẫu của mình, mình không chỉ định flag này, như sử dụng các chế độ hiển thị máy tính để bàn hiện nay ở chế độ toàn màn hình hoạt động tốt cho chương trình mẫu của mình.

Code mẫu dưới đây cho bạn thấy cách điền giá trị vào DXGI_SWAP_CHAIN_DESC cho framework của mình.


DXGI_SWAP_CHAIN_DESC sd;
sd.BufferDesc.Width = mClientWidth; // use window's client area dims
sd.BufferDesc.Height = mClientHeight;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

// No multisampling.
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;

sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.BufferCount = 1;
sd.OutputWindow = mhMainWnd;
sd.Windowed = true;
sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
sd.Flags = 0;

Tạo Device và Swap Chain

Sau khi mô tả swap chain, Bây giờ muốn tạo ra bằng cách điền vào một cấu trúc DXGI_SWAP_CHAIN_DESC, ta đã sẵn sàng để tạo ra các Device Direct3D 10 (ID3D10Device) và Swap Chain (IDXGISwapChain).  ID3D10Device là  interface quan trọng nhất của Direct3D và có thể được coi như là phần mềm điều khiển của ta trong những thiết bị phần cứng đồ họa vật lý;  thông qua interface này, chúng ta có thể tương tác với các phần cứng và hướng dẫn nó để làm những việc (chẳng hạn như xóa back buffer, chỉ định tài nguyên cho các tiến trình Rendering Pipline, và vẽ hình học). Device và swap chain có thể được tạo ra với hàm sau đây:


HRESULT WINAPI D3D10CreateDeviceAndSwapChain(
 IDXGIAdapter *pAdapter,
 D3D10_DRIVER_TYPE DriverType,
 HMODULE Software,
 UINT Flags,
 UINT SDKVersion,
 DXGI_SWAP_CHAIN_DESC *pSwapChainDesc,
 IDXGISwapChain **ppSwapChain,
 ID3D10Device **ppDevice);
  • pAdapter: Chỉ định card màn hình, ta muốn các thiết bị tạo ra để vẽ lên . Xác định null cho tham số này sử dụng các bộ card màn hình chính. Chúng ta sẽ luôn luôn sử dụng các bộ chuyển đổi chính trong các chương trình mẫu của hướng dẫn này.
  • DriveType: Nói chung, bạn sẽ luôn luôn xác định D3D10_DRIVER_TYPE_HARDWARE cho tham số này để sử dụng tăng tốc phần cứng 3D để render. Xác định D3D10_DRIVER_TYPE_REFERENCE tạo ra một thiết bị reference cái gọi là. Các thiết bị reference là một phần mềm thực hiện Direct3D với mục tiêu chính xác (nó là rất chậm vì nó là một phần mềm thực hiện). Có hai lý do để sử dụng các thiết bị reference:
    *  Để kiểm tra code nếu phần cứng của bạn không hỗ trợ; Ví dụ, để kiểm tra Direct3D 10.1 mã khi bạn không có một card đồ họa hỗ trợ DirectX3D 10.1 .
    * Để kiểm tra đối với các lỗi driver. Nếu bạn có code đó làm việc một cách chính xác với các thiết bị reference, nhưng không phải với các phần cứng, sau đó có thể là một lỗi trong các trình điều khiển phần cứng.
  • Software: Tham số này được sử dụng để cung cấp một bộ quét phần mềm. Chúng tôi luôn luôn xác định NULL vì chúng ta đang sử dụng phần cứng để render. Hơn nữa, người ta phải có một bộ quét phần mềm có sẵn để sử dụng nó.
  • Flags: Tùy chọn flag tạo thiết bị. Đối với chế độ release, điều này thường sẽ là 0 (không có flag thêm); cho chế độ debug, điều này nên được D3D10_CREATE_DEVICE_DEBUG để cho phép các lớp debug. Khi flag debug được chỉ định, Direct3D sẽ gửi tin nhắn để gỡ lỗi ra VC ++; Hình 4.5 cho thấy một ví dụ về một số các thông báo lỗi có thể xuất ra.ScreenShot_20160604231356.png
  • SDKVersion: luôn để là D3D10_SDK_VERSION.
  • pSwapChainDéc: một con trỏ đến D3D10_SWAP_CHAIN_DESC mô tả swap chain chúng ta muốn tạo.
  • ppSwapChain: Trả về swap chain.
  • ppDevice: Trả về Device.

Dưới đây là ví dụ về hàm này.


DXGI_SWAP_CHAIN_DESC sd;

/* Initialize sd */

UINT createDeviceFlags = 0;
#if defined(DEBUG) || defined(_DEBUG)
    createDeviceFlags |= D3D10_CREATE_DEVICE_DEBUG;
#endif
ID3D10Device*    md3dDevice;
IDXGISwapChain*  mSwapChain;
D3D10CreateDeviceAndSwapChain(0, D3D10_DRIVER_TYPE_HARDWARE,
    0, createDeviceFlags, D3D10_SDK_VERSION,
    &sd, &mSwapChain, &md3dDevice);



Tạo Render Target View

Như đã nói ở phần trước thì chúng ta không đươc trực tiếp đưa tài nguyên vào tiến trình. Thay vào đó chúng ta phải tạo một Resource View cho tài nguyên và đưa Resource View này vào tiến trình đó. Đặc biệt, để đưa back buffer vào merger tiến trình (để Direct3D có thể vẽ lại vào nó), chúng ta cần tạo một render target View cho back buffer. Đoạn mã ví dụ sau đây cho thấy cách thức này được thực hiện:


ID3D10RenderTargetView* mRenderTargetView;
ID3D10Texture2D* backBuffer;
mSwapChain->GetBuffer(0, __uuidof(ID3D10Texture2D),
    reinterpret_cast<void**>(&backBuffer));
md3dDevice->CreateRenderTargetView(backBuffer, 0, &mRenderTargetView);
ReleaseCOM(backBuffer);



  • Một con trỏ tới back buffer của swap chain thu được bằng cách sử dụng phương pháp IDXGISwapChain :: GetBuffer. Tham số đầu tiên của phương pháp này là một chỉ số xác định back buffer chúng ta muốn nhận được (trong trường hợp có nhiều hơn một). Trong bản demo của mình, mình chỉ sử dụng mộtback buffer, và nó có chỉ số 0. Tham số thứ hai là loại interface của bộ đệm, mà thường là luôn luôn là một texture  2D (ID3D10Texture2D). Tham số thứ ba trả về một con trỏ đến back buffer.
  • Để tạo ra Render Target View, chúng ta sử dụng phương pháp ID3D10Device :: CreateRenderTargetView. Tham số đầu tiên xác định nguồn tài nguyên đó sẽ được sử dụng như là Render Target , trong đó, trong ví dụ trên, là back buffer. Tham số thứ hai là một con trỏ đến D3D10_RENDER_TARGET_VIEW_DESC. Trong số những thứ khác, cấu trúc này mô tả các kiểu dữ liệu của các thành phần trong các tài nguyên. Nếu tài nguyên đã được tạo ra với một định dạng định kiểu (tức là không typeless), sau đó tham số này có thể là null, mà chỉ sử dụng các định dạng tài nguyên được tạo. Tham số thứ ba trả về một con trỏ đến Render Target View.
  • Gọi IDXGISwapChain :: GetBuffer làm tăng reference COM vào back buffer, đó là lý do chúng ta giải phóng nó (ReleaseCOM) vào cuối của đoạn mã.

Tạo Depth/Stencil Buffer và View

Bây giờ chúng ta cần phải tạo ra các depth / stencil bufer.  Như mô tả trong phần trước, depth buffer chỉ là một texture  2D lưu trữ các thông tin chiều sâu (và thông tin stencil nếu sử dụng stenciling). Để tạo ra texture , chúng ta cần phải điền vào một cấu trúc D3D10_TEXTURE2D_DESC mô tả texture được tạo ra, và sau đó gọi phương thức ID3D10Device :: CreateTexture2D. Cấu trúc D3D10_TEXTURE2D_DESC được định nghĩa như sau:


typedef struct D3D10_TEXTURE2D_DESC {
UINT Width;
UINT Height;
UINT MipLevels;
UINT ArraySize;
DXGI_FORMAT Format;
DXGI_SAMPLE_DESC SampleDesc;
D3D10_USAGE Usage;
UINT BindFlags;
UINT CPUAccessFlags;
UINT MiscFlags;
} D3D10_TEXTURE2D_DESC;
  • Width: chiều rộng của Texture.
  • Height: Chiều cao của Texture.
  • MipLevels: Số lượng mipmap. Vì chúng ta tạo depth/stencil buffer nên chỉ cần 1.
  • ArraySize: Số lượng của texture trong mảng texture. Vì chúng ta tạo depth/stencil buffer nên chỉ cần 1.
  • Format: Một trong những định dạng DXGI_FORMAT
  • SampleDesc: Số lượng mẫu mỗi pixel và chất lượng. Ở đây ta không dùng multisampling nên chúng ta để Count = 1 và Quality = 0
  • Usage: Một trong những định dạng DXGI_USAGE. Chỉ định texture sẽ được dùng như thế nào. Có 4 giá trị đó là:
    D3D10_USAGE_DEFAULT: Chỉ định sử dụng này nếu GPU (Graphic Processing Unit) sẽ được đọc và viết vào tài nguyên. CPU không thể đọc hoặc ghi vào một tài nguyên với việc sử dụng này. Đối với depth / stencil buffer, chúng ta chỉ định D3D10_USAGE_DEFAULT từ GPU sẽ làm tất cả việc đọc và viết vào depth / stencil buffer .
    D3D10_USAGE_IMMUTABLE: Chỉ định sử dụng này nếu nội dung của một tài nguyên không bao giờ thay đổi sau khi tạo. Điều này cho phép một số tối ưu tiềm năng, như tài nguyên sẽ được chỉ đọc bởi GPU. CPU không thể ghi vào một tài nguyên không thay đổi, ngoại trừ lúc tạo để khởi tạo các tài nguyên. CPU không thể đọc từ một nguồn tài nguyên không thay đổi.
    D3D10_USAGE_DYNAMIC: Chỉ định sử dụng này nếu ứng dụng (CPU) cần phải cập nhật các nội dung dữ liệu của các nguồn tài nguyên thường xuyên (ví dụ,mỗi khung hình). Một nguồn tài nguyên với việc sử dụng này có thể được đọc bởi GPU và được ghi bởi CPU.
    _ D3D10_USAGE_STAGING: Chỉ định sử dụng này của ứng dụng (CPU) cần phải có khả năng đọc một bản sao của tài nguyên (tức là, các nguồn tài nguyên hỗ trợ sao chép dữ liệu từ bộ nhớ video vào bộ nhớ hệ thống).
  • BindFlags: Chỉ định nơi tài nguyên sẽ được đưa vào tiến trình. nếu là depth/stencil buffer thì cần dùng D3D10_BIND_DEPTH_STENCIL cho flag này. Một vài giá trị khác cho tham số này là:
    D3D10_BIND_RENDER_TARGET: Texture sẽ được đưa vào tiến trình như là Render Target.
    D3D10_BIND_SHADER_RESOURCE: Texture sẽ được đưa vào tiến trình như shader resource.
  • CPUAccessFlags: Chỉ định cách CPU sẽ truy cập vào tài nguyên. Nếu CPU cần phải ghi vào tài nguyên, chỉ định D3D10_CPU_ACCESS_WRITE. Một tài nguyên với quyền ghi phải có sử dụng D3D10_USAGE_DYNAMIC hoặc D3D10_USAGE_STAGING. Nếu CPU cần đọc từ buffer, chỉ định D3D10_CPU_ACCESS_READ. Một buffer với quyền truy cập đọc phải có D3D10_USAGE_STAGING sử dụng. Đối với các depth/ stencil buffer, chỉ GPU viết và đọc đến depth buffer; do đó, chúng ta có thể chỉ định 0 cho giá trị này, như CPU sẽ không được đọc hoặc ghi vào depth/ stencil buffer.
  • MiscFlags: Tuỳ chọn.

Ngoài ra, trước khi sử dụng depth / stencil buffer , chúng ta phải có depth/ stencil view để bị ràng buộc vào các tiến trình render. Điều này được thực hiện tương tự như việc tạo render target view. sau đây là đoạn code cho thấy cách chúng ta tạo ra depth/stencil texture  và depth/ stencil  view của nó:

D3D10_TEXTURE2D_DESC depthStencilDesc;
depthStencilDesc.Width = mClientWidth;
depthStencilDesc.Height = mClientHeight;
depthStencilDesc.MipLevels = 1;
depthStencilDesc.ArraySize = 1;
depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthStencilDesc.SampleDesc.Count = 1; // multisampling must match
depthStencilDesc.SampleDesc.Quality = 0; // swap chain values.
depthStencilDesc.Usage = D3D10_USAGE_DEFAULT;
depthStencilDesc.BindFlags = D3D10_BIND_DEPTH_STENCIL;
depthStencilDesc.CPUAccessFlags = 0;
depthStencilDesc.MiscFlags = 0;

ID3D10Texture2D* mDepthStencilBuffer;
ID3D10DepthStencilView* mDepthStencilView;

HR(md3dDevice-&gt;CreateTexture2D(
&amp;depthStencilDesc, 0, &amp;mDepthStencilBuffer));
HR(md3dDevice-&gt;CreateDepthStencilView(
mDepthStencilBuffer, 0, &amp;mDepthStencilView));

Lưu ý là tham số thứ 2 của CreateTexture2D là một con trỏ đến data của texture tuy nhiên thì ở đây chúng ta dùng cho depth/stencil buffer nên sẽ để nó là 0.

Đưa View vào Output Merger Stage

Chúng ta vừa tạo View cho back buffer và depth buffer. Việc tiếp theo là đưa nó vào Output Merger State:


md3dDevice->OMSetRenderTargets(1, &mRenderTargetView, mDepthStencilView);</p>

Tham số đầu tiên là số lượng Render Target chúng ta muốn đưa vào. Chúng ta chỉ thiết lập một lần ở đây mà thôi. Tham số thứ 2 là con trỏ đến phần tử đầu tiên của mảng Render Target View. Tham số thứ 3 là con trỏ đến depth/stencil view.

Lưu ý là chúng ta có thể có một mảng Render Target View nhưng chỉ một depth/stencil view.

Thiết lập Viewport

Thông thường chúng ta thường vẽ trên toàn bộ back buffer nhưng đôi khi thì chúng ta chỉ muốn vẽ nó trên một khu vực của back buffer như hình dưới đây:

client_area_text
Bằng việc chỉnh sửa Viewport thì chúng ta có thể vẽ trên một khu vực của back buffer.

Để thiết lập Viewport thì chúng ta dùng đến một cấu trúc sau:


typedef struct D3D10_VIEWPORT {
INT TopLeftX;
INT TopLeftY;
UINT Width;
UINT Height;
FLOAT MinDepth;
FLOAT MaxDepth;
} D3D10_VIEWPORT;

4 tham số đầu tiên liên quan đến cửa sổ window của chúng ta. MinDepth/MaxDepth  để thiết lập giá trị nhỏ nhất và lớn nhất cho giá trị depth buffer.DirectX3D thường dùng giá trị depth buffer từ 0.0 đến 1.0

Khi đã điền đủ giá trị vào D3D10_VIEWPORT thì chúng ta set viewport qua hàm ID3D10Device:: RSSetViewports() . Dưới đây là ví dụ cụ thể:


D3D10_VIEWPORT vp;
vp.TopLeftX = 0;
vp.TopLeftY = 0;
vp.Width = mClientWidth;
vp.Height = mClientHeight;
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;

md3dDevice->RSSetViewports(1, &vp);

Bạn có thể dùng viewport để chia màn hình thành hai cho 2 người chơi. Để làm điều đó thì bạn sẽ tạo 2 viewport. Một cái cho người bên phải và một cho người bên trái.