레이블이 graphics인 게시물을 표시합니다. 모든 게시물 표시
레이블이 graphics인 게시물을 표시합니다. 모든 게시물 표시

2014-08-09

[Graphics] Sprite는 무엇인가

 Sprite는 렌더링 파이프라인을 타지 않고 target(screen이든 FBO든)에 직접 그림을 그릴 수 있게 해주는 2D bitmap을 의미한다. Sprite에 그린 그림은 rendering pipeline을 타지 않기 때문에 transform 이나 다른 효과들과 독립적으로 화면에 보이게 된다.
 이러한 특성 때문에 3D게임에서 UI를 그릴 때 이용된다.

2014-05-04

왜 Triple buffering을 사용하는가

 지난번 글에서 double bufferingVSync에 관하여 설명하였다.
 이번 글에서는 VSync가 가지는 문제와 그것을 어떻게 triple buffering이 해결하는지를 적어보도록 하겠다.

 우선 다음 문제를 풀어보자.
vertical interval이 16.6ms마다 있는 display를 사용하고
double buffering을 사용하는 드라이버에서 VSync를 사용한다고 했을 때
back buffer에서 scene 하나를 완성하는 데 걸리는 시간이 16ms인 프로그램을 작성하면
이 프로그램은 몇 fps가 나올 것인가?
 매 frame마다 하나의 scene을 16ms 동안 그리고, 남은 0.6ms는 VSync를 기다리기 때문에 16.6ms마다 한 frame을 그려서 60fps가 나온다.
 VSync를 기다리는 0.6ms가 아쉽기는 하지만 어차피 60fps 이상은 사람 눈으로 잘 구분이 안 되니 큰 손해를 보는 것은 아니다.

 문제는 아래의 상황이다.
vertical interval이 16.6ms마다 있는 display를 사용하고
double buffering을 사용하는 드라이버에서 VSync를 사용한다고 했을 때
back buffer에서 scene 하나를 완성하는 데 걸리는 시간이
홀수 번째 scene은 15ms, 짝수 번째 scene은 17ms가 걸리도록 프로그램을 작성하면
이 프로그램은 몇 fps가 나올 것인가?
 매 프레임 back buffer에 그리는 데 걸리는 평균 시간은 첫 번째 경우와 같다.
 그렇다면 이 케이스에서도 60 fps가 나올까?
 아니다. 홀수 번째 frame에서는 15ms 동안 그림을 그리고 1.6ms 동안 기다리지만, 짝수 번째 frame에서는 back buffer에 그림을 그리는데 17ms가 걸렸기 때문에 첫 번째 vertical interval 때 swap buffer를 하지 못하고, 그 다음 vertical interval까지 기다려야 한다. 즉, 그림이 완성된 뒤 16.2ms만큼 더 기다려야 화면을 바꿀 수 있다는 것이다.
 이렇게 되면 frame 2개를 그리는데 3번의 vertical interval이 필요하므로, 약 40fps가 나오게 된다. 그림을 그리는 데 걸리는 평균 시간이 같은데도 성능은 2/3로 떨어지게 되는 것이다.

 아래의 상황도 한번 보자.
vertical interval이 16.6ms마다 있는 display를 사용하고
double buffering을 사용하는 드라이버에서 VSync를 사용한다고 했을 때
back buffer에서 scene 하나를 완성하는 데 걸리는 시간이 17ms인 프로그램을 작성하면
이 프로그램은 몇 fps가 나올 것인가?
 첫 번째 상황에 비해 scene 하나를 그리는 데 걸리는 시간이 고작 1ms 늘었을 뿐이다.
 하지만 실제로 성능은 하나의 scene을 그리는데 2번의 vertical interval이 필요로 하므로, 원래 성능의 절반 정도인 30fps가 나오게 된다.

 그러면 무조건 vertical interval 사이에 한 frame을 그리도록 프로그램을 작성해서 문제를 해결하면 안 될까?
 음.... 가능하면 그렇게 하는 것이 좋다.
 하지만 프로그램의 실행 시간이라는 것은 기본적으로 환경에 많은 영향을 받기 때문에 예측하기 어렵다. 가능하면 이 현상(이 현상에 이름이 있는지는 모르겠다.)의 근본적인 원인을 해결하는 것이 좋다. 그래서 해결책으로 나온 것이 Triple buffering이다.

