
DirectX11-SDK 튜토리얼를 활용하여 공부 중이다.
HRESULT InitDevice()
{
...
RECT rc;
GetClientRect(g_hWnd, &rc);
const UINT width = rc.right - rc.left;
const UINT height = rc.bottom - rc.top;
...
}
Window의 Width, Height값을 가져옵니다.
그 때 사용하는 함수가 바로 GetClientRect 이다.
Right로 갈수록 x값이 늘어나고, Bottom으로 갈수록 y값이 늘어난다고 보면 되는듯
UINT createDeviceFlags = 0;
#ifdef _DEBUG
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
DirectX Device를 생성할 때의 플래그 설정
DirectX를 초기화 하기 위해서는 Driver랑 Context를 만드는 게 시작이라고 볼 수 있다.
D3D_DRIVER_TYPE driverTypes[] =
{
D3D_DRIVER_TYPE_HARDWARE,
D3D_DRIVER_TYPE_WARP,
D3D_DRIVER_TYPE_REFERENCE,
};
constexpr UINT numDriverTypes = ARRAYSIZE(driverTypes);
D3D_DRIVER_TYPE을 열거형으로 해서 0,1,2 순서대로 찾아보고 Device를 생성할 때 사용함.
레스터라이저라는 용어가 무슨 뜻인지 모르겠어서 찾아보았다.
정확한 뜻은 모르겠지만 하드웨어가 돌아가지 못할 때, Software상에서 렌더링을 돌려주는 역할을 하는 거 같다.
[참고자료1] [참고자료2]
D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
};
constexpr UINT numFeatureLevels = ARRAYSIZE(featureLevels);
이 부분은 DirectX의 버전을 정해주는 단계이다.
이 또한 위와 같이 0,1,2 index순서대로 찾아보고 결정된다.
DXGI_SWAP_CHAIN_DESC sd;
ZeroMemory(&sd, sizeof(DXGI_SWAP_CHAIN_DESC));
sd.BufferCount = 1;
sd.BufferDesc.Width = width;
sd.BufferDesc.Height = height;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.OutputWindow = g_hWnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.Windowed = TRUE;
이 부분은 Swapchain 구조체에 대한 정보를 설정하는 단계이다. [공식문서]
Swapchain이 뭐냐하면은 화면을 그릴 때, front buffer와 back buffer 두 가지 buffer를 생성해두고, 두 buffer를 계속 바꿔주면서 화면에 출력하는 방법을 의미한다.
Swapchain 구조체를 알아보자 (대충 보자마자 알 거 같은 것을 제외하고)
BufferCount : Back buffer 개수
BufferDesc : display mode
BufferCount가 1로 설정되어 있는데 하나의 백 버퍼를 사용한다는 뜻
SampleDesc의 Count = 1 Quality = 0으로 설정되어 있으니 멀티 샘플링이 되지 않는다는 것을 의미한다.
for (UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++)
{
g_driverType = driverTypes[driverTypeIndex];
hr = D3D11CreateDeviceAndSwapChain(
nullptr, // device를 생성하기 위한 video adapter(nullptr = default adapter)
g_driverType, // driver type
nullptr, // software handle
createDeviceFlags, // device flag
featureLevels, // feature level array
numFeatureLevels, // feature level array length
D3D11_SDK_VERSION, // sdk version
&sd, // swap chain structure
&g_pSwapChain, // 생성된 swap chain
&g_pDevice, // 생성된 device
&g_featureLevel, // 생성된 feature level
&g_pImmediateContext); // 생성된 device context
if (SUCCEEDED(hr))
{
break;
}
}
지정된 driver type들을 바탕으로 device와 swapchain이 성공적으로 생성될 때까지 순차적으로 반복한다.
ID3D11Texture2D* pBackBuffer = nullptr;
hr = g_pSwapChain->GetBuffer(
0, // 백버퍼 index
__uuidof(ID3D11Texture2D), // back buffer를 다루기 위한 interface
reinterpret_cast<LPVOID*>(&pBackBuffer) // back buffer interface output
);
if (FAILED(hr))
{
return hr;
}
[공식문서]
Swap chain으로부터 첫번째 buffer를 받아온다.
Render target view(RTV)는 뭘까?
그 이전에 DirectX에서 View라는 것은 무엇인가?
MVP패턴에서 사용되던 View라는 의미인가?
그건 당연히 아니였다.
DirectX에서는 렌더링 파이프라인를 통해 그려진다.
렌더링에 리소스들을 아래의 View를 통해 파이프라인에 Bind한다. (이를 통해 읽거나 쓸 수 있음)
여러가지 리소스 View들이 존재한다.
그 중 지금 사용할 View는 RTV이다.
RTV는 2차원 Texture를 파이프라인으로 부터 받을 때 주로 사용된다.
hr = g_pDevice->CreateRenderTargetView(
pBackBuffer, // View에서 접근할 리소스
nullptr, // RTV 정의
&g_pRenterTargetView); // RTV를 받아올 변수
pBackBuffer->Release();
if (FAILED(hr))
{
return hr;
}
Back buffer를 Release를 한 이유는, RTV를 생성했으므로 그 이후로는 back buffer에 직접 접근하지 않고 RTV를 사용하기 때문임
g_pImmediateContext->OMSetRenderTargets(
1, // Render target의 개수(최대 8개)
&g_pRenterTargetView, // Render target view의 배열
nullptr // Depth stencil view 포인터
);
OM(output-merger-stage) : 픽셀 셰이더에서 출력된 값을 Render target에 작성하는 스테이지이다.
D3D11_VIEWPORT vp;
vp.Width = static_cast<FLOAT>(width);
vp.Height = static_cast<FLOAT>(height);
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
vp.TopLeftX = 0;
vp.TopLeftY = 0;
g_pImmediateContext->RSSetViewports(
1, // 설정할 Viewport 개수
&vp // Viewport 구조체 배열
);
Viewport는 Renter target에서 그려질 영역을 의미한다.
RS(rasterizer-stage) : Geometry 셰이더로부터 출력된 primitive data를 RS에 보내서 Rasterizer에 의해 렌더링 할 픽셀 단위로 분해됨.
레스터라이징 과정 중 하나인 Viewport에 따라 그려질 영역이 설정된다.
// RTV를 Clear할 색상 설정
constexpr float clearColor[4] = { 0.0f, 0.125f,0.3f,1.0f };
g_pImmediateContext->ClearRenderTargetView(g_pRenterTargetView, clearColor);
// 화면에 출력하기
g_pSwapChain->Present(0, 0);
ClearRenderTargetView : Render target를 RGBA color로 초기화
Present : back buffer에 그려진 이미지를 출력
if (g_pImmediateContext)
{
// Resets any device context to the default settings
g_pImmediateContext->ClearState();
}
if (g_pRenterTargetView)
{
g_pRenterTargetView->Release();
}
if (g_pSwapChain)
{
g_pSwapChain->Release();
}
if (g_pImmediateContext)
{
g_pImmediateContext->Release();
}
if (g_pDevice)
{
g_pDevice->Release();
}
사용한 DirectX 객체들을 해제한다.
#include <Windows.h>
#include <d3d11.h>
//--------------------------------------------------------------------------------------
// Global Variables
//--------------------------------------------------------------------------------------
HINSTANCE g_hInstance;
HWND g_hWnd;
D3D_DRIVER_TYPE g_driverType = D3D_DRIVER_TYPE_NULL;
D3D_FEATURE_LEVEL g_featureLevel = D3D_FEATURE_LEVEL_11_0;
ID3D11Device* g_pDevice = nullptr;
ID3D11DeviceContext* g_pImmediateContext = nullptr;
IDXGISwapChain* g_pSwapChain = nullptr;
ID3D11RenderTargetView* g_pRenterTargetView = nullptr;
//--------------------------------------------------------------------------------------
// Forward declarations
//--------------------------------------------------------------------------------------
HRESULT InitWindow(HINSTANCE hInstance, int nCmdShow);
HRESULT InitDevice();
void CleanupDevice();
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void Render();
//--------------------------------------------------------------------------------------
// Entry point to the program. Initializes everything and goes into a message processing
// loop. Idle time is used to render the scene.
//--------------------------------------------------------------------------------------
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
if (FAILED(InitWindow(hInstance, nCmdShow)))
{
return 0;
}
if (FAILED(InitDevice()))
{
CleanupDevice();
return 0;
}
MSG msg = { nullptr };
while (WM_QUIT != msg.message)
{
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
Render();
}
}
CleanupDevice();
return static_cast<int>(msg.wParam);
}
HRESULT InitWindow(HINSTANCE hInstance, int nCmdShow)
{
// Register Window class
WNDCLASSEX wcex;
ZeroMemory(&wcex, sizeof(wcex));
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = reinterpret_cast<HBRUSH>((COLOR_WINDOW + 1));
wcex.lpszMenuName = nullptr;
wcex.lpszClassName = L"TutorialWindowClass";
wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);
if (!RegisterClassEx(&wcex))
{
return E_FAIL;
}
g_hInstance = hInstance;
RECT rc = { 0, 0, 640, 480 };
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);
g_hWnd = CreateWindowEx(NULL,
L"TutorialWindowClass",
L"Direct3D 11 Tutorial 1: Direct3D 11 Basics",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
rc.right - rc.left,
rc.bottom - rc.top,
nullptr,
nullptr,
hInstance,
nullptr);
if (g_hWnd == nullptr)
{
return E_FAIL;
}
ShowWindow(g_hWnd, nCmdShow);
return S_OK;
}
HRESULT InitDevice()
{
HRESULT hr = S_OK;
RECT rc;
GetClientRect(g_hWnd, &rc);
const UINT width = rc.right - rc.left;
const UINT height = rc.bottom - rc.top;
UINT createDeviceFlags = 0;
#ifdef _DEBUG
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
D3D_DRIVER_TYPE driverTypes[] =
{
D3D_DRIVER_TYPE_HARDWARE,
D3D_DRIVER_TYPE_WARP,
D3D_DRIVER_TYPE_REFERENCE,
};
constexpr UINT numDriverTypes = ARRAYSIZE(driverTypes);
D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
};
constexpr UINT numFeatureLevels = ARRAYSIZE(featureLevels);
DXGI_SWAP_CHAIN_DESC sd;
ZeroMemory(&sd, sizeof(DXGI_SWAP_CHAIN_DESC));
sd.BufferCount = 1;
sd.BufferDesc.Width = width;
sd.BufferDesc.Height = height;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.OutputWindow = g_hWnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.Windowed = TRUE;
for (UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++)
{
g_driverType = driverTypes[driverTypeIndex];
hr = D3D11CreateDeviceAndSwapChain(
nullptr, // device를 생성하기 위한 video adapter(nullptr = default adapter)
g_driverType, // driver type
nullptr, // software handle
createDeviceFlags, // device flag
featureLevels, // feature level array
numFeatureLevels, // feature level array length
D3D11_SDK_VERSION, // sdk version
&sd, // swap chain structure
&g_pSwapChain, // 생성된 swap chain
&g_pDevice, // 생성된 device
&g_featureLevel, // 생성된 feature level
&g_pImmediateContext); // 생성된 device context
if (SUCCEEDED(hr))
{
break;
}
}
if (FAILED(hr))
{
return hr;
}
ID3D11Texture2D* pBackBuffer = nullptr;
hr = g_pSwapChain->GetBuffer(
0, // 백버퍼 index
__uuidof(ID3D11Texture2D), // back buffer를 다루기 위한 interface
reinterpret_cast<LPVOID*>(&pBackBuffer) // back buffer interface output
);
if (FAILED(hr))
{
return hr;
}
hr = g_pDevice->CreateRenderTargetView(
pBackBuffer, // View에서 접근할 리소스
nullptr, // RTV 정의
&g_pRenterTargetView); // RTV를 받아올 변수
// 사용한 back buffer를
pBackBuffer->Release();
if (FAILED(hr))
{
return hr;
}
g_pImmediateContext->OMSetRenderTargets(
1, // Render target의 개수(최대 8개)
&g_pRenterTargetView, // Render target view의 배열
nullptr // Depth stencil view 포인터
);
D3D11_VIEWPORT vp;
vp.Width = static_cast<FLOAT>(width);
vp.Height = static_cast<FLOAT>(height);
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
vp.TopLeftX = 0;
vp.TopLeftY = 0;
g_pImmediateContext->RSSetViewports(1, &vp);
return S_OK;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
switch (uMsg)
{
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
void Render()
{
constexpr float clearColor[4] = { 0.0f, 0.125f,0.3f,1.0f };
g_pImmediateContext->ClearRenderTargetView(g_pRenterTargetView, clearColor);
g_pSwapChain->Present(0, 0);
}
void CleanupDevice()
{
if (g_pImmediateContext)
{
g_pImmediateContext->ClearState();
}
if (g_pRenterTargetView)
{
g_pRenterTargetView->Release();
}
if (g_pSwapChain)
{
g_pSwapChain->Release();
}
if (g_pImmediateContext)
{
g_pImmediateContext->Release();
}
if (g_pDevice)
{
g_pDevice->Release();
}
}