PDA

View Full Version : Using multiple surfaces



IaxFenris
13-09-2005, 04:31 PM
Hi there,

i am taking my first steps into game programming and have chosen unDelphiX for my first attempts.

For now, my program only draws tiles from a DXImageList on the DXDraw1.Surface. My approach might not be the most ellegant one, but i am still learning, so here is my problem:



TileImageList.Items[i].Draw(DXDraw.Surface, XCoord, YCoord, 0);

DXDraw.Flip;


Well, this way everything went fine. The program draws the playfield just as i want it to do.
Now I want to add a user interface as well but instead of rewriting the whole playfield-function and fiddling around with all those relative coordinates to place my tiles it would be much better to draw the tiles on another surface and than place the surface on the right position. This way I could use an absolut coordinate system for the playfield.

So this is how i tried to do the whole thing:

var
MapSurface : TDirectDrawSurface;

procedure TfrmTest.DXDrawInitialize(Sender: TObject);
begin
MapSurface := TDirectDrawSurface.Create(DXDraw.DDraw);
end;

procedure DrawPlayfield;
begin
TileImageList.Items[i].Draw(MapSurface, XCoord, YCoord, 0);

DXDraw.Surface.Draw(0,0, MapSurface, false);
end;

procedure DXTimer...
begin
DrawPlayfield;
DXDraw.Flip;
end;


Unfortunaley this didn't work. I tried both Draw routines with the same result.
I also replaced the
TileImageList.Items[i].Draw(MapSurface, XCoord, YCoord, 0);
with
Mapsurface.Loadfromgraphic(TileImageList.Item..... .picture.graphic)
and it drew a lonley tile onto my screen, so i think the problem has something to do with the way I draw on my new surface.

I hope someone can help me with this.
thanks in advance
IaxFenris

technomage
13-09-2005, 05:20 PM
hi

you could try looking at http://www.cerebral-bicycle.co.uk/links.asp?cat=24&subj=5

it has a whole load of tutorials about DelphiX ,almost all of which will apply to UnDelphiX. :D

Dean

IaxFenris
13-09-2005, 05:28 PM
thanks for the quick answer technomage!

The page you mentioned was, and still is, one of the main sources of my unDelphiX knowledge but i couldn't find any answer for my problem, there.

Crisp_N_Dry
13-09-2005, 08:15 PM
Well it comes down to a couple of things. Firstly, you haven't completely initialized your MapSurface. It needs a Width and Height. So


procedure TfrmTest.DXDrawInitialize(Sender: TObject);
begin
MapSurface := TDirectDrawSurface.Create(DXDraw.DDraw);
end;
should look like this


procedure TfrmTest.DXDrawInitialize(Sender: TObject);
begin
MapSurface := TDirectDrawSurface.Create(DXDraw.DDraw);
MapSurface.SetSize(DXDraw.Width,DXDraw.Height);
end;

