Vincent Parrett 764 Posted March 24, 2021 Is it just my delphi applications that behave poorly when handling dpi changes? This is when setting high dpi in the manifest to PerMonitorV2. I have verified that the controls (many of them mine) are handling this as they should (override ChangeScale). When dragging the application between monitors with different dpi's, it takes 3-4 seconds while the window flickers and repaints multiple times - the dragging operation pauses while it does this, and then the window eventually jumps to where you actually dragged it. I've been looking at other applications (that ssupport PerMonitorV2) to see how they behave, even explorer stutters a little, due I guess to the ribbon control - but the stutter is around the 200ms mark. Thunderbird seem to repaint twice but very fast. After some debugging, as far as I can tell, this is caused by all controls getting their ChangeScale method called (as you would expect) which results in calls to SetBounds, which invalidates the control causing more painting! TWinControl.ScaleControlsForDpi appears to be doing the right thing (control alignment is a perf hog), but calling EnableAlign inevitably invalidates the control, again. procedure TWinControl.ScaleControlsForDpi(NewPPI: Integer); var I: Integer; begin DisableAlign; try for I := 0 to ControlCount - 1 do Controls[I].ScaleForPPI(NewPPI); finally EnableAlign; end; end; This really show up an inherent design flaw in the vcl, there is no BeginUpdate/EndUpdate design pattern in the vcl that allows a control (or form) to disable child controls painting until it's done. Many controls implement this pattern individually, but that doesn't help in this scenario. The situation isn't helped by my using Vcl Themes either - resize (setbounds) causes serious flicker in some controls and I'm sure this is coming into play here too. I tried to fudge a BeginUpdate/EndUpdate with this : procedure TMainForm.WMDpiChanged(var Message: TWMDpi); begin SendMessage(Self.Handle, WM_SETREDRAW, NativeUInt(False), 0); try inherited; finally SendMessage(Self.Handle, WM_SETREDRAW, NativeUInt(true), 0); RedrawWindow(Self.Handle, nil, 0, RDW_INVALIDATE or RDW_UPDATENOW or RDW_ALLCHILDREN); end; end; It cut's out the visible repainting, but doesn't speed things up much If anyone has any ideas on how to tackle this I'm all ears. Share this post Link to post
pyscripter 694 Posted March 24, 2021 For start you can use the fix here https://quality.embarcadero.com/browse/RSP-30931 See also this Per monitor DPI awareness - how to prevent flickering? - VCL - Delphi-PRAXiS [en] 1 Share this post Link to post
Vincent Parrett 764 Posted March 24, 2021 21 minutes ago, pyscripter said: For start you can use the fix here https://quality.embarcadero.com/browse/RSP-30931 I already had that in place, but in this case enabling/disabling that didn't seem to make much difference. 23 minutes ago, pyscripter said: See also this Per monitor DPI awareness - how to prevent flickering? - VCL - Delphi-PRAXiS [en] LOL! I had forgotten about that thread (which I posted in), and it didn't come up when I was searching. FWIW, LockWindowUpdate did work for me in the WMDpiChanged handler - but setredraw seemed slightly safer. I've been trying to profile what is happening but the lack of a decent profiler is killing me! I've been using the sampling profiler, and it does show AlignControls showing up a lot (which is a known performance issue), need to investigate further. Share this post Link to post
Darian Miller 366 Posted March 24, 2021 I used the LockWindowUpdate(Handle) on BeforeMonitorDPIChanged and LockWindowUpdate(0) on AfterMonitorDPIChanged + set ParentFont := True in child controls and it helped me with this issue. (A gigantic delay was reduced to a huge delay.) I haven't checked out this repo in a while - does it help 10.4.2 at all? https://github.com/rruz/vcl-styles-utils Now that IDE Fix Pack is mostly integrated, I hope VCL Styles Utils would be another repo that was really nice to have to fill in gaps, but is no longer needed. Share this post Link to post
Vincent Parrett 764 Posted March 24, 2021 I spent some more time looking at this and I can only describe it as a clusterf*ck. The issue is that as the application crosses over monitors, the form receives a WM_SIZE message, which calls AlignControls (which on a complex form or container control can be very slow). Cue mega rearranging of controls and repainting. Then the application received WM_DPICHANGED, which calls SetBounds, ChangeScale (which calls AlignControls) and more arranging and repainting occurs. Then add VCL Styles into the mix - for some reason with VCL Styles that WM_SIZE get's sent twice, and then the WM_DPICHANGED is sent. I have no idea why it's sent twice, but I doubt windows is doing it. I'm not even sure windows is sending the WM_SIZE at all (still investigating this). Anyway, the upshot of this is the more controls you have on a form the worse this issue will be. Share this post Link to post
Vincent Parrett 764 Posted March 24, 2021 (edited) Ok, so I can definitively say this is a design flaw in the VCL - we are not doing anything wrong. The bug is in TCustomForm.ScaleForPPIRect the call to ScaleControlsForDpi(NewPPI) causes AlignControls to be called on the child controls and the form (depth first) - which is fine, and expected. My debug output looks like this : Debug Output: TMyPanel.AlignControls - dpi : 144 Process HighDPIDragTest.exe (3164) Debug Output: TForm3.AlignControls - dpi : 144 Process HighDPIDragTest.exe (3164) Then a few lines further down in TCustomForm.ScaleForPPIRect, it calls SetBounds and all hell breaks loose! Debug Output: TForm3.WMSize- before inherited - dpi : 144 Process HighDPIDragTest.exe (3164) Debug Output: TForm3.AlignControls - dpi : 144 Process HighDPIDragTest.exe (3164) Debug Output: TForm3.WMSize - after inherited Process HighDPIDragTest.exe (3164) Debug Output: TForm3.WMSize- before inherited - dpi : 144 Process HighDPIDragTest.exe (3164) Debug Output: TForm3.AlignControls - dpi : 144 Process HighDPIDragTest.exe (3164) Debug Output: TMyPanel.AlignControls - dpi : 144 Process HighDPIDragTest.exe (3164) Debug Output: TForm3.WMSize - after inherited Process HighDPIDragTest.exe (3164) Then it calls Realign Debug Output: TForm3.AlignControls - dpi : 144 Process HighDPIDragTest.exe (55256) So the form's AlignControls method is called 3 times, and on the panel 3 times! It should be noted that the number of times for each control depends on it or other child controls anchor or align settings (see Vcl.Controls AlignWork function), It's no wonder that things look so bad, it's because they are. Testing in 10.4.2 with VCL Styles enabled - but the code looks the same for 10.3 so this is not new. Edited March 24, 2021 by Vincent Parrett typo Share this post Link to post
Vincent Parrett 764 Posted March 24, 2021 One last one before I give up for the day.. I added a few more outputdebugstring calls. Debug Output: TForm3.WMDpiChanged - dpi : 144 Process HighDPIDragTest.exe (52784) Debug Output: TForm3.Paint Process HighDPIDragTest.exe (52784) Debug Output: TMyPanel.AlignControls - dpi : 144 Process HighDPIDragTest.exe (52784) Debug Output: TForm3.AlignControls - dpi : 144 Process HighDPIDragTest.exe (52784) Debug Output: TForm3.Paint Process HighDPIDragTest.exe (52784) Debug Output: TMyPanel.AlignControls - dpi : 144 Process HighDPIDragTest.exe (52784) Debug Output: TForm3.WMSize- before inherited - dpi : 144 Process HighDPIDragTest.exe (52784) Debug Output: TForm3.AlignControls - dpi : 144 Process HighDPIDragTest.exe (52784) Debug Output: TForm3.WMSize - after inherited Process HighDPIDragTest.exe (52784) Debug Output: TForm3.WMSize- before inherited - dpi : 144 Process HighDPIDragTest.exe (52784) Debug Output: TForm3.AlignControls - dpi : 144 Process HighDPIDragTest.exe (52784) Debug Output: TMyPanel.AlignControls - dpi : 144 Process HighDPIDragTest.exe (52784) Debug Output: TForm3.WMSize - after inherited Process HighDPIDragTest.exe (52784) Debug Output: TForm3.AlignControls - dpi : 144 Process HighDPIDragTest.exe (52784) Debug Output: TForm3.Paint Process HighDPIDragTest.exe (52784) Debug Output: TForm3.Paint Process HighDPIDragTest.exe (52784) Debug Output: TForm3.Paint Process HighDPIDragTest.exe (52784) Debug Output: TMyPanel.Paint Process HighDPIDragTest.exe (52784) All that painting doesn't make it look any prettier 🙄 This doesn't look to be something that can be fixed with a patch or detour etc, it's going to take a rewrite of swathes of the vcl code Share this post Link to post
Attila Kovacs 631 Posted March 24, 2021 @Vincent Parrett Could you check if this superfluous invalidate in Vcl.Controls has an impact on it? I forgot the details on it. if Message.Msg = WM_UPDATEUISTATE then Invalidate; // Ensure control is repainted Share this post Link to post
balabuev 102 Posted March 24, 2021 33 minutes ago, Attila Kovacs said: Could you check if this superfluous invalidate in Vcl.Controls has an impact on it? I forgot the details on it. In my demo on a form full of different controls this line is not executed at all. 1 Share this post Link to post
everybyte 7 Posted March 24, 2021 (edited) We used a workaround in our application to make a smooth transition from one monitor to another. I think the problem is not so much as refresh time but the fact that refresh kicks in when crossing monitor boundary. As a result user cannot continue moving the form (for refresh period) and this does not feel right. For smooth transition we detect the start of crossing using WM_ENTERSIZEMOVE and WM_MOVING. At that moment we ensure that the form does not update (e.g. WM_SETREDRAW) . Once movement is finished (WM_EXITSIZEMOVE) we let the form update. Still a lot of code gets executed even without refresh, so an additional trick was helpful. In our case the content of the MainForm was on an embedded form, so on crossing we set embedded form invisible and without parent. In that case it was not scaled so we avoided this on crossing. After crossing was finished we reinstated content visibility and the parent, which finished the scaling. To improve visual outlook we captured content onto scaled TImage and put it on top of the MainForm. As a result only TImage gets quickly scaled on crossing and provides (possibly blurred) visual feedback until content finishes scaling and is ready to show. Edited March 24, 2021 by everybyte 3 3 Share this post Link to post
balabuev 102 Posted March 25, 2021 (edited) My point is that from all things, happening during rescaling of a form, the most resource consuming are SetWindowPos calls, which are called from SetBounds, which are themselfs called from different places, including AlignControls. Drawing and especially async invalidation takes much less resources and time, imho. So, when we speak about form rescaling performance, we can denote it as O(n), where n - is mostly the number of SetWindowPos calls. To trace how many times SetWindowPos is actually called we can use WM_WINDOWPOSCHANGED event handlers on child controls. So, given very simple example with a single TPanel control, aligned with alClient on a form, I see three SetWindowPos on each dpi boundary cross: procedure TPanel.WMWindowPosChanged(var M: TWMWindowPosChanged); var cr: TRect; begin if (M.WindowPos.flags and SWP_NOSIZE) = 0 then begin Winapi.Windows.GetWindowRect(Handle, cr); OutputDebugString(PChar('WMWindowPosChanged: ' + cr.Width.ToString + ',' + cr.Height.ToString)); end; inherited; end; As seen from the events log the child panel is repositioned three times, and each time its size is set to different value: 638 * 380 510 * 304 640 * 382 So, in this particular case three times more work is done, than it actually required. Test project: dpi_test.zip PS: Looking more generally at this issue I have to conclude that layouting should be asynchronous. The concept of async layouting is a some kind of replacement of the global BeginUpdate/EndUpdate mentioned earlier. But, this will be too big and breaking change for VCL. And moreover, this is almost impossible for native Windows controls, such as TEdit, TListBox, etc. Edited March 25, 2021 by balabuev 1 Share this post Link to post
timfrost 79 Posted March 25, 2021 After some of my VCL applications, I find the worst performer for repaints when dragging between different resolution screens is Regedit. Share this post Link to post
Berocoder 24 Posted March 28, 2021 So most users here think VCL have poor implementaton for DPI on monitor changes. So what about other platforms on windows. Is it better handled in C#. Java, QT etc? Share this post Link to post
Guest Posted March 28, 2021 as I said before: Quote ONLY A REMASTER CODE WILL SAVE THE DELPHI ... MAYBE, ALL RAD STUDIO hug Share this post Link to post
pyscripter 694 Posted March 28, 2021 (edited) If Embarcadero wants to produce a per-monitor DPI-aware IDE (expected soon) they will have to deal with this issue. Let's see what they come up with. Edited March 28, 2021 by pyscripter Share this post Link to post
Fritzew 51 Posted March 28, 2021 21 minutes ago, Berocoder said: Delphi IDE is built with Delphi? In short: Yes Share this post Link to post
Berocoder 24 Posted March 28, 2021 Well in that case it would be the ultimate test of VCL DPI handling.😊 Share this post Link to post
Vincent Parrett 764 Posted March 28, 2021 2 hours ago, Berocoder said: Well in that case it would be the ultimate test of VCL DPI handling.😊 You would think so, however, VCL Styles were added in XE2 and I think it wasn't until 10.3 they were added to the IDE, and even then the IDE is using custom themes (notice we can't just select any vcl theme). So they don't have a great track record of dogfooding new features. Share this post Link to post
Vincent Parrett 764 Posted March 29, 2021 On 3/24/2021 at 2:18 PM, Darian Miller said: set ParentFont := True in child controls Just a word of warning about setting ParentFont := true - I had ParentFont := true on a base form class and this completely disabled scaling on all the descendant forms! This just took me an hour of messing around to reproduce in a test project. I have no idea why ParentFont was set on that form - the project is 20yrs old and I've changed version control several times over the years so couldn't find the commit where that was set. Not sure if this is expected behaviour on forms or a bug. Share this post Link to post
pyscripter 694 Posted March 29, 2021 6 hours ago, Vincent Parrett said: Just a word of warning about setting ParentFont := true I am sure you know but, the form should not have ParentFont := True (disables per monitor scaling) but controls within the form could and should. Share this post Link to post
Vincent Parrett 764 Posted March 29, 2021 1 hour ago, pyscripter said: I am sure you know but, the form should not have ParentFont := True (disables per monitor scaling) but controls within the form could and should. Yes, the project started in Delphi 5, been upgraded a few times over the years, so not sure when that property was set but it did take a while to figure out what was going on. Share this post Link to post
pyscripter 694 Posted July 11, 2023 On 3/24/2021 at 2:07 AM, Vincent Parrett said: I tried to fudge a BeginUpdate/EndUpdate with this : This is an old thread, but I have just noticed that Delphi 11 deals with this issue by calling the new LockDrawing/UnlockDrawing wrapper around WM_SETREDRAW in DoBeforeMonitorDpiChanged/DoAfterMonitorDpiChanged. They have also fixed https://quality.embarcadero.com/browse/RSP-30931. It seems to work reasonably well now. Share this post Link to post
Vincent Parrett 764 Posted July 12, 2023 On 7/12/2023 at 2:47 AM, pyscripter said: It seems to work reasonably well now. Yes, works about as well as it did with my hack, it's essentially doing the same thing. Still nowhere near as smooth as other applications though. Share this post Link to post