Adding Geometry to the World Performance Issues

Topics: Developer Forum, User Forum
Jul 12, 2012 at 9:28 AM
Edited Jul 12, 2012 at 9:29 AM

Straight to the point: I am having SEVERE performance issues just loading geometry into the World, granted that I have a lot of static geometry.  But I'm having performance issues just adding the player's geometry as well, and that is only a couple shapes on a body...

Here's the code that takes forever.  All it does is load in the level's vertices into edge shapes on seperate bodies:

 

        private void CreateEdges()
        {
            int totalVerts = 0;
            for (int bodyNum = 0; bodyNum < CompiledLevel.Bodies.Count; bodyNum++)
            {
                Console.Out.Write("bodyNum=" + bodyNum);
                Body retVal = BodyFactory.CreateBody(World);
                retVal.BodyType = BodyType.Static;
                retVal.UserData = this;
                List<CompiledPlanetlineLevel.BodyInfo> bodyVerts = CompiledLevel.Bodies[bodyNum];
                Console.Out.WriteLine(" bodyVerts.Count=" + bodyVerts.Count);
                totalVerts += bodyVerts.Count;
                for (int c = 0; c < bodyVerts.Count; c++)
                {
                    int endIndex = (c == bodyVerts.Count - 1 ? 0 : c + 1);
                    Fixture edgeFixture = FixtureFactory.AttachEdge(bodyVerts[c].edgeVert, bodyVerts[endIndex].edgeVert, retVal);
                    if (bodyVerts[c].grabbable)
                    {
                        FixtureGrabbable.Add(edgeFixture.FixtureId, bodyVerts[c].grabbable);
                    }
                }
                Bodies.Add(retVal);
            }
            Console.Out.WriteLine("totalVerts=" + totalVerts);
        }

This is what I get from the console, so you are aware of how many vertices there are:

bodyNum=1 bodyVerts.Count=49
bodyNum=2 bodyVerts.Count=20
bodyNum=3 bodyVerts.Count=45
bodyNum=4 bodyVerts.Count=6
bodyNum=5 bodyVerts.Count=18
bodyNum=6 bodyVerts.Count=18
bodyNum=7 bodyVerts.Count=449
bodyNum=8 bodyVerts.Count=12
bodyNum=9 bodyVerts.Count=20
bodyNum=10 bodyVerts.Count=4
bodyNum=11 bodyVerts.Count=10
bodyNum=12 bodyVerts.Count=10
bodyNum=13 bodyVerts.Count=16
bodyNum=14 bodyVerts.Count=32
bodyNum=15 bodyVerts.Count=138
bodyNum=16 bodyVerts.Count=19
bodyNum=17 bodyVerts.Count=10
bodyNum=18 bodyVerts.Count=27
bodyNum=19 bodyVerts.Count=14
bodyNum=20 bodyVerts.Count=26
bodyNum=21 bodyVerts.Count=275
bodyNum=22 bodyVerts.Count=19
bodyNum=23 bodyVerts.Count=34
bodyNum=24 bodyVerts.Count=11
bodyNum=25 bodyVerts.Count=16
bodyNum=26 bodyVerts.Count=48
bodyNum=27 bodyVerts.Count=48
totalVerts=1951
And here is the player's geometry that is taking FOREVER to load, not sure why. As you can see, all it is doing is creating a circle on a body and adding some sensors on it:

            geometry = BodyFactory.CreateCircle(World,
                PLAYER_H / 2, PLAYER_DENSITY, pos);
            geometry.BodyType = BodyType.Dynamic;
            geometry.IsBullet = true;
            geometry.FixedRotation = true;
            geometry.Friction = PLAYER_FRICTION;
            geometry.Restitution = PLAYER_RESTITUTION;
            geometry.IgnoreGravity = false;
            geometry.OnCollision += this.OnCollision;
            geometry.OnSeparation += this.OnSeparation;
            geometry.UserData = this;

            GroundSensor = FixtureFactory.AttachRectangle(GROUNDSENSOR_W, GROUNDSENSOR_H, 0.0f,
                new Vector2(0.0f, PLAYER_H / 2), geometry);
            GroundSensor.IsSensor = true;
            GroundSensor.OnCollision += this.OnGroundSensorCollision;
            GroundSensor.OnSeparation += this.OnGroundSensorSeparation;
            GroundSensor.UserData = geometry;

            LeftWallSensor = FixtureFactory.AttachRectangle(WALLSENSOR_W, WALLSENSOR_H, 0.0f,
                new Vector2(-PLAYER_W / 2, -PLAYER_H / 4), geometry);
            LeftWallSensor.IsSensor = true;
            LeftWallSensor.OnCollision += this.OnLeftWallSensorCollision;
            LeftWallSensor.OnSeparation += this.OnLeftWallSensorSeparation;
            RightWallSensor = FixtureFactory.AttachRectangle(WALLSENSOR_W, WALLSENSOR_H, 0.0f,
                new Vector2(PLAYER_W / 2, -PLAYER_H / 4), geometry);
            RightWallSensor.IsSensor = true;
            RightWallSensor.OnCollision += this.OnRightWallSensorCollision;
            RightWallSensor.OnSeparation += this.OnRightWallSensorSeparation;
            LeftWallGrabber = FixtureFactory.AttachRectangle(WALLGRABBER_W, WALLGRABBER_H, 0.0f,
                new Vector2((-PLAYER_W / 2), (-PLAYER_H / 2) - (WALLGRABBER_H / 2)), geometry);
            LeftWallGrabber.IsSensor = true;
            LeftWallGrabber.OnCollision += this.OnLeftWallGrabberCollision;
            LeftWallGrabber.OnSeparation += this.OnLeftWallGrabberSeparation;
            RightWallGrabber = FixtureFactory.AttachRectangle(WALLGRABBER_W, WALLGRABBER_H, 0.0f,
                new Vector2((PLAYER_W / 2), (-PLAYER_H / 2) - (WALLGRABBER_H / 2)), geometry);
            RightWallGrabber.IsSensor = true;
            RightWallGrabber.OnCollision += this.OnRightWallGrabberCollision;
            RightWallGrabber.OnSeparation += this.OnRightWallGrabberSeparation;


            LeftGroundSensor = FixtureFactory.AttachRectangle(WALLGRABBER_W, WALLGRABBER_H, 0.0f,
                new Vector2((-PLAYER_W / 2), (PLAYER_H / 2) + (WALLGRABBER_H / 2)), geometry);
            LeftGroundSensor.IsSensor = true;
            LeftGroundSensor.OnCollision += this.OnLeftGroundSensorCollision;
            LeftGroundSensor.OnSeparation += this.OnLeftGroundSensorSeparation;
            RightGroundSensor = FixtureFactory.AttachRectangle(WALLGRABBER_W, WALLGRABBER_H, 0.0f,
                new Vector2((PLAYER_W / 2), (PLAYER_H / 2) + (WALLGRABBER_H / 2)), geometry);
            RightGroundSensor.IsSensor = true;
            RightGroundSensor.OnCollision += this.OnRightGroundSensorCollision;
            RightGroundSensor.OnSeparation += this.OnRightGroundSensorSeparation;
