Fast scrolling

I’m currently working on a generic sprite surface class for use in future Java games. I’m having difficulty getting a fast scrolling function.

My approach is this: When the sprite surface is scrolled, the parts of the background which have already been drawn and are still visible are redrawn at the opposite edge of the surface, and new tiles which haven’t been drawn yet are then painted. The first part of that is accomplished like this (after calculating offsets, etc.):


Graphics bufferg = bg.getGraphics();
bufferg.drawImage(bg,
                  0, 0,
                  (int)getBounds().getWidth(), (int)getBounds().getHeight(),
                  offset_x, offset_y,
                  offset_x + (int)getBounds().getWidth(), offset_y + (int)getBounds().getHeight(),
                  null);

So basically, part of the image is being redrawn over itself. That worked fine on my Mac and resulted in a fairly fast scroll, but when I tested on Windows (any version, latest JVM from Sun), it went completely apeshit. After messing with it, I realized that the Windows version was reading from the same buffer it was writing to, so when I scrolled in a negative direction, the same row/column of tiles would be drawn repeatedly. The Mac apparently used two buffers by default.

The obvious solution was to copy the image and draw from the copy onto the original. I did that using a Toolkit call which I found in some forums (since you can’t just clone an Image or BufferedImage). It worked, but the demo immediately grinded down to ~8 FPS due to the extra copying.

So the question is, what do I do about it? Is there a way to get Windows to quickly copy the image buffer the way the Mac seems to?

If anyone needs any more information, do ask. Thanks in advance for any help! :slight_smile:

When you’re drawing a scrolling background everything gets overdrawn. You need draw the whole thing and there is nothing you can do about it. You just waste that much fillrate.

Your approach does only minimize the amount of drawImage calls, which won’t give you a big speed advantage anyways.

So… just stick to drawing the tiles. 32x32, 64x64, 128x128 or even 256x256 tiles (try one of the first two). Only draw the tiles which are on screen… and that’s about it.

What Onyx said. One addition, though. If you want performance, use BufferStrategy. Failure to do so may result in a much lower fillrate than you were hoping for. With BufferStrategy, you can potentially get hundreds of frames per second. (Asumming you’re not VSyncing in fullscreen mode.)

An example of maps and parallax scrolling can be seen in my GAGE2D library.

I had tried that before, but for some reason, the game slowed down just as much. However, since I remember having done full tile redraws for scrolling a few years back on a 166 MHz PPC using RealBasic and acheived fine framerates, it didn’t make since. Perhaps I forgot something.

I’ll give it another shot again and let you know how it goes. Thanks for the help!

Okay, I’ve set it up now so that it simply redraws each tile on every frame. Just like before, the frame rate on my machine is crawling. Just to make sure it didn’t have anything to do with the tile images, I set it to draw a simple rectangle for each tile. That managed to get the speed up to ~25 FPS, but that’s simply not enough if the game is to get any more complex than a scrolling field. Besides, who wants a bunch of red squares for a background?

I really can’t think of anything. I don’t see why it would be this slow, but then again, I’m somewhat new to Java so I might be missing something. And it might be my machine (although my previous method was able to run at 60 FPS, despite the problem on Windows which I described).

Here’s an address where you can see the applet run. Click the applet and then use the arrow keys to scroll. FPS is in the status bar. Is it just me, or is it slow for everyone else too?

http://dris.dyndns.org:8080/java/ss/16/example1.html

[EDIT: This server is a little slow, so give it a few moments to load.]

There’s no frame meter, so I can’t tell how fast it’s actually going. Still, it does look to be less than optimal. Two questions for you:

  1. Does this example use BufferStrategy?

  2. How are you loading your images? Depending on how you’re doing that, you may need to copy them into images created with GraphicsConfiguration.createCompatibleImage().

I think I figured it out.

Yes, I’m using a BufferStrategy. For my tile images, they are loaded once and stored once in a “Tileset” object which returns the tile defintions to the sprite surface. A tile definition contains the image and other information such as whether it can be walked upon.

So here’s what the problem was. I wasn’t doing the drawing within the paint(Graphics g) method. The reason was that it caused a separate problem (which I’ll describe shortly). By doing the painting without a call to repaint(), I solved that problem, but I wasn’t aware of how it was affecting the performance (for whatever reason that it was).

So now that I’m back to using the regular paint(Graphics g) method and repaint(), I have speed. Unfortunately, I also have the old problem that caused me to switch in the first place. Basically, it looked like Java’s paint method is called asynchronously or something similar. This resulted in sprites “jittering”. Since I’m now drawing all of the tiles in the same method, tiles are jittering. Perhaps it would be better to see it in action to get an idea of what I’m talking about (it’s especially apparent when scrolling in a negative direction):