which will initialise your MapSurface to the same size as your DXDraw (Change the Width and Height values to whatever is appropriate if you don't want it so big).

The next problem is with the DrawPlayField procedure.

procedure DrawPlayfield;
begin
TileImageList.Items[i].Draw(MapSurface, XCoord, YCoord, 0);

DXDraw.Surface.Draw(0,0, MapSurface, false);
end; needs to become this...



procedure DrawPlayfield;
var
Src: TRect;
begin
Src.Left:=0;
Src.Top:=0;
Src.Right:=MapSurface.Width;
Src.Bottom:=MapSurface.Height;
DXImageList1.Items[i].Draw(MapSurface,XCoord,YCoord,0);
DXDraw.Surface.Draw(0,0,Src,MapSurface,True);
end;


There are two versions of DXDraw.Surface.Draw and and while trying to get your code to work I found that the one you was using wasn't very effective without a Src RECT so I threw that in and along with the other adjustment seemed to work a treat. If you wish to only draw a part of the MapSurface then adjust the Src values accordingly. Hope this answers your question. Let us know how it goes or if you need me to explain myself better, I don't always make myself clear. :D


[EDIT : Few coding mishaps]

Traveler
13-09-2005, 08:21 PM
Hi IaxFenris,

Let me first welcome you to our PGD. I hope you'll like it here!

As for your question.
I can only think of one reason why you would want to draw everything to a temporary surface and then to the main surface, and that is when you have stationary images. Text, for example. Suppose you have all the letters in the alphabet in a single image and you want to to display a sentence from those letters. You could look up the letters one by one in a init procedure and then draw them on a seperate surface, so you can draw it later. It is an option, but really, for todays computers its really no problem to do the lookup at runtime.


But anway, let me first go through your code.


var
MapSurface : TDirectDrawSurface;

procedure DrawPlayfield;
begin
//Unless you want the coordinates and images from the list to be
//variable, there really is no need to draw it here over and over again.
//A single call in the init procedure should do just fine.
//In fact, if you do it this way, you're wasting valuable cpu power as you
//could just as well write to the dxdraw surface.
TileImageList.Items[i].Draw(MapSurface, XCoord, YCoord, 0);

//this is not correct, it should led to an error as the compiler
//doesn't know DXDraw. Also, the draw function here requires more
//parameters
DXDraw.Surface.Draw(0,0, MapSurface, false);

//this works better
Form1.DXDraw1.Surface.Draw(0,0, MapSurface.ClientRect, MapSurface, false);


end;

procedure DXTimer...
begin
DrawPlayfield;
DXDraw.Flip;
end;

procedure TfrmTest.DXDrawInitialize(Sender: TObject);
begin
MapSurface := TDirectDrawSurface.Create(DXDraw.DDraw);
end;



Up to now the code works perfectly. It compiles and runs. Only problem is that it doesn't show anything. Reason for that is because the size of your temporary surface is unkown.
In the init procedure you should also define the size of the surface.
like so

MapSurface.SetSize(256,256);


Hope that helps

Traveler
13-09-2005, 08:24 PM
Oh shoot, you've beaten me to it Crisp :D

User137
13-09-2005, 10:09 PM
As far as i know, and asked this same question here before, there is a bug in hw accelerated undelphix_v6. It only works fully with disabling hw acceleration. I still don't know if the maker has found a way around this...

IaxFenris
14-09-2005, 11:14 PM
Thanks for your answers. Both were very helpful and both brought up new questions :).

First off, the whole thing works now. The SetSize-Method did the job and not it draws my playfield in all its glory :roll:

Now the new questions. First, i am going to get a bit more specific about what I want to do. The plan is to program a nice little turn based strategy game. The extra surface i mentioned is used to draw the playfield including terrain, units, buildings and so on. The playfield can be scrolled and my routine draws only the stuff that can actualy be seen by the player. Later, I want to embed the playfield into an interface. To make the playfield independent from the interface i thought it would be a good idea to place it on a seperate surface which i could place later wherever i would like to.
So would it be better not to use a seperate surface for this but draw everything on directly on the DXDraw.Surface instead? Does the usage of multiple surfaces slow down the drawing process considerably? And for the second question, is there anything wrong with the shorter Draw-Method i used or is it just less flexible?

About your explanation crisp, it was very clear and i had no problems understanding it :)
By the way, thanks a lot traveler for writing these great tutorials. They have really been a great help.

Traveler
15-09-2005, 10:37 AM
The only problem I see is that it would require more memory & cpu power than normally because you have to create each surface beforehand. As for using part of a surface multiple times, check out the copyrect function.

I don't think multiple temporary surfaces slow down the drawing that much. And since you are planning on doing a turn based game, fps isn't going to matter anyway.

I think the methode of drawing is sort of preference. I doubt one way is faster than the other.

Crisp_N_Dry
16-09-2005, 08:42 AM
If I understand you correctly it would seem you want to have to surfaces, one for the play window and one for the interface which is overlayed on top of the play surface and then this is all flipped to the user. While this would work and isn't too much of a drain on memory it would mean blitting a very large image (the play surface) over to the screen when it could have been drawn to screen in the first place. Since this will happen every frame I'm pretty sure you will lose a lot of frames just doing this. I'll endeavor to write a test program that demonstrates the framerate differences between the two methods.

K4Z
16-09-2005, 10:25 AM
With a 2d rpg I've been working on solo for the last few years, I got a massive FPS increase after setting up multiple surfaces.

I've setup 6 surfaces for the map (Ground layer, Middle layerx2, Simple Sprite Layerx2, Above Layer), 1 surface for the Gui, 3 for Characters and NPC sprites, and 1 extra layer for misc stuff. Middle, Simple Sprite and Character layers have mutliple surfaces for animation.

