Ceres629

I've have created a simple scene that renders 8000 identical cubes. I know thats a lot of cubes, but I only get 4 FPS on a 2.0ghz dual core with a 7300 mobile graphics card at 640x480 with simply vertex shading and one directional light.

This pc can run doom3 at 60FPS at that resolution and a program i wrote in MDX that draws about 1000 detailed spheres with specular lighting runs at over 100FPS

Here is the code I'm using:

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;

namespace WindowsGame1
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
CaptureZoneSystem captureZoneSystem;
ContentManager content;
Effect effect;
FirstPersonCamera camera;
float aspectRatio = 640.0f / 480.0f;
Model cubeModel;
Vector3 modelPosition = Vector3.Zero;
Vector3 cameraPosition = new Vector3(0.0f,10.0f,10.0f);

public Game1()
{
graphics = new GraphicsDeviceManager(this);
content = new ContentManager(Services);

}

private void SetUpXNADevice()
{
graphics.PreferredBackBufferWidth = 640;
graphics.PreferredBackBufferHeight = 480;
graphics.IsFullScreen = false;
graphics.ApplyChanges();

Window.Title = "XNA Test";
IsMouseVisible = true;

}
private void SetUpCamera()
{
camera = new FirstPersonCamera(this);
camera.Initialize();
}


protected override void Initialize()
{
base.Initialize();
SetUpXNADevice();
captureZoneSystem = new CaptureZoneSystem(this, content);

SetUpCamera();
}


protected override void LoadGraphicsContent(bool loadAllContent)
{
if (loadAllContent)
{
CubeMesh.Model = content.Load<Model>("Content\\Models\\cube");
}

// TODO: Load any ResourceManagementMode.Manual content
}


protected override void UnloadGraphicsContent(bool unloadAllContent)
{
if (unloadAllContent == true)
{
content.Unload();
}
}


protected override void Update(GameTime gameTime)
{
// Allows the default game to exit on Xbox 360 and Windows
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();

captureZoneSystem.Update(gameTime);
camera.Update(gameTime);

base.Update(gameTime);
}


protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

foreach (CubeMesh cube in captureZoneSystem.capturePoints)
{

Matrix[] transforms = new Matrix[CubeMesh.Model.Bones.Count];
CubeMesh.Model.CopyAbsoluteBoneTransformsTo(transforms);

foreach (ModelMesh mesh in CubeMesh.Model.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.DirectionalLight0.Direction = new Vector3(0.0f, -1.0f, -0.4f);
effect.DirectionalLight0.Enabled = true;
effect.DirectionalLight0.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);
effect.LightingEnabled = true;
effect.AmbientLightColor = new Vector3(0.2f, 0.2f, 0.2f);
effect.DiffuseColor = cube.Color.ToVector3();

effect.World = transforms[mesh.ParentBone.Index] *
Matrix.CreateScale(cube.Scaling) *
Matrix.Identity *
Matrix.CreateTranslation(cube.Position) *
Matrix.CreateRotationX(MathHelper.ToRadians(captureZoneSystem.XRotation)) *
Matrix.CreateRotationY(MathHelper.ToRadians(captureZoneSystem.YRotation));

effect.View = camera.View;

effect.Projection = Matrix.CreatePerspectiveOffCenter(-1.0f * aspectRatio, 1.0f * aspectRatio, -1.0f, 1.0f, 1.0f, 1000.0f);

}

mesh.Draw();
}
}
}
}


}


All the cubes are held in a class called CaptureZoneSystem, which simply contains an arraylist CubeMesh which is shown below:

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework;

namespace WindowsGame1
{

public class CubeMesh
{
private Vector3 myPosition;
public Vector3 Position { get { return myPosition; } set { myPosition = value; } }

private Vector3 myRotation;
public Vector3 Rotation { get { return myRotation; } set { myRotation = value; } }

private Vector3 myScaling;
public Vector3 Scaling { get { return myScaling; } set { myScaling = value; } }

private Color myColor;
public Color Color { get { return myColor; } set { myColor = value; } }

public static Model Model;



public CubeMesh()
{

myPosition = Vector3.Zero;
myRotation = Vector3.Zero;
myScaling = new Vector3(1, 1, 1);

myColor = Color.Gold;
}

public CubeMesh(Vector3 position)
{
myPosition = position;
myRotation = Vector3.Zero;
myScaling = new Vector3(1, 1, 1);

myColor = Color.Gold;
}

public CubeMesh(Vector3 position, Vector3 scaling)
{
myPosition = position;
myRotation = Vector3.Zero;
myScaling = scaling;

myColor = Color.Red;
}
}
}

