프로그래밍을 하는 과정에서 버그의 발생과 디버그는 필연적이다. 아무리 설계가 완벽하다고 해도, 코드의 작성자가 인간인 이상 실수로 인하여 버그는 발생하기 때문에 코딩 이후에는 반드시 테스트와 디버그가 이루어져야 한다.
게임 프로그래밍의 경우에는 예외처리(Exception Handling)을 성능 상의 문제로 잘 사용하지 않고 개발 도중에 버그를 잡기 위해서 assert를 사용하는 경우가 많다. assert는 <assert.h> 헤더를 포함시키면 사용할 수 있으며 어떤 식이 참인지 거짓인지 판별해주고 그 식이 거짓이라면 에러 메시지 박스를 띄우고 어느 cpp의 몇 번째 줄에서 중단되었는지 알려주고 프로그램이 종료된다. 이 기능은 디버그 빌드에서만 작동하고 릴리즈 빌드에서는 작동하지 않는다.
기존의 assert 사용 예시
#include <assert.h>
class Player
{
// 플레이어에 대해 정의된 클래스
// ...
}
class GameManager
{
// 게임을 관리하는 매니저
// ...
static Player* GetPlayer(/*특정조건*/)
{
// 특정 조건에 맞는 플레이어를 반환한다.
// 만약 조건에 맞는 플레이어가 없다면 nullptr을 반환
}
}
int main()
{
Player * player = GetPlayer();
// 만약 player가 nullptr이라면 프로그램은 정지되고 에러 메시지 박스를 출력될 것이다.
assert(player != nullptr);
}
C++ 11에 들어서 새로 도입된 static_assert라는 것이 있는데 이것은 별도의 헤더를 포함시키지 않고도 사용할 수 있다. 이 static_assert가 기존의 assert와 다른 점은 기존의 assert는 런타임 도중에만 작동해서 해당 코드가 실행되기 전에는 에러가 발생하는지 알기 어려운 반면에 static_assert는 컴파일 타임에 발생하기 때문에 문제가 발생할 부분이라면 해당 코드가 작동하지 않을 확률이 높다고 하더라도 반드시 에러를 잡아낼 수 있다는 것이다. 다만 컴파일 타임에만 작동하는 static_assert의 특성 상 컴파일 타임에 결정되지 않았고 런타임이 되지 않으면 알 수 없는 부분에는 사용할 수 없다. 예를 들자면 위의 assert 예시 코드에서처럼 player 객체가 nullptr인지는 런타임 동안 GetPlayer()함수를 지나봐야만 결정되기 때문에 컴파일 타임에는 알 수 없어서 저런 곳에는 static_assert를 사용할 수 없다.
static_assert 사용 예시
/* 기존에 사용되던 구조체 a
struct a
{
int i
}
//*/
//* 수정된 구조체 a
struct a
{
int i;
float f;
}
//*/
int main()
{
static_assert(sizeof(a) == 8, "Old struct a used.");
}
위의 예시처럼 구조체 a가 수정된 이후에 실수로 이전 구조체를 사용하고 있는지 컴파일 타임에 확인해서 발생할 버그를 미리 막을 수 있게 된다. 다음은 각 상황에서 static_assert의 반응이다 :
실수로 이전의 구조체를 사용한 경우 컴파일 에러를 발생시킨다.
구조체를 제대로 사용하면 컴파일 에러를 발생시키지 않는다.
static_assert를 사용할 때, 주의할 점은 유니코드와 한글을 지원하지 않기 때문에 에러 메시지를 작성할 때, 멀티바이트 영어로 작성하는게 좋다.
[유니티 어필리에이트 프로그램]
아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.
IDXGISwapChain1::Present1를 호출한 이후에 디스플레이 서페이스 내부의 픽셀 관리에 대한 옵션.
typedefenumDXGI_SWAP_EFFECT
{
DXGI_SWAP_EFFECT_DISCARD = 0,
DXGI_SWAP_EFFECT_SEQUENTIAL = 1,
DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL = 3,
DXGI_SWAP_EFFECT_FLIP_DISCARD = 4
} DXGI_SWAP_EFFECT;
각 상수의 내용
DXGI_SWAP_EFFECT_DISCARD 이 플래그는 IDXGISwapChain1::Present1을 호출한 이후에 bit-block transfer(bitblt) 모델을 지정하고 DXGI가 back buffer의 내용을 지우도록 지정한다. 이 플래그는 하나 이상의 back buffer를 가진 swap chain에 대해 유효하지만 응용 프로그램은 0번 buffer(첫번째 버퍼)에 대한 읽기/쓰기 접근이 가능하다.
Note
전용 전체 화면과 UWP 전체 화면 사이에는 차이가 있다. Windows PC UWP에 Direct3D 11 응용 프로그램을 이식하는 경우, swap chain을 생성할때 UWP와 Win32에서의 동작이 다르기 때문에 DXGI_SWAP_EFFECT_DISCARD를 사용하는 것을 주의하지 않으면 GPU 성능에 좋지 않을 수 있다. 이것은 이전의 bitblt 모델에 의해서 원래 수행된 메모리 복사를 통해 연산 시간이 감소하기 때문에 UWP 응용 프로그램이 FLIP swap 모드를 강제되는 것이 원인이다.(다른 swap 모드로 설정되어 있는 경우에도). UWP 내의 Flip 모델을 사용하여 수동적으로 DX11 Discard swap chain을 변환하는 것과 가능하다면 DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL 대신에 DXGI_SWAP_EFFECT_FLIP_DISCARD를 사용할 것을 권장한다. 아래의 예시를 참조하라.
DXGI_SWAP_EFFECT_SEQUENTIAL 이 플래그는 IDXGISwapChain1::Present1을 호출한 이후에 bitblt 모델을 지정하고 DXGI가 back buffer의 내용을 지속하도록 지정한다. 첫번째 버퍼(0번 버퍼)부터 마지막 버퍼까지 순서대로 swap chain의 내용을 제시하려면 이 옵션을 사용한다. 이 플래그는 multisampling과 함께 사용할 수 없다.
DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL 이 플래그는 IDXGISwapChain1::Present1을 호출한 이후에 flip presentation 모델을 지정하고 DXGI가 back buffer의 내용을 지속하도록 지정한다. 이 플래그는 multisampling과 함께 사용할 수 없다.
Direct3D 11
이 enumeration 값은 Winsows8부터 지원되기 시작했다.
DXGI_SWAP_EFFECT_FLIP_DISCARD 이 플래그는 IDXGISwapChain1::Present1을 호출한 이후에 flip presentation 모델을 지정하고 DXGI가 back buffer의 내용을 지우도록 지정한다. 이 플래그는 multisampling과 partial presentation(부분 프레젠테이션)과 함께 사용할 수 없다. DXGI 1.4 Improvements(DXGI 1.4 개선점)를 보라.
Direct3D 11
이 enumeration 값은 Winsows10부터 지원되기 시작했다
Note
Windows Store 응용 프로그램들은 DXGI_SWAP_EFFECT_SEQUENTIAL이나 DXGI_SWAP_EFFECT_FLIP_DISCARD를 사용해야만 한다.
이 enumeration은 DXGI_SWAP_CHAIN_DESC과 DXGI_SWAP_CHAIN_DESC1 구조체에 의해서 사용된다.
Multisampling을 DXGI_SWAP_EFFECT_SEQUENTIAL이나 DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL과 함께 사용하기 위해서는, 별도의 render target에서 multisampling을 수행해야 한다. 예를 들어, 채워진 D3D11_TEXTURE2D_DESC 구조체와 ID3D11Device::CreateTexture2D 호출에 의해 multisampling된 텍스처를 생성한다(BindFlags 멤버를 D3D11_BIND_RENDER_TARGET로 설정하고 SampleDesc의 멤버를 multisampling 매개변수로 설정해야 한다). 다음은 텍스처를 위한 render-target view를 생성하고, 텍스처에 scene을 렌더링 하기 위해 ID3D11Device::CreateRenderTargetView를 호출한다. 마지막으로는 multisampling된 텍스처를 multisampling되지 않은 swap chain에 resolve하기 위해 ID3D11DeviceContext::ResolveSubresource를 호출한다.
Presentation 모델들의 중요한 차이점은 back buffer의 내용을 어떻게 Desktop Window Manager(DWM)의 composition으로 가지고 오는가 이다. bitblt 모델에서는 DXGI_SWAP_EFFECT_DISCARD와 DXGI_SWAP_EFFECT_SEQUENTIAL 값을 사용하고, IDXGISwapChain1::Present1의 매 호출마다 back buffer의 내용을 redirection surface에 복사한다. Flip 모델에서는 DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL 값을 사용하고, 모든 back buffer를 DWM과 공유한다. 따라서 DWM은 추가적인 복사 작업없이 back buffer에서 바로 작성할 수 있다. 일반적으로는 flip 모델이 bitblt 모델보다 더 효율적이며, 강화된 현재 상태에 대한 통계 등 더 많은 기능을 제공한다.
SyncInterval 파라미터에 0이 지정된 flip 모델 swap chain에 IDXGISwapChain1::Present1를 호출할 때, IDXGISwapChain1::Present1의 동작은 Direct3D 9Ex의 D3DSWAPEFFECT_FLIPEX와 D3DPRESENT_FORCEIMMEDIATE로 설정된 IDirect3DDevice9Ex:PresentEx와 같은 동작을 한다. 즉, 런타임은 큐에 저장된 모든 이전 프레임 대신에 다름 프레임을 present할 뿐만 아니라 큐에 저장된 이전 프레임들에게 남아있는 모든 잔여 시간을 종료한다.
Flip 모델이 더 효율적인지에 상관없이, Bitblt 모델은 GDI와 DirectX Presentation을 혼합할 수 있는 유일한 방법이기 때문에 응용프로그램은 여전히 bitblt 모델을 선택할 수 있다. Flip 모델에서는, 응용 프로그램은 DXGI_SWAP_CHAIN_FLAG_GDI_COMPATIBLE으로 swap chain을 생성해야 하며, back buffer에서 명시적으로 GetDC를 사용해야만 한다. Flip-모델 swap chain의 IDXGISwapChain1::Present1이 성공적인 첫번째 호출이 된 이후에, GDI는 swap chain을 파괴하고 swap chain과 연결된 HWND와 함께 더이상 작업을 수행하지 않는다. 이러한 제한 사항은 심지어 ScrollWindowEx 같은 메서드에도 확장된다.