Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Bullet Hell performance and optimization problems  (Read 24456 times)

0 Members and 3 Guests are viewing this topic.

SuperV1234

  • SFML Team
  • Full Member
  • *****
  • Posts: 190
    • View Profile
Bullet Hell performance and optimization problems
« on: June 13, 2010, 10:21:43 pm »
Full source code: http://www.mediafire.com/?wgimixyjtbt


---


I'm making a bullet hell game (danmaku) using SFML.NET and C#.
My source of inspiration is Danmakufu, a game where you can create your bullet patterns, share them, play them, etc.


In Danmakufu I can easily spawn 300 bullets every frame staying at 58-60FPS all the time.
In my own game, when I spawn 300 bullets every 3 frames, the game becomes unplayable due to slowdown.


The biggest difference between my game and Danmakufu is that my game's resolution is 1024x768, Danmakufu is 800x600. But can that make an enormous difference?


---


My code for bullets is this:


Code: [Select]
public class Entity
    {
        public float X { get; set; }
        public float Y { get; set; }
        public Game Game { get; set; }
        public bool Alive { get; set; }
        public Sprite Sprite { get; set; }


        public Entity(Game game)
        {
            Game = game;
            Alive = true;
        }


        public virtual void Update()
        {
            Sprite.Position = new Vector2(X, Y);
        }
        public virtual void Draw()
        {
            Game.RenderWindow.Draw(Sprite);
        }
        public virtual void Destroy()
        {
            Alive = false;
        }
    }
Entity is the basic class for any object in the game; you can draw and update entities.


Code: [Select]
public class Bullet : Entity
    {
        private float _angle;
        public float Angle
        {
            set { _angle = Data.ANormalize(Data.ARadians(value)); }
            get { return Data.ADegrees(_angle); }
        }
        public float Speed { get; set; }


        public Bullet(Game game) : base(game)
        {
            Game.BulletManager.Entities.Add(this);
        }


        public override void Draw()
        {
            base.Draw();
        }
        public override void Update()
        {
            base.Update();


            X = X + Speed * (float)Math.Cos(_angle);
            Y = Y + Speed * (float)Math.Sin(_angle);
            Sprite.Rotation = 360-Angle;


            if(Game.Playfield.IsInPlayfield(X, Y) == false)
            {
                Destroy();
            }
        }
        public override void Destroy()
        {
            base.Destroy();
 
            Game.BulletManager.Entities.Remove(this);        
        }
    }
The Bullet class inherits from Entity. When it is spawned, it is added in the Game.BulletManager.Entities list. When it is deleted, it is removed from the same list.
If you're curious about the stuff happening in the "Angle" property, here are the related methods:
Code: [Select]
public static float ARadians(float angle)
        {
            return angle * 3.14f / 180f;
        }
        public static float ADegrees(float angle)
        {
            return angle * 180f / 3.14f;
        }
        public static float ANormalize(float angle)
        {
            if (angle > 360)
            {
                angle = angle - (360 * Convert.ToInt32((angle / 360)));
            }


            if (angle < 0)
            {
                angle = angle + (360 * Convert.ToInt32((angle / 360)));
            }


            return angle;
        }
---


Here's the code for the Manager class and the Game class:


Code: [Select]
public class Manager
    {
        public List<Entity> Entities { get; set; }


        public Manager()
        {
            Entities = new List<Entity>();
        }


        public void Update()
        {
            for (int index = 0; index < Entities.Count; index++)
            {
                Entity entity = Entities[index];
                if (entity.Alive)
                {
                    entity.Update();
                }
            }
        }


        public void Draw()
        {
            for (int index = 0; index < Entities.Count; index++)
            {
                Entity entity = Entities[index];
                if (entity.Alive)
                {
                    entity.Draw();
                }
            }
        }
    }
Pretty straightforward. Updating a manager updates all of its entities; drawing a manager draws all of its entities.