The cube model is loaded in from a .x file and stored in a static member of the CubeMesh class.

Any clues as to what is causing such poor performance


Re: XNA Framework Poor XNA performance or my poor code... or normal?

Kyle_W

Well, a few thoughts...

- You should not be executing the following two lines on every frame:

                Matrix[] transforms = new Matrix[CubeMesh.Model.Bones.Count];
                CubeMesh.Model.CopyAbsoluteBoneTransformsTo(transforms);

Since you are saying that these are simple cubes, they probably don't have bones anyway. You can probably just use Matrix.Identity in place of transforms[mesh.ParentBone.Index] in the world matrix. In either case, it also appears that applying Matrix.Identity after the scaling matrix may be unnecessary. You may be able to remove it.

- Also, if the projection and view matrices are not changing, there is no need to re-assign them on every frame.

- You might see some benefit by working with the X and Y rotation angles purely as radians without having to call the MathHelper.ToRadians() method. There are two pi radians in a circle.

- For any cube that doesn't have a change to its world matrix between frames, you could precompute and store the world matrix rather than rebuilding the matrix redundantly on every frame. Also, even if the position or rotation changes between frames, you could still precompute the parts of the world matrix that don't change, such as the Matrix.Identity * Matrix.CreateScale() portion.

- You should also call all of the following lines once instead of every frame:

                        effect.DirectionalLight0.Direction = new Vector3(0.0f, -1.0f, -0.4f);
                        effect.DirectionalLight0.Enabled = true;
                        effect.DirectionalLight0.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);
                        effect.LightingEnabled = true;
                        effect.AmbientLightColor = new Vector3(0.2f, 0.2f, 0.2f);
                        effect.DiffuseColor = cube.Color.ToVector3();





Re: XNA Framework Poor XNA performance or my poor code... or normal?

dczraptor

Set your effect variables that don't change outside the draw method, then, change your draw method to something like this

public void Draw(GameTime gameTime)

{

graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

foreach (ModelMesh mesh in CubeMesh.Model.Meshes)
{

BasicEffect effect = (BasicEffect)model.Effects[0];

foreach (CubeMesh cube in captureZoneSystem.capturePoints)
{

effect.DiffuseColor = cube.Color.ToVector3();

effect.World = Matrix.CreateScale(cube.Scaling) *
Matrix.CreateTranslation(cube.Position) *
Matrix.CreateRotationX(MathHelper.ToRadians(captureZoneSystem.XRotation)) *
Matrix.CreateRotationY(MathHelper.ToRadians(captureZoneSystem.YRotation));

mesh.Draw();

}

}

}






Re: XNA Framework Poor XNA performance or my poor code... or normal?

Ceres629

Thanks for you advice so far, I have rewritten the draw code using your advice and my frame rate has doubled from 4FPS to 8FPS. Is there any particular reason why much more complex models like the enhanced mesh model in the directX sample browser has a fully textured model with over 500,000 faces and 300,000 vertices can render at 60fps yet 8000 textureless cubes render so much slower

I don't understand this :S here is my code so far.

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;

namespace WindowsGame1
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
CaptureZoneSystem captureZoneSystem;
ContentManager content;
Effect effect;
FirstPersonCamera camera;
float aspectRatio = 640.0f / 480.0f;
Model cubeModel;
Vector3 modelPosition = Vector3.Zero;
Vector3 cameraPosition = new Vector3(0.0f,10.0f,10.0f);
Matrix[] transforms;

public Game1()
{
graphics = new GraphicsDeviceManager(this);
content = new ContentManager(Services);

}

private void SetUpXNADevice()
{
graphics.PreferredBackBufferWidth = 640;
graphics.PreferredBackBufferHeight = 480;
graphics.IsFullScreen = false;
graphics.ApplyChanges();

Window.Title = "XNA Test";
IsMouseVisible = true;

}


private void SetUpCamera()
{
camera = new FirstPersonCamera(this);
camera.Initialize();
}


protected override void Initialize()
{
base.Initialize();
SetUpXNADevice();

captureZoneSystem = new CaptureZoneSystem(this, content);

SetUpCamera();
}


