News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

WM_PAINT not working as expected

Started by jj2007, October 23, 2015, 11:53:57 PM

Previous topic - Next topic

jj2007

When playing with this proggie, I noticed somewhat unexpected behaviour. This is the paint handler (PtDC is the plot DC, i.e. canvas, OnP0 is the procedure that does user-defined graphics):

  CASE WM_PAINT
invoke BeginPaint, hWnd, addr ps
push eax
mov edi, offset apStruct
mov PtDC, APs.apMemDC
.if GuiSized  ; trigger complete repaint only after WM_SIZE, otherwise use memdc bitmap
invoke SelectObject, PtDC, wcx.hbrBackground
invoke PatBlt, PtDC, 0, 0, APs.apRectG.right, APs.apRectG.bottom, PATCOPY
call OnP0 ; Event Paint
and GuiSized, 0
.endif
pop eax
invoke BitBlt, eax, 0, 0, APs.apRectG.right, APs.apRectG.bottom, PtDC, 0, 0, SRCCOPY 
invoke EndPaint, hWnd, addr ps


Works perfectly when a) resizing the window, b) moving it around. When parts of the the window move beyond the desktop, it gets repainted when moving back. So far, so good.

The unexpected behaviour is that when moving another window over this one, there are traces of unpainted areas left. Either the BitBlt doesn't copy everything (unlikely), or the destination DC doesn't catch the uncovering by the "other" window properly. As if WM_PAINT messages got lost somewhere.

This is particularly accentuated when dragging the "other" window near the top or bottom. The window manager (this is Win7-64) suggests then to enlarge the window vertically, the whole desktop gets a dark blue overlay. And in this very moment, it seems the paint handler doesn't handle it :(

Similar behaviour for professional stuff like Thunderbird or MS Word, when dragging the top window while the desktop turns blue, you can notice temporarily unpainted areas. They get painted shortly after, as if there was a WM_TIMER event taking care of that.

Is a timer the right way to deal with it? Has anybody else stumbled over this phenomenon?

P.S.: Found this in an old 2000 article by JM Newcomer - I wonder if newer Windows versions still do it exactly like this:
QuoteWhen there are no other messages in the message queue, and no WM_TIMER to send, Windows simulates a WM_PAINT message! It is never, ever in the queue.

TouEnMasm


seems to be too many code in the wm_paint message.
Try to prepare a dc before show it.
sample here:
http://masm32.com/board/index.php?topic=1917.msg19916#msg19916
Fa is a musical note to play with CL

dedndave

first, you are trying to control the update region from within the WM_PAINT handler
second, you are ignoring the update region specified in the ps.rcPaint RECT structure

rather than doing it that way,
use InvalidateRect with an lpRect pointer to tell it what portion to update
and, in the paint handler, use only ps.rcPaint to determine which area to re-draw

jj2007

Quote from: ToutEnMasm on October 24, 2015, 02:09:10 AMseems to be too many code in the wm_paint message.

One BitBlt is "too many" code??

QuoteTry to prepare a dc before show it.

That's what the code does.

Quote from: dedndave on October 24, 2015, 02:18:26 AM
first, you are trying to control the update region from within the WM_PAINT handler
second, you are ignoring the update region specified in the ps.rcPaint RECT structure

rather than doing it that way,
use InvalidateRect with an lpRect pointer to tell it what portion to update
and, in the paint handler, use only ps.rcPaint to determine which area to re-draw

Yep, that sounds good :t

In practice, it would mean that when dragging the small top window over my window, multiple paint events would be triggered, with only small rectangles to be updated via BitBlt. My current method always triggers a full BitBlt; I've implemented a WM_TIMER based solution that works fine, but what you suggest is closer to the manual, so I will give it a try. Thanks :icon14:

dedndave

i typically go for a comprimise in speed vs simplicity
i create a memory DC and draw the entire contents of the client
then, only update the device DC according to ps.rcPaint

while, in some cases, the code could be faster by only updating the partial memory DC,
it doesn't save much because drawing in a memory DC is very fast - it's drawing into a device DC that is slow
and, in some cases, the code required to update a partial memory DC would be very complex
also - there are times when it cannot be done (StretchBlt, for example - hard to make it work correctly)

this makes for simple, fast code....
wmPaint PROC USES EBX ESI EDI hWnd:HWND

    LOCAL   ps          :PAINTSTRUCT
    LOCAL   hbmpMem     :HBITMAP

