DirectX 디바이스 리소스 작업
Windows 스토어 DirectX 게임에서 Microsoft DXGI(DirectX Graphics Infrastructure)의 역할을 이해합니다. DXGI는 하위 수준 그래픽 및 그래픽 어댑터 리소스를 구성하고 관리하는 데 사용되는 API 집합입니다. 그것없이, 당신은 창에 게임의 그래픽을 그릴 수있는 방법이 없을 것입니다.
이러한 방식으로 DXGI를 생각해 보세요. GPU에 직접 액세스하고 리소스를 관리하려면 앱에 설명하는 방법이 있어야 합니다. GPU에 대해 필요한 가장 중요한 정보는 픽셀을 그리는 위치이므로 해당 픽셀을 화면으로 보낼 수 있습니다. 일반적으로 이를 "백 버퍼"라고 합니다. GPU 메모리에서 픽셀을 그린 다음 "대칭 이동" 또는 "교환"하고 새로 고침 신호로 화면으로 보낼 수 있는 위치입니다. DXGI를 사용하면 해당 위치와 해당 버퍼를 사용할 수 있는 수단을 얻을 수 있습니다( 스왑 체인 은 스왑 가능한 버퍼 체인이므로 여러 버퍼링 전략을 허용).
이렇게 하려면 스왑 체인에 쓸 수 있는 액세스 권한과 스왑 체인에 대한 현재 백 버퍼를 표시하는 창에 대한 핸들이 필요합니다. 또한 두 항목을 연결하여 운영 체제가 백 버퍼의 내용으로 창을 새로 고치도록 요청하면 됩니다.
화면에 그리는 전체 프로세스는 다음과 같습니다.
- 앱에 대한 CoreWindow 를 가져옵니다.
- Direct3D 디바이스 및 컨텍스트에 대한 인터페이스를 가져옵니다.
- CoreWindow에 렌더링된 이미지를 표시하는 스왑 체인을 만듭니다.
- 그리기 위한 렌더링 대상을 만들고 픽셀로 채웁니다.
- 스왑 체인을 제시!
앱에 대한 창 만들기
가장 먼저 해야 할 일은 창을 만드는 것입니다. 먼저 WNDCLASS 인스턴스를 채워 창 클래스를 만든 다음 RegisterClass를 사용하여 등록합니다. 창 클래스에는 사용하는 아이콘, 정적 메시지 처리 함수(나중에 자세히 설명) 및 창 클래스의 고유한 이름을 포함하여 창의 필수 속성이 포함됩니다.
if(m_hInstance == NULL)
m_hInstance = (HINSTANCE)GetModuleHandle(NULL);
HICON hIcon = NULL;
WCHAR szExePath[MAX_PATH];
GetModuleFileName(NULL, szExePath, MAX_PATH);
// If the icon is NULL, then use the first one found in the exe
if(hIcon == NULL)
hIcon = ExtractIcon(m_hInstance, szExePath, 0);
// Register the windows class
WNDCLASS wndClass;
wndClass.style = CS_DBLCLKS;
wndClass.lpfnWndProc = MainClass::StaticWindowProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = m_hInstance;
wndClass.hIcon = hIcon;
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = m_windowClassName.c_str();
if(!RegisterClass(&wndClass))
{
DWORD dwError = GetLastError();
if(dwError != ERROR_CLASS_ALREADY_EXISTS)
return HRESULT_FROM_WIN32(dwError);
}
다음으로 창을 만듭니다. 또한 방금 만든 창 클래스의 이름과 창에 대한 크기 정보를 제공해야 합니다. CreateWindow를 호출하면 HWND라는 창에 대한 불투명 포인터를 다시 가져옵니다. HWND 포인터를 유지하고 창에 그리는 데 사용하는 DXGI 스왑 체인을 만들 때(특히 중요) 삭제 또는 다시 만들기를 포함하여 창을 참조해야 할 때마다 사용해야 합니다.
m_rc;
int x = CW_USEDEFAULT;
int y = CW_USEDEFAULT;
// No menu in this example.
m_hMenu = NULL;
// This example uses a non-resizable 640 by 480 viewport for simplicity.
int nDefaultWidth = 640;
int nDefaultHeight = 480;
SetRect(&m_rc, 0, 0, nDefaultWidth, nDefaultHeight);
AdjustWindowRect(
&m_rc,
WS_OVERLAPPEDWINDOW,
(m_hMenu != NULL) ? true : false
);
// Create the window for our viewport.
m_hWnd = CreateWindow(
m_windowClassName.c_str(),
L"Cube11",
WS_OVERLAPPEDWINDOW,
x, y,
(m_rc.right-m_rc.left), (m_rc.bottom-m_rc.top),
0,
m_hMenu,
m_hInstance,
0
);
if(m_hWnd == NULL)
{
DWORD dwError = GetLastError();
return HRESULT_FROM_WIN32(dwError);
}
Windows 데스크톱 앱 모델에는 Windows 메시지 루프에 대한 후크가 포함됩니다. 창 이벤트를 처리하는 "StaticWindowProc" 함수를 작성하여 이 후크에서 기본 프로그램 루프를 기반으로 해야 합니다. Windows에서 클래스 인스턴스의 컨텍스트 외부에서 호출하므로 정적 함수여야 합니다. 다음은 정적 메시지 처리 함수의 매우 간단한 예입니다.
LRESULT CALLBACK MainClass::StaticWindowProc(
HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
switch(uMsg)
{
case WM_CLOSE:
{
HMENU hMenu;
hMenu = GetMenu(hWnd);
if (hMenu != NULL)
{
DestroyMenu(hMenu);
}
DestroyWindow(hWnd);
UnregisterClass(
m_windowClassName.c_str(),
m_hInstance
);
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
이 간단한 예제는 프로그램 종료 조건에 대해서만 검사. WM_CLOSE, 창을 닫도록 요청될 때 전송되고, 창이 화면에서 실제로 제거된 후에 전송되는 WM_DESTROY. 전체 프로덕션 앱은 다른 창 이벤트도 처리해야 합니다. 창 이벤트 전체 목록은 창 알림을 참조 하세요.
기본 프로그램 루프 자체는 Windows에서 정적 메시지 프로시전을 실행할 수 있도록 허용하여 Windows 메시지를 승인해야 합니다. 동작을 포크하여 프로그램을 효율적으로 실행할 수 있도록 지원합니다. 각 반복은 사용할 수 있는 경우 새 Windows 메시지를 처리하도록 선택해야 하며, 큐에 메시지가 없으면 새 프레임을 렌더링해야 합니다. 다음은 매우 간단한 예제입니다.
bool bGotMsg;
MSG msg;
msg.message = WM_NULL;
PeekMessage(&msg, NULL, 0U, 0U, PM_NOREMOVE);
while (WM_QUIT != msg.message)
{
// Process window events.
// Use PeekMessage() so we can use idle time to render the scene.
bGotMsg = (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE) != 0);
if (bGotMsg)
{
// Translate and dispatch the message
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
// Update the scene.
renderer->Update();
// Render frames during idle time (when no messages are waiting).
renderer->Render();
// Present the frame to the screen.
deviceResources->Present();
}
}
Direct3D 디바이스 및 컨텍스트에 대한 인터페이스 가져오기
Direct3D를 사용하는 첫 번째 단계는 ID3D11Device 및 ID3D11DeviceContext의 인스턴스로 표시되는 Direct3D 하드웨어(GPU)에 대한 인터페이스를 획득하는 것입니다. 전자는 GPU 리소스의 가상 표현이며, 후자는 렌더링 파이프라인 및 프로세스의 디바이스 독립적 추상화입니다. ID3D11Device에는 일반적으로 렌더링이 발생하기 전에 자주 호출하지 않는 그래픽 메서드가 포함되어 픽셀 그리기를 시작하는 데 필요한 리소스 집합을 획득하고 구성합니다. 반면 ID3D11DeviceContext에는 버퍼 및 뷰 및 기타 리소스 로드, 출력 병합기 및 래스터라이저 상태 변경, 셰이더 관리, 상태 및 셰이더를 통해 해당 리소스 전달 결과 그리기 등 모든 프레임에서 호출하는 메서드가 포함되어 있습니다.
이 프로세스에는 기능 수준 설정이라는 매우 중요한 부분이 있습니다. 기능 수준은 가장 낮은 기능 집합으로 D3D_FEATURE_LEVEL_9_1 현재 최고로 D3D_FEATURE_LEVEL_11_1 앱이 지원하는 최소 하드웨어 수준을 DirectX에 알려줍니다. 가능한 가장 광범위한 대상 그룹에 도달하려면 최소값으로 9_1을 지원해야 합니다. 잠시 시간을 내어 Direct3D 기능 수준을 읽고 게임에서 지원하려는 최소 및 최대 기능 수준을 스스로 평가하고 선택한 의미를 이해합니다.
Direct3D 디바이스와 디바이스 컨텍스트 모두에 대한 참조(포인터)를 가져와 DeviceResources 인스턴스(ComPtr 스마트 포인터)에 클래스 수준 변수로 저장합니다. Direct3D 디바이스 또는 디바이스 컨텍스트에 액세스해야 할 때마다 이러한 참조를 사용합니다.
D3D_FEATURE_LEVEL levels[] = {
D3D_FEATURE_LEVEL_11_1
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1,
};
// This flag adds support for surfaces with a color-channel ordering different
// from the API default. It is required for compatibility with Direct2D.
UINT deviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
#if defined(DEBUG) || defined(_DEBUG)
deviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
// Create the Direct3D 11 API device object and a corresponding context.
Microsoft::WRL::ComPtr<ID3D11Device> device;
Microsoft::WRL::ComPtr<ID3D11DeviceContext> context;
hr = D3D11CreateDevice(
nullptr, // Specify nullptr to use the default adapter.
D3D_DRIVER_TYPE_HARDWARE, // Create a device using the hardware graphics driver.
0, // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE.
deviceFlags, // Set debug and Direct2D compatibility flags.
levels, // List of feature levels this app can support.
ARRAYSIZE(levels), // Size of the list above.
D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Windows Store apps.
&device, // Returns the Direct3D device created.
&m_featureLevel, // Returns feature level of device created.
&context // Returns the device immediate context.
);
if (FAILED(hr))
{
// Handle device interface creation failure if it occurs.
// For example, reduce the feature level requirement, or fail over
// to WARP rendering.
}
// Store pointers to the Direct3D 11.1 API device and immediate context.
device.As(&m_pd3dDevice);
context.As(&m_pd3dDeviceContext);
스왑 체인 만들기
좋아: 그릴 창이 있고 데이터를 보내고 GPU에 명령을 제공하는 인터페이스가 있습니다. 이제 함께 가져오는 방법을 살펴보겠습니다.
먼저 DXGI에 스왑 체인의 속성에 사용할 값을 알려줍니다. DXGI_SWAP_CHAIN_DESC 구조를 사용하여 이 작업을 수행합니다. 데스크톱 앱에는 6개의 필드가 특히 중요합니다.
- 창: 스왑 체인이 전체 화면인지 아니면 창에 잘렸는지 여부를 나타냅니다. 이전에 만든 창에 스왑 체인을 배치하려면 이를 TRUE로 설정합니다.
- BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT 설정합니다. 이는 스왑 체인이 그리기 화면이 됨을 나타내며 이를 Direct3D 렌더링 대상으로 사용할 수 있습니다.
- SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL 설정합니다.
- 형식: DXGI_FORMAT_B8G8R8A8_UNORM 형식은 32비트 색을 지정합니다. 3개의 RGB 색 채널 각각에 대해 8비트, 알파 채널의 경우 8비트입니다.
- BufferCount: 기존 이중 버퍼링 동작에 대해 이 값을 2로 설정하여 떼어내지 않도록 합니다. 그래픽 콘텐츠가 모니터 새로 고침 주기를 두 개 이상 사용하여 단일 프레임을 렌더링하는 경우 버퍼 수를 3으로 설정합니다(예: 임계값이 16ms 이상인 경우 60Hz).
- SampleDesc: 이 필드는 다중 샘플링을 제어합니다. 대칭 이동 모델 스왑 체인의 경우 개수를 1로, 품질을 0으로 설정합니다. (플립 모델 스왑 체인과 함께 다중 샘플링을 사용하려면 별도의 다중 샘플링 렌더링 대상을 그린 다음 해당 대상을 표시하기 직전에 스왑 체인으로 확인합니다. 예제 코드는 Windows 스토어 앱의 다중 샘플링에 제공됩니다.)
스왑 체인에 대한 구성을 지정한 후에는 스왑 체인을 만들려면 Direct3D 디바이스(및 디바이스 컨텍스트)를 만든 동일한 DXGI 팩터리를 사용해야 합니다.
짧은 형식:
이전에 만든 ID3D11Device 참조를 가져옵니다. IDXGIDevice3(아직 없는 경우)로 업캐스트한 다음 IDXGIDevice::GetAdapter를 호출하여 DXGI 어댑터를 가져옵니다. IDXGIAdapter::GetParent(IDXGIAdapter가 IDXGIObject에서 상속함)를 호출하여 해당 어댑터에 대한 부모 팩터리를 가져옵니다. 이제 다음 코드 샘플에 표시된 것처럼 CreateSwapChainForHwnd를 호출하여 해당 팩터리를 사용하여 스왑 체인을 만들 수 있습니다.
DXGI_SWAP_CHAIN_DESC desc;
ZeroMemory(&desc, sizeof(DXGI_SWAP_CHAIN_DESC));
desc.Windowed = TRUE; // Sets the initial state of full-screen mode.
desc.BufferCount = 2;
desc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.SampleDesc.Count = 1; //multisampling setting
desc.SampleDesc.Quality = 0; //vendor-specific flag
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
desc.OutputWindow = hWnd;
// Create the DXGI device object to use in other factories, such as Direct2D.
Microsoft::WRL::ComPtr<IDXGIDevice3> dxgiDevice;
m_pd3dDevice.As(&dxgiDevice);
// Create swap chain.
Microsoft::WRL::ComPtr<IDXGIAdapter> adapter;
Microsoft::WRL::ComPtr<IDXGIFactory> factory;
hr = dxgiDevice->GetAdapter(&adapter);
if (SUCCEEDED(hr))
{
adapter->GetParent(IID_PPV_ARGS(&factory));
hr = factory->CreateSwapChain(
m_pd3dDevice.Get(),
&desc,
&m_pDXGISwapChain
);
}
이제 막 시작하는 경우 여기에 표시된 구성을 사용하는 것이 가장 좋습니다. 이제 이 시점에서 이전 버전의 DirectX에 이미 익숙한 경우 다음을 묻는 메시지가 표시될 수 있습니다. "왜 이러한 모든 클래스를 다시 살펴보는 대신 동시에 디바이스 및 스왑 체인을 만들지 않았습니까?" 대답은 효율성입니다. 스왑 체인은 Direct3D 디바이스 리소스이며 디바이스 리소스는 해당 리소스를 만든 특정 Direct3D 디바이스에 연결됩니다. 새 스왑 체인을 사용하여 새 디바이스를 만드는 경우 새 Direct3D 디바이스를 사용하여 모든 디바이스 리소스를 다시 만들어야 합니다. 따라서 위와 같이 동일한 팩터리로 스왑 체인을 만들어 스왑 체인을 다시 만들고 이미 로드한 Direct3D 디바이스 리소스를 계속 사용할 수 있습니다.
이제 운영 체제의 창, GPU 및 해당 리소스에 액세스하는 방법 및 렌더링 결과를 표시하는 스왑 체인이 있습니다. 남은 것은 모든 것을 하나로 묶는 것입니다!
그리기용 렌더링 대상 만들기
셰이더 파이프라인에는 픽셀을 그릴 리소스가 필요합니다. 이 리소스를 만드는 가장 간단한 방법은 ID3D11Texture2D 리소스를 픽셀 셰이더가 그릴 백 버퍼로 정의한 다음 해당 텍스처를 스왑 체인으로 읽는 것입니다.
이렇게 하려면 렌더링 대상 보기를 만듭니다. Direct3D에서 뷰는 특정 리소스에 액세스하는 방법입니다. 이 경우 뷰를 사용하면 픽셀당 작업을 완료할 때 픽셀 셰이더가 텍스처에 쓸 수 있습니다.
이에 대한 코드를 살펴보겠습니다. 스왑 체인에서 DXGI_USAGE_RENDER_TARGET_OUTPUT 설정하면 기본 Direct3D 리소스를 그리기 화면으로 사용할 수 있습니다. 따라서 렌더링 대상 뷰를 얻으려면 스왑 체인에서 백 버퍼를 가져와서 백 버퍼 리소스에 바인딩된 렌더링 대상 뷰를 만들어야 합니다.
hr = m_pDXGISwapChain->GetBuffer(
0,
__uuidof(ID3D11Texture2D),
(void**) &m_pBackBuffer);
hr = m_pd3dDevice->CreateRenderTargetView(
m_pBackBuffer.Get(),
nullptr,
m_pRenderTarget.GetAddressOf()
);
m_pBackBuffer->GetDesc(&m_bbDesc);
깊이 스텐실 버퍼도 만듭니다. 깊이 스텐실 버퍼는 ID3D11Texture2D 리소스의 특정 형태일 뿐이며, 일반적으로 카메라에서 장면의 개체 거리에 따라 래스터화 중에 어떤 픽셀이 우선적으로 그려지는지 결정하는 데 사용됩니다. 깊이 스텐실 버퍼는 래스터화 중에 특정 픽셀이 분리카드 또는 무시되는 스텐실 효과에도 사용할 수 있습니다. 이 버퍼는 렌더링 대상과 크기가 같아야 합니다. 최종 래스터화 전후에 셰이더 파이프라인에서 단독으로 사용되므로 프레임 버퍼 깊이 스텐실 텍스처에서 읽거나 렌더링할 수 없습니다.
또한 깊이 스텐실 버퍼에 대한 뷰를 ID3D11DepthStencilView로 만듭니다. 보기는 셰이더 파이프라인에 기본 ID3D11Texture2D 리소스를 해석하는 방법을 알려 줍니다. 따라서 이 보기를 제공하지 않으면 픽셀당 깊이 테스트가 수행되지 않으며 장면의 개체가 최소한 약간 내부적인 것처럼 보일 수 있습니다.
CD3D11_TEXTURE2D_DESC depthStencilDesc(
DXGI_FORMAT_D24_UNORM_S8_UINT,
static_cast<UINT> (m_bbDesc.Width),
static_cast<UINT> (m_bbDesc.Height),
1, // This depth stencil view has only one texture.
1, // Use a single mipmap level.
D3D11_BIND_DEPTH_STENCIL
);
m_pd3dDevice->CreateTexture2D(
&depthStencilDesc,
nullptr,
&m_pDepthStencil
);
CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D);
m_pd3dDevice->CreateDepthStencilView(
m_pDepthStencil.Get(),
&depthStencilViewDesc,
&m_pDepthStencilView
);
마지막 단계는 뷰포트를 만드는 것입니다. 화면에 표시되는 백 버퍼의 표시되는 사각형을 정의합니다. 뷰포트의 매개 변수를 변경하여 화면에 표시되는 버퍼 부분을 변경할 수 있습니다. 이 코드는 전체 화면 스왑 체인의 경우 전체 창 크기 또는 화면 해상도를 대상으로 합니다. 재미를 위해 제공된 좌표 값을 변경하고 결과를 관찰합니다.
ZeroMemory(&m_viewport, sizeof(D3D11_VIEWPORT));
m_viewport.Height = (float) m_bbDesc.Height;
m_viewport.Width = (float) m_bbDesc.Width;
m_viewport.MinDepth = 0;
m_viewport.MaxDepth = 1;
m_pd3dDeviceContext->RSSetViewports(
1,
&m_viewport
);
그리고 그것은 당신이 창에 픽셀을 그리는 아무것도에서 이동하는 방법입니다! 시작할 때 DXGI를 통해 DirectX가 픽셀 그리기를 시작하는 데 필요한 핵심 리소스를 관리하는 방법을 숙지하는 것이 좋습니다.
다음으로 그래픽 파이프라인의 구조를 살펴보겠습니다. DirectX 앱 템플릿의 렌더링 파이프라인 이해를 참조하세요.
관련 항목
-
다음으로