3

My app has been drawing its graphics from a worker thread for over 10 years now and I've never had any problems with it. The worker thread draws to my HWND (created by the main thread) like this:

hdc = GetDC(hwnd);
SetDIBitsToDevice() ... or StretchDIBits()
ReleaseDC(hwnd, hdc);

After having ported my app to other platforms, I began to realize that drawing from any other thread than the main thread is usually a no-go on many platforms (e.g. macOS). My research has shown that this might be true for Win32 as well but I'm still lacking a definite answer.

Thus, my question:

Is it allowed to draw to my window like shown above from a worker thread that did not create the window it is drawing to? Note that the worker thread is really the only thread that draws to the window. The main thread doesn't do any drawing. Not even in WM_PAINT. Drawing in WM_PAINT is unnecessary in my case because the worker thread draws at 50fps.

If it isn't allowed, what's the best way to delegate drawing from the worker thread to the main thread?

Andreas
  • 9,245
  • 9
  • 49
  • 97
  • http://stackoverflow.com/questions/5622850/hwnd-thread-affinity-painting-from-a-different-thread – VuVirt Feb 03 '17 at 17:47
  • This was definitely wrong on Win32, and always has been. If it worked reliably (and I suspect it didn't, the bugs were just hard to reproduce), it was an accident. All drawing should be done in response to `WM_PAINT`, period. Why would an application ever need to draw at 50 fps? If this is a game or some other kind of fancy business that actually needs 50 fps, you should probably be using DirectX or whatever. – Cody Gray - on strike Feb 03 '17 at 18:11
  • Anyway, if you have to do this spread across multiple threads: have your worker thread prepare the DIB section, and then have it force the window to redraw itself immediately by calling the `RedrawWindow`. The main thread will then receive a `WM_PAINT` message, and inside of that message handler, you draw the DIB section into the DC you receive along with the message. Again, it isn't clear what using multiple threads buys you here, but this will make it work. – Cody Gray - on strike Feb 03 '17 at 18:13
  • @CodyGray: But MSDN explicitly says that drawing outside `WM_PAINT` is allowed. See here: https://msdn.microsoft.com/de-de/library/windows/desktop/dd162492(v=vs.85).aspx – Andreas Feb 03 '17 at 19:27
  • Yes, drawing outside of `WM_PAINT` is allowed, it's just probably not what you want to do. The part that won't work is obtaining the DC for another window and blitting *from* that. – Cody Gray - on strike Feb 03 '17 at 19:50
  • I'm sorry but I don't understand what you mean. What do you mean by "obtaining the DC for another window and blitting from that"? In the code in my original posting I obtain the DC for my window and I blit *to* it. I don't understand what you mean by saying "for another window" and "blitting *from* it". Could you explain? – Andreas Feb 03 '17 at 20:02
  • 2
    I'm not sure it's *quite* as wrong as @CodyGray suggests but it's not "best practice". Problems normally arise with synchronising drawing of the worker thread and the UI thread, but if the UI thread does no drawing of its own it's probably safe. (note that you still need to respond to `WM_PAINT` even if you don't do any drawing for it). Another approach could be to create a child window on the worker thread. – Jonathan Potter Feb 03 '17 at 21:24
  • Ah…sorry. I guess I misread the code. I would swear there was something else there that I was seeing. Oh well; lots of things going on at once. Well then, no. What you're doing is technically allowed. It just isn't an especially good idea. – Cody Gray - on strike Feb 04 '17 at 12:13

1 Answers1

5

Is it allowed to draw to my window like shown above from a worker thread that did not create the window it is drawing to?

It may not be the best solution to your problem, but it's safe, as long as you respect the documented rules for GetDC:

  • Note that the handle to the DC can only be used by a single thread at any one time.
  • ReleaseDC must be called from the same thread that called GetDC.

If you do render to the same device context from multiple threads, you are responsible for synchronizing accesses to it.*

As explained in the comments, a better solution would be to generate the DIB from the worker thread, and have this thread update the window by calling RedrawWindow. The main thread can then StretchBlt in its WM_PAINT handler. Calling RedrawWindow across threads implements a synchronization barrier. When the call returns, rendering on the target thread has run to completion, and it's safe to re-use the DIB.


* See Thread affinity of user interface objects, part 2: Device contexts.

IInspectable
  • 46,945
  • 8
  • 85
  • 181