Code: [Select]
public class Game
    {
        public bool Running { get; set; }
        public RenderWindow RenderWindow { get; set; }
        public Manager BulletManager { get; set; }
        public Playfield Playfield { get; set; }
        public int Frame { get; set; }


        //DEBUG---
        public int DebugFrame { get; set; }
        private int kk = 0;
        private int ff = 1;
        private bool kkb = true;
        Image image = new Image(Environment.CurrentDirectory + @"/Data/Images/b_pellet.png");
        //--------


        public Game()
        {
            Running = true;
            RenderWindow = new RenderWindow(new VideoMode(1024,768), "Bullet Hell Test");
            RenderWindow.UseVerticalSync(true);
            RenderWindow.Show(true);
            BulletManager = new Manager();
            Playfield = new Playfield(this, 250, 50, 774, 718) {Offset = 25};
            Frame = 0;
           
            Debug();


            Run();
        }


        public void Run()
        {
            while(Running)
            {
                Update();
                Draw();
            }
        }


        public void Update()
        {
            BulletManager.Update();


            if(DebugFrame == 10)
            {
                Debug();
                DebugFrame = 0;
            }


            DebugFrame++;
            Frame++;


            Console.WriteLine("{0} --- {1}", Convert.ToInt32(RenderWindow.GetFrameTime() * 1000), BulletManager.Entities.Count);
        }
        public void Draw()
        {
            RenderWindow.Clear(Color.White);
            Playfield.DrawInside();
            BulletManager.Draw();
            Playfield.DrawOutside();
            RenderWindow.Display();
        }


        //--------------------------------------------------------------------------------------------


        public Bullet CreateBullet()
        {
            return new Bullet(this);
        }


        //--------------------------------------------------------------------------------------------


        public void Debug()
        {
            float step = 300;    


            for(int i = 0; i < step; i++)
            {
                Bullet temp = new Bullet(this);
                   
                        temp.Angle = (i*(360/step));
                        temp.Speed = 5f;
                        temp.Sprite = new Sprite(image);
                        temp.X = Playfield.GetCenter().X;
                        temp.Y = Playfield.GetCenter().Y;
            }


            if (kk < 120)
            {
                kk = kk + 5;
            }
            else
            {
                kk = 120;
            }


            ff = ff + 22;
        }
    }
This one is pretty straightforward too, ignoring the debug variables that I used before to create cool-looking patterns.
Update calls the "Debug" method every 10 frames, which spawns bullets.
Bullets aren't spawned by recycling, but instead by creating "new" bullets. I've chosen this because I haven't noticed big performance improvements recycling bullets in the past.
Draw draws bullets and the playfield, which creates the usual "shoot 'em up" borders around the playing screen.


---


So, I've created this thread to kindly ask you what am I doing wrong; why can't my game spawn the same amount of bullets as Danmakufu without slowing down?


Is it a SFML.NET limitation?

---


I have also another problem:


Game.BulletManager.Entities.Remove(this); --- this line in the Bullet -> Destroy method makes a single bullet move in a strange way while uncommented.


I think this is because the for loops skips or makes an unnecessary additional cycle.
How can I prevent this?


---

Thanks.

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Bullet Hell performance and optimization problems
« Reply #1 on: June 13, 2010, 10:46:42 pm »
It's hard to tell from your real code. It would be easier if you could test a minimal application which simply draws as many sprites as in your game, to see if it's a performances problem. If it runs fine then there's probably something wrong, and you should try to find out which part of the code is guilty by commenting stuff, testing etc.
Laurent Gomila - SFML developer

SuperV1234

  • SFML Team
  • Full Member
  • *****
  • Posts: 190
    • View Profile
Bullet Hell performance and optimization problems
« Reply #2 on: June 13, 2010, 10:51:41 pm »
Before I do that, is there any effective way to understand if it's either the Draw or the Update loop which causes the biggest slowdown?

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Bullet Hell performance and optimization problems
« Reply #3 on: June 13, 2010, 10:52:30 pm »
Disable one, then the other, and compare performances.
Laurent Gomila - SFML developer

SuperV1234

  • SFML Team
  • Full Member
  • *****
  • Posts: 190
    • View Profile
Bullet Hell performance and optimization problems
« Reply #4 on: June 13, 2010, 11:03:21 pm »
Quote from: "Laurent"
Disable one, then the other, and compare performances.


If I disable Update then bullets won't be spawned, and even if I make it happen, they won't reach the end of the screen and be deleted.

I need some kind of timer that tells me how much time a method required to fully run. Is there anything like that?

SuperV1234

  • SFML Team
  • Full Member
  • *****
  • Posts: 190
    • View Profile
Bullet Hell performance and optimization problems
« Reply #5 on: June 13, 2010, 11:15:20 pm »
Nevermind, I've measured the time using TimeSpans and DateTimes.

Update method takes 1 ms.
Draw method takes 15~18 ms.

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Bullet Hell performance and optimization problems
« Reply #6 on: June 13, 2010, 11:19:21 pm »
I guess that Visual Studio has a good profiler for C# (it has one for C++). But maybe it is available only in the professional edition.

Anyway, if Draw is the guilty one, we're back to my first anwser: write a small app that only draws stuff, to see if it's related to your code or to your configuration.
Laurent Gomila - SFML developer

SuperV1234

  • SFML Team
  • Full Member
  • *****
  • Posts: 190
    • View Profile