Can anybody tell me what I'm doing wrong? Why does it take so long to load the geometry of the level and even the player?
Jul 12, 2012 at 9:30 AM
Edited Jul 12, 2012 at 9:34 AM

Also sorry about the messed up formatting, this forum's code snippet thing is NOT working for me...

 

Also just to clarify, the performance is FINE on PC, but on XBOX 360 it is crazy slow!

Jul 12, 2012 at 2:14 PM

Are all your static bodies created at (0,0)? They are basically overlapping...

Also could be a related issue - in Box2D you specify the position in the BodyDef so when the Body is actually inserted into the world it already has it's world transform set. Farseer went with a different pattern - allowing you to first create the Body (at 0,0) then translate it with Body.Position. This is a big no-no in Box2D - the argument is that it can overload the QuadTree and make it very unbalanced.

Try to profile the code and see where's the hold-up actually. I'll be very interested in the results.

Jul 12, 2012 at 5:02 PM

Wow thanks for the quick insight, jerry!

Yeah I had no clue about positioning of bodies and how that affects the internals at all.  Currently I don't have access to a version of visual studio with profiling, but I was planning on doing that as soon as I have access to one in the next day or so.  I'll update this thread as soon as I have more info.

Jul 12, 2012 at 11:12 PM

Okay I just made some changes like you suggested, and I am now assigning the static bodies in my CreateEdges() function to have a position in their constructor.  It did actually speed things up a bit, but not dramatically so.  It went from loading in about 6 seconds to about 4-5.  One thing I noticed though is that the player's geometry, which gets loaded after the level's, loads somewhat faster now.  Is this expected behavior as I am assuming?  And is it possible to get any better performance out of this?  5 seconds seems like a long time to load a level to be honest, although it isn't game breaking... Keeping in mind that this testing is being done on my xbox 360.  On PC it's almost instant load time, but who cares about that.

Jul 13, 2012 at 1:40 PM

I'm afraid you have to run a profiler on the device. I know xbox has some issues with garbage collection. Perhaps there is a lot of garbage created during your startup. It could be on the user level or on the library level (I'd still keep an eye on the broadphase).

Jul 22, 2012 at 5:46 PM

Sorry I still haven't had a chance to run the code through a profiler, but I have more information.  I've managed to improve performance by adding another step in the process on top of my previous addition of assigning body positions to the level geometry's body constructors.

Now what I'm doing, is every Nth vertex I am running a world.step(0.06f); call in the middle of the CreateEdges() procedure.  I set N to be 6, and that seems like a good number.  The reason I am doing this is because before, I had massive amounts of lag immediately following the games loading functions (while it was running for the first few seconds), and I found that if I ran the world.step function a bunch of times after the level loaded all of its static geometry (about 215 times, experimentally obtained), then there would be no lag spike when I started running the game.  Any insight on why this happens would be nice, as I am curious as to why this happens.

Aside from this however, I think I have now solved my performance issues.  All my levels are loading either instantaneously, or in less than 2 seconds, which seems very nice to me.  Thanks for the help, jerry!

Jul 22, 2012 at 8:07 PM

World.Step() probably helps because it cleans the broadphase somehow.

Just setting the Position will call SetTransform() which will first call SetTransformIgnoreContacts() then ContactManager.FindNewContacts(). If the contact manager is slow (or producing garbage) then it will be the bottleneck. I've seen posts claiming that CCD produces garbage and CCD is enabled for static bodies. There is also warm starting to consider... However my understanding of the internals is at it's limit at this point so take all I wrote with a grain of salt :)

You can test by calling SetTransformIgnoreContacts() instead of setting Body.Position.

Coordinator
Aug 7, 2012 at 2:52 AM

There are several issues here.

1. The amount of fixtures that you are adding to the simulation at the same time, will make the DynamicTree broadphase go a bit crazy. See the Tiles test inside the Testbed project for more info on this.

2. Setting properties to their default value will actually sometimes have a negative effect on performance

Example: retVal.BodyType = BodyType.Static;

Sometimes the engine does a lot of work to change the properties, even if the properties are set to their default value.

3. You will make the garbage collector go crazy once on every collection if all your objects have references to each other.

example: retVal.UserData = this;

I would recommend that you only use the UserData property when you really need it. If you need more information on how to optimize your game. See some of the many articles I have written on the subject.