-
-
Notifications
You must be signed in to change notification settings - Fork 36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PathGradientBrush fails to render a complex polygon #117
Comments
|
It's not that this doesn't complete, its that it is very slow. I'm pulling a trace to see if there is any obvious hot spots here |
|
Thanks for having a look into this @tannergooding really appreciate it! I’d like to review our internal API for brushes to see if we can optimise them. As I recall everything is calculated per pixel (maybe unavoidable). |
|
Essentially most of the time (almost 50%) is spent in the call to The allocation graph for live objects ends up looking like this: |
|
In this scenario, almost all allocations are for 2 points (so 16 bytes).
This is being done for every edge (all 7200 of them) for every pixel (all 480,000) of them, so its quite a lot of very short lived, non-reused allocations (which wrap the pooled allocations). |
|
This is the kind of scenario that would be greatly improved by an Manually doing an IMemoryOwner<PointF> memory = null;
try
{
var maxIntersections = this.path.MaxIntersections;
Span<PointF> buffer;
if (maxIntersections < (1024 / sizeof(PointF)))
{
PointF* points = stackalloc PointF[maxIntersections];
buffer = new Span<PointF>(points, maxIntersections);
}
else
{
memory = allocator.Allocate<PointF>(this.path.MaxIntersections);
buffer = memory.Memory.Span;
}
// code
}
finally
{
memory?.Dispose();
}Cuts the execution time (under the profiler) for |
|
Doing something like pre-building an There are likely pro's and cons to both approaches here. The root cause is the number of allocations being done and each fix "resolves" that by reducing the number of allocations required (either by removing them entirely or by moving them higher up and allowing sharing). |
|
I'm not familiar with this code, and chiming in for just a second, but probably the buffer could be reused across all |
|
I’ll have a look at all this ASAP. Can’t focus just now though as have oral shingles. Happy to follow whatever lead you both come up with. |
Right, that's roughly what I referred to by pre-building an I think the principle of the change is that This looks to be achievable today without extending |
|
It seems to me that a |
|
That seems reasonable and avoids the need for It does mean that the allocation potentially persists for much longer than needed, however, and is dependent on |
|
The pixel-agnostic |
|
A chink in the plan. I think we should have a deeper look at our underlying API here. |
|
I think we should either remove the parallelism, or somehow find a solution that eliminates synchronization concerns by design, by creating an isolated context per-thread. (For example we can probably create a separate The hard thing is that this would require extending the |
|
I've been looking at ImageSharp.Drawing/src/ImageSharp.Drawing/Processing/BrushApplicator.cs Lines 84 to 115 in b8ff644
I've also noticed we request a buffer from the ImageSharp.Drawing/src/ImageSharp.Drawing/Shapes/InternalPath.cs Lines 203 to 212 in b8ff644
ImageSharp.Drawing/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs Lines 204 to 210 in b8ff644
|
I would consider (upon overriding Apply) trying
+1 for that. Note that |
|
In one of my prototypes, I had just extended It was basically: internal abstract TPixel this[int x, int y, IDisposable data] { get; }
internal abstract IDisposable GetDataForApply();
Applicators that didn't do need it could just return |
|
Interesting. Thanks! I'm going to do some experimentation here to see if I can clean up the API a little based upon this conversation. |
|
There's a bit of me that wonders whether we should add the following method to That would allow us to reduce allocations from per-scanline to per interval in the fill processor. Might be overkill though. I'll focus on optimizing the per-scanline operation first. |



SixLabors.ImageSharp v1.0.2
SixLabors.ImageSharp.Drawing v1.0.0-beta11
Running in Visual Studio 2019 v.16.8.3 on Windows 10 v.2004 in .NET 5.0.1-servicing.20575.16
Using the SixLabors.ImageSharp.Drawing.Processing.FillPathExtensions's Fill method, I can render a complex polygon using a SolidBrush, but using a PathGradientBrush the program doesn't finish executing and 'hangs' with high memory usage.
Code to reproduce:
The text was updated successfully, but these errors were encountered: