Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Page Flipping Problem - Flicker

This thread is locked; no one can reply to it. rss feed Print
Page Flipping Problem - Flicker
armond
Member #8,282
January 2007

Hi! I'm using a combination of page flipping and double buffering. I'm trying to use pageflip for background scrolling and Double buffering for drawing all the sprites, bullets, etc. However, my screen seems to flicker so much. I don't know what I'm doing wrong... Here are my classes, I hope you could help me. Thanks!


I have a class which is in-charge of overall drawing/rendering.

[code]
#include "globals.h"

GameManager::GameManager()
{
}

GameManager::~GameManager()
{
}

void GameManager::blitBuffer()
{
acquire_screen();

blit(buffer, screen, 0, 0, 0, 0, GAME_WIDTH, GAME_HEIGHT);

release_screen();

clear_bitmap(buffer);
}

void GameManager::blitVideoBuffer()
{
//acquire_screen();
acquire_bitmap(videoBuffer);

blit(videoBuffer, screen, 0, 0, 0, 0, GAME_WIDTH, GAME_HEIGHT);

release_bitmap(videoBuffer);

show_video_bitmap(videoBuffer);

//release_screen();

//clear_bitmap(videoBuffer);
}

BITMAP* GameManager::buffer;
BITMAP* GameManager::videoBuffer;

const short int GameManager::GAME_WIDTH = 640;
const short int GameManager::GAME_HEIGHT = 480;
const short int GameManager::COLOR_DEPTH = 16;

int GameManager::game_score;

[/code]


Now, I have a GameBackground class in which I try to scroll the parallax background.

[code]
#include "globals.h"


GameBackground::GameBackground(BITMAP* bitmap, BackgroundScrollType scrollType, int scrollSpeed, int x, int y)
{
this->bitmap = bitmap;
this->scrollType = scrollType;
this->scrollSpeed = scrollSpeed;
this->x = x;
this->y = y;

//tmpBuffer = create_bitmap(SCREEN_W, SCREEN_H);
tmpBuffer = create_video_bitmap(SCREEN_W, SCREEN_H);
}

GameBackground::~GameBackground()
{
destroy_bitmap(tmpBuffer);
}

void GameBackground::update()
{
scrollBackground();
}

void GameBackground::scrollBackground()
{
switch(scrollType)
{
case HORIZONTAL_LEFT:
scrollLeft();
break;
default:
break;
}
}

void GameBackground::draw()
{
//draw_sprite(GameManager::buffer, tmpBuffer, 0, 0);
draw_sprite(GameManager::videoBuffer, tmpBuffer, 0, 0);
}

void GameBackground::setScrollSpeed(int speed)
{
scrollSpeed = speed;
}

void GameBackground::scrollLeft()
{
x -= scrollSpeed;

int bw = bitmap->w;
int bh = bitmap->h;

int xOffSet = (SCREEN_W + bw);

if ( x > bw || x < -(bw) )
{
x = 0;
}

int newX = 0;

for (int i = -(bw); i < (xOffSet); i += bw)
{
newX = (x + i);

for (int j = 0; j < SCREEN_H; j += bh)
{
//draw tile
//Optimization: draw only parts that are visible to the screen
if ( ( newX + bw) > 0 )
{
draw_sprite(tmpBuffer, bitmap, newX, j);
}
else
{
break;
}

}
}
}

[/code]


And the update and draw methods for GameBackground class are called in this class which functions as the main entry point of my game...

[code]
#include "globals.h"

