SwapChain

 

 

예를 들어 모니터에 하나의 프레임 버퍼를 가지고 게임 세상을 표현한다고 생각해보자.

매 프레임마다 버퍼를 지우고 그리거나 이미 그려진 버퍼에 덮어 쓰게 될것이다.

전자 같은 경우에는 지우고 다시 그리다보니 찰나의 시간에 지워진 버퍼가 보여지게 되고 이것이 화면 깜빡임(Screen Flickering)현상으로 보여지게 될것이다.

그리고 후자 같은 경우에는 덮어 쓰는 도중에 현재 그리는 프레임 데이터와 이전 프레임 데이터가 같이 보이는 순간이 생겨 화면 찢어짐(Screen Tearing) 현상이 생길 것이다.

결국 그려지는 과정이 보이다보니 보는 사람 입장에서는 불편할 수 밖에 없을것이다.

 

이러한 현상들을 피하고자 사용하는 방법이 바로 2개의 프레임 버퍼를 이용하는 것이다.

2개의 프레임 버퍼는 각각 화면 바깥의 텍스처인 후면 버퍼(Back Buffer)와 화면에 직접 표시되는 전면 버퍼(Front Buffer)를 뜻한다.

완전한 한 프레임을 후면 버퍼에 그리고 이전 프레임을 표현했던 전면 버퍼와 후면 버퍼의 역할을 맞바꾸게 된다.

이렇게 하면 화면을 보는 사용자에게는 프레임이 그려지는 과정이 나타나지 않는다.

사용자는 그저 완성된 프레임만을 볼 뿐이다. 이러한 기법을 더블 버퍼링(Double Buffering)이라고 부른다.

 

 

 


 

아래는 인프런 강사님이 설명 해주신 스왑체인에 관한 내용이다.

 

 교환 사슬
 [외주 과정]
 - 현재 게임 세상에 있는 상황을 묘사
 - 어떤 공식으로 어떻게 계산할지 던져줌
 - GPU가 열심히 계산 (외주)
 - 결과물 받아서 화면에 그려준다
 [외주 결과물]을 어디에 받지?
 - 어떤 종이(Buffer)에 그려서 건내달라고 부탁해보자
 - 특수 종이를 만들어서 -> 처음에 건내주고 -> 결과물을 해당 종이에 받는다 OK
 - 우리 화면에 특수 종이(외주 결과물) 출력해준다
 [?]
 - 그런데 화면에 현재 결과물 출력하는 와중에, 다음 화면도 외주를 맡겨야 함
 - 현재 화면 결과물은 이미 화면 출력에 사용중
 - 특수 종이를 2개 만들어서, 하나는 현재 화면을 그려주고, 하나는 외주 맡기고...
 - Double Buffering!
 - [0] [1]
 현재 화면 [1]  <-> GPU 작업중 [2] BackBuffer
 -> [2] 작업 완료
 -> 현재 화면 [2]  <-> GPU 작업중 [1] BackBuffer
 -> ... 반복
 Double Buffering을 이용해 화면출력과 BackBuffer작업 처리
 구식 소총 느낌? 총을 쏘고 뒤는 장전 뒤가 앞에 나와 총을 쏘고 앞은 뒤로가 총을 장전...
  • 스왑 체인은 전면 버퍼와 후면 버퍼 사이의 플러핑을 위한 체인으로 순차적으로 연결된 프레임 버퍼들의 집합.
  • Front Buffer: 모니터와 직접 연결, 그래픽 카드만 접근 가능
  • Back Buffer: 응용 프로그램이 그래픽 작업을 하는 영역이다.
  • 프레젠팅 , 프레젠테이션(Presentation)
    • Back Buffer를 Front Buffer로 옮기는 작업. (플리핑. Flipping)

프레젠팅.

 


<소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 void SwapChain::Init(const WindowInfo& info, ComPtr<IDXGIFactory> dxgi, ComPtr<ID3D12CommandQueue> cmdQueue)
{
    // 이전에 만든 정보 날린다
    _swapChain.Reset();
 
    DXGI_SWAP_CHAIN_DESC sd; // 종이를 만들고있다 ↓
    sd.BufferDesc.Width = static_cast<uint32>(info.width); // 버퍼의 해상도 너비
    sd.BufferDesc.Height = static_cast<uint32>(info.height); // 버퍼의 해상도 높이
    sd.BufferDesc.RefreshRate.Numerator = 60// 화면 갱신 비율
    sd.BufferDesc.RefreshRate.Denominator = 1// 화면 갱신 비율
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // 버퍼의 디스플레이 형식  8비트씩 4개 32비트
    sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
    sd.SampleDesc.Count = 1// 멀티 샘플링 OFF
    sd.SampleDesc.Quality = 0;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // 후면 버퍼에 렌더링할 것 
    sd.BufferCount = SWAP_CHAIN_BUFFER_COUNT; // 전면+후면 버퍼 각각 번갈아 작업해야함
    sd.OutputWindow = info.hwnd;
    sd.Windowed = info.windowed;
    sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; // 전면 후면 버퍼 교체 시 이전 프레임 정보 버림
    sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
 
    // dxgi 출력과 관련된 것들이 다 들어가있다.
    dxgi->CreateSwapChain(cmdQueue.Get(), &sd, &_swapChain); // 결과물을 swapChain에 넣음
 
 
    for (int32 i = 0; i < SWAP_CHAIN_BUFFER_COUNT; i++)
        _swapChain->GetBuffer(i, IID_PPV_ARGS(&_rtvBurffer[i])); // 버퍼를 rendertargets에 저장
}
cs

 

 

Header &amp;amp; Cpp

위 소스에서 위쪽은 헤더, 아래쪽은 Cpp 부분인데,

헤더를 보면 _renderTargets[SWAP_CHAIN_BUFFER_COUNT]로 배열이 선언되어있고,

SWAP_CHAIN_BUFFER_COUNT2이기 때문에 총 2개의 리소스를 저장 가능한 배열이 만들어진 것이다.

 

SwapIndex() 함수를 보면 _backBufferIndex를 조정하여 인덱스를 바꿈으로써 버퍼를 스왑을 하는 것을 볼 수 있다.

+ Recent posts