http://dris.dyndns.org:8080/java/ss/17/example1.html

Just the same as before, but with the performance increased by using the paint method, along with the new resulting problem. Perhaps it’s just my machine.

Thanks a lot for the help! It would take me forever to figure all of this out on my own (despite my previous experience with other programming languages).

[By the way, the FPS should appear in your browser’s status bar.]

[quote]Yes, I’m using a BufferStrategy. For my tile images, they are loaded once and stored once in a “Tileset” object which returns the tile defintions to the sprite surface. A tile definition contains the image and other information such as whether it can be walked upon.
[/quote]
My Wild Ass Guess of the day is that you’re loading your images wrong. Dollars to donuts says that copying the images to compatible images before using them would increase performance to more than acceptable levels.

[quote]So here’s what the problem was. I wasn’t doing the drawing within the paint(Graphics g) method. The reason was that it caused a separate problem (which I’ll describe shortly). By doing the painting without a call to repaint(), I solved that problem, but I wasn’t aware of how it was affecting the performance (for whatever reason that it was).
[/quote]
That would be right if this was a 1.1 applet. However, with Java 1.4 and above, you’re going to get better performacne from a BufferStrategy.

[quote]So now that I’m back to using the regular paint(Graphics g) method and repaint(), I have speed. Unfortunately, I also have the old problem that caused me to switch in the first place. Basically, it looked like Java’s paint method is called asynchronously or something similar.
[/quote]
It is asynchronous. “repaint()” schedules the event thread to call paint(), then returns immediately.

[quote]This resulted in sprites “jittering”. Since I’m now drawing all of the tiles in the same method, tiles are jittering.
[/quote]
Well, you’ve got two problems causing jitter that I can see. One is that you should always keep things in ONE thread. Period, end of story, and the rest of you better not confuse this poor guy or I’ll kick your butts. :slight_smile:

If you use two threads, you’re going to run into the issue with your rendering coordinates changing in the middle of a render. The solution is to move your frame by frame code into the paint() method. (Which technically you shouldn’t be using in the first place.)

The other problem you appear to be having is a lack of double buffering. This causes tearing and other artifacts as the screen refreshing in the middle of a paint. BufferStrategy handles this automatically for you, and even attempts page flipping on the vid card if the conditions are right.

[quote]By the way, the FPS should appear in your browser’s status bar.
[/quote]
Ugh. Don’t do that. The communication with the browser can add all kinds of unknown garbage and delays. Just use drawString to stick a number in the corner.

Out of curiousity, does this HAVE to be an applet? You may find the whole thing a lot easier to figure out if you start with a full screen application.

Well, the images are stored in regular Java Image objects, but those Image objects are collected into a Tileset object which returns the Image objects to the sprite surface upon request.

I’m using the BufferStrategy, but within the regular paint() method rather than somewhere else.

Currently, I am creating one new thread to loop continuously, handle input, and call repaint(). Is this the one thread which you are referring to, or should I not even create that one?

Do you mean that I should have the main game loop within the paint method? Handling input, etc?

Since I’m using BufferStrategy, all of that should be handled. Here’s my Wild Ass Guess: first, Java calls paint() and it goes on painting the tiles to the BufferStrategy. But before that first call is finished, it is called again, so the next frame begins painting before the first one was even finished. This is further evidenced by the fact that bringing the frame rate limit down to a lower FPS results in considerably less or completely nonexistent jittering. Hence why not using repaint() solved the jitter problem (since it was synchronous, which would explain why it was also slower–the next frame could not begin until the first one was finished).

Yes, I was just being lazy. :wink:

I know I want the final product to be an applet. I’m not sure what the difference would be as far as implementing the sprite surface.

Thanks again! Hopefully we can get this figured out. If it helps, I can email you the source and give you a better idea as to how it all works.

[quote]Well, the images are stored in regular Java Image objects, but those Image objects are collected into a Tileset object which returns the Image objects to the sprite surface upon request.
[/quote]
There’s your problem. Copy them onto BufferedImages before use. See the following Wiki Entry:

http://wiki.java.net/bin/view/Games/LoadingSpritesWithImageIO

[quote]I’m using the BufferStrategy, but within the regular paint() method rather than somewhere else.
[/quote]
:o You really don’t want to do that. The point of BufferStrategy is to allow for a tight loop renderer independent of the GUI rendering thread! Your code should look something like this:


