双缓冲

双缓冲中得一个缓存用于展示当前帧,另一个是我们下一帧要展示的,画面操作先画到下一帧, 给用户展示是会直接把一帧一下子展示出来。

class Framebuffer
{
public:
  // Constructor and methods...

private:
 static const int WIDTH = 160;
 static const int HEIGHT = 120;

 char pixels_[WIDTH * HEIGHT];
};

缓冲区拥有一些基本操作:将整个缓冲区清理为默认颜色,对指定位置的像素值进行设置。

void Framebuffer::clear()
{
 for (int i = 0; i < WIDTH * HEIGHT; i++)
 {
  pixels_[i] = WHITE;
 }
}

void Framebuffer::draw(int x, int y)
{
 pixels_[(WIDTH * y) + x] = BLACK;
}

const char* Framebuffer:: getPixels()
{
 return pixels_;
}

一个笑脸场景

class Scene
{
public:
 void draw()
 {
  buffer_.clear();
  buffer_.draw(1, 1); buffer_.draw(4, 1);
  buffer_.draw(1, 3); buffer_.draw(2, 4);
  buffer_.draw(3, 4); buffer_.draw(4, 3);
 }

 Framebuffer& getBuffer() { return buffer_; }

private:
 Framebuffer buffer_;
};

但这是有问题,显卡驱动可以在任何时刻对缓冲区调用 getPixels(),甚至是下面这样的时机

buffer_.draw(1, 1); buffer_.draw(4, 1);
// < - Video driver reads pixels here!

buffer_.draw(1, 3); buffer_.draw(2, 4);
buffer_.draw(3, 4); buffer_.draw(4, 3);

当面情况发生,对用于而言,笑脸眼睛还在,嘴不见了,因为Scene还没画出来,要使用双缓冲解决

class Scene
{
public:
 Scene()
 : current_(&buffers_[0]),
  next_(&buffers_[1])
 {}

 void draw()
 {
  next_->clear();
  next_->draw(1, 1);
  // ...
  next_->draw(4, 3);
  swap();
 }

 Framebuffer& getBuffer() { return *current_; }

private:
 void swap()
 {
  // Just switch the pointers.


  Framebuffer* temp = current_;
  current_ = next_;
  next_ = temp;
 }

 Framebuffer buffers_[2];
 Framebuffer* current_;
 Framebuffer* next_;
};

双缓冲并非只针对图形

双缓冲模式所解决的核心问题是对状态不同时进行修改与访问的冲突,造成此问题的原因通常由两个, 一种是,状态直接被另一个线程或终端的代码所直接访问。 另一种,进行状态修改的代码访问到了其正在修改的那个状态。

人工非智能

假设我们正在为一个关于闹剧的游戏中的所有事务构建行为系统。

游戏有一个舞台,上面很多“演员”在追逐打闹

class Actor
{
public:
 Actor() : slapped_(false) {}

 virtual ~Actor() {}
 virtual void update() = 0;

 void reset()   { slapped_ = false; }
 void slap()    { slapped_ = true; }
 bool wasSlapped() { return slapped_; }

private:
 bool slapped_; // 这个角色有没有被打巴掌
};

当update时,角色可以对其他角色调用slap方法来对其扇巴掌并通过调用wasSlapped方法来获知对方是否已经被扇过巴掌。

class Stage
{
public:
 void add(Actor* actor, int index)
 {
  actors_[index] = actor;
 }

 void update()
 {
  for (int i = 0; i< NUM_ACTORS; i++)
  {
   actors_[i]->update();
   actors_[i]->reset();
  }
 }

private:
 static const int NUM_ACTORS = 3;

 Actor* actors_[NUM_ACTORS];
};

角色定义一个具体的子类,我们的喜剧演员很简单,他面朝一个指定角色,不论谁给他一巴掌, 他就冲着他对面的角色扇一巴掌。

