16 May, 2024
Intent Causes a series of sequential operations to appear instantaneous or simultaneous.
Double buffering solves screen tearing in a simple way
As soon as the new framebuffer is ready, the screen starts reading from the new frame and releases the old framebuffer
Actually this is page flipping. Classical double buffering copies the new framebuffer on the old framebuffer when the changes are complete.
We have some state that is being modified incrementally.
That same state may be accessed in the middle of modification.
We want to prevent the code that’s accessing the state from seeing the work in progress.
We want to be able to read the state and we don’t want to have to wait while it’s being written.
class Framebuffer { public: Framebuffer() { clear(); } void clear() { for (int i = 0; i < WIDTH * HEIGHT; i++) { pixels_[i] = WHITE; } } void draw(int x, int y) { pixels_[(WIDTH * y) + x] = BLACK; } const char* getPixels() // Video driver needs this { return pixels_; } private: static const int WIDTH = 160; static const int HEIGHT = 120; char pixels_[WIDTH * HEIGHT]; };
class Scene { public: void draw() { buffer_.clear(); 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); } Framebuffer& getBuffer() { return buffer_; } private: Framebuffer buffer_; };
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; // The next time screen asks for the pointer, it gets the new one } 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()
on the actor so that it has a chance to do some processing. Critically, from the user’s perspective, all actors should appear to update simultaneously.The actors need a stage where they can interact, so let’s build that:
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]; };
To get things going, let’s define a concrete actor subclass. Our comedian here is pretty simple. He faces a single actor. Whenever he gets slapped — by anyone — he responds by slapping the actor he faces.
class Comedian : public Actor { public: void face(Actor* actor) { facing_ = actor; } virtual void update() { if (wasSlapped()) facing_->slap(); } private: Actor* facing_; };
Now, let’s throw some comedians on a stage and see what happens. We’ll set up three comedians, each facing the next. The last one will face the first, in a big circle:
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);
We’ll slap Harry to get things going and see what happens when we start processing:
harry->slap(); stage.update();
Remember that the update() function in Stage updates each actor in turn, so if we step through the code, we’ll find that the following occurs:
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
Let’s see what happens when we run our experiment again:
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
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_; };
This also requires a small change in Stage:
void Stage::update() { for (int i = 0; i < NUM_ACTORS; i++) { actors_[i]->update(); } for (int i = 0; i < NUM_ACTORS; i++) // swap buffers { actors_[i]->swap(); } }
swapBuffers()
,swap chains
,endDraw()
method.Intent Decouple the progression of game time from user input and processor speed.
while (true) { char* command = readCommand(); handleCommand(command); }
while (true) { Event* event = waitForEvent(); dispatchEvent(event); }
while (true) { processInput(); update(); // update world, run physics render(); // write to the framebbuffer // copy framebuffer to screen }
A game loop runs continuously during gameplay. Each turn of the loop, it processes user input without blocking, updates the game state, and renders the game. It tracks the passage of time to control the rate of gameplay.
while (true) { processInput(); update(); render(); }
while (true) { double start = getCurrentTime(); processInput(); update(); render(); sleep(start + MS_PER_FRAME - getCurrentTime()); }
update
double lastTime = getCurrentTime(); while (true) { double current = getCurrentTime(); double elapsed = current - lastTime; processInput(); update(elapsed); render(); lastTime = current; }
void update(int elapsed) { double dist = SpeedPerSec/1000*elapsed; move(dist) }
double previous = getCurrentTime(); double lag = 0.0; while (true) { double current = getCurrentTime(); double elapsed = current - previous; previous = current; lag += elapsed; processInput(); while (lag >= MS_PER_UPDATE) { update(); lag -= MS_PER_UPDATE; } render(); }
MS_PER_UPDATE
too small
Intent Simulate a collection of independent objects by telling each to process one frame of behavior at a time.
Entity skeleton; while (true) { // Patrol right. for (double x = 0; x < 100; x++) { skeleton.setX(x); } // Patrol left. for (double x = 100; x > 0; x--) { skeleton.setX(x); } }
Entity skeleton; bool patrollingLeft = false; double x = 0; // Main game loop: while (true) { if (patrollingLeft) { x--; if (x == 0) patrollingLeft = false; } else { x++; if (x == 100) patrollingLeft = true; } skeleton.setX(x); // Handle user input and render game... }
// Skeleton variables... Entity leftStatue; Entity rightStatue; int leftStatueFrames = 0; int rightStatueFrames = 0; // Main game loop: while (true) { // Skeleton code... if (++leftStatueFrames == 90) { leftStatueFrames = 0; leftStatue.shootLightning(); } if (++rightStatueFrames == 80) { rightStatueFrames = 0; rightStatue.shootLightning(); } // Handle user input and render game... }
Entity skeleton; bool patrollingLeft = false; double x = 0; Entity leftStatue; Entity rightStatue; int leftStatueFrames = 0; int rightStatueFrames = 0; // Main game loop: while (true) { if (patrollingLeft) { x--; if (x == 0) patrollingLeft = false; } else { x++; if (x == 100) patrollingLeft = true; } skeleton.setX(x);
if (++leftStatueFrames == 90) { leftStatueFrames = 0; leftStatue.shootLightning(); } if (++rightStatueFrames == 80) { rightStatueFrames = 0; rightStatue.shootLightning(); } // Handle user input and render game... }
update
methodupdate()
d.update()
on each entity
update
method that simulates one frame of the object’s behavior.void Skeleton::update(double elapsed) { if (patrollingLeft_) { x -= elapsed; if (x <= 0) { patrollingLeft_ = false; x = -x; } } else { x += elapsed; if (x >= 100) { patrollingLeft_ = true; x = 100 - (x - 100); } } }