SpriteBatch drawing using a Vector2 rotation for optimized drawing

Looking at the source for SpriteBatch.Draw() methods, I see that Math.Sin & Math.Cos are called for every draw, even if rotation is zero! This seems like a major performance waste when drawing large quantities of sprites.

My first suggestion of all there should be a simple !=0 check in here for skipping Sin & Cos when value is zero, which it is a lot of the time for particles, 2D games objects, interface, etc.

Also, a lot of the time I find myself having the rotation in a much cheaper Vector2 format (calculation wise), and have to calculate the float rotation just to draw a sprite, thus making these expensive completely unnecessary Math functions twice!

My second suggestion is to add an additional override to the SpriteBatch.Draw() method that takes in a Vector2 rotation instead, this means the DrawInternal() must also be updated.

Any thoughts? I’m surprised this hasn’t been caught already. Is there any way to implement this myself and get it added to the monogame framework?

Can you clarify what you mean by rotation in a Vector2?

Performance improvements are always welcome. Code submissions are done through the GitHub pull request process.

SpriteBatch.Draw() always calls SpriteBatch.DrawInternal() internally, which always takes in a float rotation value (radians) and supplies this to the SpriteBatchItem.Set(…float sin, float cos) method. Thus the rotation value needs to run through Math.Sin & Math.Cos.

A lot of the time when calculating rotations for items, it’s a lot cheaper to just have rotations set using a vector (ie. sin & cos values in a Vector2), and for these situations it would be quite a performance improvement to be able to supply them directly to the SpriteBatch.Draw() to first avoid having to convert my own Vector2 values into a radian rotation (using Atan2) and then internally converting this rotation back using Sin & Cos.

i.e. the improvement would be to loose a Sin & Cos call for each drawn sprite, and a lot of time one Atan2.

I will try make this improvement myself and try to figure out the pull request system and submit it. I’m a bit new to github so it’s high time I learn to use it in case I find anything more to fix later.

edit:
Followup question, how much am I allowed to change within the mono framework in my pull request without disrupting the “closely resemble XNA as possible” project guideline?
I would need change/add overload to SpriteBatch.DrawInternal and add an additional overload for SpriteBatch.Draw() which makes use of this optimization.

To be honest, I have never seen a public API take sin and cos values separately for a single rotation. I can understand the potential performance improvements, but we also need to be careful of confusing the user with an obscure set of parameters in a public API.

Changes to public APIs are not permitted except for exceptional circumstances. Adding public APIs amd changes to internal APIs will need to be reviewed closely We would also need test cases added to verify functionality.

I would suggest implementing and submitting the first idea (skip calculating sin and cos if rotation is zero) before beginning to tackle the next step.

Ok I finally got around to trying this out. Frankly I’m quite surprised that it wasn’t more of a performance boost. I did a bunch of testing and it turns into a 2-5% performance increase for the different types of drawing that utilize the new methods, but the results could also vary quite a bit between runs of the application, which I can’t really understand why. The testing was done simply drawing 500 times of each type of test and timing how long it took. Here are some example results.

Is it even worth make a pull request when the increase was so small?

Perhaps Math.Sin and Math.Cos have shortcuts for inputs of 0.0? It is what I would do.

1 Like