;------------------------------

    INVOKE  BeginPaint,hWnd,addr ps
    xchg    eax,ebx                                                       ;EBX = ps.hdc
    INVOKE  CreateCompatibleDC,ebx
    xchg    eax,esi                                                       ;ESI = hdcMem
    INVOKE  CreateCompatibleBitmap,ebx,rcClient.right,rcClient.bottom
    INVOKE  SelectObject,esi,eax
    mov     hbmpMem,eax

;at this point, prepare the entire client area in the memory DC

    mov     ecx,ps.rcPaint.right
    mov     edx,ps.rcPaint.bottom
    mov     eax,ps.rcPaint.left                                           ;EAX = left
    mov     edi,ps.rcPaint.top                                            ;EDI = top
    sub     ecx,eax                                                       ;ECX = width
    sub     edx,edi                                                       ;EDX = height
    INVOKE  BitBlt,ebx,eax,edi,ecx,edx,esi,eax,edi,SRCCOPY

    INVOKE  SelectObject,esi,hbmpMem                                      ;EAX = hbmpCompat
    INVOKE  DeleteObject,eax
    INVOKE  DeleteDC,esi
    INVOKE  EndPaint,hWnd,addr ps
    ret

wmPaint ENDP


(rcClient is updated in the WM_SIZE handler)

jj2007

Quote from: dedndave on October 24, 2015, 03:48:16 AMthis makes for simple, fast code...

That is basically (pun intended) what I am doing. Except that I BitBlt the whole memdc to the device dc - the partial option fails half of the time (it works when dragging slowly). It's not a big deal anyway, the blitting takes typically less than a millisecond, while re-building the memdc must be done only on resize events, and it takes about 15 ms.

I attach the latest version, which works with a timer. It seems everybody does it that way, MS Word, Thunderbird, Acrobat Reader show the same odd behaviour when dragging a small window over them in this 'blue screen' mode close to the upper border. In this moment, i.e. while dragging a window over another, apparently no timer messages are being passed to the underlying window, and it stops updating.

dedndave

i have never had any trouble with that code, and have used it in several places (probably 100 programs or more)
i suggest, that if it does not work correctly, something else is wrong   :P

jj2007

Can you post a working example? Just to make sure it isn't my video driver :(

dedndave

i think you'll find a few "dynamic" examples in this thread...

http://masm32.com/board/index.php?topic=1969.15

i wouldn't suspect the driver, though
i was suggesting maybe something else is amiss with your code   :P

jj2007

Quote from: dedndave on October 24, 2015, 01:05:57 PM
i think you'll find a few "dynamic" examples in this thread...

Nice examples, but when you take away the dynamic generation of a fresh bitmap, i.e. you do only the BitBlt of the existing bitmap, they show the same behaviour when you drag a little window over them. In fact, dragging a window over poly3 just erases the curve, i.e. no updating...

The "standard" handling, correct me if I'm wrong, is that an app draws on the memdc once, and from then on (except if there is a need to produce new, different content) the WM_PAINT handler just BitBlts the memdc onto the devicedc. We are arguing basically if that BitBlt should copy the complete bitmap, or only the parts marked by the OS for repainting. My experience with the "partial" blitting is not the best, and complete blitting costs about 1 millisecond.

So the only reason to go for partial blitting would be a demo that it works. What I see, though, is that professional apps work with timers, which puzzles me because it goes against the theory that the OS tells you through the WM_PAINT message which rectangles need repainting.

To see the difference, take PolyBezier code of reply #54 and modify as follows:

    INVOKE  BeginPaint,hWnd,addr ps
    mov     edi,uScnHeight
    mov     esi,uScnWidth
    if 0 ; JJ - no artefacts
  sub     edi,rcMainClient.bottom
  sub     esi,rcMainClient.right
  sar edi, 1
  sar esi, 1
  INVOKE  BitBlt,ps.hdc,0,0,rcMainClient.right,rcMainClient.bottom,hdcBezier,esi,edi,SRCCOPY
  ; deb 1, "Test", eax, $Err$(), rcMainClient.right,rcMainClient.bottom

    else ; original: artefacts when dragging smaller window over the canvas
  mov     ecx,ps.rcPaint.right
  mov     edx,ps.rcPaint.bottom
  mov     eax,ps.rcPaint.left         ;EAX = window hdc left
  mov     ebx,ps.rcPaint.top          ;EBX = window hdc top
  sub     edi,rcMainClient.bottom
  sub     esi,rcMainClient.right
  sub     ecx,eax                     ;ECX = update width
  sub     edx,ebx                     ;EDX = update height
  sar     edi,1                       ;EDI = image origin top
  sar     esi,1                       ;ESI = image origin left
  ; deb 1, "Test", eax, ebx, ecx, edx, esi, edi
  INVOKE  BitBlt,ps.hdc,eax,ebx,ecx,edx,hdcBezier,esi,edi,SRCCOPY
    endif
    INVOKE  EndPaint,hWnd,addr ps

