Walking offscreen buffers - webstart demo

(Edit, got it webstarted now, see later post)
An idea I had for speeding up the tiling part of isometric games (Note this does not involve object sprites, only terrain tiles). I guess somebody else has (as always) done this before but here goes:

Problem 1: The typical diamond shaped tile consists of 50% transparent because you have to draw the bounding rectangle- this translates into drawing twice as many pixels than necessary when tiling.
Problem 2: Large tilesets take up a lot of vram if all of the tiles are managed images.

Solution: prerender into a number of large rectangular images that can be reused every time the area they cover should be drawn.

So here is the basic algorithm, I use 2 collections of buffers: pool and active
pool is just a Vector, while active is a Hashtable, where the images are stored with their topleft corner as key (I’m just using new Point(x, y))


for each image in active
   if (distance(image, screen ) >maxDistance)
   {
      move image from active to pool
   } 

for each rectangular area that should be drawn on screen
   if an image for that area already exists in active use that image
   else fetch an image from pool (or create a new image if pool is empty) and render to it
   draw the required image to screen.

I call this “walking buffers” because the old buffers become new buffers as you move across the map.
This picture illustrates:
Gray lines mark the boundaries of the buffers as they’re drawn onto the screen.
The yellow rectangle is a “mini” map, blue rectangle is the screen, red rectangles are tiles in the pool, green are active tiles.

http://www.javaengines.dk/images/walkingtiles.png

Now the actual images to be drawn on screen each frame can be opaque and no drawing is wasted.
Also - the original images need not be managed, since they’re only used for drawing onto the buffers - not onto the screen.
One problem is of course that those new images take up both VRAM and main memory - but with large tilesets the extra VRAM needed could be less that what is saved by not using managed original tiles.

The actual drawing code, notice that I wrap the images in BigTile objects to supply information on the topleft corner coordinates - the object also handles rendering onto the image. The Point origo is the screens position relative to the map coordinate set (which is not the world coordinate set, I work with 3 coordinate sets in my engine - I can expand on this if it’s confusing)


//Set up limits for the rectangles to draw
int xStart = (int) Math.floor(origo.x / (double) bigTileWidth) * bigTileWidth;
int xEnd = (int) Math.floor((origo.x + getSize().width) / (double) bigTileWidth) * bigTileWidth;
int yStart = (int) Math.floor(origo.y / (double) bigTileHeight) * bigTileWidth;
int yEnd = (int) Math.floor((origo.y + getSize().height) / (double) bigTileHeight) * bigTileWidth;

//Move old buffers to pool 
Vector<BigTile> tiles = new Vector<BigTile>(activeTiles.values());
for (int i = 0; i < tiles.size(); i++)
{
	BigTile tile = tiles.get(i);
	if (
	(origo.x - (tile.x + bigTileWidth) > maxDistance) ||
	(tile.x - (origo.x + getSize().width) > maxDistance) ||
	(origo.y - (tile.y + bigTileHeight) > maxDistance) ||
	(tile.y - (origo.y + getSize().height) > maxDistance))
	{
		activeTiles.remove(new Point(tile.x, tile.y));
		tilePool.add(tile);

	}
}
//do the drawing
g.translate(-origo.x, -origo.y);
for (int x = xStart; x <= xEnd; x += bigTileWidth)
	for (int y = yStart; y <= yEnd; y += bigTileHeight)
	{
		BigTile tile = activeTiles.get(new Point(x, y));
		if (tile == null)
		{
			if (tilePool.isEmpty())
			{
				tile = new BigTile(x, y, bigTileWidth, bigTileHeight);
				activeTiles.put(new Point(x, y), tile);
			} else
			{
				tile = tilePool.get(tilePool.size() - 1);
				tilePool.removeElementAt(tilePool.size() - 1);
				tile.x = x;
				tile.y = y;
				activeTiles.put(new Point(x, y), tile);
			}
			tile.paint();//Render the offscreen buffer
		}

		g.drawImage(tile.img, x, y, this);
	}
g.translate(origo.x, origo.y);


I use 256 X 256 images, didn’t notice any difference between 512 and 256 but 256 wastes less memory.

Have you comapred this with a simple single back buffer that you shift and fil lthe edge(s) on?

Historically, thats the usual technique

To make this work I should use 2 backbuffers right?
first blitting the old backbuffer to the current one with the required offset, then filling the edges, then blitting current to screen.
Then swap current and new.
Unless I can blit an image onto itself? In that case I would still need the same number of blits but less memory - but I can see some problems coming when it starts copying already modified pixels.

As I see it this requires 1 blit of most of the area, a few small blits and then a blit of the full area.
I doubt this is faster than what I’m doing, but I might give it a try, thanks.

(And I can’t use the general backbuffer since it’s polluted with sprites that have probably moved)

[quote]Unless I can blit an image onto itself?
[/quote]
You can - but I would wager it is incredibly inefficient.

Got it webstarted now, would love to hear some framerates:
http://www.javaengines.dk/test/walkingbuffers.jnlp

If reporting framerates:
set “visualize buffers” option to OFF for comparable results.
please report framerates with and without walking tiles.
Include panel size as written under the fps counter.
include system specs if possible: CPU, RAM, graphics card, OS, other

Size 792,502
simple mode: 130fps.
Walking Buffers: 160fps.
CPU 100%…
Inspiron 1ghz Pentium III laptop.

i’d imagine your method would be better for continuous scrolling but that’s just a guess.

-judd

Hi

The frame rate is all over the place. I click on FPS start and have seen anything from 6 up to 188 for simple, walking seems to be anywhere from 499 to 539. Both test with visualise buffers off.

Size 749,517

Pentium M 1500 laptop with 1gig ram

Running Linux with a Geforce 5650FX go

Endolf

Yeah I’ll be using continuous scrolling for the game itself (“camera” will move with your avatar Diablo-style).
The event-handling I’m using is just borrowed from the map-editor.