Class Comedian :public Actor
{
public:
 void face(Actor* actor) { facing_ = actor; }

 virtual void update()
 {
  if (wasSlapped()) facing_>slap();
 }

private:
 Actor* facing_;
};
Stage stage;

Comedian* harry = new Comedian();
Comedian* baldy = new Comedian();
Comedian* chump = new Comedian();

harry->face(baldy);
baldy->face(chump);
chump->face(harry);

stage.add(harry, 0);
stage.add(baldy, 1);
stage.add(chump, 2);

harry面向baldy
baldy面向chump
chump面向harry

我们向harry脸上来一巴掌,表演拉开序幕

harry->slap();
stage.update();
Stage updates actor 0 (Harry)
 Harry was slapped, so he slaps Baldy
Stage updates actor 1 (Baldy)
 Baldy was slapped, so he slaps Chump
Stage updates actor 2 (Chump)
 Chump was slapped, so he slaps Harry
Stage update ends

但如果上面添加三个角色的代码替换为

stage.add(harry, 2);
stage.add(baldy, 1);
stage.add(chump, 0);

让我们再来实验看看会发生什么:

Stage updates actor 0 (Chump)
 Chump was not slapped, so he does nothing
Stage updates actor 1 (Baldy)
 Baldy was not slapped, so he does nothing
Stage updates actor 2 (Harry)
 Harry was slapped, so he slaps Baldy
Stage update ends
Stage updates actor 0 (Chump)
 Chump was not slapped, so he does nothing
Stage updates actor 1 (Baldy)
 Baldy was slapped, so he slaps Chump
Stage updates actor 2 (Harry)
 Harry was not slapped, so he does nothing
Stage update ends

这违背了我们对角色的要求:我们希望他们平行地运转;而他们在某帧更新中的顺序不应该对结果产生影响。

双缓存每个角色的“被扇巴掌”状态

class Actor
{
public:
 Actor() : currentSlapped_(false) {}

 virtual ~Actor() {}
 virtual void update() = 0;

 void swap()
 {
  // Swap the buffer.

  currentSlapped_ = nextSlapped_;

  // Clear the new "next" buffer.

  nextSlapped_ = false;
 }
 void slap()    { nextSlapped_ = true; }
 bool wasSlapped() { return currentSlapped_; }

private:
 bool currentSlapped_;
 bool nextSlapped_;
};

舞台更新

void Stage::update()
{
  for (inti = 0; i< NUM_ACTORS; i++)
 {
  actors_[i]->update();
 }

 for (inti = 0; i< NUM_ACTORS; i++)
 {
  actors_[i]->swap();
 }
}

再看,先给harry来一巴掌

stage.add(harry, 2);
stage.add(baldy, 1);
stage.add(chump, 0);
harry.slap();
stage.update();
Stage updates actor 0 (Chump)
 Chump was not slapped, so he does nothing
Stage updates actor 1 (Baldy)
 Baldy was not slapped, so he does nothing
Stage updates actor 2 (Harry)
 Harry was not slapped, so he does nothing
Stage update ends
进行了swap
Stage updates actor 0 (Chump)
 Chump was not slapped, so he does nothing
Stage updates actor 1 (Baldy)
 Baldy was not slapped, so he does nothing
Stage updates actor 2 (Harry)
 Harry was slapped, so he slaps Baldy
Stage update ends
进行了swap
Stage updates actor 0 (Chump)
 Chump was not slapped, so he does nothing
Stage updates actor 1 (Baldy)
 Baldy was slapped, so he slaps Chump
Stage updates actor 2 (Harry)
 Harry was not slapped, so he does nothing
Stage update ends

使用双缓冲,就不用考虑update遍历顺序问题了。

缓冲区如何交换

还有一种很酷的技巧,用自身的状态了处理。

class Actor
{
public:
 static void init() { current_ = 0; }
 static void swap() { current_ = next(); }

 void slap()    { slapped_[next()] = true; }
 bool wasSlapped() { return slapped_[current_]; }

private:
 static int current_;
 static int next() { return 1current_; }

 bool slapped_[2];
};