Xbox 360 performance with ConvexHull

Discuss any questions about BEPUphysics or problems encountered.
Post Reply
indiefreaks
Posts: 8
Joined: Fri Aug 19, 2011 10:30 pm

Xbox 360 performance with ConvexHull

Post by indiefreaks »

Hi,

I'm currently working on Ace on Steroids, an Astreroids clone using the Indiefreaks Game Framework which goals aims to create a set of tutorials explaining how to make a game from scratch with the framework, its SunBurn engine core component and obviously BEPUPhysics.
You can see the current stage of development in my video feed here: http://www.youtube.com/user/IndiefreakS ... ture=guide

As you can see everything works very nicely and I get a nice fixed 30 fps on Windows however, when porting the project to Xbox 360, I'm getting some really laggy behaviors when an asteroid gets destroyed. I've read the forums and the documentation to get it all optimized for Xbox 360 but I'm still meeting some horrible lags which I know why they are caused: ConvexHull broad and narrow phases.

I tried defining different settings on the overall simulation with no success:
1. I set multithreading as recommended:

Code: Select all

            physics.Space.ThreadManager.AddThread(o => System.Threading.Thread.CurrentThread.SetProcessorAffinity(1), null);
            physics.Space.ThreadManager.AddThread(o => System.Threading.Thread.CurrentThread.SetProcessorAffinity(3), null);
            physics.Space.ThreadManager.AddThread(o => System.Threading.Thread.CurrentThread.SetProcessorAffinity(5), null);
2. I set internal time stepping:

Code: Select all

            physics.UseInternalTimeStepping = true;
3. I applied the lowest settings:

Code: Select all

            SolverSettings.DefaultMinimumIterations = 0;
            physics.Space.Solver.IterationLimit = 5;
            GeneralConvexPairTester.UseSimplexCaching = false;
But I'm still getting lags which leads me to think that I won't be able to use ConvexHull for my 3 game entities: PlayerSpaceShip, EnemySpaceShip & Asteroid. Bullets use a simple Box entity.
I wish I could since they bring the closest to reality collisions.