PlayState::PlayState()
{
currentLevel = 1;

GameManager::game_score = 0;

//create buffer image (double bufferring)
GameManager::buffer = create_bitmap(SCREEN_W, SCREEN_H);
//create video image (page flipping)
GameManager::videoBuffer = create_video_bitmap(SCREEN_W, SCREEN_H);

//load sprites
BitmapManager::addBitmap(SHIP_SPRITE, "graphics/fs.bmp");
BitmapManager::addBitmap(VULCAN_SPRITE, "graphics/vulcan.bmp");
BitmapManager::addBitmap(ASTEROID_SPRITE_FLYING, "graphics/asteroid_flying.bmp");
BitmapManager::addBitmap(ASTEROID_SPRITE_HIT, "graphics/asteroid_hit.bmp");
BitmapManager::addBitmap(ASTEROID_SPRITE_DYING, "graphics/asteroid_destroyed.bmp");
BitmapManager::addBitmap(BULLET_HEAD_SPRITE_FLYING, "graphics/bullet_head_flying.bmp");
BitmapManager::addBitmap(BULLET_HEAD_SPRITE_HIT, "graphics/bullet_head_hit.bmp");
BitmapManager::addBitmap(SPRITE_EXPLOSION, "graphics/explosion.bmp");
BitmapManager::addBitmap(SWIRL_SPRITE_FLYING, "graphics/swirl_flying.bmp");
BitmapManager::addBitmap(SWIRL_SPRITE_HIT, "graphics/swirl_hit.bmp");
BitmapManager::addBitmap(SWIRL_SPRITE_DYING, "graphics/swirl_destroyed.bmp");
BitmapManager::addBitmap(POWER_UP, "graphics/power_up.bmp");

//load SFX
SoundManager::addSample(METEOR_DESTROYED_SFX, "sfx/meteordestroyed.wav");
SoundManager::addSample(VULCAN_GUN_FIRE_SFX, "sfx/vulcan.wav");
SoundManager::addSample(POWER_UP_SFX, "sfx/power_up.wav");

//ship = new Ship(BitmapManager::SHIP_SPRITE, 50, 50, 50, 32, 1);
//Ship(int x, int y, int w, int h, int speed, int recoveryRate, int recoveryTime, Status stat, int lfe);
ship = new Ship(50, 50, 50, 45, 3, MAX_RECOVERY_RATE, MAX_RECOVERY_TIME, ALIVE, MAX_PLAYER_LIFE);

//add a weapon for the ship
ship->addWeapon(VULCAN_GUN, WeaponFactory::getWeapon(VULCAN_GUN) );
ship->setCurrentWeapon(VULCAN_GUN);
ship->move(0, (SCREEN_H / 2) );
}

PlayState::~PlayState()
{
BitmapManager::removeAll();

destroy_bitmap(GameManager::buffer);

delete ship;
}

PlayState* PlayState::getInstance()
{
if (instance == NULL)
{
instance = new PlayState();
}

return instance;
}

void PlayState::init()
{
}

void PlayState::pause()
{
}