Triple buffering은 기본적으로 Double buffering이랑 같지만 2개의 back buffer와 front buffer, 총 3개의 buffer를 사용한다.
 Triple buffering이 어떻게 문제를 해결하는지 이해하기 쉽게 해주는 다음 그림을 보자. 아래는 copy를 하는 경우만 나와 있지만, vsync를 사용한다면 copy든 flip이든 vertical interval 동안만 update가 가능하므로 flip과 큰 차이는 없다.
http://en.wikipedia.org/wiki/Multiple_buffering
 위 그림의 4번 시나리오는 Double buffering과 vsync를 사용하는 경우 중 한 scene을 그리는 시간이 한 frame rate보다 길면 어떻게 되는가를 보여주는 자료다.
 draw B가 끝나는 부분을 자세히 보자. 아쉽게도 draw B는 한 프레임을 약간 넘어가 vertical interval 동안 화면을 갱신하지 못하게 되었다. 이렇게 타이밍을 놓친 scene은 그다음 vertical interval이 되어야 화면에 보일 수 있다. 그전까지는 여전히 draw A에서 그렸던 장면이 화면에 남아 있게 된다. 이것이 위에서 말했던 성능 저하의 원인이다.

 다시 위 그림에서 5번 시나리오를 보자. 이건 4번과 똑같은 일을 triple buffering을 사용하여 처리한 것이다.
 double buffer에서는 draw Abuffer 1에서 video memory로 옮겨진 뒤 Vertical interval이 될 때까지 draw B가 시작하지 못했지만, triple buffering을 사용하는 경우 여분의 buffer가 하나 더 있기 때문에 draw B를 바로 시작할 수 있다.

 이렇게 식으로 triple buffering을 이용하면 VSync로 인한 성능 저하를 줄일 수 있다.
 하지만 surface만큼의 메모리를 더 사용해야 하는 단점이 있어서 아직 대부분의 그래픽 카드에서 기본으로 지원하는 기능은 아니다.
 하지만 새로 나오는 그래픽 카드들에는 그래픽 카드에서 지원하는 기능으로 들어가기 시작했고, 지원 하지 않는 그래픽 카드에서는 FBO를 이용하여 구현할 수 있다.

2014-05-03

copy와 flip - Double buffering의 2가지 기법

 Buffer는 그림을 그리기 위해 메모리에 잡아놓은 영역을 의미한다.
 그래픽 드라이버가 화면을 갱신해야 할 때 이 영역에서 그림을 읽어 화면을 그린다.

 과거의 graphic card(혹은 요새 나오는 저 사양의 embedded 용 graphic card)에서는 single buffering이라는 기법을 이용한다.
 single buffering이란, 말 그대로 한 개의 버퍼만을 사용하는 방식이다. 완성된 그림을 buffer에 그리고, 화면에 그림을 그려야 할 때마다 이 buffer에서 화면으로 그림을 가져가는 것이다.
 가장 기본적인 구현이지만, 이런 방식은 buffer에서 화면으로 가져가는 동안 buffer를 업데이트하지 못한다는 단점이 있다.
 이런 단점을 해결하기 위하여 double buffering이라는 기법을 사용한다.

Double buffering

 Double buffering에서는 front buffer와 back buffer라고 불리는 2개의 buffer를 사용한다. back buffer에 그림을 그리고 한 프레임이 완성되면 back buffer와 front buffer를 바꾼다. (이를 swap buffer라고 한다.) 화면에 그림이 필요할 때는 언제나 front buffer에서 화면을 가져간다.
 화면이 읽어들이는 buffer는 언제나 front buffer이므로 화면을 업데이트할 필요가 있으면 언제나 back buffer에 그리면 된다.
 이때 back buffer에서 front buffer로 옮기는 방식에는 2가지 방법이 있다.