public void run()
{
    BufferStrategy strategy = getBufferStrategy();
    Graphics g;

     while(true)
     {
          g = strategy.getDrawGraphics();

          //Update based on the keyboard events
          //Render something wicked cool

          g.dispose();
          strategy.show();

          //Little breathing room
          Thread.yield();
     }
}

[quote]Currently, I am creating one new thread to loop continuously, handle input, and call repaint(). Is this the one thread which you are referring to, or should I not even create that one?
[/quote]
If you were doing it like above, that would be the correct way of doing it. Unfortunately, you’re doing it 1.1 style, so all your logic would need to be in paint(). In fact, most 1.1 applets simply called “repaint()” at the end of the “paint()” method.

[quote]Do you mean that I should have the main game loop within the paint method? Handling input, etc?
[/quote]
What I really mean is forget the paint method ever existed. In fact, it wouldn’t hurt if you called “setIgnoreRepaint(true)” so that the component never calls “paint()” ever again.

[quote]Since I’m using BufferStrategy, all of that should be handled. Here’s my Wild Ass Guess: first, Java calls paint() and it goes on painting the tiles to the BufferStrategy. But before that first call is finished, it is called again, so the next frame begins painting before the first one was even finished.
[/quote]
Can’t happen. There’s only one rendering and events thread, so all repaint request are placed in a queue.

[quote]I know I want the final product to be an applet. I’m not sure what the difference would be as far as implementing the sprite surface.
[/quote]
There technically is no difference if you’re doing it right. The issue, however, is that Applets have a lot of extra stuff to manage that might get in the way of figuring out how to code it right.

FWIW, applets are usually considered dead here, and we have something of a crusade to see that they die the good death. The replacement technology of choice is Java Webstart. :slight_smile:

A few helpful links:

Sun Tutorial on BufferStrategy - Note how they tell you NOT to put bufferstrategy code in the paint method!
Kevin Glass’s Space Invaders Tutorial - Perhaps a bit more accessible.

Thanks, that was very helpful. I’ll try employing your tips tonight when I get home.

Originally I had completely skipped using Java’s paint() and repaint(), as you suggested, and putting BufferStrategy drawing back into paint() seemed to make things faster. However, it appears that I was doing other things wrong which factored into the whole thing. I’ll check it out tonight.

Thanks a lot for the help. I guess it’s kind of hard to get used to Java after using other APIs with similar but oh-so-slightly different approaches that yield completely different results.

I think I’m about to come to the conclusion that scrolling in a Java applet is just not going to be fast at all.

I decided to eliminate everything but the very basics. Right now, the applet starts up, starts a thread which loops continously calling render() on my sprite surface class and reporting the frame rate. The render() method looks like this:


            if (bs == null)
            {
                  createBufferStrategy(2);
                  bs = getBufferStrategy();
                  setIgnoreRepaint(true);
            }
            
            int sx = this.scroll_x;
            int sy = this.scroll_y;
            
            Graphics bufferg = bs.getDrawGraphics();
            
            // Get scroll offsets
            int offset_x = sx - min_tile_x * tile_size;
            int offset_y = sy - min_tile_y * tile_size;
            
            for (int y = min_tile_y; y <= max_tile_y; y++)
            {
                  for (int x = min_tile_x; x <= max_tile_x; x++)
                  {
                        int start_x = (x - min_tile_x) * tile_size - offset_x;
                        int start_y = (y - min_tile_y) * tile_size - offset_y;
                        
                        bufferg.setColor(Color.BLACK);
                        bufferg.fillRect(start_x, start_y, 63, 63);
                        bufferg.setColor(Color.WHITE);
                        bufferg.drawLine(start_x + 63, start_y,
                                                 start_x + 63, start_y + 63);
                        bufferg.drawLine(start_x,      start_y + 63,
                                                 start_x + 63, start_y + 63);
                  }
            }
            
            // Draw the buffer to the screen
            bufferg.dispose();
            bs.show();

There are no constraints on the loop to sync the framerate, so it should simply go as fast as possible. So what’s the fastest FPS I can acheive while drawing a bunch of black boxes with white outlines? Between 40 and 50 FPS. That would be fine if it were a fully-rendered scene with dynamic lighting, particles, sprites, and other fun effects. But no, this is just a bunch of black boxes with white outlines. I should be able to get much better FPS than 50.

It’s definitely slow drawing causing the problem, because removing the loop that draws the black boxes suddenly gives me a framerate in the tens of thousands (albeit with a blank window as a result).