void PlayState::run()
{
GameStateManager::pushState( GameOverState::getInstance() );
GameStateManager::getCurrentState()->run();//--execute run method
GameStateManager::popState(); //--pop the element on top

loadLevel(currentLevel);

//begin main game loop
while ( !key[KEY_ESC] )
{

while (GameTimer::game_ticker > 0)
{
//move left
if ( key[KEY_A] )
{
ship->move( ship->x - ship->getSpeed(), ship->y );
if ( ship->x <= 0 )
{
ship->move( ship->x + ship->getSpeed(), ship->y );
}
}
//move down
if ( key[KEY_S] )
{
ship->move( ship->x, ship->y + ship->getSpeed() );

if ( (ship->y + ship->height) >= SCREEN_H )
{
ship->move( ship->x, ship->y - ship->getSpeed() );
}
}
//move right
if ( key[KEY_D] )
{
ship->move( ship->x + ship->getSpeed(), ship->y );

if ( (ship->x + ship->width) >= SCREEN_W )
{
ship->move( ship->x - ship->getSpeed(), ship->y );
}
}
//move up
if ( key[KEY_W] )
{
ship->move( ship->x, ship->y - ship->getSpeed() );

if ( ship->y <= 20 )
{
ship->move( ship->x, ship->y + ship->getSpeed() );
}
}
//fire weapon
if ( key[KEY_K] )
{
ship->fireWeapon();
}
//change weapon
if ( key[KEY_TAB] )
{
//ship->fireWeapon();
}
//fire special
if ( key[KEY_L] )
{
}

//exit to menu
if ( key[KEY_ESC] )
{
break;
}

//scroll the background
bg->update();
bg->draw();

//animate the ship
ship->update();


//move all powerups
for (std::vector<PowerUp*>::iterator it = PowerUp::powerUpPool.begin(); it != PowerUp::powerUpPool.end(); ++it)
{
PowerUp* pUp = (*it);

if ( pUp->isActive() )
{
pUp->update();
pUp->draw();

if ( ship->collides(pUp) )
{
pUp->makeInactive();
ship->upgradeWeapon();
play_sample( SoundManager::getSample(POWER_UP_SFX), 255, 0, 2000, 0);
}
}

pUp = NULL;
}

//move all enemies
for(std::vector<Enemy*>::iterator it = EnemyFactory::enemyPool.begin(); it != EnemyFactory::enemyPool.end(); ++it)
{
Enemy* e = *it;

//enemy is alive or dying
if ( !( e->isDead() ) || e->isDying() )
{
e->update();
e->draw();

//enemy is alive, ship is not recovering and ship hits enemy
if ( !( e->isDead() ) && !( ship->isRecovering() ) && ship->collides(e) )
{
ship->takeDamage(10); //TODO: change to enemy attack power
e->takeDamage(1);
}
}
}

//move all projectiles
for (std::vector<Projectile*>::iterator it = ProjectileFactory::projectilePool.begin(); it != ProjectileFactory::projectilePool.end(); ++it)
{

if ( (*it)->isVisible() == true && (*it)->isActive() )
{
Projectile* p = *it;
p->update(); //(*it)->update();
p->draw(); //(*it)->draw();


if ( p->isHostile() && !( ship->isRecovering() ) )
{
/* check if ship collides projectile instead of
projectile collides ship because ship provides
a more precise collision that a player would not
get annoyed with
*/
if ( ship->collides(p) )
{
ship->takeDamage(10); //change this to projectile power
p->makeInactive();
}
}

//check for collision on bullet and enemies
for(std::vector<Enemy*>::iterator eit = EnemyFactory::enemyPool.begin(); eit != EnemyFactory::enemyPool.end(); ++eit)
{
Enemy* e = *eit;

//projectile hits an enemy
if ( p->collides(e) && p->isActive() && !( p->isHostile() ) && !( e->isDead() ) && !( e->isDying() ) )
{
e->takeDamage(1); //TODO: change 1 to weapon power
p->makeInactive();

//player killed the enemy
if ( !( e->isAlive() ) )
{
GameManager::game_score += 10;
}

//exit the inner loop
break;
}
}

}
else
{
Projectile* p = *it;
it = ProjectileFactory::projectilePool.erase(it);
// we must decrement, because it will be incremented when the loop starts over again
--it;
delete p;
}
}


//control game speed
GameTimer::game_ticker--;

ship->draw();

textout_ex(GameManager::buffer, font, "Life: ", 10, 10, makecol(0, 0, 255), -1);
//drawLifeBar(int x, int y, int length, height, BITMAP* bitmap);
drawLifeBar(50, 0, ship->getLife() , 20, GameManager::buffer);

textprintf_centre_ex(GameManager::buffer, font, SCREEN_W/2, 10, makecol(255, 255, 255), makecol(0, 0, 0), "Current Weapon: %s", ship->getCurrentWeapon()->getWeaponName() );
textprintf_centre_ex(GameManager::buffer, font, (SCREEN_W - (SCREEN_W/8) ), 10, makecol(255, 255, 255), makecol(0, 0, 0), "Score: %d", GameManager::game_score);

textprintf_centre_ex(GameManager::buffer, font, SCREEN_W/2, 176, makecol(255, 255, 255), makecol(0, 0, 0), "num=%d", GameTimer::game_ticker);

//blit buffer
GameManager::blitVideoBuffer();
GameManager::blitBuffer();
}

}

unloadLevel(currentLevel);
}

void PlayState::update()
{
}

void PlayState::clean()
{
}

void PlayState::loadLevel(int level)
{
PACKFILE* levelMap;

switch(level)
{
case 1:
levelMap = pack_fopen("levels/level_01.lvl", "r");

createLevel(levelMap);

bg = new GameBackground(BitmapManager::addBitmap(SPACE_BACKGROUND, "graphics/bg.bmp"), HORIZONTAL_LEFT, 1, SCREEN_W, SCREEN_H);

break;
default:
break;
}

if (levelMap)
{
pack_fclose(levelMap);
}
}

