News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Flicker, DirectDraw, WaitForVerticalBlank

Started by Jimg, October 26, 2005, 04:16:54 PM

Previous topic - Next topic

Jimg

I just spent the last week and a half trying to cure flicker problems in a dialog with a dozen edit boxes.  The problem occurs when the user resized the dialog and I internally resize the edit boxes to fit  ( along with some labels and group boxes).

I tried all the usual fixes to no avail.  Then I found some code by MichaelW designed to reduce tearing in a graphics app.  After first resizing all the controls without updating the screen, then using the directdraw function GetScanLine to wait for the vertical retrace/blanking and then doing an invalidaterect of the whole dialog solved the problem.

I have several questions about the code though-

I've never used directdraw before, and the information is very confusing.

First, this structure is created:

.data
    lpdd_1      dd 0
    scanLine    dd 0
    maxScanLine dd 0
    vbStart     dd 0
    vbEnd       dd 0
    temp        dd 0

Then a call to-
invoke DirectDrawCreate,NULL,ADDR lpdd_1,NULL

1. Does this create something that I have to ultimately delete before terminating my program to avoid memory leaks?


Next, there is a proc included that looks like this:

DDGetScanLine proc lpScanLine:DWORD

    push  lpScanLine
    push  lpdd_1
    mov   eax, lpdd_1
    mov   eax, [eax]
    call  DWORD PTR[eax+DDO_GetScanLine]
    ret

DDGetScanLine endp


Which is always called with-

invoke DDGetScanLine, ADDR scanLine

2.  Is the relative locations of the data variables defined above important?  That is, is it a structure of some kind, or just a collection of variables?


Next, the following code is executed:

    mov   ebx, 100000
    .WHILE (ebx)
        invoke DDGetScanLine, ADDR scanLine
        .IF (eax == DD_OK)
            mov   eax, scanLine
            .IF (eax > maxScanLine)
                mov   maxScanLine, eax
            .ENDIF
        .ENDIF
        dec   ebx
    .ENDW

3.  I can't see where maxScanLine is used anywhere else, is this code necessary to initialize something I am not seeing? (Again, same as question 2, is the location of maxScanLine important because the whole data structure above is used somehow?)


And similarly, this code computes some more variables I'm not sure about needing-

    invoke DDGetScanLine, ADDR scanLine
    .WHILE (scanLine)
        invoke DDGetScanLine, ADDR scanLine
    .ENDW
    .WHILE (TRUE)
        invoke DDGetScanLine, ADDR scanLine
        .IF (eax != DDERR_VERTICALBLANKINPROGRESS)
            m2m   vbStart, scanLine
            inc   vbStart
        .ELSE
            .WHILE (eax == DDERR_VERTICALBLANKINPROGRESS)
                invoke DDGetScanLine, ADDR scanLine
            .ENDW
            m2m   vbEnd, scanLine
            .BREAK
        .ENDIF
    .ENDW




I can see where it might be interesting to see the results, but is it necessary to set up everything else?






P1

Disable the paint on your controls until you finish with the resize.  Then force the repaint.

This also has a speed benefit.

Regards,  P1  :8)

Tedd

No snowflake in an avalanche feels responsible.

Jimg

Hi Tedd-

That was one of the many things I tried and there was still the ficker caused by the system erasing everything to the background (in gray) and then redrawing the control (in white) giving a gray flash.  I tried suppressing the erase, but it is absolute required because the controls change size, and the old position needs to be erased.

Hi P1-

It sound like you are suggesting I subclass all the controls, correct?  I briefly played with that to try to erase only those parts that would need erasing, but it was terribly complicated because of overlap of controls, etc.   Or are you saying that I can suppress all painting some other way?  I tried supperessing wm_paint in the dlgproc while I was resizing controls, and then sending a wmpaint to the dlgproc when I was done, but the controls were not repainted.   Doing an invalidaterect caused the controls to paint, but also returned the flicker.

P1

No, not exactly.

When I was loading controls with lots of data the re-paint/re-drawing was an issue.  So I temporarily disabled the re-draw.

invoke SendMessage TreeView1.hWnd, WM_SETREDRAW, FALSE, 0

Of course, you will need to enum all the controls to false, then to true.  I had no success to setting the windows' redraw to false.

Regards,  P1  :8)

Tedd

Well it should only be erased once per re-paint, so unless you're re-painting every second, is it really that bad?

Quick thought: if the flicker is caused by filling the window with the background brush ('erase') and then redrawing everything on top of that, then maybe you should set the background brush to something more appropriate (gray in your case?) That should remove most of the noticable flicker.
No snowflake in an avalanche feels responsible.

Jimg

I don't think I'm explaining very well :'(

The app will change the size of the edit, static, and groupbox controls to fit the size of the dialog.  The user grabs the side of the dialog and resizes it.  As he is doing so, I get the WM_SIZE message and do a MoveWindow,eax,L,T,W,H,FALSE for each control with its new size and position.  The FALSE prevents any paint or redraw caused by anything I am doing.  When all the controls are set to their new position, I do a single InvalidateRect,hWin,0,TRUE to update all the changes.  I also tried using DeferWindowPos/EndDeferWindowPos with the exact same results.  When windows ultimately does the paint, it erases the area of the white edit boxes with the default background color and then repaint the new edit box in white.  The flash of gray background is quite visible and annoying where the edit box was and is.  I'll try to put together a simple stripped down example to look at.