jj2007

When searching around for more evidence, I found an example by MichaelW in the old forum:

        invoke BeginPaint, hwndDlg, ADDR ps ; http://www.masmforum.com/board/index.php?topic=13204.msg102538#msg102538

usebm2=0 ; 1=use 2nd bitmap, 0=don't
if usebm2
invoke CreateCompatibleBitmap, ps.hdc, clientW, clientH
push eax
invoke SelectObject, ps.hdc, eax
push eax
endif

        invoke BitBlt, ps.hdc, 0, 0, clientW, clientH, hdcBM, 0, 0, SRCCOPY

if usebm2
pop eax
invoke SelectObject, ps.hdc, eax
pop eax
invoke DeleteObject, eax
endif

        invoke EndPaint, hwndDlg, ADDR ps


I've added the usebm2=? for testing, and it turns out that there is no difference on Win7-64 ::)

Furthermore, I found this intriguing remark made by a prominent member of this forum:
If the bErase parameter is TRUE for any part of the update region, the background is erased in the entire region, not just in the specified part.

Does that imply if one pixel is marked for background delete, then the whole client area has to be repainted??

P.S.: Once upon a time, an evil extraterrestrial psychopath named Baltoro suggested:
You can call GetUpdateRect to determine if there is an update region. However, MSDN states:
QuoteThe update rectangle retrieved by the BeginPaint function is identical to that retrieved by GetUpdateRect.

BeginPaint automatically validates the update region, so any call to GetUpdateRect made immediately after the call to BeginPaint retrieves an empty update region.

jj2007

New version attached, needs MB 25 Oct 2015.
Painting is reliable, cpu usage very low, no leaks.

dedndave

the PolyBezier programs work perfectly on my machine

if you are doing something that is CPU intensive, it may be delayed
WM_PAINT is a low priority message, only dispatched when nothing else is in the queue
so, if you are crunching on some other message, it will be delayed
i can see where that might cause remnants

i recall some OpenGL programs that stuck intensive things in the message loop

jj2007

Quote from: dedndave on October 26, 2015, 02:40:03 AM
the PolyBezier programs work perfectly on my machine

There is nothing wrong with your programs, Dave :t

jj2007

Runs just fine on Win7-64:

  lea ecx, pmc
  invoke GetProcessMemoryInfo, rv(GetCurrentProcess), ecx, PROCESS_MEMORY_COUNTERS
  deb 4, "wss", pmc.WorkingSetSize
  .if !flag
inc flag
For_ ct=0 To 999
invoke CreateCompatibleBitmap, APs.apDC, APs.apRectG.right, APs.apRectG.bottom ; hDC, not memDC, otherwise it's black & white
.Break .if !eax
Next
deb 4, "Out", ct, eax, $Err$(), APs.apDC, APs.apRectG.right, APs.apRectG.bottom
  .endif
  lea ecx, pmc
  invoke GetProcessMemoryInfo, rv(GetCurrentProcess), ecx, PROCESS_MEMORY_COUNTERS
  deb 4, "wss", pmc.WorkingSetSize


No errors whatsoever, 1000 bitmaps get created, each 888x609 pixels, about 1.6 MB each. The working set could barely handle a single one...

This stuff is badly documented. There is a vaguely related thread here, involving inter alia Bogdan and Ultrano, in case somebody is interested.

Out
ct              1000
eax             328043
$Err$()         Operazione completata.

APs.apDC        -67041261
APs.apRectG.right       888
APs.apRectG.bottom      609
wss     pmc.WorkingSetSize      2732032


P.S.: At 1975 iterations, CreateCompatibleBitmap returns zero but no error; afterwards, the system is very sluggish, but it eventually recovers.