My current ideas to solve this situation are:
A. Recreate simpler models for my ConvexHull instances with the hope of getting significant better results in exchange of lowering the collision realism.
B. Use compound bodies made of several boxes to be as close as possible to the rendered shapes
C. Use even simpler entities such as Box entities but this one would be ultimatelly applied as it would be less than realistic :(

Hopefully, I can get it done with A. but I would like to get your advice and see if I didn't miss any opportunity to get ConvexHull working lagless.

For a better understanding, here is the code I use to create the Physics instance which actually is a TransformableShape as I wanted to share a static ConvexHull instance for all my asteroids.

Code: Select all

 /// <summary>
        /// Creates a new instance
        /// </summary>
        /// <param name="collisionObject">The ParentObject this instance will be associated with</param>
        /// <param name="model">The Model used to compute the ConvexHull shape used for collision & physics</param>
        /// <remarks>If a previously created instance uses the same Model as the one provided here, it will reuse the computed ConvexHull instance to save CPU</remarks>
        public ConvexHullCollisionMove(ICollisionObject collisionObject, Model model) : base(collisionObject)
        {
            ModelReferencesToDelete.Clear();

            foreach (var modelConvexHull in ModelConvexHulls)
            {
                if (!modelConvexHull.Key.IsAlive)
                {
                    ModelReferencesToDelete.Add(modelConvexHull.Key);
                }
                else if (modelConvexHull.Key.Target == model)
                {
                    ConvexHull convexHull = modelConvexHull.Value;
                    
                    TransformableShape shape = new TransformableShape(convexHull.CollisionInformation.Shape, Matrix3X3.CreateFromMatrix(ParentObject.World));

                    SpaceObject = new Entity<ConvexCollidable<TransformableShape>>(
                        new ConvexCollidable<TransformableShape>(shape),
                        ParentObject.Mass,
                        convexHull.LocalInertiaTensor,
                        convexHull.Volume);
                    Entity.Position = ParentObject.World.Translation;
                }
            }

            foreach (var modelReference in ModelReferencesToDelete)
            {
                ModelConvexHulls.Remove(modelReference);
            }

            if (SpaceObject == null)
            {
                Vector3[] vertices;
                int[] indices;

                TriangleMesh.GetVerticesAndIndicesFromModel(model, out vertices, out indices);

                var modelReference = new WeakReference(model);
                var convexHull = new ConvexHull(vertices, ParentObject.Mass);

                ModelConvexHulls.Add(modelReference, convexHull);

                TransformableShape shape = new TransformableShape(convexHull.CollisionInformation.Shape, Matrix3X3.CreateFromMatrix(ParentObject.World));

                SpaceObject = new Entity<ConvexCollidable<TransformableShape>>(
                    new ConvexCollidable<TransformableShape>(shape),
                    ParentObject.Mass,
                    convexHull.LocalInertiaTensor,
                    convexHull.Volume);
                Entity.Position = ParentObject.World.Translation;
            }

            ParentObject.World.GetScaleComponent(out CollisionObjectScale);
        }
Norbo
Site Admin
Posts: 4929
Joined: Tue Jul 04, 2006 4:45 am

Re: Xbox 360 performance with ConvexHull

Post by Norbo »

As you can see everything works very nicely and I get a nice fixed 30 fps on Windows however, when porting the project to Xbox 360, I'm getting some really laggy behaviors when an asteroid gets destroyed. I've read the forums and the documentation to get it all optimized for Xbox 360 but I'm still meeting some horrible lags which I know why they are caused: ConvexHull broad and narrow phases.
By the looks of the videos, it doesn't seem like the broad or narrow phase should be an issue, even on the Xbox360. It looks like the big asteroids spawn around 5 subasteroids when they die. In the worst case, that would be around 14 very brief collision pairs. For very complicated shapes (hundreds or thousands of vertices) on the Xbox360, that could cause a few dropped frames, but not much more than that. Having fewer vertices in the convex hull would certainly help reduce this cost, but I don't think it's the main issue.

I would guess a much larger amount of time is spent initializing the entities- assuming, of course, that they are being made on the fly, not at load time. Initializing a complicated convex hull can be expensive, especially on the Xbox360.

The inertia tensor generated by the convex hull won't match the transformable shape for any non-identity transform. Computing the inertia tensor and volume is expensive; since it's already known to be inaccurate, it could be faked entirely. A custom inertia tensor could be made directly and it would likely behave just fine. Since they're all fairly round, you could probably get away with a sphere's inertia tensor. A sphere's inertia tensor happens to be very simple: the diagonal elements are (2/5) * mass * radius * radius, all other elements are zero. Since it's already an approximation, approximating the radius too won't be much of a problem. Volume is only used by certain systems like buoyancy. Unless there's a need for it in your simulation, you could safely just set it to 1 or some other arbitrary number.

By doing the above, a ConvexHull entity is no longer needed. Only a ConvexHullShape needs to be created. Loading all the possible ConvexHullShapes up front would prevent runtime hitches. The creation of a transformable shape and an entity from it will be very cheap so long as the faked inertia and volume are supplied.
2. I set internal time stepping:
Code:
physics.UseInternalTimeStepping = true;
This can actually cause a significant slowdown if there's a performance spike (like multiple complicated entities being initialized). It might try to get through 2-3 time steps per update to get back to 'real time,' tripling the cost of physics and causing FPS to plummet temporarily.

If you don't need it on for other reasons (such as staying in approximate sync with network peers or just desiring update rate independence), turning it off might make it a lot more tolerable. Instead of crunchy hitch, it might turn into a few slower frames. The initial cause might still cause a crunchy hitch though, so it's best to fix the cause if possible.
3. I applied the lowest settings:
Code:
SolverSettings.DefaultMinimumIterations = 0;
physics.Space.Solver.IterationLimit = 5;
GeneralConvexPairTester.UseSimplexCaching = false;
Technically, GeneralConvexPairTester.UseSimplexCaching = true is faster than false, but it won't matter. The gain is small in normal situations where there are persistent contacts. It will be entirely unnoticeable in a simulation with a bunch of brief impacts.
My current ideas to solve this situation are:
A. Recreate simpler models for my ConvexHull instances with the hope of getting significant better results in exchange of lowering the collision realism.
B. Use compound bodies made of several boxes to be as close as possible to the rendered shapes
C. Use even simpler entities such as Box entities but this one would be ultimatelly applied as it would be less than realistic

Hopefully, I can get it done with A. but I would like to get your advice and see if I didn't miss any opportunity to get ConvexHull working lagless.
If collision handling is truly the source of the slowdown, then the convex hulls are likely too complicated and Option A would solve that problem. A reasonable single ConvexHull will be faster than a compound object of simple primitives. A single box would obviously be faster still, but it shouldn't be necessary. The Xbox360 is slow, but not that slow. A bunch of objects floating around in space and rarely interacting is one of the cheapest possible simulations; even the Xbox360 should be able to handle hundreds of objects relatively quickly (although the size of the playfield might make it a bit too crowded for that to be feasible :)).
indiefreaks
Posts: 8
Joined: Fri Aug 19, 2011 10:30 pm

Re: Xbox 360 performance with ConvexHull

Post by indiefreaks »

I would guess a much larger amount of time is spent initializing the entities- assuming, of course, that they are being made on the fly, not at load time. Initializing a complicated convex hull can be expensive, especially on the Xbox360.

The inertia tensor generated by the convex hull won't match the transformable shape for any non-identity transform. Computing the inertia tensor and volume is expensive; since it's already known to be inaccurate, it could be faked entirely. A custom inertia tensor could be made directly and it would likely behave just fine. Since they're all fairly round, you could probably get away with a sphere's inertia tensor. A sphere's inertia tensor happens to be very simple: the diagonal elements are (2/5) * mass * radius * radius, all other elements are zero. Since it's already an approximation, approximating the radius too won't be much of a problem. Volume is only used by certain systems like buoyancy. Unless there's a need for it in your simulation, you could safely just set it to 1 or some other arbitrary number.
So, if I got it right, what you suggest here would be to create as many inertia tensors and volumes that are required in the game during the loading sequence (in my case, for the asteroids, there would be only 3: one for each asteroid size/mass) and then, when creating a ConvexHullShape, pass the relevant ones in the constructor. Am I right?

So my new Entity should be created using:
new Entity<ConvexCollidable<TransformableShape>()
and passing the cached ConvexHullShape, a Matrix containing the LocalInteriaTensor value and the float Volume, right?
indiefreaks
Posts: 8
Joined: Fri Aug 19, 2011 10:30 pm

Re: Xbox 360 performance with ConvexHull

Post by indiefreaks »

I worked on the A. way: getting a lower vertice count on the model used to build the ConvexHullShape & using a cached ConvexHullShape, LocalInertiaTensor and Volume that I apply to a new Entity<ConvexCollidable<TransformableShape>> instance.
I may do something wrong as I still get the initialization issues but this time in a worse manner: the game hangs until the entity is created while before, the instance was created almost instantly but the lag was occuring on multiple frames.

This brings me to think that it isn't the creation of the Entity that generate the lag initially since the creation of the entity was created and then, on the following frames, the framerate goes down significantly.

However, I'll post here the code I use to create the asteroid to see if you can find anything I'm doing wrong:

In the AsteroidFactory class used to create asteroids and use them from a pool:

Code: Select all

        public void LoadContent(IContentCatalogue catalogue, ContentManager manager)
        {
            RenderModel = manager.Load<Model>("Models/asteroid.model");
            _collisionModel = manager.Load<Model>("Models/asteroidCollision");

            // the ConvexHullCollisionMove class is the class which wraps a Entity<ConvexCollidable<TransformableShape>> instance
            // the BuildConvexHullShapeFromModel(Model model) is a static helper method that gets vertices from the passed model and create a ConvexHullShape instance using BEPUPhysics helper classes.
            _asteroidShape = ConvexHullCollisionMove.BuildConvexHullShapeFromModel(_collisionModel);
        }

        private static void Create(AsteroidSize asteroidSize, Vector3 position, Vector3 direction, Vector3 rotationForce)
        {
            // I create or pool a new asteroid instance
            Asteroid asteroid = _asteroidsPool.New();
            asteroid.AsteroidSize = asteroidSize;

            // I set a few SunBurn SceneObject properties
            asteroid.UpdateType = UpdateType.Automatic;
            asteroid.CollisionType = CollisionType.Collide;
            asteroid.Visibility = ObjectVisibility.RenderedAndCastShadows;

            // This is an IGF component which provides Session related information and allows me to set a few behaviors (not important in this case)
            // It actually forces the asteroid to always have its position Y set to 0f & simulate a wrap screen by changing the asteroid position to the other side of the screen when reaching its borders
            asteroid.Components.Add(SessionManager.CurrentSession.CreateNonPlayerAgent<AsteroidAgent>());

            // depending on the size of the asteroid to create, I set a few properties
            float scaleFactor = 1;
            float sphereInertiaTensor = 0f;
            switch (asteroidSize)
            {
                case AsteroidSize.Big:
                    scaleFactor = Asteroid.BigScale;
                    asteroid.Mass = Asteroid.BigMass;
                    break;
                case AsteroidSize.Medium:
                    scaleFactor = Asteroid.MediumScale;
                    asteroid.Mass = Asteroid.MediumMass;
                    break;
                case AsteroidSize.Small:
                    scaleFactor = Asteroid.SmallScale;
                    asteroid.Mass = Asteroid.SmallMass;
                    break;
            }

            // and here I create the inertia tensor that will be passed to the entity using your suggested method
            sphereInertiaTensor = (2/5)*asteroid.Mass*asteroid.WorldBoundingSphere.Radius*asteroid.WorldBoundingSphere.Radius;
            var asteroidInertiaTensor = new Matrix3X3(
                sphereInertiaTensor, 0f, 0f,
                0f, sphereInertiaTensor, 0f,
                0f, 0f, sphereInertiaTensor);

            // I test if the asteroid instance already has an Entity, if not I create a new one, otherwise, I simply set the new properties to it.
            // note that the ConvexHullCollisionMove class automatically retrieves the Mass property from the asteroid instance it is attached to.
            if (asteroid.CollisionMove == null || asteroid.CollisionMove.GetType() != typeof (ConvexHullCollisionMove))
            {
                // I create the new Entity instance using the cached values and set a few Entity parameters
                asteroid.CollisionMove = new ConvexHullCollisionMove(asteroid, _asteroidShape, asteroidInertiaTensor, 1f)
                                             {
                                                 Scale = new Vector3(scaleFactor),
                                                 Entity =
                                                     {
                                                         AngularDamping = 0f,
                                                         LinearDamping = 0f,
                                                         Material =
                                                             {
                                                                 Bounciness = 1f,
                                                                 KineticFriction = 0.5f
                                                             },
                                                     }
                                             };
            }
            else
            {
                // I just change the Scale property value to apply the right transform to the Entity in case the asteroid instance is coming from the Pool and the 
                ((ConvexHullCollisionMove) asteroid.CollisionMove).Scale = new Vector3(scaleFactor);
            }

            asteroid.World = Matrix.CreateScale(scaleFactor)*Matrix.CreateTranslation(position);

            asteroid.CollisionMove.ApplyObjectForce(direction);
            ((ConvexHullCollisionMove) asteroid.CollisionMove).Entity.ApplyAngularImpulse(ref rotationForce);

            Application.SunBurn.ObjectManager.Submit(asteroid);
            ActiveAsteroids.Add(asteroid);
        }
Now, the ConvexHullCollisionMove class is set the following way:

Code: Select all

using System;
using BEPUphysics.Collidables.MobileCollidables;
using BEPUphysics.CollisionShapes.ConvexShapes;
using BEPUphysics.DataStructures;
using BEPUphysics.Entities;
using BEPUphysics.MathExtensions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using SynapseGaming.LightingSystem.Collision;

namespace Indiefreaks.Xna.Physics.Entities
{
    /// <summary>
    /// ConvexHull based BEPUEntityCollisionMove class implementation
    /// </summary>
    public class ConvexHullCollisionMove : BEPUEntityCollisionMove<Entity<ConvexCollidable<TransformableShape>>, Vector3>
    {
        // This constructor creates an Entity using the provided model (no caching)
        public ConvexHullCollisionMove(ICollisionObject collisionObject, Model model) : base(collisionObject)
        {
            var shape = new TransformableShape(BuildConvexHullShapeFromModel(model), Matrix3X3.CreateFromMatrix(ParentObject.World));

            SpaceObject = new Entity<ConvexCollidable<TransformableShape>>(
                new ConvexCollidable<TransformableShape>(shape),
                ParentObject.Mass);
            Entity.Position = ParentObject.World.Translation;

            ParentObject.World.GetScaleComponent(out CollisionObjectScale);
        }

        // This constructor uses cached values submitted to the Entity
        public ConvexHullCollisionMove(ICollisionObject collisionObject, ConvexHullShape shape, Matrix3X3 localInertiaTensor, float volume) : base(collisionObject)
        {
            var transformableShape = new TransformableShape(shape, Matrix3X3.CreateFromMatrix(ParentObject.World));

            SpaceObject = new Entity<ConvexCollidable<TransformableShape>>(
                new ConvexCollidable<TransformableShape>(transformableShape),
                ParentObject.Mass,
                localInertiaTensor,
                volume);
            Entity.Position = ParentObject.World.Translation;

            ParentObject.World.GetScaleComponent(out CollisionObjectScale);
        }

        protected override void OnCollisionObjectScaleChanged()
        {
            Entity.CollisionInformation.Shape.Transform = Matrix3X3.CreateFromMatrix(Matrix.CreateScale(CollisionObjectScale)*CollisionObjectWorldTransform);
        }

        /// <summary>
        /// Applies the current ISpaceObject World matrix changes to the ParentObject.
        /// </summary>
        public override void End()
        {
            ParentObject.World = Matrix.CreateScale(CollisionObjectScale)*Entity.WorldTransform;
        }

        public static ConvexHullShape BuildConvexHullShapeFromModel(Model model)
        {
            Vector3[] vertices;
            int[] indices;

            TriangleMesh.GetVerticesAndIndicesFromModel(model, out vertices, out indices);

            return new ConvexHullShape(vertices);
        }
    }
}
And finally, the above End() method is preceeded by an obvious Begin() which is executed before Space.Update() is called so that if the attached SceneObject World transform is changed by the game, the Entity gets properly notified. The Begin() method is implemented one step above in the class hierarchy: on the BEPUEntityCollisionMove<TEntity> class:

Code: Select all

        /// <summary>
        /// Applies eventual ParentObject collision information changes to the current ISpaceObject instance.
        /// </summary>
        public override void Begin()
        {
            if (ParentObject.CollisionType != _collisionType)
            {
                _collisionType = ParentObject.CollisionType;

                switch (_collisionType)
                {
                    case CollisionType.Collide:
                        {
                            Entity.CollisionInformation.CollisionRules.Personal = CollisionRule.Normal;
                            break;
                        }
                    case CollisionType.Trigger:
                    case CollisionType.None:
                    default:
                        {
                            Entity.CollisionInformation.CollisionRules.Personal = CollisionRule.NoSolver;
                            break;
                        }
                }
            }

            if (ParentObject.Mass != Entity.Mass)
                Entity.Mass = ParentObject.Mass;

            if (ParentObject.AffectedByGravity != Entity.IsAffectedByGravity)
                Entity.IsAffectedByGravity = ParentObject.AffectedByGravity;

            ParentObject.World.SRTMatrixToRTMatrix(out CollisionObjectWorldTransform);
            
            Entity.WorldTransform = CollisionObjectWorldTransform;

            if (_objectForce != Vector3.Zero)
            {
                Entity.ApplyLinearImpulse(ref _objectForce);
                _objectForce = Vector3.Zero;
            }

            if (_worldForce != Vector3.Zero)
            {
                Entity.LinearMomentum += _worldForce;
            }
        }
Thanks
indiefreaks
Posts: 8
Joined: Fri Aug 19, 2011 10:30 pm

Re: Xbox 360 performance with ConvexHull

Post by indiefreaks »

Arghhhhhhh! I'm so so so upset against myself.

Since I was getting a new behavior, I decided to revert my changes back to where I was when we started this thread and rework solely on the lower vertices count model for my asteroid.
I was still getting this weird lag and therefore looked at what was going on. Then I noticed that small line of code where I was adding the Entity to my BEPUPhysicsRenderer (wraps the BEPUPHysicsDrawer project most of)...
I therefore removed that line and Tadaaaa! No more huge lag (just a small one that is due to the Entity<ConvexCollidable<TransformableShape>> instance initialization)!

I looked at the code of the InstancedModelDrawer class to see that each time you add an entity to it, it just creates a new set of indices and vertices out of it... That was making the lag!

Thanks anyway Norbo for your help. I'll see if I can get this smaller lag out (just 1 or 2 frames but I like it to be perfect :p).
Post Reply