여러 순차 작업의 결과를 한 번에 보여준다.
순차적으로 혹은 동시에 진행되는 여러 작업을 한 번에 모아서 봐야할 때가 있다.
예를 들어 렌더링을 할 때 화면을 그리는 중간 과정이 보이면 안되며,
매 프레임 완성되면 한 번에 보여줘야 한다.
프레임 버퍼에 값을 쓰는 픽셀을 비디오 드라이버에서 프레임 버퍼를 읽는 픽셀이 추월한다면 테어링이 발생한다. 이래서 이중 버퍼 패턴이 필요하다.
(= 이전 프레임에는 하나도 안 보이다가, 다음 프레임에 전체가 보여야 한다.)
프레임 버퍼를 두 개 준비해 A 버퍼에는 지금 프레임에 보일 값을 두고, 렌더링 코드는 B 버퍼에 값을 쓴다.
A버퍼를 다 그린 후에는 버퍼를 교체하고, 이제 B 버퍼를 읽으라고 비디오 하드웨어에게 알린다.
이렇게 점차적으로 수정되는 버퍼를 밖에서는 한 번에 바뀌는 것처럼 보이기 위해,
버퍼 클래스는 현재 버퍼와 다음 버퍼 두 개의 버퍼를 가져아 한다.
정보를 읽을 때는 현재 버퍼, 정보를 쓸 때는 다음 버퍼에 접근한다.
다 읽으면(다 보여주면) 현재 버퍼를 교체해 다음 버퍼가 보여지게 한다. 현재 버퍼는 새로운 다음 버퍼가 되어 재사용된다.
테어링이 방지하고자 할 때 사용한다. 더 보편적인 상황으로 치환한다면
버퍼의 교체 연산은 원자적이어야 한다. 즉 교체 중에는 두 버퍼 모두 접근할 수 없어야 한다.
대게 포인터만 바꾸면 되지만, 만약 (버퍼 교체 시간 > 버퍼에 값을 쓰는 시간) 이면 이중 버퍼 패턴의 의미가 없다.
따라서 메모리가 더 필요하다. 메모리가 부족한 기기는 이중 버퍼 패턴 말고 다른 방법을 찾아야 한다.
class Framebuffer {
public:
Framebuffer() { Clear(); }
void Clear() {
for (int i = 0; i < WIDTH * HEIGHT; ++i) {
pixels_[i] = '0';
}
}
void Draw(int x, int y)
{
pixels_[(WIDTH * y) + x] = '1';
}
const char* GetPixels() { return pixels_; }
private:
static const int WIDTH = 160;
static const int HEIGHT = 120;
char pixels_[WIDTH * HEIGHT];
};
Clear()
: 전체 버퍼를 흰색으로 채움
Draw()
: 특정 픽셀에 검은색 입력
GetPixels()
: 픽셀 데이터를 담고 있는 메모리 배열에 접근. 비디오 드라이버가 화면을 그리기 위한 버퍼를 읽을 때 을 호출함.
class Scene{
public:
void Draw() {
buffer_->clear();
buffer_->draw(1, 1);
buffer_->draw(4, 1);
// ...
}
Franebuffer& GetBuffer() { return buffer_; }
private:
Framebuffer buffer_;
};
Scene
클래스는 여러번 draw()
를 호출해 버퍼에 원하는 그림을 그린다.
매 프레임마다 어떤 장면을 그려야 할 지 게임 코드가 알려주면, 버퍼를 clear()
하고 한 번에 하나씩 그리고자 하는 픽셀을 찍는다.
비디오 드라이버가 내부 버퍼에 접근 가능하도록 getBuffer()
를 제공한다.
그러나 비디오 드라이버가 아무 때나 getBuffer()
를 호출하면 안된다.
draw()
중에 버퍼를 읽으면 안되기 때문이다.
class Scene{
public:
Scene() : current_(&buffers_[0]), next_(&buffers_[1]) {}
void Draw() {
next_->Clear();
next_->Draw(1, 1);
next_->Draw(4, 1);
next_->Draw(1, 3);
next_->Draw(2, 4);
next_->Draw(3, 4);
next_->Draw(4, 3);
Swap();
}
Franebuffer& GetBuffer() { return *current_; }
private:
void Swap() {
Framebuffer* temp = current_;
current_ = next_;
next_ = temp;
}
Framebuffer buffers_[2];
Framebuffer* current_;
Framebuffer* next_;
};
렌더링할 픽셀은 next_
에 쓰고, 비디오 드라이버는 current_
에 접근하여 픽셀을 읽는다.
버퍼 교체 연산은 읽기/쓰기 버퍼 모두 사용 불가하므로, 최대한 빠르게 교체해야 한다.]