[DirectX] DirectX 그리고 장치 초기화
in Study on WinAPI&DirectX
DirectX란?
DirectX 라이브러리가 그래픽카드를 제어하는 함수를 모아놓은 것이다.
어떻게 프로그램이 장치를 제어하는 것일까?
DirectX 라이브러리는 Microsoft에서 제공하는 라이브러리로 그래픽카드가 연산을 하도록 명령을 모아놓은 라이브러리이다.
그리고 그래픽카드를 제어하는 언어는 비단 DirectX뿐만 아니라, OpenGL 이라는 언어도 존재한다.
DirectX를 VS에서 사용하기
VS를 설치하면 DirectX 헤더가 따라온다. 그래서 미리 컴파일된 헤더(pch 등)나 global헤더에서 다음과 같은 내용을 입력해주면 된다.
// global 헤더
#include <d3d11.h> // DirectX11
#include <d3dcompiler.h> // Shader 컴파일
#include <DirectXMath.h> // DX Math
#include <DirectXPackedVector.h> // 자주 사용하는 벡터 헤더
using namespace DirectX;
using namespace DirectX::PackedVector;
#pragma comment(lib, "d3d11")
#pragma comment(lib, "d3dcompiler")
#include "struct.h"
장치 초기화를 위한 CDevice 클래스 형성
// 헤더파일
class CDevice
: public CSingleton<CDevice>
{
private:
HWND m_hWnd;
ID3D11Device* m_Device; // GPU 메모리 할당
// 스마트 포인터
// ComPtr<ID3D11Device> m_Device;
ID3D11DeviceContext* m_Context; // GPU 제어, 렌더링, 동작 수행
IDXGISwapChain* m_SwapChain;
ID3D11Texture2D* m_RTTex;
ID3D11RenderTargetView m_RTV;
ID3D11Texture2D* m_DSTex;
ID3D11DepthStencilView* m_DSV;
D3D11_VIEWPORT m_ViewPort;
// 렌더 타겟 해상도
UINT m_iRenderWidth; // GPU 렌더 상 해상도
UINT m_iRenderHeight; // GPU 렌더 상 해상도
public:
int init(HWND _hWnd, UINT _iWidth, UINT _iHeight); // 초기화 함수
private:
int CreateSwapChain();
int CreateView();
public:
ID3D11Device* GetDevice() { return m_Device.Get(); }
ID3D11DeviceContext* GetDeviceContext() { return m_Context.Get(); }
public:
CDevice();
~CDevice();
}
// cpp파일
#include "pch.h"
#include "CDevice.h"
CDevice::CDevice() :
m_hWnd(nullptr),
m_Device(nullptr),
m_Context(nullptr),
m_SwapChain(nullptr),
m_RTTex(nullptr),
m_RTV(nullptr),
m_DSTex(nullptr),
m_DSV(nullptr),
m_ViewPort{}
{
}
CDevice::~CDevice()
{
if (m_Device != nullptr) m_Device->Release();
if (m_Context != nullptr) m_Context->Release();
if (m_SwapChain != nullptr) m_SwapChain->Release();
if (m_RTTex != nullptr) m_RTTex->Release();
if (m_RTV != nullptr) m_RTV->Release();
if (m_DSTex != nullptr) m_DSTex->Release();
if (m_DSV != nullptr) m_DSV->Release();
}
int CDevice::init(HWND _hWnd, UINT _iWidth, UINT _iHeight)
{
m_hWnd = _hWnd;
m_iRenderWidth = _iWidth;
m_iRenderHeight = _iHeight;
int iFlag = 0;
#ifdef _DEBUG
iFlag = D3D11_CREATE_DEVICE_DEBUG; // Debug 버전에서 문제 발생 감지용
#endif
D3D_FEATURE_LEVEL eLevel = D3D_FEATURE_LEVEL::D3D_FEATURE_LEVLE_11_0;
if(FAILED(D3D11CreateDevice(nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
iFlag,
nullptr,
0,
D3D11_SDK_VERSION,
&m_Device,
&eLevel,
&m_Context)))
{
MessageBox(nullptr, L"Device, Context 생성 실패", L"Device 초기화 에러", MB_OK);
return E_FAIL;
};
if (FAILED(CreateSwapChain()))
{
MessageBox(nullptr, L"SwapChain 생성 실패", L"Device 초기화 에러", MB_OK);
return E_FAIL;
}
if (FAILED(CreateView()))
{
MessageBox(nullptr, L"View 생성 실패", L"Device 초기화 에러", MB_OK);
return E_FAIL;
}
return S_OK;
}
int CDevice::CreateSwapChain()
{
DXGI_SWAP_CHAIN_DESC tDesc = {};
tDesc.OutputWindow = m_hWnd; // 출력 윈도우
tDesc.Windowed = true; // 창모드, 전체화면 모드 여부 (true : 창모드)
tDesc.BufferCount = 1; // DirectX 11에서는 1을 넣고, 12 에서는 2를 넣는다.
tDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
tDesc.BufferDesc.Width = m_iRenderWidth; // 윈도우 해상도와 매치
tDesc.BufferDesc.Height = m_iRenderHeight; // 윈도우 해상도와 매치
tDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // RGBA 각 8비트
tDesc.BufferDesc.RefreshRate.Denominator = 1; // 분자 (화면 갱신 비율)
tDesc.BufferDesc.RefreshRate.Numerator = 60; // 분모 (화면 갱신 비율)
tDesc.BufferDesc.Scaling = DXGI_MODE_SACLING_UNSPECIFIED;
tDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER::DXGI_MODE_SACNLINE_ORDER_UNSPECIFIED;
tDesc.SampleDesc.Count = 1;
tDesc.SampleDesc.Quality = 0;
tDesc.Flags = 0;
tDesc.SwapEffect = DXGI_SWAP_EFFECT::DXGI_SWAP_EFFECT_DISCARD;
// SwapChain 생성
IDXGIDevice* pDXGIDevice = nullptr;
IDXGIAdapter* pAdapter = nullptr;
IDXGIFactory* pFactory = nullptr;
HRESULT hr = S_OK;
hr = m_Device->QueryInterface(__uuidof(IDXGIDevice), (void**)&pDXGIDevice);
hr = pDXGIDevice->GetParent(__uuidof(IDXGIAdapter), (void**)&pAdapter);
hr = pAdapter->GetParent(__uuidof(IDXGIFactory), (void**)&pFactory);
hr = pFactory->CreateSwapChain(m_Device, &tDesc, &m_SwapChain);
pDXGIDevice->Release();
pAdapter->Release();
pFactoty->Release();
return S_OK;
}
int CDevice::CreateView()
{
m_SwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&m_RTTex);
if (FAILED(m_Device->CreateRenderTargetView(m_RTTex, nullptr, &m_RTV)))
{
return E_FAIL;
}
// DepthStencil 용도 텍스쳐 생성
D3D11_TEXTURE2D_DESC tDesc = {};
tDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
// 반드시 렌더 타겟과 같은 해상도로 설정해야 한다.
tDesc.Width = m_iRenderWidth;
tDesc.Height = m_iRenderHeight;
tDesc.ArraySize = 1;
tDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
tDesc.CPUAccessFlags = 0;
tDesc.Usage = D3D11_USAGE_DEFAULT;
tDesc.MipLevels = 1; // 원본만 생성, 2이상 : 저화질 텍스쳐 여러 장 생성
tDesc.SampleDesc.Count = 1;
tDesc.SampleDesc.Quality = 0;
if (FAILED(m_Device->CreateTexture2D(&tDesc, nullptr, &m_DSTex)))
{
return E_FAIL;
}
// DepthStencilView 생성
if (FAILED(m_Device->CreateDepthStencilView(m_DSTex, nullptr, &m_DSV)))
{
return E_FAIL;
}
return S_OK;
}
Device 클래스는 GPU에게 명령을 내리는 수단으로 사용될 것이다.
- ID3D11Device* : GPU 메모리 할당
- ID3D11DeviceContext* : GPU 제어, 렌더링, 동작 수행
- m_RTTex(RenderTarget Texture)
- m_DSTex(DepthStencil Texture) : 픽셀 단위로 깊이를 기록하는 역할. 렌더 순서에 영향
시스템 메모리(RAM)의 4가지 영역과 저장되는 정보
- Stack 메모리 : 함수의 호출, 그리고 이와 관계된 지역변수와 전역변수
- Heap 메모리 : 동적으로 할당된 변수
- Code 메모리 : 코드
- Data 메모리 : 정적변수 전역변수
GPU의 특징
GPU는 메모리와 프로세서가 합쳐진 구조이다.
대량의 일을 처리하기 위해서 설계된 부품. CPU에 비해서 코어수가 압도적으로 많으나, GPU 코어의 연산 처리 능력은 CPU의 코어 보다 떨어진다.
그래서 대량의 단순 연산을 진행할 때 GPU가 적합하다.
FrontBuffer와 BackBuffer와 SwapChain
GPU의 FrontBuffer의 그림을 시스템 메모리의 비트맵에 복사를 해서 윈도우에서 그림을 볼 수 있다.
즉, GPU의 그림을 시스템 메모리로 송출하는 역할을 한다.
BackBuffer에서 그림을 그렸으면 그 버퍼를 FrontBuffer로 바꾼다.
그렇게 FrontBuffer가 된 BackBuffer가 송출을 시작하고
BackBuffer가 된 FrontBuffer에서 그림을 그리기 시작한다.
이렇게 FrontBuffer와 BackBuffer가 매 프레임 마다 서로 바뀌는데
이들을 바꾸는 함수가 바로 SwapChain이다.
DirectX View의 종류
GPU 메모리를 사용하는 Texture와 Buffer를 원본 리소스로 분류하고, View 객체를 통해서 리소스를 전달하는 역할을 한다.
View의 종류는 아래와 같다.
- ID3D11RenderTargetView
- ID3D11DepthStencilView
- ID3D11ShaderResourceView
- ID3D11UnorderedAccessView
스마트 포인터의 사용
// global 헤더에서
#include <wrl.h>
using namespace Microsoft::WRL;
을 추가한다.
그리고
ComPtr<ID3D11Device> m_Device;
이렇게 변경해주면 스마트 포인터를 사용할 수 있다.
스마트 포인터는 객체임을 참조하라.
ComPtr로 받으면 Release를 직접 수동적으로 할 필요가 없어진다.
따라서 아래와 같이 사용할 수 있다.
ComPtr<ID3D11Device> m_Device;
ComPtr<ID3D11DeviceContext> m_Context; // GPU 제어, 렌더링, 동작 수행
ComPtr<IDXGISwapChain> m_SwapChain;
ComPtr<ID3D11Texture2D> m_RTTex;
ComPtr<ID3D11RenderTargetView> m_RTV;
ComPtr<ID3D11Texture2D> m_DSTex;
ComPtr<ID3D11DepthStencilView> m_DSV;
이렇게 하면 소멸자에 있는 아래 코드가 필요없어진다.
if (m_Device != nullptr) m_Device->Release();
if (m_Context != nullptr) m_Context->Release();
if (m_SwapChain != nullptr) m_SwapChain->Release();
if (m_RTTex != nullptr) m_RTTex->Release();
if (m_RTV != nullptr) m_RTV->Release();
if (m_DSTex != nullptr) m_DSTex->Release();
if (m_DSV != nullptr) m_DSV->Release();
그리고 위에서 작성한 코드도 바꾸어주어야 한다.
GetAddressOf() 함수는 객체 멤버의 주소를 반환하는 원리를 이용하여
스마트 포인터의 주소를 반환하는 함수이다.
기존에서 &__형태로 주소를 반환하던 자리에 GetAddressOf()함수를 사용하자.
정리하면 아래와 같이 작성해주면 된다.
// 헤더파일
class CDevice
: public CSingleton<CDevice>
{
private:
HWND m_hWnd;
ComPtr<ID3D11Device> m_Device;
ComPtr<ID3D11DeviceContext> m_Context; // GPU 제어, 렌더링, 동작 수행
ComPtr<IDXGISwapChain> m_SwapChain;
ComPtr<ID3D11Texture2D> m_RTTex;
ComPtr<ID3D11RenderTargetView> m_RTV;
ComPtr<ID3D11Texture2D> m_DSTex;
ComPtr<ID3D11DepthStencilView> m_DSV;
D3D11_VIEWPORT m_ViewPort;
// 렌더 타겟 해상도 (Vec2 사용시)
Vec2 m_vRenderResolution;
// 렌더 타겟 해상도 (Vec2 미사용시)
UINT m_iRenderWidth; // GPU 렌더 상 해상도
UINT m_iRenderHeight; // GPU 렌더 상 해상도
public:
int init(HWND _hWnd, UINT _iWidth, UINT _iHeight); // 초기화 함수
void Present() { m_SwapChain->Present(0, 0); }
public:
ID3D11Device* GetDevice() { return m_Device.Get(); }
ID3D11DeviceContext* GetDeviceContext() { return m_Context.Get(); }
private:
int CreateSwapChain();
int CreateView();
public:
CDevice();
~CDevice();
}
// cpp파일
#include "pch.h"
#include "CDevice.h"
CDevice::CDevice() :
m_hWnd(nullptr),
m_Device(nullptr),
m_Context(nullptr),
m_SwapChain(nullptr),
m_RTTex(nullptr),
m_RTV(nullptr),
m_DSTex(nullptr),
m_DSV(nullptr),
m_ViewPort{}
{
}
CDevice::~CDevice()
{
}
int CDevice::init(HWND _hWnd, UINT _iWidth, UINT _iHeight)
{
m_hWnd = _hWnd;
m_iRenderWidth = _iWidth;
m_iRenderHeight = _iHeight;
int iFlag = 0;
#ifdef _DEBUG
iFlag = D3D11_CREATE_DEVICE_DEBUG; // Debug 버전에서 문제 발생 감지용
#endif
D3D_FEATURE_LEVEL eLevel = D3D_FEATURE_LEVEL::D3D_FEATURE_LEVLE_11_0;
if(FAILED(D3D11CreateDevice(nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
iFlag,
nullptr,
0,
D3D11_SDK_VERSION,
m_Device.GetAddressOf(),
&eLevel,
m_Context.GetAddressOf())))
{
MessageBox(nullptr, L"Device, Context 생성 실패", L"Device 초기화 에러", MB_OK);
return E_FAIL;
};
if (FAILED(CreateSwapChain()))
{
MessageBox(nullptr, L"SwapChain 생성 실패", L"Device 초기화 에러", MB_OK);
return E_FAIL;
}
if (FAILED(CreateView()))
{
MessageBox(nullptr, L"View 생성 실패", L"Device 초기화 에러", MB_OK);
return E_FAIL;
}
// 출력 타겟 설정
m_Context->OMSetRenderTargets(1, m_RTV.GetAddressOf(), m_DSV.Get());
// View Port 설정
m_ViewPort.TopLeftX = 0.f;
m_ViewPort.TopLeftY = 0.f;
Vec2 vWindowResol = CEngine::GetInst()->GetWindowResolution();
m_ViewPort.Width = vWindowResol.x;
m_ViewPort.Height = vWindowResol.y;
m_ViewPort.MinDepth = 0.f;
m_ViewPort.MaxDepth = 1.f;
m_Context->RSSetViewPorts(1, &m_ViewPort);
return S_OK;
}
int CDevice::CreateSwapChain()
{
DXGI_SWAP_CHAIN_DESC tDesc = {};
tDesc.OutputWindow = m_hWnd; // 출력 윈도우
tDesc.Windowed = true; // 창모드, 전체화면 모드 여부 (true : 창모드)
tDesc.BufferCount = 1; // DirectX 11에서는 1을 넣고, 12 에서는 2를 넣는다.
tDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
tDesc.BufferDesc.Width = m_iRenderWidth; // 윈도우 해상도와 매치
tDesc.BufferDesc.Height = m_iRenderHeight; // 윈도우 해상도와 매치
tDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // RGBA 각 8비트
tDesc.BufferDesc.RefreshRate.Denominator = 1; // 분자 (화면 갱신 비율)
tDesc.BufferDesc.RefreshRate.Numerator = 60; // 분모 (화면 갱신 비율)
tDesc.BufferDesc.Scaling = DXGI_MODE_SACLING_UNSPECIFIED;
tDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER::DXGI_MODE_SACNLINE_ORDER_UNSPECIFIED;
tDesc.SampleDesc.Count = 1;
tDesc.SampleDesc.Quality = 0;
tDesc.Flags = 0;
tDesc.SwapEffect = DXGI_SWAP_EFFECT::DXGI_SWAP_EFFECT_DISCARD;
// SwapChain 생성
ComPtr<IDXGIDevice> pDXGIDevice = nullptr;
ComPtr<IDXGIAdapter> pAdapter = nullptr;
ComPtr<IDXGIFactory> pFactory = nullptr;
HRESULT hr = S_OK;
hr = m_Device->QueryInterface(__uuidof(IDXGIDevice), (void**)pDXGIDevice.GetAddressOf());
hr = pDXGIDevice->GetParent(__uuidof(IDXGIAdapter), (void**)pAdapter.GetAddressOf());
hr = pAdapter->GetParent(__uuidof(IDXGIFactory), (void**)pFactory.GetAddressOf());
hr = pFactory->CreateSwapChain(m_Device.Get(), &tDesc, m_SwapChain.GetAddressOf());
return S_OK;
}
int CDevice::CreateView()
{
m_SwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)m_RTTex.GetAddressOf());
if (FAILED(m_Device->CreateRenderTargetView(m_RTTex.Get(), nullptr, m_RTV.GetAddressOf())))
{
return E_FAIL;
}
// DepthStencil 용도 텍스쳐 생성
D3D11_TEXTURE2D_DESC tDesc = {};
tDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
// 반드시 렌더 타겟과 같은 해상도로 설정해야 한다.
tDesc.Width = m_iRenderWidth;
tDesc.Height = m_iRenderHeight;
tDesc.ArraySize = 1;
tDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
tDesc.CPUAccessFlags = 0;
tDesc.Usage = D3D11_USAGE_DEFAULT;
tDesc.MipLevels = 1; // 원본만 생성, 2이상 : 저화질 텍스쳐 여러 장 생성
tDesc.SampleDesc.Count = 1;
tDesc.SampleDesc.Quality = 0;
if (FAILED(m_Device->CreateTexture2D(&tDesc, nullptr, m_DSTex.GetAddressOf())))
{
return E_FAIL;
}
// DepthStencilView 생성
if (FAILED(m_Device->CreateDepthStencilView(m_DSTex.Get(), nullptr, m_DSV.GetAddressOf())))
{
return E_FAIL;
}
return S_OK;
}