So my conclusion is that the drawing routines, at least in an applet, are way too slow. If I’m still missing something, let me know. Certainly I am, because Runescape (which runs in an applet and is fully 3D rendered) isn’t even this slow.

Thanks again.

[EDIT: By the way, the applet with the black boxes is here:]
http://dris.dyndns.org:8080/java/ss/18/example1.html

I’m getting 300+ fps with the black boxes. You really should make it a fullscreen app or convert into hybrid fullscreen app/applet. This way if the drawing is still slow you can more easily debug your code.

In case you don’t know how to convert an applet into a fullscreen app. I’m not saying you don’t


public static void main(String[] args)
{
    GraphicsConfiguration ge = 
    GraphicsEnvironment.getLocalGraphicsEnvrionment();
    final GraphicsDevice gd = gd.getDefaultScreenDevice();
    JFrame frame = new JFrame();
    frame.addKeyListener(new KeyAdapter()
    {
           public void keyPressed(KeyEvent e)
           {
                   if(e.getKeyCode == KeyEvent.VK_ESCAPE)
                   {
                       gd.setFullScreenWindow(null);
                   }
           }
     });
     JApplet a = new SubclassOfJApplet();
     frame.setIgnoreRepaint(true);
     frame.setUndecorated(true);
     frame.setResizable(false);
     frame.add(a);
     gd.setFullScreenWindow(frame);
     a.init();
}       

This should work.
Sorry the codes not properly aligned in the keyPress method

[quote]There are no constraints on the loop to sync the framerate, so it should simply go as fast as possible. So what’s the fastest FPS I can acheive while drawing a bunch of black boxes with white outlines? Between 40 and 50 FPS.
[/quote]
Funny. I get 300-350. :slight_smile: And that’s on a puny 733MHz PIII with a GeForce2 GTS card. Speaking of which, what video card do you have, and when did you last update the drivers?

And didn’t I tell you not to put your frame meter in the status bar? Also, you might be interested to know that images may fill faster than primitive operations. The reason is that Java2D is caching them on the video card, then asking for VRam to VRam transfers. Ain’t nothing faster than that! :slight_smile:

I have a GeForce4 MX, latest drivers. And that’s kind of infuriating. Mac video drivers really suck I guess, because I know I don’t get the performance on games and other graphics that I should be getting.

Well, at least I know the problem is on my machine and nobody else’s.

Yeah, I know. :stuck_out_tongue: I was rebuilding the code repeatedly and just wanted the fastest, sloppiest way to display the framerate that I could do. Rest assured, when I actually use the sprite surface, the status bar will be left alone.

Yes, that makes sense, especially for more complex vector graphics. Of course, each “tile” that I draw (in the isometric extension class) is actually combining halves and quarters of the isometric tiles that intersect that 64x64 block, so more draw routines take place than there are tiles on screen. However, I’m sure there isn’t a whole lot of overhead in that.

Anyways, thanks for the tips (you too gamehaser)! I’m going to try and find out what’s making my machine run so slowly.

[quote]I have a GeForce4 MX, latest drivers. And that’s kind of infuriating. Mac video drivers really suck I guess, because I know I don’t get the performance on games and other graphics that I should be getting.
[/quote]
Well THAT explains everything! Mac OS X is not ready for prime time Java Gaming yet. JOGL and LWJGL work fine, but Java2D runs through the SLLLLLLLOOOOOOOOOWWWWWWW Cocoa APIs. Apple hasn’t fixed it because they’re waiting for Sun to complete the OpenGL pipeline for Java2D in Java 1.5. It should get released in OS X 10.4 and solve all of our performance woes.

Yes, that’s the conclusion that I came to after a talk with a friend of mine who had worked with Java graphics on the Mac before. Also, Apple’s Java developer boards were full of complaints about the terrible Java2D performance.

The fact that it’s a separate issue from Java used with OpenGL explains why Java games like Runescape run fine on my Mac.

I can’t really wait for Apple to fix the problem, so I took a recommendation that my aforementioned friend gave me. I’ll be moving development to Python. I’ve written a graphics demo in Python (after learning it, of course), and it runs beautifully on all tested platforms. I had always wanted an excuse to learn Python (which is what I had been saying about Java before I learned it), so I took it.

Of course, all the time I’ve spent working with and learning Java won’t be wasted, since it will surely come handy in the future for non-game applications. And who knows, maybe I’ll come back to it for games when Tiger is widespread.

Thanks again for the help, everyone!