void PlayState::createLevel(PACKFILE* packFile)
{
std::string line;
char buf[255];

int currX = 0;
int currY = 0;
int xIncrement = (SCREEN_W / 20);
int yIncrement = (SCREEN_H / 20);

if (!packFile)
{
allegro_message("COULD NOT CREATE LEVEL!: %s", allegro_error);
}

while ( pack_feof(packFile) == 0 )
{
//read a line from the file (levelMap)
line = pack_fgets(buf, sizeof(buf), packFile);

//loop through each character in the line
for (int i = 0; i < line.length(); i++)
{
//only an empty space
if ( line.at(i) == ' ')
{
currX += xIncrement;
}
else if( line.at(i) == 'P' )
{
PowerUp* pUp = new PowerUp(currX, currY, 32, 32, 2, true);

PowerUp::powerUpPool.push_back(pUp);

pUp = NULL;

currX += xIncrement;
}
else //create an enemy object
{
Enemy* e = EnemyFactory::getEnemy( line.at(i) );
//set initial position of the enemy
e->move(currX, currY);
EnemyFactory::enemyPool.push_back(e);

e = NULL;

currX += xIncrement;
}
}

currX = 0;
currY += yIncrement;
}
}

void PlayState::unloadLevel(int level)
{
switch(level)
{
case 1:
std::vector<Enemy*>().swap(EnemyFactory::enemyPool);
std::vector<Projectile*>().swap(ProjectileFactory::projectilePool);
std::vector<PowerUp*>().swap(PowerUp::powerUpPool);
delete bg;
break;
default:
break;
}
}

void PlayState::drawLifeBar(int x, int y, int length, int height, BITMAP* bitmap)
{
//rectfill(BITMAP *bmp, int x1, int y1, int x2, int y2, int color);
if (length <= 0)
{
length = 0;
}

rectfill(bitmap, x, y, (x + length), height, 255);
}

PlayState* PlayState::instance;
int PlayState::currentLevel;
Ship* PlayState::ship;

[/code]


I'm trying to use page flipping to gain extra performance since my game runs pretty slow on PIII machines and below. I just want to try it out and see if it makes any difference. Thanks!
Tobias Dammers
Member #2,604
August 2002
avatar

Quote:

void GameManager::blitVideoBuffer()
{
//acquire_screen();
acquire_bitmap(videoBuffer);

blit(videoBuffer, screen, 0, 0, 0, 0, GAME_WIDTH, GAME_HEIGHT);

release_bitmap(videoBuffer);

show_video_bitmap(videoBuffer);

//release_screen();

//clear_bitmap(videoBuffer);
}

This is not proper page flipping. You blit the bitmap AND flip. This is not right.
There are, basically, two double buffering approaches you can implement here - blitting the back buffer, or flipping.
If you go the blitting route, you would first create a bitmap as the back buffer; each frame, you render the scene to the back buffer, and then use blit() to copy the pixels to the screen.
In page flipping, you don't touch "screen" at all. Instead, you create TWO memory bitmaps, A and B. Set A as the front buffer by calling show_video_bitmap() on it, and render your scene to B. When you're done, swap the buffers: call show_video_bitmap() on B, which will be displayed instantly, and render the next frame to A (which will be invisible). The idea behind this is not to copy pixel data, but to change the video memory location from which the hardware's DAC reads pixels.

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

armond
Member #8,282
January 2007

Does create_video_bitmap(); give you a memory bitmap and not a video bitmap?

And by rendering, you mean calling draw_sprite() and probably drawing on the BITMAP returned by create_video_bitmap? Thanks!

Tobias Dammers
Member #2,604
August 2002
avatar

Quote:

Does create_video_bitmap(); give you a memory bitmap and not a video bitmap?

Please read TFM.
create_bitmap() -> memory bitmap
create_video_bitmap() -> video bitmap (hence the 'video' in the function name).

Quote:

And by rendering, you mean calling draw_sprite() and probably drawing on the BITMAP returned by create_video_bitmap? Thanks!

Rendering your scene. I don't know what else to call it. Draw all the sprites and lines and whatnot, basically everything you want to display, to the back buffer, not to the screen. Most likely, this will involve draw_sprite(), but it doesn't have to.
That's what frame buffering is all about: Since you cannot draw all objects at once, the user would normally be able to watch you drawing the scene: First, the background would appear, then the tile map, then some characters, and finally, some overlay like score, level, text, health bars etc. You don't want this, because it looks ugly. So you draw to an invisible buffer instead, and when you're done drawing everything, you make the contents of the buffer visible in one go - either by copying the pixel data with a call to blit(), or by changing the location the video hardware reads from (show_video_bitmap()).

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

armond
Member #8,282
January 2007

