Beginning Direct3D 10
Welcome to our new series of tutorials about Direct3D 10 Programming, To be able to follow this tutorial you need to be familiar with C++ programming I also have presumed that you have a little knowledge about Win32 API Programming and you know how to create a simple window using this API (if you don't please check the Win32 API tutorials first).
Direct3D 10 came with a lot of promises; it has come to bring the visual level of games and real-time applications to the next level. Dramatic increase of quality and performance of recent graphics hardware has created new needs and necessities and Direct3D 10 is going to answer to those, and install a flexible platform for further improvements and new features.
Basic Concepts
Front Buffer:
A buffer which is bound to the actual pixels on the monitor screen.
Back Buffer:
D3D uses back buffer as an output buffer and then swaps back buffer and front buffer in each frame to represent the scene. This avoids flicking of the image on the monitor.
RenderTarget:
A surface that is used by the Direct3D device to render onto.
DXGI
The first new component in DirectX 10's design which we are going to talk about is DirectX Graphics Infrastructure which is the primary device independent layer that provides access to graphics hardware; DXGI handles some basic functionalities that were implemented previously in D3D API.
The application can talk to DXGI through Direct3D 10 API but sometimes you need direct access to DXGI for example to enumerate video adapters, present the scene, and etc.
Direct3D Device
Most of Direct3D's core functionalities are accessible through Direct3D Device Interface so we need to create a D3D Device first, but before that we need to have a visible window.
// Create A Window Class Structure
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.hInstance = GetModuleHandle(NULL);
wc.lpfnWndProc = WndProc;
wc.lpszClassName = "GPORG";
wc.style = CS_HREDRAW | CS_VREDRAW;
// Register Window Class
RegisterClassEx(&wc);
// Create Window
hWnd = CreateWindowEx(0, "GPORG", "GameProgrammer.org Direct3D 10 Tutorial",
WS_OVERLAPPEDWINDOW|WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, width,
height, NULL,NULL,wc.hInstance,0); |
This code registers a new window class and creates a new window using the class. (This code is fully explained in the Win32 SDK tutorials section).
Now we create a D3D10 Device by calling D3D10CreateDeviceAndSwapChain() function.
DXGI_SWAP_CHAIN_DESC swapChainDesc;
ZeroMemory( &swapChainDesc, sizeof(swapChainDesc) );
swapChainDesc.BufferCount = 1;
swapChainDesc.BufferDesc.Width = width;
swapChainDesc.BufferDesc.Height = height;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.OutputWindow = hWnd;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.Windowed = TRUE;
if( FAILED( D3D10CreateDeviceAndSwapChain( NULL, D3D10_DRIVER_TYPE_HARDWARE, NULL, 0, D3D10_SDK_VERSION, &swapChainDesc, &pSwapChain, &pDevice ) ) )
{
Error("Failed to create device and swap chain.");
return false;
} |
As its name implies this function creates a Direct3D device and a DXGI Swap Chain. Swap chain is responsible for getting the data from D3D device and represent it on the screen. To create a swap chain D3D needs to know a couple of information about it which you need to provide by DXGI_SWAP_CHAIN_DESC structure. With a look at DXGI_SWAP_CHAIN_DESC definition you can see it contains two other structures: DXGI_MODE_DESC and DXGI_SAMPLE_DESC.
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 holds description of a display mode.
typedef struct DXGI_MODE_DESC
{
UINT Width;
UINT Height;
DXGI_RATIONAL RefreshRate;
DXGI_FORMAT Format;
DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;
DXGI_MODE_SCALING Scaling;
} DXGI_MODE_DESC; |
We've just used Width, Height and Format parameters in here, which in order describe width, height and display format of our buffer.
DXGI_SAMPLE_DESC describes multi-sampling parameters for a resource
typedef struct DXGI_SAMPLE_DESC
{
UINT Count;
UINT Quality;
} DXGI_SAMPLE_DESC; |
Count represents the number of samples per pixel and the second parameter describes image quality level. By setting Count to 1 and Quality to 0 we disable the multi-sampling feature.
Back to DXGI_SWAP_CHAIN_DESC structure we'll cover the rest of parameters:
BufferCount is the number of buffers in the swap chain; we only want to create a front buffer in here so we set it to 1. By setting BufferUsage to DXGI_USAGE_RENDER_TARGET_OUTPUT we declare that the buffer will be used as an output render target. OutputWindow is a handle to the window that we want to use to represent the rendering output. Final parameter Windowed indicates whether the application starts in fullscreen mode or in windowed mode. (You can toggle fullscreen mode with ALT+Enter in runtime).
Now let's have a closer look at the D3D10CreateDeviceAndSwapChain() function:
HRESULT D3D10CreateDeviceAndSwapChain(
IDXGIAdapter *pAdapter,
D3D10_DRIVER_TYPE DriverType,
HMODULE Software,
UINT Flags,
UINT SDKVersion,
DXGI_SWAP_CHAIN_DESC *pSwapChainDesc,
IDXGISwapChain **ppSwapChain,
ID3D10Device **ppDevice); |
The first parameter is a pointer to a DXGI Adapter, by setting it to NULL, Direct3D will create a default adapter for us. The second parameters describe which type of display driver to use; most likely we want to use the hardware driver so we set it to D3D10_DRIVER_TYPE_HARDWARE. The next parameter should be null unless you want to use a custom software rasterizer which is very unlikely. We don't use any special flags so we set Flags to 0. SDKVersion should always be set to D3D10_SDK_VERSION,for the next parameter we should pass a pointer to the DXGI_SWAP_CHAIN_DESC structure that we just created. Two last parameters are the pointers to the swap chain and the D3D device that Direct3D will create for us.
Now that we've created D3D device and swap chain we need to connect them so that the back buffer in the swap chain can be used as a render target by the D3D device (You may wonder why this isn't done automatically by D3D10CreateDeviceAndSwapChain() function, the answer is it gives you a lot more flexibility in this way). To make this connection we need to get the back buffer from the swap chain as a texture, create a render target using the texture and pass it to D3D device as the active render target and this code will just do that:
ID3D10Texture2D *pBackBuffer;
if( FAILED( pSwapChain->GetBuffer( 0, __uuidof( ID3D10Texture2D ), (LPVOID*)&pBackBuffer ) ) )
{
Error("Failed to create back buffer.");
return false;
}
if(FAILED( pDevice->CreateRenderTargetView( pBackBuffer, NULL, &pRenderTargetView )))
{
Error("Failed to create render target view.");
return false;
}
pBackBuffer->Release();
pDevice->OMSetRenderTargets( 1, &pRenderTargetView, NULL ); |
The initialization part is done here, now we want to use Direct3D to clear the screen.
pDevice->ClearRenderTargetView( pRenderTargetView, D3DXVECTOR4(0, 0, 0, 1) );
pSwapChain->Present( 0, 0 ); |
pDevice->ClearRenderTargetView() function clears the entire render target with the supplied color which is black here (r=0, g=0, b=0, a=1), after that we've used the pSwapChain->Present() to present the scene on the screen, swap chain will handle swapping the back buffer and front buffer for us automatically. And we're done, we've just created our first Direct3D 10 application here. It would be more fun if we had some shapes on the screen, you can refer to MSDN for more information about Direct3D 10 or wait for our next tutorial in http://www.gameprogrammer.org.
Code
At last I provide the complete source code of the application, to make it work you need to create an empty project in Visual Studio, set the character-set to 'Use Multi-Byte Character Set' in project settings, and add a cpp file including this code:
#include <windows.h>
#include <d3d10.h>
#include <d3dx10.h>
#define Error(X) MessageBox(NULL, X, "Error", MB_OK)
class Application
{
public:
Application();
~Application();
bool CreateD3DWindow(int width, int height);
bool Initialize();
void Render(float deltaTime);
void MainLoop();
private:
static LRESULT CALLBACK WndProc( HWND hWnd , UINT message , WPARAM wParam , LPARAM lParam);
HWND hWnd;
IDXGISwapChain* pSwapChain;
ID3D10RenderTargetView* pRenderTargetView;
ID3D10Device* pDevice;
int width, height;
};
#pragma comment (lib, "d3d10.lib")
#pragma comment (lib, "d3dx10.lib")
Application::Application()
{
}
Application::~Application()
{
}
LRESULT CALLBACK Application::WndProc( HWND hWnd , UINT message , WPARAM wParam , LPARAM lParam)
{
switch(message){
case WM_CLOSE:
case WM_DESTROY:
PostQuitMessage(0);
break;;
}
return DefWindowProc(hWnd,message,wParam,lParam);
}
bool Application::CreateD3DWindow(int width, int height)
{
this->width = width;
this->height = height;
// Create A Window Class Structure
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.hInstance = GetModuleHandle(NULL);
wc.lpfnWndProc = WndProc;
wc.lpszClassName = "GPORG";
wc.style = CS_HREDRAW | CS_VREDRAW;
// Register Window Class
RegisterClassEx(&wc);
// Create Window
hWnd = CreateWindowEx(0,
"GPORG", "GameProgrammer.org Direct3D 10 Tutorial",
WS_OVERLAPPEDWINDOW|WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, width, height,
NULL,NULL,wc.hInstance,0);
return true;
}
bool Application::Initialize()
{
DXGI_SWAP_CHAIN_DESC swapChainDesc;
ZeroMemory( &swapChainDesc, sizeof(swapChainDesc) );
swapChainDesc.BufferCount = 1;
swapChainDesc.BufferDesc.Width = width;
swapChainDesc.BufferDesc.Height = height;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.OutputWindow = hWnd;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.Windowed = TRUE;
if( FAILED( D3D10CreateDeviceAndSwapChain( NULL, D3D10_DRIVER_TYPE_HARDWARE, NULL,
0, D3D10_SDK_VERSION, &swapChainDesc, &pSwapChain, &pDevice ) ) )
{
if( FAILED( D3D10CreateDeviceAndSwapChain( NULL, D3D10_DRIVER_TYPE_REFERENCE, NULL,
0, D3D10_SDK_VERSION, &swapChainDesc, &pSwapChain, &pDevice ) ) )
{
Error("Failed to create device and swap chain.");
return false;
}
}
ID3D10Texture2D *pBackBuffer;
if( FAILED( pSwapChain->GetBuffer( 0, __uuidof( ID3D10Texture2D ), (LPVOID*)&pBackBuffer ) ) )
{
Error("Failed to create back buffer.");
return false;
}
if(FAILED( pDevice->CreateRenderTargetView( pBackBuffer, NULL, &pRenderTargetView )))
{
Error("Failed to create render target view.");
return false;
}
pBackBuffer->Release();
pDevice->OMSetRenderTargets( 1, &pRenderTargetView, NULL );
D3D10_VIEWPORT vp = {0, 0, width, height, 0, 1};
pDevice->RSSetViewports( 1, &vp );
return true;
}
void Application::Render(float deltaTime)
{
pDevice->ClearRenderTargetView( pRenderTargetView, D3DXVECTOR4(0, 0, 0, 1) );
pSwapChain->Present( 0, 0 );
}
void Application::MainLoop()
{
MSG msg;
long prevTime = GetTickCount(), curTime = GetTickCount();
while(true)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if(msg.message==WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else{
Render((curTime-prevTime)/1000.f);
prevTime = curTime;
curTime = GetTickCount();
}
}
}
INT WINAPI WinMain( HINSTANCE , HINSTANCE , LPSTR , INT )
{
Application app;
if(app.CreateD3DWindow(640, 480))
{
if(app.Initialize())
{
app.MainLoop();
}
}
return 0;
} |
Download Source |