Unlike regular apps where we don't need to worry too much about performance, developing real-time games requires us to continuously test our game and make sure that it has good performance.
The most obvious parameter is the FPS value. Like everything in life it is a bit more complicated than it might seem.
I will start with explaining the meaning of FPS and what you should expect.
It is important to understand the following statements:
1. Games look great when they run at 60 fps.
2. Games look good or fine when they run at 30 fps.
3. Games look bad when they keep failing to run at the desired FPS. The user inputs will be ignored and the whole game will be unresponsive.
The X2 framework is designed based on these statements.
X2.TargetFPS sets the desired iterations per second. This is set to 60 by default (30 in debug mode).
This means that the game loop and the physics engine will run at 60 iterations per second.
Ideally the screen will also be updated 60 times per second.
The value shown at the bottom of the screen counts the number of frames ready to be drawn (BC_BitmapReady in X2Utils).
The conclusion from statements #2 and #3 is that it is better to quickly lower the FPS ourselves than to let the game fail to keep up with the desired pace.
If the measured FPS is smaller than TargetFPS - 5 then it will only create a new frame every second iteration. This means that the measured FPS will be halved. The game loop will still run at 60 loops per second.
GS.ShouldDraw will be False during the non-drawing iterations.
If you are creating a delegate class then you should avoid making drawings when GS.ShouldDraw is False as the drawing tasks will not be used.
It will try to return to TargetFPS after a while.
Explicitly setting the TargetFPS to 30 is not exactly the same as letting the FPS fall to 30 because the game loop continues to run at 60 FPS in the later case.
Some numbers
Based on my tests with the current examples:
- All examples run at 60 FPS on an old Nexus 5 from 2013 and all newer phones tested. Older devices (2010 - 2012) are not considered a relevant target for XUI2D.
- All examples run at 60 FPS on an old iPhone 5S and all newer phones tested.
- All examples run at 60 FPS on a Samsung SM-T580 10'' tablet.
You can see benchmark scores for the mentioned Android devices here: https://www.androidbenchmark.net/passmark_chart.html
Devices with score less than 2000 - 2500 are probably too weak for XUI2D.
- All examples run at 60 FPS on the four computers (Mac / PC) available here. Old netbooks are not considered a relevant target for XUI2D. XUI2D is multithreaded and will probably not run smooth on weak dual core PCs.
Tips
- Measure performance in release mode with debug drawing disabled.
- Test performance on all the relevant platforms. Different platforms behave differently.
- Always try to reuse graphics. Especially if the bodies can rotate. X2SpriteGraphicCache will create a new graphic for each rotation (based on the angle interval).
- Monitor the logs. It shows when new graphics are added to the cache.
- Pay attention to the number of bodies. If it keeps growing then you have a problem.
- Check 'destory if invisible' option (make sure not to create the body outside of the visible screen as it will be destroyed immediately).
- Uncheck 'tick if invisible'.
- BodyWrapper.IsInvisible tells you whether the body is invisible. You can use it to avoid drawing off-screen bodies.
- The simplest way to set the graphics is with 'graphic file x' in the map editor.
- Pay extra care when drawing with Canvas. See how it is done in the various examples.
- Prefer BitmapCreator over Canvas when you can.
- Once the drawing is ready you should extract a CompressedBC out of it and reuse it when possible.
- Never do something for x frames. Think in seconds or milliseconds instead.
- X2.TimeStepMs returns the duration of each step.
- New: You can add STATS to the build configuration symbols. It will add many log messages with the duration of the various tasks in the game loop.
- High resolution background images can have a large negative effect on the performance. Scale down the image:
In many cases the visual change will not be noticeable.
- If you see a significant slowdown when a body rotates for the first time then you should consider calling X2.GraphicCache.WarmGraphic. This is done in the Monster Truck example.
It prepares all the rotated frames instead of building them when needed.
It can have a negative effect if you use it too much as it can fill the cache.
- That's it for now. More tips will be added...
The most obvious parameter is the FPS value. Like everything in life it is a bit more complicated than it might seem.
I will start with explaining the meaning of FPS and what you should expect.
It is important to understand the following statements:
1. Games look great when they run at 60 fps.
2. Games look good or fine when they run at 30 fps.
3. Games look bad when they keep failing to run at the desired FPS. The user inputs will be ignored and the whole game will be unresponsive.
The X2 framework is designed based on these statements.
X2.TargetFPS sets the desired iterations per second. This is set to 60 by default (30 in debug mode).
This means that the game loop and the physics engine will run at 60 iterations per second.
Ideally the screen will also be updated 60 times per second.
The value shown at the bottom of the screen counts the number of frames ready to be drawn (BC_BitmapReady in X2Utils).
The conclusion from statements #2 and #3 is that it is better to quickly lower the FPS ourselves than to let the game fail to keep up with the desired pace.
If the measured FPS is smaller than TargetFPS - 5 then it will only create a new frame every second iteration. This means that the measured FPS will be halved. The game loop will still run at 60 loops per second.
GS.ShouldDraw will be False during the non-drawing iterations.
If you are creating a delegate class then you should avoid making drawings when GS.ShouldDraw is False as the drawing tasks will not be used.
B4X:
Public Sub Tick (GS As X2GameStep)
If GS.ShouldDraw Then
GS.DrawingTasks.Add(...))
End If
End Sub
It will try to return to TargetFPS after a while.
Explicitly setting the TargetFPS to 30 is not exactly the same as letting the FPS fall to 30 because the game loop continues to run at 60 FPS in the later case.
Some numbers
Based on my tests with the current examples:
- All examples run at 60 FPS on an old Nexus 5 from 2013 and all newer phones tested. Older devices (2010 - 2012) are not considered a relevant target for XUI2D.
- All examples run at 60 FPS on an old iPhone 5S and all newer phones tested.
- All examples run at 60 FPS on a Samsung SM-T580 10'' tablet.
You can see benchmark scores for the mentioned Android devices here: https://www.androidbenchmark.net/passmark_chart.html
Devices with score less than 2000 - 2500 are probably too weak for XUI2D.
- All examples run at 60 FPS on the four computers (Mac / PC) available here. Old netbooks are not considered a relevant target for XUI2D. XUI2D is multithreaded and will probably not run smooth on weak dual core PCs.
Tips
- Measure performance in release mode with debug drawing disabled.
- Test performance on all the relevant platforms. Different platforms behave differently.
- Always try to reuse graphics. Especially if the bodies can rotate. X2SpriteGraphicCache will create a new graphic for each rotation (based on the angle interval).
- Monitor the logs. It shows when new graphics are added to the cache.
- Pay attention to the number of bodies. If it keeps growing then you have a problem.
- Check 'destory if invisible' option (make sure not to create the body outside of the visible screen as it will be destroyed immediately).
- Uncheck 'tick if invisible'.
- BodyWrapper.IsInvisible tells you whether the body is invisible. You can use it to avoid drawing off-screen bodies.
- The simplest way to set the graphics is with 'graphic file x' in the map editor.
- Pay extra care when drawing with Canvas. See how it is done in the various examples.
- Prefer BitmapCreator over Canvas when you can.
- Once the drawing is ready you should extract a CompressedBC out of it and reuse it when possible.
- Never do something for x frames. Think in seconds or milliseconds instead.
- X2.TimeStepMs returns the duration of each step.
- New: You can add STATS to the build configuration symbols. It will add many log messages with the duration of the various tasks in the game loop.
- High resolution background images can have a large negative effect on the performance. Scale down the image:
B4X:
X2.SetBitmapWithFitOrFill(ivBackground, xui.LoadBitmapResize(File.DirAssets, "Sky.jpg", ivBackground.Width / 2, ivBackground.Height / 2, False))
- If you see a significant slowdown when a body rotates for the first time then you should consider calling X2.GraphicCache.WarmGraphic. This is done in the Monster Truck example.
It prepares all the rotated frames instead of building them when needed.
It can have a negative effect if you use it too much as it can fill the cache.
- That's it for now. More tips will be added...
Last edited: