Jump to content

Paint.NET's use of threading in effects....


Recommended Posts

Goodday :D ,

This is a question for Rick B. I am very interested in efficient use of threads with multiproc setups.

Was reading the articles about Paint.NET and multireading/dual-core optimization by Rick.

There is a statement in there that says "This, as well as the thread synchronization that goes with the progressive effect rendering, is easily the most complex code in Paint.NET.....". (which I didn't quite understand coz I thought the work it divided up into disjoint "regions").

Now, I was able to probe a little bit into this (I did Gaussian blur of a photo) and check what was happening (Mine is a 2P HT Xeon) (bring out the Gaussian blur dialog and use the slider to switch to a big "number" and watch the processors get busy)

There are 4 threads doing PaintDotnet.Effects.BlurEffect and it seems I could not detect any contention of locks (actually no calling of even monitor enter/exit) during the blurring at all.

Then there are 4 threads (poolthreads) that are occasionally active (They are drivn by async tasks enqueued by the main thread). (the main thread uses the Pdnlib's threadpooling APIs to control these 4 poolthreads).

So summarizing my Qs:

1. The first set of 4 threads actually doing the blur ::: is there really no need to synchronize anything between them? or did I miss their synchronization action...)

and more importantly,

2. what is the "complex" synchronization being mentioned by the article then? (it is not merely referring to the thread(pool) helper classes in pdnlib??)

thanks in advance,


Link to comment
Share on other sites

Here goes. The threads that are explicitely active for rendering an effect are:

1. The main UI and event-sink thread

2. The rendering dispatcher thread

3 through (3 + N - 1): The rendering threads, where N is the number of logical processors in your system. So for a 2x Xeon w/ HT, this would be threads 3 through 6.

Thread 1 creates thread 2 which is then responsible for receiving commands from thread 1 such as start, stop, and restart rendering. The latter is important because when you change the settings on the effect configuration dialog, the effect's configuration "token" changes and the rendering must be completely restarted with the new settings.

The rendering work is then divided up into a bunch of work items (a bunch of disjoint regions like you said). I think the latest versions of Paint.NET divide it up in to 100 x #Processors work items or something. The rendering threads are given a reference to this array, and they know how many processors there are, and which thread # they are, so they just loop through the work item array like such (pseudo code of course)...

for (int i = M; i {

Where M is the thread # (zero through N-1) and N is the number of processors. There is no need to synchronize these rendering threads against each other, but they do need to bubble an event back up to the main thread ("thread 1" listed above) to report that they finished rendering part of the effect.

The reason this is complex is that in a naive implementation there is a circular lock dependency among the threads. Here is the scenario that took quite a long time to debug and fix (I don't think it was fixed until 2.1 for sure, infact):

a) Thread 1 starts up Thread 2 and tells it to render stuff.

B) Stuff gets rendering, and then halfway through ...

c) Thread 3+J (one of the rendering threads) reports that it has finished rendering something, and then waits for receipt that this event was processed.

d) Before receiving the event from ©, Thread 1 tells Thread 2 to restart the rendering, and then waits for a signal that rendering has restarted.

e) Thread 2 receives this event from (d), and signals all of the rendering threads to stop, and then waits for a signal that they have done this.

So, Thread 1 is blocking on an event from Thread 2 which is blocking on events from all the other threads ... one of which is waiting for confirmation from thread 1.


It works fine now, I believe the fix was that © does not wait for the event it sends to be processed. Since that article was published there are definitely areas of Paint.NET that are now more complicated. As it turns out, concurrency is actually much less complicated than the temporal juggling performed by the Move tools that were rewritten for v2.5...

The Paint.NET Blog: https://blog.getpaint.net/

Donations are always appreciated! https://www.getpaint.net/donate.html


Link to comment
Share on other sites

thanks for finding the time to answer this. Much appreciated.

So I see the threads you mentioned under categories 1,2,3.

Now there seems to be another group of threads there, which I described in my original post.

>>Then there are 4 threads (poolthreads) that are occasionally active (They are drivn by async tasks enqueued by the main thread). (the main thread uses the Pdnlib's threadpooling APIs to control these 4 poolthreads).

You didn't mention them I am guessing because they are not really involved in the rendering of an effect??

Q. So what are those guys for? (general servicing OnPaint etc stuff am I right?)

Q. by the delicate controlling of rendering dispatch thread and the rendering threads (which helped the app avoid deadlocks) you are referring to the stuff in BackgroundEffectRenderer::Start, ::ThreadFunction, ::Abort (and the use of the threadShouldStop variable to communicate with the workers that they should stop). Is that right? Just want to know i am looking at the right place ;-)



Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Create New...