Basic drawing techniques using agate
Hi, I'm back with one of my noob questions.
Today I need some opinions regarding the proper drawing of a gamescreen.
Let's say I draw my main game screen. I draw the surfaces for my tile based map, I draw some surfaces for the main Interface, etc.
And I draw a sprite for a character which can move over the map.
This all is static content - the interface doesn't change it's position or appearance (till someone pushes a button or so). The map allways remains the same (until someone scrolls). Actually the only thing that needs to be redrawn every frame is the character sprite.
But what I do now is to redraw every of these hundreds of surfaces every frame to assemble my main gamescreen. That's because I do a Display.Clear() after each frame start.
If I just draw the static content once, skip the Display.Clear() and just redraw the character sprite... well you get this smear effect if the character moves. Because the character's still on the screen on it's previous position and the next frame you draw it somewhere else.
So whats the best way to circumvent these unnecessary Surface.Draw()'s?
What I tried was:
a)
Render all static content to a seperate surface, display it once.
Then CarveSubSurface() to save the part of the static content where I'll place my character.
Draw the character.
In the next frame redraw the previously saved subsurface (restores a proper gamescreen w/o char).
Save new subsurface at new position.
Draw char at new position.
b)
A similar approach was to render everything once to screen. Before each character update I know on which tile I want to place it, so I just redraw these tiles and then place my char on top of them.
Problem here: Works for my character problem, but what if the thing that changes every frame is not a char but something like a custom mouse cursor?
A mosue cursor is over tiles of my map (no prob here) but also over interface components and other stuff.
So whats the best approach? Regarding performace and managment.
- Render static content to a seperate surface and carve the part that changes before an update? (test result: 1150fps)
- Render all content every frame ignoring if it has changed? (test result: 820fps)
- Pack all surfaces into a huge list, pick the one which is at the position where screen content changes (i.e. character) and redraw it beforehand? (test result: 1350 fps)
- other technique I missed?
I'll see if I can figure it out from your(kanato) ball buster.net game, but maybe someone else has a suggestion for me.
But certainly I'm again down on the wrong track... :)
TIA,
Zimble
PS: Sorry - lot of text but you know... explanations in foreign languages... ;)
What I normally do, and what's done in Ball: Buster is to just redraw everything every frame. It's not the most optimized solution, but even with a lot going on it's still quite fast on a reasonably modern graphics card. Generally speaking, with AgateLib the bottleneck on any computer is going to be the video card performance. So I think the best optimization is a modification of (a) where you use the Draw(Rectangle srcRect, Rectangle destRect) overload instead of CarveSubSurface. That may improve performance a little bit, but will likely decrease the amount of memory allocated, reducing pressure on the GC.
Actually, you could render all your static stuff to a surface and just redraw that whole surface each time and that might perform pretty well. Or, you could just use the Draw overload to repaint parts of the screen that have changed.
This is a pretty age-old question really, and something I hope to shine a little light on.
Before I do, I'll first admit that I've not used AgateLib (yet) - but plan too.
I too am drawn by the fact it's within .net (very cosy).
I think if I like what I find, I'd like to help on the documentation, and make suggestions for further development of the project.
For now, let's take a very quick trip down memory lane - as I've been around these games machines for a very long time. so close your eyes, and go to your happy place, and remember, this is only reality as it was...
The first computers in the home were pioneered by companies such as Atari, Commodore, and Sinclair. Funny how their products are better known than the manufacturer. All these manufactures used variations of the same hardware to make their machines. Not so different from today. You have a processor, you have some other chips to handle graphics, and another for sound... easy as pie.
However, the very problem you're talking about is exactly what hardware manufacturers were facing, and their concept was to separate background from sprites. The hardware mixed the two, and so, you never had the sprites leaving trails behind as you describe. I'd associate that with Bobs on the Amiga. (Blitter OBjectS)
The problem with the hardware was, they had a fixed number of sprites - 8 of them to be exact. You could employ amazing tricks to increase that on the Commodore 64, because it could track its verticle refresh. This fact is what gave rise to more colours being injected through hardware-tricks. so called Copper Bars were born on the C64. The Amiga just did a better job!
I'll leave it there - as a snapshot in time, because a revolution was happening every year, and up until the early 90's things were handled in this manner of hardware, until the brute force of powerful computing made another kind of graphics processing possible. That's where we are today - where we literally have a computer inside out computer to handle graphics, thus the name, graphics processor (GPU).
None of that helps you find a solution, or explains Bobs! So lets cover that a second, and think of both the advantages and disadvantages of the systems we have today.
When the C64 gave way to the Amiga - obviously its hardware had something to do with it. The Amiga had a graphics processor, two, if you include the Co-Processor (known as copper) - and its hardware still maintained that 8 sprite limitation. But that was more of a backward compatibility in terms of the hardware. the Amiga was going down a road of raw processing power. It had very physical control of what you see on screen through bit-planes. Stuff of legends now, as far as computers are concerned. Literally, the more bit-planes you used, the more colours you could display. Up to 8 x bitplanes on the AGA powered Amigas gave 256 base colours with no tricks. The copper could and did increase that (at a cost) to give those funny named HAM Mode pictures. For those who've never seem them, they're like looking at a pig picture that was compressed as a JPEG (.JPG) with maximum compression. You get these little artifacts on screen that aren't in the original, so to speak.
That aside, it meant to get past the 8 sprite limit, you had to start employing Blitted Objects (Bobs) to be those objects. This is more akin to what we have today, still.
The blitted objects exist physically on the screen, or surface, if you will, and replace anything that was underneath it before you blitted (put) it there. Which is why when you physically move that sprite around with AgateLib, you leave the old image there. but because you're moving it so slowly, it just looks like a trail of colour, and very strange. interesting none the less. This didn't happen with sprites! so sprites rule, right?
Yet with these bobs, you don't have a limit of how many you can put on screen. yay for Bobs!
So the problem of that sliding trail of colour you leave behind is even more fun when you do it with a double buffered display, because then, the bob is drawn on the same surface, every other update. That means its residue is there one frame, gone the next, there the next frame, and gone the next. You get a shimmering effect. Very cool, but quite useless, unless you want to intentionally use it like a blurring effect or something - maybe pick up a drunken pill?
So your solutions are actually re-inventing the wheel. Lets think about them a second, and elaborate.
You have a surface you use for everything, and leave all your static stuff there. Then you copy the areas of that surface that are about to get drawn over, and then you draw your player or whatever. From that point on, you have to replace the background as it was (you did save it) then copy out the new area of the screen where you're about to draw - then draw the player again. Phew! It's a lot of work. 3 x drawings (replace, copy, draw) for one object such as the player on screen.
So if you know you're only going to have a small number of sprites (its a generic term really, even though technically everything, including the mouse, it a Bob) This method is great for saving electric, and keeping your PC cool.
Question: What if you want to have a moving background? Lets think about that a second.
The Original Amiga had those physical Bobs, and it did scrolling games. How?
Programmers reasoned that the only parts of the screen than physically changed were the edges where new information was being drawn. They had to compensate for this with the graphics area being larger than what you could see, and its also why games appeared in kind of "window" - but we don't have that issue nowadays.
So they had to 1. replace all the original images, as above. 2. draw and move the screen (movement was hardware). 3. Take into consideration the movement of the screen with relation to the position of sprites. (a little math). 4. copy out the areas they were about to draw on, and then, 5. draw on them.
Sounds complicated, and it is. Same problem as you, lots of memory moving, and not a lot being drawn on screen. Lets complicate this a little further. we've not even mentioned double buffering!
The idea of a double buffer, is that you have one for drawing on, and the other for displaying. I've avoided the whole "how the screen is displayed" discussion - as it won't help us until now.
The same way you write a letter - from left to right, then from top to bottom. you work your way along the lone, and when done, you come down to the next. I'm just describing the way you write a page. The same is true of how a screen is drawn. it physically takes those digital 0's and 1s and turns then into a colour that is drawn on screen, much as you write letters to make up each word on the page. but we're drawing colours, not letters. Those dots of colour are what we call pixels - and are the basis of all things graphical. All graphics are made up of hundreds of colour dot.
So the point to all this? If you know how much of the screen has been physically drawn, you can re-use the things that have already been drawn, and simply move them in a position that hasn't been drawn yet. Thats how copper bars are done, and its how sprites are re-used to appear on screen as many sprites. but it comes at a cost. You have to completely organise the drawing operation from top to bottom, and from left to right - and keep track of the actual drawing process to make all those tricks possible. A nightmare!
Therefore, the number of things you can have is limited to how fast you could draw them. On the C64, this was often passed, and you got sprites that disappeared, and re-appeared, or flashed, as the game also slowed down.
Realising the folly of trying to do too much, the Amiga programmers opted for the double-buffer approach. The idea being to do all your drawing on some invisible surface meant you had all the time in the world to get it right, and you didn't have to care about what order things got drawn in. Liberating! (for the programmer)
so lets take your first method one step further. Try to imagine this level of complexity, because now, as your drawing a sprite on screen, you don't just have to worry about where it's about to be, but also where it was before that too! You have to replace the area of the screen you used two updates ago, and then draw on that. Does your brain hurt yet?
All this is a very long way of saying -the number of objects you put on screen today has a direct affect on what method best suits your purpose. Putting one player on the screen to wander around a static 2d maze, I'd definitely opt for your first suggestion. But knowing that the game is going to be filled with hundreds of ghosts, like the original Gauntlet game means you might want a serious re-think, as this method won't be very good. That's when Kanato's suggestion of having a whole surface with all the static background drawn on the screen in one hit, and then drawing your objects on top - would definitely be quicker.
The other idea you had of redrawing all the tiles, and then the sprite on top is a killer for the hardware, and feels like a massive waste of processing power, but may well be a good solution, depending on your needs, such as a scrolling background. Or is it?
Kanato's suggestion would still work very well for a scrolling screen game. Think about it like the Amiga above if the surface is larger than what is visible, so your physical screen is like a window into this bigger surface, then you can get away with some pretty neat stuff. Games like Skidmarks on the Amiga use a surface 4 times the size of the screen, and simply moved around the window over the surface. This is a good use of the graphics memory too.
But a scrolling game would require a virtual sized surface, or you're need terrabytes of graphics memory to make your level! So the original clean and easy copy a portion of the surface to your screen could become a confusing 4 x copies of portions of the that surface, to try and avoid making more draws to that buffer than need be. This really needs a diagram to help explain - but it would save you a small amount of time.
So now lets ask the question - what if I want my backgrounds to animate? Like water moving, or fire or something. In the old days, a lot of tricks were employed such as colour cycling - but that only works when you have a palette to cycle. Nowadays we have no choice but to physically drawn each animation on the screen. In this scenario, is there much difference between the requirements for the background, as the requirements for sprites? I don't think so. the only caveat here, is that background needs to be drawn first, so it stays in the background.
This is where I believe there is still a lot to be learned from the past. On any of those old early 1980's computers, you could re-define a character such as, "A" for instance, and instead of printing an A on screen it might look like a stick-man. But here's the trick. Where ever the A is on screen is displayed as a stick-man!
so by constantly re-defining the "A" to an animation frame on a stick man who's waving, you could fill the screen with "A"'s and have as many waving stick men as you like. This concept was used to animate background, to make waves go up and down in games such as Pitfall 2, or to make more believable fire than colour cycling ever could (the c64 could not colour cycle like an Amiga anyway)
Only way around this as it stands, is with hundreds of little commands to the graphics processor to move very small bits of memory around. It may be more efficient to do this in memory with the processor, and then copy the memory-surface to the graphics card.
Me personally, am looking to produce games at a resolution of 400 x 300. This isn't so far away from the old Amiga resolutions, or what arcade machines used. Therefore, I can get a hold of old graphics, and still use them, and make cool retro games :-)
If people only knew the true power of a 486, they'd be surprised at what they could achieve if today's hardware was used properly. I think what you're feeling reflects that in part. For me, it's a necessity, as I want my stuff to work on less than 2Gz processors, with graphics built into the motherboard.
Wish me luck - as I do you!
Yeah, there is something lost on modern hardware that was available on really old stuff. The modern operating system introduces abstractions that let us write code that runs on a large variety of machines, at the cost of being able to work with the metal. We gain effects like blending, scaling, rotation, and not having to worry about a palette, and then we lose effects that can be done with the palette, or by making changes during the hblank period, etc.
Some of the more impressive effects from old demos could be rewritten using shaders on modern hardware. In fact, shaders should make it so these old effects could be written much more easily, more robustly, and be accessible on a variety of hardware, and take advantage of hardware acceleration. But, this requirement of being supported on low end machines with built-in graphics is where these things fall a bit short.
Suggestions and help on the documentation are welcome. The documentation suffers a lot when it comes to the documentation of parameters to functions, and elaborating on these in the XML comments in the source code would be very helpful.