Thank you, I've been reading TFM for several hours now. I just wanted to be sure and that's why I asked. Now, I tried changing from video bitmap to memory bitmap as you said. However, it doesn't show anything on the screen. When I use video bitmaps, the background overlaps all other sprites. But I'm sure I'm rendering and drawing the background first. You can see my trial-and-error stuff through comments here in my code. I also made a variable called frameCounter so I'd be changing the buffer on which to draw on in its every increment. Thanks!

1#include "globals.h"
2 
3 
4GameBackground::GameBackground(BITMAP* bitmap, BackgroundScrollType scrollType, int scrollSpeed, int x, int y)
5{
6 this->bitmap = bitmap;
7 this->scrollType = scrollType;
8 this->scrollSpeed = scrollSpeed;
9 this->x = x;
10 this->y = y;
11
12 frameCounter = 0;
13
14 //tmpBuffer = create_bitmap(SCREEN_W, SCREEN_H);
15 //bufferA = create_video_bitmap(SCREEN_W, SCREEN_H);
16 //bufferB = create_video_bitmap(SCREEN_W, SCREEN_H);
17 bufferA = create_bitmap(SCREEN_W, SCREEN_H);
18 bufferB = create_bitmap(SCREEN_W, SCREEN_H);
19}
20 
21GameBackground::~GameBackground()
22{
23 destroy_bitmap(bufferA);
24 destroy_bitmap(bufferB);
25}
26 
27void GameBackground::update()
28{
29 scrollBackground();
30}
31 
32void GameBackground::scrollBackground()
33{
34 switch(scrollType)
35 {
36 case HORIZONTAL_LEFT:
37 scrollLeft();
38 break;
39 default:
40 break;
41 }
42}
43 
44void GameBackground::draw()
45{
46 //draw_sprite(GameManager::buffer, tmpBuffer, 0, 0);
47 //draw_sprite(GameManager::videoBuffer, tmpBuffer, 0, 0);
48 if (frameCounter % 2 == 0)
49 {
50 show_video_bitmap(bufferA);
51 }
52 else
53 {
54 show_video_bitmap(bufferB);
55 }
56 frameCounter++;
57}
58 
59void GameBackground::setScrollSpeed(int speed)
60{
61 scrollSpeed = speed;
62}
63 
64void GameBackground::scrollLeft()
65{
66 x -= scrollSpeed;
67
68 int bw = bitmap->w;
69 int bh = bitmap->h;
70
71 int xOffSet = (SCREEN_W + bw);
72
73 if ( x > bw || x < -(bw) )
74 {
75 x = 0;
76 }
77
78 int newX = 0;
79
80 for (int i = -(bw); i < (xOffSet); i += bw)
81 {
82 newX = (x + i);
83
84 for (int j = 0; j < SCREEN_H; j += bh)
85 {
86 //draw tile
87 //Optimization: draw only parts that are visible to the screen
88 if ( ( newX + bw) > 0 )
89 {
90 if (frameCounter % 2 == 0)
91 {
92 draw_sprite(bufferB, bitmap, newX, j);
93 }
94 else
95 {
96 draw_sprite(bufferA, bitmap, newX, j);
97 }
98 }
99 else
100 {
101 break;
102 }
103
104 }
105 }
106}

And BTW, I also have a double buffer after the update and draw routines for this class's object is called. But the pageflip is called first.

Kauhiz
Member #4,798
July 2004

I didn't bother to read through your source, so sorry if this isn't helping. Anyway, there's a gereat article on page flipping in the wiki: http://wiki.allegro.cc/Page_flipping. I suggest you read through that and see if it helps at all.

---
It's Ridge Racer! RIIIIIDGE RAAAAACER!

Kris Asick
Member #1,424
July 2001

Tobias: I think you had initially confused Armond because you said to use show_video_bitmap() on memory bitmaps:

Quote:

Instead, you create TWO memory bitmaps, A and B. Set A as the front buffer by calling show_video_bitmap() on it...

Armond: The two simplest ways to render to the screen are Double Buffering and Page Flipping.

Double Buffering allows you to take certain liberties you can't with Page Flipping, such as not syncing to the monitor refresh rate and being able to render to the buffer at any point in time. Page Flipping has the advantage of taking less CPU time and being smoother.