protected override void LoadGraphicsContent(bool loadAllContent)
{
if (loadAllContent)
{
CubeMesh.Model = content.Load<Model>("Content\\Models\\cube");
transforms = new Matrix[CubeMesh.Model.Bones.Count];
CubeMesh.Model.CopyAbsoluteBoneTransformsTo(transforms);

}

foreach (ModelMesh mesh in CubeMesh.Model.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.Projection = Matrix.CreatePerspectiveOffCenter(-1.0f * aspectRatio, 1.0f * aspectRatio, -1.0f, 1.0f, 1.0f, 1000.0f);

effect.DirectionalLight0.Direction = new Vector3(0.0f, -1.0f, -0.4f);
effect.DirectionalLight0.Enabled = true;
effect.DirectionalLight0.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);
effect.LightingEnabled = true;
effect.AmbientLightColor = new Vector3(0.2f, 0.2f, 0.2f);
}
}
// TODO: Load any ResourceManagementMode.Manual content
}


protected override void UnloadGraphicsContent(bool unloadAllContent)
{
if (unloadAllContent == true)
{
content.Unload();
}
}


protected override void Update(GameTime gameTime)
{
// Allows the default game to exit on Xbox 360 and Windows
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();

captureZoneSystem.Update(gameTime);
camera.Update(gameTime);

base.Update(gameTime);
}


protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

foreach (ModelMesh mesh in CubeMesh.Model.Meshes)
{
BasicEffect effect = (BasicEffect)mesh.Effects[0];
effect.View = camera.View;

foreach (CubeMesh cube in captureZoneSystem.capturePoints)
{
effect.DiffuseColor = cube.Color.ToVector3();

effect.World = transforms[mesh.ParentBone.Index] *
Matrix.CreateScale(cube.Scaling) *
Matrix.CreateTranslation(cube.Position) *
Matrix.CreateRotationX(MathHelper.ToRadians(captureZoneSystem.XRotation)) *
Matrix.CreateRotationY(MathHelper.ToRadians(captureZoneSystem.YRotation));

mesh.Draw();
}

}
}
}


}




Re: XNA Framework Poor XNA performance or my poor code... or normal?

dczraptor

I think it's partly because of all the calls to effect.World and effect.DiffuseColor in each frame (8000 * 2 = 1600 state changes), while a single high-poly model one sets these things twice. I've heard that changing those values are a bit of a drain on the graphics card.




Re: XNA Framework Poor XNA performance or my poor code... or normal?

Kris Nye

The ModelMesh.Draw() function is a convenience function. If you are writing an application where optimal performance is a goal. You shouldn't be using this method to perform your drawing.

The Draw function implementation looks something like this:

while (enumerator2.MoveNext())
{
ModelMeshPart part1 = enumerator2.Current;
Effect effect1 = part1.Effect;
if (effect1 == null)
{
throw new InvalidOperationException(Resources.ModelHasNoEffect);
}
effect1.Begin(saveStateMode);
try
{
using(IEnumerator<EffectPass> enumerator1 = effect1.CurrentTechnique.Passes.GetEnumerator())
{
while (enumerator1.MoveNext())
{
EffectPass pass1 = enumerator1.Current;
pass1.Begin();
part1.Draw();
pass1.End();
}
continue;
}
continue;
}
finally
{
effect1.End();
}
}


