Repository navigation
Drawing text is too slow #61
Description
Prerequisites
- I have written a descriptive issue title
- I have verified that I am running the latest version of ImageSharp.Drawing
- I have verified if the problem exist in both
DEBUGandRELEASEmode - I have searched open and closed issues to ensure it has not already been reported
(Wasn't sure if this should go here or in SixLabors.Fonts)
I'm trying to convert a working game engine from system.drawing, but my game engine (which usually hits 60 FPS without a problem) shows less than 10 FPS, due to a debug label which shows the cursor position and constantly updates as the cursor moves.
When I measured the text rendering time, I saw that ImageSharp.Drawing takes somewhat between 90ms and 170ms (median: 134ms) to render the text, and system.drawing takes ~0.3ms for rendering the text, so somewhat between x300 and x400 better performance.
Here's the analysis from xamarin profiler:

FindIntersectionWithOrientation seems to be the biggest bottleneck: I'm completely ignorant when it comes to text rendering, I'm curious why we even need to find intersections when rendering text, I always imagined a glyph is just a series of path instructions that you map to size and render as is.
Steps to Reproduce
- Run
DemoDesktopfrom here: https://github.com/tzachshabtay/MonoAGS/tree/ImageSharp (using Visual Studio for Mac) - Move your cursor around after the loading screen, and see the slowness
- The slow line is here: https://github.com/tzachshabtay/MonoAGS/blob/080bba5c186db4b88cb96e7c4940874cd5327102/Source/Engine/AGS.Engine/Misc/ImageSharp/ImageSharpTextDraw.cs#L89
If you want to compare to the system.drawing version, checkout the master branch, the equivalent line is here:
https://github.com/tzachshabtay/MonoAGS/blob/cadd7acbef6b1a97e47166681ba7e349074c4b47/Source/Engine/AGS.Engine.Desktop/Drawing/DesktopBitmapTextDraw.cs#L93
System Configuration
- ImageSharp.Drawing version: 1.0.0-beta0009
- Other ImageSharp packages and versions: ImageSharp 1.0.0-rc0003
- Environment (Operating system, version and so on): Mac OSX Mojave 10.14
- .NET Framework version:
Mono JIT compiler version 6.4.0.208 (2019-06/07c23f2ca43 Wed Oct 2 04:52:23 EDT 2019)
Activity
JimBobSquarePants commentedon Jun 26, 2020
Yeah, we've got a fair amount of performance work to do...
There's probably a fair amount of low hanging fruit....I can take ~15% pretty easily from
InternalPath.FindIntersection(in Segment source, in Segment target)by moving the method to theSegmentstruct which allows inlining but I think we need to do a proper review. @tocsoft is there anything you particularly would like another pair of eyes on?tocsoft commentedon Jun 26, 2020
the find intersections stuff is definitely the hottest path in the whole of our vector drawing so anything to help in there would be awesome.... other things to consider is our whole rasterization algorithm is rather naive and there might be changes we can make in there instead that could reduce the amount of can lines we perform (by default we process each pixel I think 16 times while anti-aliasing)
JimBobSquarePants commentedon Jun 26, 2020
@tocsoft I've managed to take 25% off the processing time for that method so far. Having a look at some intrinsics for additional boosts.
I have a question regarding the double precision code. My current implementation is using single precision and all the tests are passing. Is there a test I can run to prove I still require double precision?
JimBobSquarePants commentedon Jun 26, 2020
@tzachshabtay I just had a quick look at your code and it looks like you are rendering the string without antialiasing in the System.Drawing version. Our default options have antialiasing enabled. Could you turn that off and then please compare performance.
tzachshabtay commentedon Jun 27, 2020
Ah, turned off antialiasing, I'm now getting between 25-65ms (median: 45ms).
So, great improvement, still too slow.
FindIntersectionsWithOrientationis still the bottleneck.JimBobSquarePants commentedon Jun 27, 2020
We'll have a look but there's no guarantee that we will be able to make dramatic improvements any time soon. There's a lot to assess.
antonfirsov commentedon Jun 29, 2020
@tocsoft @JimBobSquarePants we don't need a generic intersection finding algorithm, since one of the lines is always horizontal. Additionally, the intersection finding should be a bulk operation (faster by default, even faster if SIMD optimized).
#7 is here to deal with this, I don't think it's worth to do other optimizations before implementing that.
JimBobSquarePants commentedon Jun 29, 2020
@antonfirsov The current implementation I have is based on DirectX Maths which has an SSE path I can quite easily implement if I know that I can carry on with single precision. I've not done any work adding that and won't until I can derive a test to ensure my implementation can replace the current double precision one.
I'm really scratching the surface with the API at the moment so I wasn't aware of #7
antonfirsov commentedon Jun 29, 2020
@tzachshabtay we need to rasterize the polygon. The current naive approach is to start by calculating
line x lineintersections for each horizontal pixel line crossing the polygon lines. (@tocsoft correct me if I'm wrong.) We'd be more than happy to accept a community contribution improving this.tocsoft commentedon Jun 29, 2020
That's correct except we do it multiple times per line to create a series of subpixels which we use for antialias purposes.
JimBobSquarePants commentedon Jun 29, 2020
Current (25% faster) segment intersection impl.
ImageSharp.Drawing/src/ImageSharp.Drawing/Shapes/InternalPath.cs
Line 926 in ec4d6d3
tocsoft commentedon Sep 2, 2020
If I recall correctly I did try profiling this stuff (it was a little while ago) after this was applied and it did nicely help offset that as the point of contention.
Code by @JimBobSquarePants seemed to work fine and help. However after implementing it the next bottleneck is the pixel blending (which lives over in the core library rather than in Drawing). I think the blender would need to be looked at next... just changing the rendering algorithm will not fix the problem we still need up to route through that blending code used by the brushes.