To Double Buffer, all you need to do is have a regular bitmap to draw on as your buffer, and then you blit that to the screen at the end of your game loop. You don't need to ever call the acquire or release commands using this approach. You can also add the vsync() command to just before the blit to potentially improve the smoothness of the display. (However, poor timer logic or a slow computer may degrade performance when using vsync().)

To Page Flip, you need to make three BITMAP pointers. Two will be video bitmaps made with create_video_bitmap(), the third will be a reference pointer which swaps between the other two video pages every frame. You must group all of your drawing to the reference bitmap into one chunk that lies between acquire and release commands for the reference bitmap. Once you render everything and release the reference bitmap, call show_video_bitmap() on the reference pointer, then switch the reference pointer to the alternate video bitmap. (Thus the video bitmap you draw to "flips" every frame.)

Two things to note when you do Page Flipping:

1. By default, show_video_bitmap() will automatically vsync. You can disable this behaviour, but then the resulting display quality really isn't much better than Double Buffering.

2. The very first video bitmap you make with create_video_bitmap() will almost certainly use the same video memory as the screen. (Not really a problem, it just means if you're going to put more stuff in video memory than the video pages you should probably define the video pages first.

Hope that helps. ;)

I personally recommend double buffering because bad things can happen if you do too much stuff between an acquire/release pair. (And because page flipping + Allegro != working properly on my particular computer.)

--- Kris Asick (Gemini)
--- http://www.pixelships.com

--- Kris Asick (Gemini)
--- http://www.pixelships.com

gnolam
Member #2,030
March 2002
avatar

I'd do away with the acquire/releases altogether. They're not actually needed, and mainly just increase the risk of you fucking up somewhere further down the line.

--
Move to the Democratic People's Republic of Vivendi Universal (formerly known as Sweden) - officially democracy- and privacy-free since 2008-06-18!

Kris Asick
Member #1,424
July 2001

Quote:

I'd do away with the acquire/releases altogether. They're not actually needed, and mainly just increase the risk of you fucking up somewhere further down the line.

True for Double Buffering, not true for Page Flipping, since in Page Flipping you render everything directly to video memory. (Thus if you removed the acquire and release commands there, video memory would have to be locked and unlocked for every single call to draw something to video memory. This is why you can remove them with double buffering, since you generally only need to make one render to video memory (the screen) per frame that way.)

Unless of course you combine double buffering with page flipping, but then you're just wasting CPU time and eliminating the speed boost page flipping provides.

--- Kris Asick (Gemini)
--- http://www.pixelships.com

--- Kris Asick (Gemini)
--- http://www.pixelships.com

Kauhiz
Member #4,798
July 2004

Quote:

True for Double Buffering, not true for Page Flipping, since in Page Flipping you render everything directly to video memory.

Nope, gnolam's right. You never need the acquire/release calls.

TFM said:

You never need to call the function explicitly as it is low level, and will only give you a speed up if you know what you are doing. Using it wrongly may cause slowdown, or even lock up your program.

---
It's Ridge Racer! RIIIIIDGE RAAAAACER!

Kris Asick
Member #1,424
July 2001

Quote:

Nope, gnolam's right. You never need the acquire/release calls.

Permit me to explain what exactly those commands are doing.

DirectX, in all its glory, is made in such a way that in order to successfully write to video memory that memory must first be locked so that Windows doesn't mess with it while a write is happening. This memory lock takes a good amount of CPU time to engage, and while it's active, making any kind of system call can do bad things.

Allegro controls the state of this lock with its acquire and release commands. Because video memory must be locked in order to draw to it, Allegro will automatically lock and unlock video memory when you draw to it if you haven't locked it already.

So long as you only make one video memory draw per frame, using acquire and release doesn't save you any time.

But, when doing proper page flipping, ALL rendering is happening direct to video memory, which means if you don't enclose all those functions within an acquire/release pair, video memory will be locked and unlocked for every single call to stuff like putpixel, blit, draw_sprite, etc... which will eat CPU time.

If you're rendering everything to a buffer, blitting that to video memory and then doing a page flip, that's essentially combining double buffering with page flipping, which doesn't give you the speed benefits page flipping is supposed to provide.

--- Kris Asick (Gemini)
--- http://www.pixelships.com

--- Kris Asick (Gemini)
--- http://www.pixelships.com

Kauhiz
Member #4,798
July 2004

Quote:

This memory lock takes a good amount of CPU time to engage

Really? So what is "a good amount", I mean, I've never used the acquire/release calls anywhere and I've never ran into any problems. How much speed can you actually gain by adding those calls?

---
It's Ridge Racer! RIIIIIDGE RAAAAACER!

Tobias Dammers
Member #2,604
August 2002
avatar

Quote:

Tobias: I think you had initially confused Armond because you said to use show_video_bitmap() on memory bitmaps:

You are, of course, right. That is utter nonsense I wrote there.

Armond:
I haven't really read all of your code there, but on first glance, it looks as if you were allocating the frame buffers inside the background class, which doesn't look right to me. The background is just one of many objects that draw something onto the back buffer; it has no business with the underlying mechanics of the frame buffering system.
IMO, you should split your code up into the following overall modules (no matter what the genre):
- A framebuffer module that handles setting up, flipping, and shutting down the frame buffering system. You can either make two classes with the same interface for memory-blitting and page flipping, or one class that can do both. Either way, the public interface should look something like this:

class cFramebuffer {
// protected / private members left out.
  public:
    cFramebuffer(int buffer_method = MEMORY_BLIT); 
// The parameter is only needed if you put all buffer methods into the same class.
// Otherwise, each buffering method would have its own constructor.
// If you have a dedicated class for each buffering method, the member funcs below
// need to be virtual.
    ~cFramebuffer();
    void flip(); // takes the appropriate action depending on the buffer method used.
    BITMAP* get_backbuffer();
    const char* get_buffer_mode_desc() const; // optional, and useful mainly for debugging purposes;
};

Of course, this doesn't have to be a class; were you using plain C, you'd just provide the plain functions in the header and use static variables in the .c file. In C++, though, a class is a much better choice.
If you want to go all fancy, make the framebuffer class a singleton, but I suggest you don't do that for now. The key here is ENCAPSULATION: make sure that nothing except the frame buffer module itself ever touches the internal buffers directly - only through the get_backbuf() method. This gives you more control over who draws what and where to.
- A game module that manages your objects, scene, backgrounds, timing, etc. - pretty much everything that is in-game. For drawing, it first calls the global framebuffer's get_backbuffer() method, draws everything to the bitmap it provides (typically calling its sub-modules' draw() methods), and finally calls framebuffer->flip(). The game module should - at least for more complex games - be split up into more modules, e.g. one for the background, one for characters, one for timing, etc. A typical drawing routine might look like this:

cGame::draw() {
  // note that this method does not know which frame buffering method is being used.
  BITMAP* backbuf = framebuffer->get_backbuffer(); // get a valid back buffer from the framebuffer module
  assert(backbuf); // make sure we really have a back buffer!
  // OK, now draw our scene back-to-front:
  background->draw(backbuf, scroll_x, scroll_y);
  for (int i = 0; i < num_actors; ++i)
    actor<i>->draw(backbuf, scroll_x, scroll_y);
  // if you want to display overlay like scores, health bars, etc., here's the place
  framebuffer->flip(); // Tell the framebuffer module we're done with this frame.
}

- A "main" module that sets up allegro, creates a global frame buffer object, spawns the game module, and finally cleans up by destroying the game and frame buffer objects and exiting allegro.

The nice thing about this approach is that once you have one frame buffering system ready to go, the interface can be reused, so if you later want to add another option, you only need to change the frame buffer module, and add a few lines to the main module, while all the other modules can remain unchanged.

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

Kitty Cat
Member #2,815
October 2002
avatar

Quote:

Really? So what is "a good amount", I mean, I've never used the acquire/release calls anywhere and I've never ran into any problems.

"A good amount" can be anywhere from near instantanious to several tens to hundreds of milliseconds, or more. Since it involves a mutex lock, if something else is holding that lock, your program will pause until the lock becomes free. There could be other factors involved too.. I'm not very familiar with the Windows code.

But as mentioned, as long as you draw to the screen/video bitmaps, the screen must be acquired no matter what. And if all you do is blit a memory buffer to the screen, there's nothing to gain since Allegro will acquire for you.

--
"Do not meddle in the affairs of cats, for they are subtle and will pee on your computer." -- Bruce Graham

Kauhiz
Member #4,798
July 2004

Quote:

But as mentioned, as long as you draw to the screen/video bitmaps, the screen must be acquired no matter what. And if all you do is blit a memory buffer to the screen, there's nothing to gain since Allegro will acquire for you.

