DirectX11 장치 초기화 – Device, DeviceContext, SwapChain, RenderTargetView, Viewport 구성과 렌더링 준비 구조 만들기
DirectX11을 사용해 GPU 렌더링을 하기 위해서는 가장 먼저 렌더링을 위한 기본 구성 요소들(Device, Context, SwapChain 등)을 초기화해야 한다. 이 구성은 앞으로 모든 그래픽 작업의 기초 토대가 된다.
기본적으로 아래의 5단계를 거친다:
ID3D11Device: GPU 리소스를 생성하는 커맨드 센터ID3D11DeviceContext: 생성된 리소스를 파이프라인에 연결하고 명령 전달IDXGISwapChain: 후면 버퍼와 전면 버퍼를 교환하여 화면에 출력ID3D11RenderTargetView: 후면 버퍼를 GPU에 설명하는 뷰 객체D3D11_VIEWPORT: 실제로 출력할 화면의 위치와 크기 지정DirectX는 COM(Component Object Model) 기반으로 구현되어 있어 모든 객체는 참조 카운트를 기반으로 동작한다.
수동으로 AddRef()와 Release()를 호출하면 실수하기 쉽기 때문에, Microsoft::WRL::ComPtr을 사용해 자동으로 참조를 관리하는 것이 필수적이다.
#include <wrl.h>
using namespace Microsoft::WRL;
ComPtr<ID3D11Device> _device;
ComPtr<ID3D11DeviceContext> _deviceContext;
ComPtr<IDXGISwapChain> _swapChain;
SwapChain은 후면 버퍼에서 전면 버퍼로 전환하는 역할을 한다.
스왑 체인은 이 둘 사이의 고속 복사 및 교체를 관리한다.
DirectX에서 텍스처만으로는 어디에 그려야 할지 알 수 없다. 따라서 텍스처에 대해 "이건 최종 결과물을 그리는 용도입니다"라고 알려주는 뷰(View)를 만들어야 한다.
ID3D11RenderTargetView는 이러한 역할을 한다.
GPU가 출력할 화면의 크기와 위치를 명확히 인식할 수 있도록 설정하는 구조체가 D3D11_VIEWPORT다. DirectX에서는 3D 공간의 결과를 2D 화면으로 출력할 때 반드시 설정이 필요하다.
렌더링을 하기 위한 기본 흐름은 아래와 같다:
RenderBegin()RenderEnd()| 용어 | 설명 |
|---|---|
| COM 객체 | Microsoft의 COM 기반으로 만든 객체. 참조 카운트 기반 |
| ComPtr | COM 객체를 자동으로 관리해주는 스마트 포인터 |
| ID3D11Device | GPU 리소스를 생성하는 객체 |
| ID3D11DeviceContext | 렌더링 파이프라인에 명령을 전달하는 객체 |
| IDXGISwapChain | 후면/전면 버퍼를 관리하고 교환해주는 구조 |
| ID3D11RenderTargetView | 텍스처에 렌더링용 뷰(View)를 부여하는 객체 |
| D3D11_VIEWPORT | 출력할 화면의 크기와 위치 지정 |
| D3D11CreateDeviceAndSwapChain | Device, Context, SwapChain을 한 번에 생성하는 함수 |
class Game
{
public:
Game();
~Game();
void Init(HWND hwnd);
void Update();
void Render();
private:
void CreateDeviceAndSwapChain();
void CreateRenderTargetView();
void SetViewport();
void RenderBegin();
void RenderEnd();
private:
HWND _hwnd;
uint32 _width = 0;
uint32 _height = 0;
ComPtr<ID3D11Device> _device;
ComPtr<ID3D11DeviceContext> _deviceContext;
ComPtr<IDXGISwapChain> _swapChain;
ComPtr<ID3D11RenderTargetView> _renderTargetView;
D3D11_VIEWPORT _viewport = { 0 };
float _clearColor[4] = { 0.5f, 0.5f, 0.5f, 0.5f };
};
void Game::Init(HWND hwnd)
{
_hwnd = hwnd;
_width = GWinSizeX;
_height = GWinSizeY;
CreateDeviceAndSwapChain();
CreateRenderTargetView();
SetViewport();
}
void Game::CreateDeviceAndSwapChain()
{
DXGI_SWAP_CHAIN_DESC desc = {};
desc.BufferDesc.Width = _width;
desc.BufferDesc.Height = _height;
desc.BufferDesc.RefreshRate.Numerator = 60;
desc.BufferDesc.RefreshRate.Denominator = 1;
desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
desc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.BufferCount = 1;
desc.OutputWindow = _hwnd;
desc.Windowed = true;
desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
HRESULT hr = ::D3D11CreateDeviceAndSwapChain(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
0,
nullptr,
0,
D3D11_SDK_VERSION,
&desc,
_swapChain.GetAddressOf(),
_device.GetAddressOf(),
nullptr,
_deviceContext.GetAddressOf()
);
CHECK(hr);
}
void Game::CreateRenderTargetView()
{
ComPtr<ID3D11Texture2D> backBuffer;
HRESULT hr = _swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)backBuffer.GetAddressOf());
CHECK(hr);
hr = _device->CreateRenderTargetView(backBuffer.Get(), nullptr, _renderTargetView.GetAddressOf());
CHECK(hr);
}
void Game::SetViewport()
{
_viewport.TopLeftX = 0.f;
_viewport.TopLeftY = 0.f;
_viewport.Width = static_cast<float>(_width);
_viewport.Height = static_cast<float>(_height);
_viewport.MinDepth = 0.f;
_viewport.MaxDepth = 1.f;
}
void Game::RenderBegin()
{
_deviceContext->OMSetRenderTargets(1, _renderTargetView.GetAddressOf(), nullptr);
_deviceContext->ClearRenderTargetView(_renderTargetView.Get(), _clearColor);
_deviceContext->RSSetViewports(1, &_viewport);
}
void Game::RenderEnd()
{
HRESULT hr = _swapChain->Present(1, 0);
CHECK(hr);
}
void Game::Render()
{
RenderBegin();
// TODO: 이후에 삼각형, 메시 등을 그릴 공간
RenderEnd();
}
CreateDeviceAndSwapChain()은 핵심 3요소를 한 번에 생성하는 함수로, 무조건 알고 있어야 한다.RenderTargetView로 감싸야 한다.RenderBegin()과 RenderEnd()의 구성은 프레임의 기본 단위이며, 이 구조는 앞으로 셰이더, 정점 버퍼, 드로우콜을 넣는 핵심 루틴이다.