Copy

 첫 번째 방법은 copy라고 부르는 방법이다. 이 방법은 front buffer는 그래픽 메모리에 만든다. back buffer는 그래픽 메모리나 시스템 메모리 어디에 만들어도 상관없지만, 보통 copy를 사용하는 경우는 그래픽 메모리를 아끼기 위한 경우가 많아서 back buffer는 시스템 메모리에 잡는다.
 프로그램이 그림을 그리라고 하면 이를 back buffer에 그리고, swap buffer를 하게 되면 back buffer에 있는 내용을 front buffer로 복사해 가게 된다.
http://docs.oracle.com/javase/tutorial/extra/fullscreen/doublebuf.html
 전 frame에 그렸던 그림을 그대로 유지할 수 있어 화면 일부분만을 업데이트하는 경우에는 효율적이다. 하지만 대부분의 3d 엔진은 buffer를 지우고 처음부터 다시 그리기 때문에 이런 방식은 비효율적이다.
 게다가 copy를 사용하는 중에 화면을 업데이트하는 중에 copy가 발생하면 tearing 현상이 발생한다.

Tearing

 Buffer란 기본적으로 memory이기 때문에 차례대로 접근할 수밖에 없다. 따라서 back buffer에서 front buffer로 그림을 복사할 때 위에서부터 순서대로 그림을 그려야 한다. 이 때문에 화면의 일부는 이전 frame을 그리고 나머지는 현재의 frame을 그리는 현상이 생기게 된다.
 그렇게 되면 아래와 같이 찢어지는 화면이 보이게 되는데 그것을 tearing이라고 한다.
http://en.wikipedia.org/wiki/Screen_tearing

Page flipping

 front buffer에 화면을 업데이트하는 순간 전 frame과 새 frame이 겹치는 순간이 있는 것이 문제라면, 한순간에 front buffer를 완성할 수 는 없을까?
 그래서 나온 방법이 Page flipping이라는 방법이다.
http://docs.oracle.com/javase/tutorial/extra/fullscreen/doublebuf.html
 Flip은 back buffer와 front buffer를 둘 다 그래픽 메모리에 만들어 놓고, display가 가리키는 buffer를 바꾸는 방식이다.
 이렇게 하면 front buffer에 old frame이 남아 있는 경우는 없다.
 게다가 대부분의 3D 엔진들이 동작하는 방법이 화면을 전부 지우고 새롭게 그림을 그리는 방식이기 때문에 대부분의 경우에 이전 그림을 유지하고 있을 필요도 없고(필요하면 front buffer에서 읽어서 back buffer에 복사하기는 한다), 복사하는 것보다는 포인터만 바꾸는 것이 시간이 조금 걸리기 때문에 더 빠르다.

 flip을 사용하면 front buffer에 이 전 frame과 현재의 frame이 동시에 존재하는 일은 없다. 하지만 flip을 사용해도 tearing은 발생한다. display의 구현 때문이다.
 display는 매 순간 front buffer에서 데이터를 읽어서 화면을 갱신시킨다. 문제는 순식간에 화면을 전부 갱신시키는 것이 아니라 위에서부터 순서대로 갱신시키기 때문이다. 그래서 화면을 갱신하는 중에 flip이 발생하면 old frame과 새 frame이 동시에 나오는 일이 생기게 된다.

 이런 문제를 해결하기 위해 Vertical synchronization이라는 방법을 사용한다.

Vertical Synchronization

 과거 빔을 사용하는 CRT 모니터를 사용하던 시절에는 오른쪽 아래까지 빔을 이용하여 그림을 그리고 나면, 빔을 왼쪽 위로 돌려보내는 시간이 필요로 했다. 이 시간을 Vertical Blanking Interval 혹은 vertical interval이라고 하는데 CRT 모니터를 사용하지 않는 지금도 프로토콜의 하위 호환성을 위하여 데이터를 보내는 VBI를 둔다. 이 interval 동안은 화면을 갱신하지 못하기 때문에 이 기간에 flip이나 copy를 하여 buffer를 갱신한다면 tearing 현상은 발생하지 않는다.
 물론 VSync에도 단점이 없는 것은 아니다. swap buffer를 언제나 vertical interval에만 하므로 frame의 최소 시간이 vertical interval 이하가 되지 못한다.
 하지만 사람의 눈은 보통 일정 이상의 fps를 인식하지 못하고, display의 interval은 보통 그 한계 근처에서 설정되어 있기 때문에 큰 문제는 안 된다.