Yeah, I know. I'd just like to know what kind of speed differences we're talking about.

---
It's Ridge Racer! RIIIIIDGE RAAAAACER!

Tobias Dammers
Member #2,604
August 2002
avatar

For blitting a memory buffer to screen: zero.
For drawing directly to video memory: depends. Hugely.

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

Kauhiz
Member #4,798
July 2004

Quote:

For drawing directly to video memory: depends.

On what. Amount of stuff getting drawn, obviously, but how about hardware? Anything else? If I don't see a big difference on my system, could there still be a big difference on another system?

---
It's Ridge Racer! RIIIIIDGE RAAAAACER!

Kris Asick
Member #1,424
July 2001

Kauhiz: It is dependent on a number of factors, but the speed difference can be insane if you do enough writes.

For instance, if you draw 200 objects directly to the screen on one update, but fail to call the acquire and release commands first, those 200 objects will each call those commands themselves. Thus instead of one locking operation you suddenly have 200.

And again, this is only if you do all of your drawing directly to video memory. If you draw everything to a memory buffer then blit that to video memory, the lock is only engaged once because regular bitmaps don't need to be locked to be written to.

Here's a quick example you can test:

1// Example 1 - No Locking
2v = 0;
3do {
4 v++;
5 for (z = 0; z < 640; z++) for (zz = 0; zz < 480; zz++)
6 putpixel(screen,z,zz,makecol(z+v,zz-v,(z+zz)*v));
7} while (!keypressed());
8 
9// Example 2 - With Locking
10v = 0;
11do {
12 v++;
13 acquire_screen();
14 for (z = 0; z < 640; z++) for (zz = 0; zz < 480; zz++)
15 putpixel(screen,z,zz,makecol(z+v,zz-v,(z+zz)*v));
16 release_screen();
17} while (!keypressed());

You're going to find that Example 2 runs much faster than Example 1.

Note however that not all operating systems require locking. For instance, these two examples will likely run the same speed if you run them in DOS.

--- Kris Asick (Gemini)
--- http://www.pixelships.com

--- Kris Asick (Gemini)
--- http://www.pixelships.com

Kauhiz
Member #4,798
July 2004

Quote:

If you draw everything to a memory buffer then blit that to video memory, the lock is only engaged once because regular bitmaps don't need to be locked to be written to.

Why does everyone keep saying this? :P I'M NOT RETARDED! ::) Besides, I knew that much before the thread anyway.

Sorry, Kris, but you really didn't help that much. Well, apart from the operating system thing.

---
It's Ridge Racer! RIIIIIDGE RAAAAACER!

Kris Asick
Member #1,424
July 2001

Kauhiz: We're helping you perfectly fine, you just don't like our answers. You want to know exactly what factors go into the delay of locking a video bitmap. The trouble is, everything is a factor. The speed of the computer, the speed of the video card, the speed of your RAM, the speed of the video RAM, the state of your CPU registers... no matter what computer you are on the lock can take anywhere from an insignificant amount of CPU time to a rather large amount of CPU time to engage. There's absolutely no way to calculate or control it so there's no sense trying to.

All you need to understand is that locking and unlocking a video bitmap multiple times in one frame is slower than locking and unlocking once per frame. By how much? There's no way to know until you do it.

--- Kris Asick (Gemini)
--- http://www.pixelships.com

--- Kris Asick (Gemini)
--- http://www.pixelships.com

Kauhiz
Member #4,798
July 2004

Quote:

We're helping you perfectly fine, you just don't like our answers.

Nope, you're not answering my question. I don't really need help, since I don't use allegro for graphics anymore, I'm just curious.

Quote:

The speed of the computer, the speed of the video card, the speed of your RAM, the speed of the video RAM, the state of your CPU registers...

That's the answer I was looking for, thanks.

---
It's Ridge Racer! RIIIIIDGE RAAAAACER!

Tobias Dammers
Member #2,604
August 2002
avatar

Quote:

The speed of the computer, the speed of the video card, the speed of your RAM, the speed of the video RAM, the state of your CPU registers...

...the graphics mode you're in, directx version, other active processes...

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

armond
Member #8,282
January 2007

Thank you people. I think you've done everything you can to help this poor fella. It's up to me now to incorporate your ideas with my code. If I succeed, I'll get back at you and post the working code. Thanks!

Go to: