환경 구축를 통해서 프로젝트의 생성까지 모두 끝나고 빌드하면 다음과 같은 화면을 볼 수 있다.

하지만 아직 DirectX
를 통해 렌더링되는 화면은 아니다. 이제부터 DirectX11 API
를 이용해 빈 화면을 우선 렌더링 해보도록 하자.
디바이스 객체들을 만들자
DirectX11 API를 이용하기 위해서 우선적으로 생성해야 하는 객체들이 있다. ID3D11Device
, ID3D11DeviceContext
, IDXGISwapChain
인터페이스 객체 3개가 필요하다. 편의상 이들을 통칭해서 디바이스 객체들이라고 하자. 이들은 렌더링에 필요한 리소스를 생성하고 렌더링 파이프라인에 연결하고 스크린 버퍼를 갱신하기 위한 객체들이라고 보면 된다.
DirectX가 무엇일까?
DirectX11은 Graphics API이다. GPU를 이용하려면 GPU제조사에서 제공하는 하드웨어 드라이버를 이용해야 한다. GPU 하드웨어 제조사가 하나면 상관이 없지만 여러 제조사라면 이 모든 제조사의 드라이버에 대해 학습을 해야 하고, 로우 레벨에서의 프로그래밍이 필요하다.
DirectX는 이런 제조사의 하드웨어 드라이버 레벨에서 프로그래밍을 하지 않고 공통으로 사용되는 API를 이용해서 여러 제조사의 GPU를 사용할 수 있도록 해준다.
디바이스 객체들의 역할
각 디바이스 객체가 제공하는 인터페이스 함수들을 보면 대략적으로 어떤 역할들을 하는지 알 수 있다. 마이크로 소프트 공식 홈페이지에서 자세히 설명이 되어 있다. 일단은 간략하게 각 객체가 어떤 역할들을 하는지 알아보자.
ID3D11Device
ID3D11Device
는 대체적으로 리소스나 쉐이더 객체등을 생성할 수 있는 함수들을 제공한다.
CreateBuffer
,CreateTexture2D
,CreateRenderTargetView
,CreateVertexShader
,CreateComputeShader
등
ID3D11DeviceContext
ID3D11DeviceContext
는 렌더링 파이프라인에 리소스나 쉐이더를 연결하는 함수들을 제공한다. 함수들의 네이밍에 앞 2글자 ( IA
, VS
, HS
등 )는 렌더링 파이프라인 스테이지의 약어이다.
IASetIndexBuffer
,IASetInputLayout
,VSSetConstantBuffers
,HSSetSamplers
,DSSetShader
등
IDXGISwapChain
IDXGISwapChain
은 스크린 버퍼와 관련된 함수들을 제공한다.
Present
,ResizeBuffers
,SetFullscreenState
등
이 스왑 체인을 만들기 위해선 Description Structure
를 통해서 어떤 스왑 체인을 만들지 결정을 해야 한다. 이는 DXGI_SWAP_CHAIN_DESC
를 통해서 전달한다. 레퍼런스는 DXGI_SWAP_CHAIN_DESC에서 확인할 수 있다. 주로 버퍼의 갯수, 버퍼의 용도, 버퍼의 포맷등을 결정하게 된다.
다중 버퍼링
스크린에 렌더링하기 위한 버퍼의 개수는 최소 1개는 있어야 하지만 다수를 이용할 수 있다. 버퍼의 내용이 스크린에 표시되는 동안 버퍼의 내용을 수정해서는 안되기 때문에 2개 이상의 버퍼를 만든 후, 하나의 버퍼를 스크린에 표시 ( Present
)하는 동안 다른 하나의 버퍼에 다음 프레임을 렌더링하는 것이다. 이 과정이 끝나면 다시 렌더링이 끝난 버퍼를 스크린에 표시하고 나머지 하나를 갱신한다.
디바이스 객체 생성 코드
디바이스 객체를 생성하는 코드는 다음과 같다.
DXGI_SWAP_CHAIN_DESC scd;
ZeroMemory( &scd, sizeof( DXGI_SWAP_CHAIN_DESC ) );
scd.BufferCount = 1;
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
scd.OutputWindow = hWnd;
scd.SampleDesc.Count = 4;
scd.Windowed = True;
D3D11CreateDeviceAndSwapChain( nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, nullptr, nullptr, nullptr, D3D11_SDK_VERSION, &scd, &Swapchain, &Device, nullptr, &DeviceContext );
D3D11CreateDeviceAndSwapChain
함수에 대한 레퍼런스는 D3D11CreateDeviceAndSwapChain에서 자세히 알 수 있다. 이 함수를 통해 3개의 객체를 모두 생성하면 이제 비로소 DirectX11 API
를 이용하기 위한 준비가 끝난 것이다.
CRD11Device
위에서 설명한 코드들을 가지고 클래스로 만들어보자.
class CRD11Device
{
private:
ID3D11Device* DevicePtr = nullptr;
ID3D11DeviceContext* DeviceContextPtr = nullptr;
IDXGISwapChain* SwapChainPtr = nullptr;
public:
/// Constructor
CRD11Device() = default;
/// Create Dx11 device ojects.
bool Create( HWND hWnd );
/// Get Dx11 device object.
ID3D11Device* GetDevice() const { return DevicePtr; }
/// Get Dx11 device context object.
ID3D11DeviceContext* GetDeviceContext() const { return DeviceContextPtr; }
/// Get Dx11 swap chain object.
IDXGISwapChain* GetSwapChain() const { return SwapChainPtr; }
};
Create
에서 윈도우 핸들을 전달받아 객체들을 생성해준다.
bool CRD11Device::Create( HWND hWnd )
{
DXGI_SWAP_CHAIN_DESC scd;
ZeroMemory( &scd, sizeof( DXGI_SWAP_CHAIN_DESC ) );
scd.BufferCount = 1;
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
scd.OutputWindow = hWnd;
scd.SampleDesc.Count = 1;
scd.Windowed = true;
HRESULT hr = D3D11CreateDeviceAndSwapChain
(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
0,
nullptr,
0,
D3D11_SDK_VERSION,
&scd,
&SwapChainPtr,
&DevicePtr,
nullptr,
&DeviceContextPtr
);
if ( FAILED( hr ) ) return false;
return true;
}
이 함수는 윈도우 핸들이 생성되는 시점에서 호출해주면 끝이다.
int APIENTRY wWinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow )
{
// 코드 생략
HWND hWnd = CreateWindowW( szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr );
if ( !hWnd ) return false;
// GD11은 CRD11Device의 전역 인스턴스이다.
GD11.Create( hWnd );
// 코드 생략
}
이렇게 DirectX11
디바이스 객체들의 사용 준비가 끝났다. 다음엔 DirectX11 API
를 이용해서 빈 화면을 렌더링 해보자.
이전글 : [DirectX11] 1. 환경 구축을 해보자
다음글 : [DirectX11] 3. 초기화 – DirectX11 API를 이용한 빈 화면 렌더링 하기