Now aside from the above mentioned Effect state changes that can be moved outside of the inner loop, the above Effect calls colored in red only need to be called once each in order to render all 1000 of your meshes, but since you're calling the draw method 1000 times, each of these methods is called 1000 times. That is almost certainly where the majority of your slowdown is coming from. You know you're models only have 1 effect, so begin the effect and pass outside of the inner loop, call the ModelMeshPart.Draw() method 1000 times (it's just a simple efficient call to the underlying Graphics object), then end the effect pass and end the effect.

Furthermore, if you want really efficient performance drawing identical models on windows, you need to use something called "Instancing" which is covered in direct3d tutorials, but I hear it doesn't improve performance much on Xbox360.




Re: XNA Framework Poor XNA performance or my poor code... or normal?

Ceres629

I'm a bit lost Kris, i'm very much a beginner I will have time later to really go back and learn the details of much of the code that i'm using, but for now I just need the code up and working so my knowledge is very basic and I don't quite understand the code you wrote..

Are you saying I can just replace the ModelMesh.Draw() function with the code that you listed above






Re: XNA Framework Poor XNA performance or my poor code... or normal?

Ceres629

I'm a bit lost Kris, i'm very much a beginner I will have time later to really go back and learn the details of much of the code that i'm using, but for now I just need the code up and working so my knowledge is very basic and I don't quite understand the code you wrote..

Are you saying I can just replace the ModelMesh.Draw() function with the code that you listed above

Oops double post.




Re: XNA Framework Poor XNA performance or my poor code... or normal?

Kyle_W

Good information from Kris. I haven't played with hardware instancing, but it does appear to be possible in XNA based on the fact that it is mentioned in the MDX to XNA Migration Guide:

http://msdn2.microsoft.com/en-us/xna/aa937797.aspx





Re: XNA Framework Poor XNA performance or my poor code... or normal?

Ceres629

Geometric instancing seems to be exactly what i need, but I cant find any XNA code implementing it.

From what i've seen so far it is far to advanced for me to attempt on my own.

It is a bit annoying that in order to draw the minimum 64000 cubes i need i only get like 1FPS, but outside of learning hardware instancing it doesn't seem anything can be done.




Re: XNA Framework Poor XNA performance or my poor code... or normal?

Kyle_W

Personally, I'm very annoyed that there is no way for me to write the next Half Life sequel in 5 lines of code or less.

Why on earth do you need 64000 cubes anyway





Re: XNA Framework Poor XNA performance or my poor code... or normal?

Ceres629

I need a lot of cubes because i'm basically using it as away to visualise some 3 dimensional data that I have.

I found some MDX code which draws (almost 100,000 cubes using hardware instancing and it runs at a decent 50FPS on my card which would be perfect). True hardware instancing doesn't seem as difficult as shader instancing, so i'm trying to find some XNA tutorials on that since i have a ps3.0 card.

Haven't had any luck though...




Re: XNA Framework Poor XNA performance or my poor code... or normal?

Kyle0654

BasicEffect effect = (BasicEffect)mesh.Effects[0];
effect.View = camera.View;

foreach (CubeMesh cube in captureZoneSystem.capturePoints)
{
effect.DiffuseColor = cube.Color.ToVector3();

effect.World = transforms[mesh.ParentBone.Index] *
Matrix.CreateScale(cube.Scaling) *
Matrix.CreateTranslation(cube.Position) *
Matrix.CreateRotationX(MathHelper.ToRadians(captureZoneSystem.XRotation)) *
Matrix.CreateRotationY(MathHelper.ToRadians(captureZoneSystem.YRotation));

mesh.Draw();
}

I'd say it looks like you're switching the effect and effect variables way too many times per frame, as well as not passing the GPU enough data to be truly effective. A cube is composed of 8 vertices. Modern GPUs focus on fast parallel processing of data - which means they can process tons of vertices/pixels all at the same time (well, virtually the same time). I think the Xbox 360's GPU has something like 48 pipelines (or maybe it's 3 pipelines with 16 stages each...or something like that). If you're only passing it 8 vertices per call though, it's not going to be able to perform like it should.

If you're just drawing a bunch of cubes, you might just want to construct them yourself, and use a VertexPositionColor format for the vertices (that way you don't have to change the color every time). Then you'll probably want to transform your vertices before sending them to the GPU. That way, you can lump all of your vertices into one call to the GPU, allowing it to effectively work its magic.

Of course, the method you're using may also need to be looked at if you're having performance problems. You're visualizing 3D data - which sounds like you're doing volume rendering, in which case there are several methods that are much more efficient than drawing a ton of cubes (though not necessarily fast). Do a search for "Volume Rendering" - might give you some ideas.





Re: XNA Framework Poor XNA performance or my poor code... or normal?

Shawn Hargreaves - MSFT

Drawing 8000 individual objects is never going to be fast. There is a relatively low limit on how many draw calls you can issue per frame: NVidia and ATI were both recommending high performance games aim for something around 100 to 200 the last time I checked.

This is entirely because of CPU overhead, and nothing to do with graphics card performance. So it doesn't make any difference how complicated those objects are. 8000 cubes will take exactly the same amount of time to render as 8000 highly detailed meshes with fancy lighting, because in both cases your bottleneck is the CPU having to issue that many draw calls, and the GPU will mostly be sitting idle.

The trick to good performance is to draw a few complex objects, rather than many simple ones. Hardware instancing can be one way to tackle this, but depending on your app it might be easier to look at packing many of your cubes into a vertex buffer on the fly (ie. programmatically constructing a mesh) so you can then draw the whole thing in a single batch.





Re: XNA Framework Poor XNA performance or my poor code... or normal?

soulmate75

I think it was Nitschke who said that using the for each construct is very slow and it should be replaced with a normal for loop to speed things up. I mean Ben of XNA Racer fame and no not the philosopher Nietzsche.

Have you tried that to see if it speeds things up