After trying everything I could find, I was thrilled to find the simple solution of redrawing while the vertical retrace (blanking) is happening.  The erase and redraw all take place while the monitor is not actually drawing on the screen, so the user never sees any part of the intermediate steps.  The only real problem I see is that it requires directx.  How many people do not have directx in some form on their windows based computer?  I would have prefered to find some other way, but there doesn't seem to be any way to get this blanking interval that doesn't require an additional device driver as it is determined by looking at some port bits in ring 0.  Any thoughts on other ways to get the vertical blanking interval will be greatly appreciated.

Another possible method that I just haven't been able to find much information on is that there is a way to double-buffer a dialog in .net code.  I don't now or ever plan to use .net, but I would certainly be interested in anyway to double-buffer the dialog in normal code.

Jimg

Here's a somewhat stripped down test app.  Grab the right border and stretch it out.  To see the effect of waiting for the vertical blanking interval, uncomment the line near the end of the code that says-

call WaitForVerticalBlank



[attachment deleted by admin]

Tedd

I don't think you can get vblank without hardware access.
I was going to suggest double-buffering, but can't really think how to do it... Ownerdraw the dialog box?
No snowflake in an avalanche feels responsible.

P1

Jimg,

I have had a chance to look at your demo of your problem.

The re-size process erases and re-draw your controls, giving the flicker, when the window background is exposed.

Some ideas:

1.  Temporarily use white as the dialog back ground.  I sort of look for code on that, but it might be easier to say than to do.
2.  Reverse Idea, Temporarily change control(s) background to match window's background.  Also provides a visual feedback to the resizing process.
3.  Interleave the wait between resizing of controls.  It seems you can not resize all the controls without the next trace occuring.
4.  Some form of "invoke PostMessage, hWin, WM_SETREDRAW, FALSE, 0"  will control when the redraw occurs.  I had some interesting affects with the window when I was experimenting with it.

I am going with 2, easy with side benefits.

Regards,  P1  :8)




P1

5.  Set all the controls to Visible = False.

Regards,  P1  :8)

Jimg

Ok, I looked at everything you suggested, even those in jest.  The most interesting is
"3.  Interleave the wait between resizing of controls.  It seems you can not resize all the controls without the next trace occuring."

This implies that you tried the vertical blanking and still got flicker.  On my machine, the vertical blanking code completely removed the flicker.  So I moved the   "wait for retrace/invalidate rect"   code to a routine, did a postmessage to my self (WM_USER) to let windows finish whatever it was in the middle of doing, and then did the wait/invalidate, and I got much worse flicker than originally.

Since I was able to remove the flicker in one instance, but the same code run at a slightly different time made it worse, it means the code itself did not solve the problem, it just did a time shift so the redraw didn't flicker.  Which would probably work differently on different computers, and so is no solution at all.  I'm trying to trace what goes on inbetween but no luck so far.

All I know for sure it that this is getting way too complicated :eek

MichaelW

Jim,

The code I posted was intended to get and display the maximum scan line, the vertical blank start and end scan lines, and the approximate refresh rate. Everything else was just a hoop I had to jump through to accomplish the task. I don't know the answer to your first question. I have seen code like this:

// Get a DirectDraw 1 interface
if (FAILED(DirectDrawCreate(NULL ,&throwAway, NULL)))
  throw (EXC_DDRAW_INIT);        // throw an exception
...
// release the DirectDraw1 interface
throwAway->Release();

But I have no idea how you would do this with MASM code.

I think the answer to your second question is on the page I used as my primary information source when I wrote the code:

http://www.gamedev.net/reference/articles/article757.asp#DD

And for your third question, I determined the value of maxScanLine so I could display it, nothing more.

I am surprised that you could not resize everything in one vertical frame. Even if your vertical refresh were running at 80Hz, you would still have 12.5ms. It might be worth mentioning that since you have access to the current scan line you can arrange to have more than a single vertical frame time to complete your update, without tearing or flickering. Problems with tearing or flickering are usually the result of the screen update overtaking the screen refresh, or the screen refresh overtaking the screen update. This causes either the lower (update overtaking refresh) or the upper (refresh overtaking update) part of the update area to appear first, followed by the other part during the next refresh cycle. Assuming the screen refresh is overtaking the screen update, you could increase the time you have to complete the update by starting the update immediately after the current scan line passes the area you will be updating first.
eschew obfuscation

Jimg

Thanks Michael-

I think the real secret here is to do the waitforverticalblank JUST before the screen will actually be updated.  Unfortunately, I have no idea when that is.  Somewhere buried in BeginPaint??  I need to do a lot more googling I guess.  I'm beginning to think part of the problem is the WM_CTLCOLOREDIT etc.  messages that occur before the WM_PAINT but I haven't figured out how to handle them yet.

Jimg

And here I am again, a year and a half later, with a new program with lots of controls, and the same flickering problem I never solved.  Anyone have a sure-fire answer yet?