Bullet Hell performance and optimization problems
« Reply #7 on: June 13, 2010, 11:48:32 pm »
Did a quick test: around 5000 8x8 sprites slow down the application.

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Bullet Hell performance and optimization problems
« Reply #8 on: June 14, 2010, 12:12:11 am »
What graphics card do you have? On which OS ? Are your drivers up-to-date?
Laurent Gomila - SFML developer

SuperV1234

  • SFML Team
  • Full Member
  • *****
  • Posts: 190
    • View Profile
Bullet Hell performance and optimization problems
« Reply #9 on: June 14, 2010, 09:55:31 am »
I have an nVidia GTX 275, my OS is Windows 7, my drivers are up-to-date.

Spodi

  • Full Member
  • ***
  • Posts: 150
    • View Profile
    • http://www.netgore.com/
Bullet Hell performance and optimization problems
« Reply #10 on: June 14, 2010, 10:39:46 am »
Running the attached program, I get a constant spit-out of about: "13 ---- 2000". Runs nice and smooth.

Core 2 Duo E6750
nVidia 8800 GTS 320 MB
Windows 7 (x86-64)

You could try updating to the latest version of SFML since it looks like you are using a build from 4/4/2010. What I namely have in mind is the commit made on 4/24, where Laurent added the calling conventions for the .NET bindings, which was really killing performance with native assembly calls in some rare cases due to some expensive exception checking stuff that could come with .NET (I forget the name of that all - prefer to wipe that bad experience from my memory).

Finally, you can also try building for release and/or running the .exe directly, not through Visual Studio, just in case the debugger is doing something funky.

SuperV1234

  • SFML Team
  • Full Member
  • *****
  • Posts: 190
    • View Profile
Bullet Hell performance and optimization problems
« Reply #11 on: June 14, 2010, 10:49:06 am »
Quote from: "Spodi"
Running the attached program, I get a constant spit-out of about: "13 ---- 2000". Runs nice and smooth.

Core 2 Duo E6750
nVidia 8800 GTS 320 MB
Windows 7 (x86-64)

You could try updating to the latest version of SFML since it looks like you are using a build from 4/4/2010. What I namely have in mind is the commit made on 4/24, where Laurent added the calling conventions for the .NET bindings, which was really killing performance with native assembly calls in some rare cases due to some expensive exception checking stuff that could come with .NET (I forget the name of that all - prefer to wipe that bad experience from my memory).

Finally, you can also try building for release and/or running the .exe directly, not through Visual Studio, just in case the debugger is doing something funky.


You mean SFML 2.0?

Anyway I'm not using the debugger since it decreases the performance a lot :)

SuperV1234

  • SFML Team
  • Full Member
  • *****
  • Posts: 190
    • View Profile
Bullet Hell performance and optimization problems
« Reply #12 on: June 14, 2010, 10:53:43 am »
Using a profiler I noticed that SFML.Graphic.Sprite instances do not get cleaned up. Running a program at 60FPS for a long time can make 60.000, 70.000 Sprite instances. How can I prevent that?

SuperV1234

  • SFML Team
  • Full Member
  • *****
  • Posts: 190
    • View Profile
Bullet Hell performance and optimization problems
« Reply #13 on: June 14, 2010, 02:46:25 pm »


Here is some useful profiler info.

The bullets are generated by this code, that is executed every frame.

Code: [Select]
public Bullet CreateBullet()
        {
            if (BulletManager.Recycle.Count > 0)
            {
                Bullet temp = (Bullet) BulletManager.Recycle[0];
                BulletManager.Recycle.RemoveAt(0);
                BulletManager.Entities.Add(temp);
                return temp;
            }

            return new Bullet(this);
        }

        public void Debug()
        {
            float step = 6;    

            for(int i = 0; i < step; i++)
            {
                for (int n = 1; n < 6; n++)
                {
                    Bullet temp = CreateBullet();

                    temp.Angle = (i * (360 / step)) + kk * n;
                    temp.Speed = 4f - (n / 2.5f);
                    temp.Sprite = new Sprite(image);
                    temp.X = Playfield.GetCenter().X;
                    temp.Y = Playfield.GetCenter().Y - 120;
                }
            }

            kk = kk + 22;
        }


Things I noticed from the profiler:
1) Recycling bullets works: the instance number of the bullets is always the same.
2) SFML Sprites instances keep growing and growing! Am I creating new instances? Isn't the garbage collector supposed to delete them?
3) Even if SFML Sprites keep growing, that's not the problem which causes low FPS, because I can spawn 5000 bullets at the same time, then stop spawning bullets, and it will still lag. However I want to look into this problem and fix it.

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Bullet Hell performance and optimization problems
« Reply #14 on: June 14, 2010, 02:54:21 pm »
Quote
Code: [Select]
return new Bullet(this);

Aren't you supposed to store this one in BulletManager.Entities?
Laurent Gomila - SFML developer