Adam Miles

Hi,

I'm still trying to draw large gridmap terrains on the 360 and so far am having a lot of success. Performance seems to be a lot higher than my 6800GT, perhaps by about a factor of about 4. The issue I'm having though is really rather strange and I've been wondering for the last couple of hours quite how it could be happening.

I create a huge vertex buffer (as big as XNA allows before throwing an UnauthorizedAccessException at me) for the gridmap and draw the entire world (4million polygons) in a single draw call. Except the frame rate is good under some circumstances and bad under others, and the factors that make the frame rate plummet really shouldn't affect the frame rate at all. If I create only this one VertexBuffer and try drawing, I get a measly framerate of 31fps, but this is a very solid frame rate almost as if it is locked there.

So what is the cure for this 31fps Well, create another huge 64MB vertexbuffer, fill it with data or not, it still has the same good effect. Simply by making a second, huge vertex buffer, the framerate more than doubles up to 61-62fps, exactly what you'd expect as it is synched to go 60fps. Why does creating a huge extra vertex buffer and not doing anything with it increase the frame rate so much

The other cure to the "31fps" lock I seem to hit is intercepting the PresentationInterval on device creation and setting it to Immediate. With this set, the frame rate jumps to about 69-70, only limited by the performance of the 360 now rather than the 60fps synching. With smaller worlds that push the 360 far less, the frame rate increases to 100, 200 fps depending on the world size (as you'd expect).

My understanding was that PresentationInterval.One should try and display a frame once for every time the display refreshes, ie, 60 times per second on a 60hz monitor. Of course, even if I was completely mistaken, it still doesn't explain how this extra vertex buffer magically helps the frame rate to over 60.

I've uploaded/zipped up my project "TT360" and it should just deploy if anyone would like to try it. I've commented a couple of areas within the code where you can try commenting/uncommenting the creation of this extra vertex buffer, and also where to intercept the PresentationInterval. The frame rate is output to the console one per second along with the PresentationInterval being used. I also draw a piece of road in each corner of the map to illustrate that all the primitives of the grid are infact being drawn. Frame rate is best when looking down towards the world rather than up, I guess DirectX does some of it's own culling best when looking down ( ).

Controls are fairly simple, dual analog sticks for moving around, triggers move vertically up and down, holding the right bumper/shoulder will increase movement speed by a factor of 10 for when you want to move around quickly. Pressing A will go to wireframe mode, B back to solid fill. "Back" will quit the 'game'.

http://aj.uwcs.co.uk/TT360/TT360_Test.zip

My problem basically then is that I don't want to use .Immediate, as this causes tearing, and so leaves me with .One, except the only way to get 60fps is to declare a 64MB vertexbuffer into memory and do nothing with it... crazy.

If anyone has any comments about either the problem or my code (don't be too harsh!) then I'd be glad to hear :)

Thanks,

Adam Miles



Re: XNA Framework Strangest 'Bug' I've Found Yet

abi

Hey Adam,

I tested your code and it runs fine at 70fps without changing your vertex buffer code and without adding additional vertex buffers. There must be something else wrong, maybe you create too much objects every frame and the 64mb vertex buffer is not healthy for the Xbox 360 either.

Couple of notes:
- I changed your initialization code to the following (for testing and getting more fps):

//obs: manager.PreparingDeviceSettings += new EventHandler<PreparingDeviceSettingsEventArgs>(manager_PreparingDeviceSettings);

// Telling the Xbox 360 which resolution we want does not matter
manager.IsFullScreen = true;
// Tell the Xbox 360 the resolution anyways, the viewport is just 800x600 else!
manager.PreferredBackBufferWidth =
GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
manager.PreferredBackBufferHeight =
GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;

// Turn off the vertical retrace sync stuff
manager.SynchronizeWithVerticalRetrace = false;
this.IsFixedTimeStep = false;

- To get more accurate frame rates I changed your draw code (millis is now a float):

millis += (float)gameTime.ElapsedRealTime.TotalMilliseconds;
frames++;

if (millis > 1000)
{
System.Diagnostics.Trace.WriteLine("Frames: " + frames);// + " and " + manager.GraphicsDevice.PresentationParameters.PresentationInterval);
frames = 0;
millis = 0;
}

- The main 2 improvements were made in the Terrain class. First of all you should NOT create a new vertex declaration every frame (it is always the same) and secondly you should avoid using foreach loops because it generates new enumerators, which have to be picked up by the compact .NET framework on the Xbox 360, which does not support a generational GC. It is always better to create absolutely no new data every frame, just reuse existing stuff!

....
VertexDeclaration vertDecl = null;

public Terrain(int width, int length)
{
this.width = width;
this.length = length;

basicEffect = new BasicEffect(GameEngine.manager.GraphicsDevice, null);
basicEffect.Texture = grassTexture;
basicEffect.TextureEnabled = true;
grassTexture = GameEngine.content.Load<Texture2D>("Content/Textures/grass");

BuildGrid();

vertDecl = new VertexDeclaration(GameEngine.manager.GraphicsDevice, VertexPositionNormalTexture.VertexElements);
}
....

The render code:

public void Render(PPCamera camera)
{
GraphicsDevice device = GameEngine.manager.GraphicsDevice;

basicEffect.Begin();
basicEffect.Texture = grassTexture;

basicEffect.World = Matrix.Identity;
basicEffect.View = camera.View;
basicEffect.Projection = camera.Projection;

device.RenderState.CullMode = CullMode.CullCounterClockwiseFace;

//foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
EffectPass pass = basicEffect.CurrentTechnique.Passes[0];
{
pass.Begin();
device.VertexDeclaration = vertDecl;
device.Indices = indexBuffer;
device.Vertices[0].SetSource(vertexBuffer, 0, VertexPositionNormalTexture.SizeInBytes);
device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices.Length, 0, prims);
pass.End();
}

basicEffect.End();
}

