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로 복사해 가게 된다.

https://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이라고 한다.

https://en.wikipedia.org/wiki/Screen_tearing

Page flipping

front buffer에 화면을 업데이트하는 순간 전 frame과 새 frame이 겹치는 순간이 있는 것이 문제라면, 한순간에 front buffer를 완성할 수 는 없을까?

그래서 나온 방법이 Page flipping이라는 방법이다.

https://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은 보통 그 한계 근처에서 설정되어 있기 때문에 큰 문제는 안 된다.

댓글

  1. 더블 버퍼링을 사용하는데도 Screen tearing이라는 현상이 생겨서 원인이 궁금했는데 도움이 되었습니다. Page flipping 방식을 사용해 봐야겠네요. 감사합니다.

    답글삭제

댓글 쓰기

이 블로그의 인기 게시물

[C++] enum class - 안전하고 쓰기 쉬운 enum

RAII는 무엇인가

Log Aggregator 비교 - Scribe, Flume, Fluentd, logstash

[Python] cache 데코레이터로 최적화하기

[Web] SpeechSynthesis - TTS API