2

A problem I've noticed once on my Win7 system, thought it was a DWM bug as it was fixed after a reboot. But now I'm realizing that it's happening (as default behavior) on other people's systems, and it's the normal behavior on Surface Pro as well.

How to reproduce the problem: implement, using GDI, a basic lasso system. Define a rectangle controlled by the mouse, when the rectangle changes, invalidate the old one & the new one (or invalidate a union of both rectangles, either as a new rectangle or a complex region, it doesn't matter, the "bug" still shows anyway).

During wm_paint, you just erase the background & paint the rectangle (it has to be a rectangle outline, the problem won't be visible if it's a filled rectangle). You can do double-buffering if you wanna be sure that it's not a flickering problem (& trust me it's not).

So what you'll see, if you have a system like mine (desktop Win7 with a geforce, aero on), is a normal lasso system, with no more ghosting than the monitor's own. On other systems (like Surface Pro, to define a fully known system), you'll see, as you extend the lasso outwards, the border of the lasso disappearing. A bit like LCD ghosting, but massively more noticable.

Now, instead of invalidating the lasso's rectangle, try invalidating the whole window. And there, no more ghosting.

What I found out is that it's not the invalidation that "fixes" it, it's GDI access. You can as well invalidate the whole rectangle, but only paint the lasso's zone, still ghosting. But if you paint the lasso's zone and draw a single little pixel on each corners of the window, no more ghosting.

There must be something in DWM, probably since version 1.1, that uses some kind of caching of the bounding box of the last GDI access, and for some weird reason, what falls within the last bounding box will get immediately on screen, while the new part will be delayed by at least 1 frame.

This is pretty bad because it's breaking very basic window invalidation that everyone uses, and I haven't found any way to fix it (other than by invalidating the whole window of course, but that'd be stupid, & besides it's a problem that affects the whole GDI, so you get poor visual results everywhere).

Again it's most likely in DWM 1.1, I don't think you can get this in Vista, but I'm not sure. I also don't know why it doesn't do that on my desktop, possibly it depends on the graphic card's driver.

So if anyone happens to know more about this...

JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
user2516466
  • 81
  • 2
  • 7
  • It is quite difficult to follow it. You'd get better responses if you made a compilable test project to illustrate the issue. Did you code it in C++? – ahmd0 Jun 25 '13 at 22:54
  • no, Delphi, that's why. I could make a video of the result, though. But it's quite simple: 1. define a rectangle, 2. implement wm_mousemove to make the lower-right of the rectangle follow the mouse, invalidate the rectangle (& the rectangle only!) before & after the move, 3. implement wm_paint to fill a background & draw the rectangle *using GDI* (use double-buffering if you want, not necessary, the problem will be obvious if you have it). That's all. – user2516466 Jun 26 '13 at 10:22
  • Edit: video from a system on which it's happening (if links work here): http://youtu.be/iU_zhKZKzNA – user2516466 Jun 26 '13 at 11:21
  • It seems like a pixel is missing on each end of the rect. Or when you draw it, it gets XORed over that makes it disappear on one end. It's hard to tell without looking at the code. So go ahead post the Delphi code in your question and I'll take a look. – ahmd0 Jun 26 '13 at 18:17
  • Here it is, but what I'm describing is pretty simple and affects *all apps* (that use GDI), which is why I'm wondering why no one ever pointed it out. This is only a way to make it very obvious. Possible reasons that you wouldn't see it in other apps are that it's only visible when something "moves" and not the whole window is invalidated. http://flstudio.image-line.com/help/publicfiles_gol/Unit1.pas So it's not a bug in the code & that's for sure, but what I'm looking for is others who have noticed this & have a workaround. And again, if you have a system like mine, it won't be any visible. – user2516466 Jun 26 '13 at 23:40
  • Yes, finally! I'm having the same exact bug in C++. Even when using ::FrameRgn to check that the region I'm using is correct, even the edges of this fall outside the "real" update region when I move the object. The DWM docs are quite lacking in this respect. :( – Alex Mar 20 '14 at 14:29

2 Answers2

5

An update on this. I didn't find any clean solution, but a hack that works.

First, I also discovered that this "bug" seems to affect all systems, just not the same way.

Using DwmGetCompositionTimingInfo, once can make v-synced GDI animation. That's in theory, because in practice it won't work, because of the same "bug", even on systems not visibly affected by what I describe above. The DWM will simply decide not to refresh anything when there isn't enough to fresh, and that will cause frameskips when scrolling a window using ScrollWindowEx and not enough pixels are invalidated.

So what's the solution? Fool the DWM into thinking that it has to swap buffers immediately, as it's what the whole problem is about. When doing GDI operations, one might think that the result will appear at the DWM's next vblank-synced refresh, but it's not the case. Some parts will, some will be delayed. So the trick is to force the DWM to swap, here's what I found about it:

-first, DWMFlush does not do that (nor does GDIFlush). DWMFlush is more or less a WaitForVBlank anyway

-DWMSetPresentParameters doesn't seem to allow this either, even though I didn't bother that much with that function since it's already gone in Windows 8

-the DWM seems to refresh when there are enough pixels to swap, but it also seems to be partitionned, quite possibly into wide but short rectangles (that's what it appears to be on Surface Pro - but not on my desktop). Whether it's related to apertures to the VRAM or segmentation into small textures, I have no idea, really, maybe someone knows?

So what works for me: telling the GDI to refresh vertical stripes, 1 pixel wide, about 500 pixels apart, all over the screen. If you only do it on parts of the screen, you'll still get flicker on other parts. It also works using horizontal stripes but then you'll need a lot more of them, that's why I believe that the segmentation is done in wide, short rectangles. You can kinda see those rectangles when tricking the DWM.

Which GDI functions work? Many do, but they don't all have the same CPU usage. First, forget GetPixel, it works but the CPU usage is obviously extremely high. Second, the GDI seems to be intelligent enough to detect things that can be buffered just as commands, so filling a rectangle with a null brush, or drawing empty text, won't force the DWM to refresh. What does work is BitBlt or AlphaBlend using empty vertical stripes. The CPU usage is still ok, even though it's not so far from blitting a whole screen. It has to be done on a top-level window, not the desktop.

Creating a Direct2D render target for the DC & doing a begin/enddraw also works, but it's normal since it will force a refresh of the full rectangle, & thus the CPU cost is higher.

If anyone knows a better way to force a DWM refresh, I'd like to know. But since Windows 8 already made obsolete most of the interesting DWM functions (fortunately DwmGetCompositionTimingInfo still partially works, or it would be impossible do vsynced timers without DirectX), I'm not sure there's any better way.

Also, it doesn't have to be done on all top-level windows. You can see the effect working on a Windows desktop blue lasso when it gets near a top-level window that invalidates stripes, it stops flickering as soon as it enters a zone near it (the segmentation I'm talking of above).

user2516466
  • 81
  • 2
  • 7
  • Thanks for figuring this out! It's strange that a bug so severe is treated by everyone like a small visual trifle. – Alex Mar 20 '14 at 14:46
  • 2
    Note that the workaround I described here gives troubles with OpenGL. I had to go for another hack, which is a giant, click-through & transparent layered window on top of the real windows. It's possible to make it "refresh" at low CPU cost, in a way that it doesn't require to swap a huge mem chunk with the graphic card. As a bonus, it also allows you to get a smooth 60FPS with desktop compositing enabled, something normally impossible. – user2516466 Oct 23 '14 at 12:12
0

Here's a couple of general GDI-related pointers regarding your code:

  1. When you call InvalidateRect API it may not immediately dispatch WM_PAINT notification, so calling InvalidateRect two times in a row like you did in your TForm1.FormMouseMove method is most certainly causing this visual effect. The first redrawing is not yet processed when you call it the second time.

  2. I'm not sure what Canvas.Rectangle exactly does "on the inside" or which APIs it calls. I'm assuming it is using DrawFocusRect, in which case you have to be aware that its drawing is XORed, so doing it twice over the same rectangle will erase it.

OK, so here's what I'd do if I were drawing that selection box:

  1. Call InvalidateRect API only once. For that you will have to calculate the bounding rectangle that will include position of the selection box before and after the move. If I'm not mistaking, you can use UnionRect API for that, or just calculate the bounding rect yourself.

  2. If avoiding a visual lag is important, I'd do all the drawing of the selection box in TForm1.FormMouseMove. You can get a device context by calling GetDC API on your window handle. After that do the same drawing. In this case you will not need any invalidation. (Sorry, I can't give you a procedure for Delphi. This is how I'd do it with plain WinAPIs.)

EDIT: After reviewing your C++ code I was able to reproduce the visual flicker you're describing. I also made two C++ projects, derived from your original code in hopes of solving it: Here's a simple version and here's the one with mouse dragging support. None of them solved the issue though.

I was able to reproduce the flicker on a Windows 7 desktop. After having done some tests I am convinced now that this visual artifact is caused by a display driver. In my case the desktop has ATI Radeon video card. This is how I was able to conclude that: I ran the test executable on a different (older) desktop with Windows XP OS and the flicker was not present. I ran the exact same executable in a virtual machine with Windows XP installed in it on the desktop computer that was producing the flicker. The flicker was present when the executable ran in a Windows XP in that virtual machine. Since the same video driver is responsible for rendering the actual desktop and the one in the virtual machine, there's most definitely some "funny business" going on with optimization or caching in the video driver, that is causing this artifact.

So, the question now is how to resolve it. In my second build of your project, I added the code to render the whole client area for the window to eliminate the possibility of a calculation error, but that did not help. So at this point you're helpless to resolve it via plain APIs.

Your next steps should probably be these:

  • Contact the driver maker for the video card that you're experiencing this issue on. See if they help. I'd show them your YouTube video of the issue and give them the C++ project to reproduce it with.

  • Post a question on Windows Driver Development forums, preferably for video driver developers. Unfortunately it's a dwindling community, so expect long delays.

  • Change the tags on your question here. Add these: C, C++, WinAPI, GDI, DWM and remove what you have there now. This way it will be visible for a Win32 dev community so you'll get more views.

Other than that, good luck! This is such a minor bug (even if one can call it that) that I doubt that you'll get any serious attention to it. Although this is very admirable, in my book, that you're trying to achieve such perfection for your software.

Also, if you need me to second you on this, I can do so.

ahmd0
  • 16,633
  • 33
  • 137
  • 233
  • Oh come on don't give me an answer like I'm a noob, I've been working on FL Studio for 15 years, I'm a programmer for 20. What's in the code is hardly linked to Delphi, it relies on pure Windows messages: wm_MouseMove, InvalidateRect, wm_Paint, GDI's FillRect and Rectangle. 4 little basic Windows functions, & the code is tiny. And as I wrote, it affects other apps too. I'm describing a problem that affects way more desktops than I thought (only not mine, but it affects Surface Pro meaning it's serious enough). Btw as I had written, invalidating a union of both rectangles has the same effect. – user2516466 Jun 27 '13 at 09:38
  • (I'm asking someone to transl. it to C++, if that's what's required for anyone to take it seriously) Btw "InvalidateRect may not immediately dispatch a WM_PAINT", it *will* not immediately, never has, never will. And yes the result of the problem totally looks like it is dispatch. one then the other, insead of invalidating the rects. Also note that I've rep. the prob at M$ here http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/7de13a8d-0bcb-4e4b-a975-e2935c226e24/dwm-invalidaterect, but of crs there is very little activity on their forums so I'm not expecting anyone to see it there. – user2516466 Jun 27 '13 at 09:58
  • C++ sources & executable here: flstudio.image-line.com/help/publicfiles_gol/lassotest_cpp.zip – user2516466 Jun 27 '13 at 12:41
  • @user2516466: I didn't mean to be disparaging to you. I have no way of knowing who you are. I have a similar experience with C/C++/WinAPIs. So let me review your C++ project and I'll get back to you. – ahmd0 Jun 27 '13 at 17:27
  • @user2516466: OK, it's way better to work with a C project. Here's what I meant in my reply above, done with native WinAPIs: http://sdrv.ms/14xtG7S I tried it on several monitors and it worked fine. So if it still produces this ghosting effect, then it is your screen. As far as blaming Microsoft for it, as much as I am critical of Microsoft, I don't think they have anything to do with it. – ahmd0 Jun 27 '13 at 19:39
  • Your exe suffers from the problem, on a Surface Pro, so considering that Microsoft designed Surface Pro, I think I'm allowed to blame Microsoft for it, no? Anyway: trust me that this problem affects a lot of desktops & tablets, we have enough testers to know. If you happen to have access to a Win8 tablet some day, just try your exe on it. As far as blaming a monitor for it.. what could this have to do with monitors? Graphic driver, maybe, it depends how accelerated the DWM is. But it doesn't seem to be linked to a manufacturer (that or nVidia & Intel are to blame). – user2516466 Jun 27 '13 at 20:08
  • @user2516466: I updated my original post. And yes, I was able to repro the issue on a Windows 7 desktop. Although I failed to find a solution with just WinAPIs... – ahmd0 Jun 28 '13 at 03:13
  • Ok, but you shouldn't conclude that it's a driver problem because it did it in a virtual machine running XP. First, this requires the DWM, thus will never appear in XP. Second, XP in a virtual machine still relies on the DWM of the host of the virtual machine to render, thus the problem will appear. I'm not saying it's not a driver prob & it may be, but I've gathered it happen. with at least 3 different graphic cards. I also don't agree that it's a minor defect, it's major. It's only minor for a lasso, but trust me that it makes a whole desktop ugly, it's of crs not restric. to this demo. – user2516466 Jun 28 '13 at 17:17
  • @user2516466: I also tested it on a couple of Vista/7 machines: desktops & laptops and your described flicker wasn't present. OK, if you think it's DWM, why don't you disable it on the affected machine and run the test. Although, again, the video driver may act differently in that case. On the side note, I tried calling `GdiFlush` or `DwmFlush` after each drawing cycle, but it didn't help. – ahmd0 Jun 28 '13 at 22:22
  • I don't think it's the DWM, it IS the DWM, which still doesn't exclude the driver that powers it. (& also the DWM is forced on since Win8). Yes as I described in the MS article, neither GDIFlush nor DWMFlush help, although DWMFlush reduces the visible problem because it acts as a long pause. I've tried many things, and also found a "solution" (but not really, only pointing the cause even more), as written in the MS forum (the "solution" being an invalidation of a single pixel in each corner of the full window to fool the DWM). – user2516466 Jun 29 '13 at 12:45