Originally the Drawer had to loop through the map tiles, sprites, gui, etc and draw each tile graphic every frame. But with multiple surfaces the drawer only loops through everything once and draws to the surfaces, and only again if a specific layer changes, and only that layer gets redrawn.

I also use something like:
DxDraw.Surface.Draw(0,0,rect(offx,offy,screenW+off x,screenH+offy), < Surface >, true);
to only draw what is on screen, with transparency.


..anyway, before multiple surfaces I had a FPS of 12, and now I get 70 FPS :), and thats on a 333mhz cpu. The games runs so fast now, I have to have a Sleep(200) in the main loop for it run properly :o

Crisp_N_Dry
16-09-2005, 10:28 AM
Okay, I made a test program with DelphiX so if you got unDelphiX I'm not sure whether the source will load correctly. The results were quite surprising. I found that there was an average of 20fps difference between the two methods on my PC, this could be quite different on a low end machine. Here's the link to the exe with source. Hit enter to switch between the two drawing methods. Feel free to recompile before running, I won't get offended :D let me know what sort of results you get. Mine hovered around 420 for fast mode and 400 for slow mode. I was expecting a bigger gap but that's what tests are all about.

http://zupload.com/download.php?file=getfile&filepath=6340

Oh and just so you know, this obviously isn't optimised in any way. For instance it is a very bad idea to use the Surface.Canvas. It is a very slow way of drawing text to screen IIRC. It's better to have a font sheet with all letters and characters on just blit them to the suface when needed. But that's a subject for another post/test program.

One more thing - lowering the color depth also seems to boost FPS by a huge amount. Although 8-bit requires a decent palette so it depends on what kind of art you are gonna be using.

Traveler
16-09-2005, 11:56 AM
An interesting approach K4Z. I used to use surface.clientrect, which is the same as rect(0,0, width, height). I never actually thought of clipping the rect, using an offset. I'll keep that trick in mind!


The games runs so fast now, I have to have a Sleep(200) in the main loop for it run properly
From this it gather you're using framebased movement and not timebased movement?

Crisp_N_Dry, what specs do you have? I tried your sample (exe only) and got ~229 fps with both methods. (my specs: 3ghz, 1gig ram, 6800 gt)

K4Z
16-09-2005, 12:54 PM
At the moment the RPG is using frame based movement, was in the middle of switching to time based when my pc died, so developement on the game has stopped for now :(.


I tested Crisp_N_Dry's sample as well, and got around 240 fps on my 3ghz, 512 meg ram, geforce.

I did a simple modification - made a new surface and created a 'BuildScreen' function that loops through the tiles and the other graphic and draws to the surface. Only when you switch to 'Slow' (lol) draw mode it calls the 'BuildScreen' function once.

So the only thing the DrawSlow function does now is:

DXDraw1.Surface.Draw(0,0,NewSurface.ClientRect,New Surface,False);
DXDraw1.Surface.Canvas.TextOut(0,0,'Surface overlay method');
DXDraw1.Surface.Canvas.Release;

And now I get 2560 fps, lmao 8) . over 3000 if I remove all textout() functions.


Edit: Running the .exe straight out of the .rar gave me 200 fps, compiling with no modifications gave me 240 fps :?:

Traveler
16-09-2005, 02:25 PM
Edit: Running the .exe straight out of the .rar gave me 200 fps, compiling with no modifications gave me 240 fps Question

Interesting: I tried the same using an older delphiX version. (ie no undelphix) I recompiled the given source with D7 and got a massive increase as well. from 229 fps to ~310 fps?! How odd. Could this be a difference in Delphi version?

Crisp_N_Dry
16-09-2005, 04:34 PM
Very unusual. I compiled using Delphi 4 Standard. My specs are
AMD Athlon 2800 (Running at 2200 due to heat issues).
1gb Crucial 400mhz.
NVidia Geforce 5800 128mb.

Strange about the massive jump between Delphi compiles. Never thought the optimization was that major. Also weird how I got a higher framerate with a lower end graphics card than you guys.

K4Z
17-09-2005, 02:21 AM
Here's the modifications I've made to Crisp_N_Dry's sample.

http://zupload.com/download.php?file=getfile&filepath=6885 (http://zupload.com/download.php?file=getfile&filepath=6885)