Finally some testing (all tested in 1280x1024, will be faster in 800x600 and slower in HDTV 1920x1080):
70-72 fps for a 1024x1024 grid
>200 fps for a 512x512 grid
>1150 fps for a 64x64 grid

I strongly suggest that you DON'T use a 64MB vertex buffer, that pretty much kills everything else you want to do on your Xbox 360 GPU performance wise. It just has not an unlimited amount of memory and you should always try to keep things small on a console.
512x512 is still way too big IMO, but it runs fine. Better use smaller grids and put many of them into the scene, then you can switch off any you currently don't see or when they are too far away.

I personally used a 256x256 grid in my last project and the landscape rendering (with a complex shader using 4 textures) is only taking up 20-30% of the GPU time. The landscape was about 4km x 4km, big enough for several levels ^^





Re: XNA Framework Strangest 'Bug' I've Found Yet

kthorsen

So what IS the largest grid supported



Re: XNA Framework Strangest 'Bug' I've Found Yet

Adam Miles

How long is a piece of string If by "large" you mean number of squares then it depends on the size of the vertices you are using I guess.

If you filled all 256MB ( ) of memory with vertices and your vertices were 32 bytes (PositionNormalTexture) then you get 8,388,608 vertices, the square root of which is 2,896, so you could do 2895 x 2895. But then you'd have to have multiple vertex buffers, one for each quarter of the grid. If you used a small vertex type (PositionColor) maybe then you could get more vertices into the 256MB and thus draw bigger grids. As abi said you'd certainly need some culling of unseen areas by then as you're drawing 16M polygons (assuming 2 triangles per square) and I wouldn't expect much more than about 16-17 fps.

I find it strange abi that it wasn't running at 31fps when you tested it "out of the box". I've just got GSE working on Vista (after having previously been using XP, as I thought I wouldn't be able to get it working) and of course have the exact same problem. Given that the 360 is a stable platform to develop for I can't see why it would run any different on anyone else's 360. I'm very grateful for your comments on how I might be able to optimise what I've done, I'll certainly give it a whirl again tonight and see what I come up with.

Thanks,

Adam Miles





Re: XNA Framework Strangest 'Bug' I've Found Yet

Alpha095

So abi pertaining to what you said about the foreach loop. Should I just replace it with a normal for loop in my code to avoid any issues with the GC What are the consequences to using foreach opposed to some other looping structure with the compact framework



Re: XNA Framework Strangest 'Bug' I've Found Yet

CodePfo

Alpha: from what I gather, using foreach will create a bunch of objects that will sit in yet-to-be-collected land, which will slow down the GC when it activates, and also cause it to activate more often (because it will get filled more often). I wouldn't use foreach on methods that run every frame or fairly often, but if it's for initialization or once in a while it should be ok.