Press SHIFT to switch to my mehod, ENTER to Toggle between both of Crisp_N_Dry's mehods.

Under my method, I get around 2600 fps. And thats still without any optimizations, just drawing to a seperate layer.

Traveler
17-09-2005, 10:50 AM
The games runs so fast now, I have to have a Sleep(200) in the main loop for it run properly

You might want to consider putting timebased movement in there as your latest modification of Crisp_N_Dry' sample gave me around 7000 fps.
There's no way sleep(200) is going to help you with that. :)

{edit: and thats 600 :D}

IaxFenris
17-09-2005, 11:04 AM
With your new method, I get around 6300 frames while the other two only get arround 225 FPS on my AthlonXP 2000+.

Well, I got another question concerning surfaces. Here is some sourcecode to illustrate my problem:



// Version A
procedure TfrmTest.DrawInterface;
begin
surfInterface.Fill(0);
InterfaceImages.Items.Items[0].Draw(surInterface, 0, 0, 0);
end;

procedure TfrmTest.DXDrawInitialize(Sender: TObject);
begin
DXDrawTimer.Enabled := true;
surInterface := TDirectDrawSurface.Create(DXDraw.DDraw);
surInterface.SetSize(DXDraw.Width, DXDraw.Height);

DrawInterface;
end; // Version A

// Version B
procedure TfrmTest.DXDrawInitialize(Sender: TObject);
begin
DXDrawTimer.Enabled := true;
surInterface := TDirectDrawSurface.Create(DXDraw.DDraw);
surInterface.SetSize(DXDraw.Width, DXDraw.Height);

surfInterface.LoadFromGraphic(InterfaceImages.{... }.graphic)
end; // Version B

// A & B
procedure TfrmTest.DXDrawTimerTimer(Sender: TObject; LagCount: Integer);
begin
DXDraw.Surface.Draw(0, 0, surInterface, true);
DXDraw.Flip;
end; // A & B


Version A dosent bring anything on my screen, Version B does. If I would call DrawInterface at the Timer Event it would draw it as well. Isn't it possible to draw Images of an DXImageList athe DXDrawInitialize Event?

Traveler
17-09-2005, 11:59 AM
Nope it isn't. The DXDrawInitialize is for initializing. Like loading images, initializing variables, setting timers to true.

It kinda forces you a bit to keep your code structured, ie:

start game
init game variables
process game
deinit game variables (free objects etc)
close game

IaxFenris
17-09-2005, 02:17 PM
Well. in my case it screws the structure of my code a little bit up, as I have to initialize my static playfield-frame somewhere else.

Unfortunatley, LoadFromGraphic-Method is not applicable, as I have split the frame into 4 pieces to save space.

I guess an Init-Flag in the Timer-Event will do the job as well.

Crisp_N_Dry
17-09-2005, 03:04 PM
I got 4700 in 8 bit without recompile.
1300 in 16 bit with recompile - Delphi 4
1100 in 32 bit with recompile - Delphi 4

Although I'm not sure this method would be so useful when it comes to animating and scrolling since all tiles would need a redraw (All/Some depending on whether you used a dirty rectangles method). I've quite enjoyed this little experiment and shall bear you guys in mind when my latest project gets to the open testing stage.

K4Z
18-09-2005, 12:28 AM
I handle animation by, at start up, drawing each animation frame to a different surface.
eg: Something like GroundSurfaces: array[0..2] of TDirectDrawSurface.

So, if you have a FrameIndex variable like you normally would, you just use that value to get the proper surface.
This way you never have to loop through all the tiles again, which is the main fps killer. Normally, the only Surfaces that would need to be redrawn will be those with tiles, sprites, etc that move around, but if you only have around 10 moving sprites, thats a hell of a lot quicker to loop through then a 100x100xEachLayer tile map everyframe.

And of course, animation would also have to be timebased, lol at 1000+ fps the animation might be little too fast 8)

and don't forget to clip the drawing to the screen WxH with something like:
Draw(0,0,rect(offx,offy,screenW+offx,screenH+offy) , Surface, true);
no point in drawing what you can't see :wink:

blackvoid
17-01-2006, 02:44 PM
Hi,

I would be really interested in this source code, but the file on zupload has expired. Could you guys re-upload it?
Or send it to me: info1848@yahoo.com

Thanks