From 43ab079260c83acfe0eb6e3609683a09fa35a51f Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 2 Jun 2019 15:47:01 +0300 Subject: [PATCH] Some hacks to reduce left/top resize jitter --- .../Platform/ITopLevelWithWin32JitterHacks.cs | 13 +++++++ src/Avalonia.Controls/TopLevel.cs | 9 +++++ .../Rendering/DeferredRenderer.cs | 18 ++++++++- src/Avalonia.Visuals/Rendering/IRenderer.cs | 2 + .../Rendering/ImmediateRenderer.cs | 5 +++ .../Interop/UnmanagedMethods.cs | 30 +++++++++------ src/Windows/Avalonia.Win32/WindowImpl.cs | 38 +++++++++++++++++-- 7 files changed, 98 insertions(+), 17 deletions(-) create mode 100644 src/Avalonia.Controls/Platform/ITopLevelWithWin32JitterHacks.cs diff --git a/src/Avalonia.Controls/Platform/ITopLevelWithWin32JitterHacks.cs b/src/Avalonia.Controls/Platform/ITopLevelWithWin32JitterHacks.cs new file mode 100644 index 0000000000..a11de89558 --- /dev/null +++ b/src/Avalonia.Controls/Platform/ITopLevelWithWin32JitterHacks.cs @@ -0,0 +1,13 @@ +using System; + +namespace Avalonia.Controls.Platform +{ + /// + /// This file is needed to represent how awesome Windows is + /// See https://stackoverflow.com/questions/53000291/how-to-smooth-ugly-jitter-flicker-jumping-when-resizing-windows-especially-drag for more details + /// + public interface ITopLevelWithWin32JitterHacks + { + Action Win32JitterLastFrameRepaint { get; set; } + } +} diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 21bd0e4e57..1dcce3dcd7 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using System.Reactive.Linq; using Avalonia.Controls.Notifications; +using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Input.Raw; @@ -114,6 +115,9 @@ namespace Avalonia.Controls impl.Resized = HandleResized; impl.ScalingChanged = HandleScalingChanged; + if (impl is ITopLevelWithWin32JitterHacks jitterHacks) + jitterHacks.Win32JitterLastFrameRepaint += HandleWin32JitterRepaint; + _keyboardNavigationHandler?.SetOwner(this); _accessKeyHandler?.SetOwner(this); styler?.ApplyStyles(this); @@ -271,6 +275,11 @@ namespace Avalonia.Controls Renderer?.Paint(rect); } + protected virtual void HandleWin32JitterRepaint() + { + Renderer?.RepaintLastFrameIfExists(); + } + /// /// Handles a closed notification from . /// diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 0d077d2a3a..0ebbba66a6 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -31,6 +31,7 @@ namespace Avalonia.Rendering private volatile IRef _scene; private DirtyVisuals _dirty; private IRef _overlay; + private IRef _lastRenderedScene; private int _lastSceneId = -1; private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects(); private IRef _currentDraw; @@ -129,6 +130,9 @@ namespace Avalonia.Rendering var scene = _scene; _scene = null; scene?.Dispose(); + scene = _lastRenderedScene; + _lastRenderedScene = null; + scene?.Dispose(); } Stop(); @@ -176,6 +180,11 @@ namespace Avalonia.Rendering Render(true); } + public void RepaintLastFrameIfExists() + { + Render(true, true); + } + /// public void Resized(Size size) { @@ -229,7 +238,7 @@ namespace Avalonia.Rendering internal void UnitTestRender() => Render(false); - private void Render(bool forceComposite) + private void Render(bool forceComposite, bool doNotUpdateScene = false) { using (var l = _lock.TryLock()) { @@ -256,12 +265,17 @@ namespace Avalonia.Rendering } - var (scene, updated) = UpdateRenderLayersAndConsumeSceneIfNeeded(GetContext); + var (scene, updated) = + doNotUpdateScene ? + (_lastRenderedScene?.Clone(), false) : + UpdateRenderLayersAndConsumeSceneIfNeeded(GetContext); using (scene) { if (scene?.Item != null) { + _lastRenderedScene?.Dispose(); + _lastRenderedScene = scene.Clone(); var overlay = DrawDirtyRects || DrawFps; if (DrawDirtyRects) _dirtyRectsDisplay.Tick(); diff --git a/src/Avalonia.Visuals/Rendering/IRenderer.cs b/src/Avalonia.Visuals/Rendering/IRenderer.cs index 36a1f7d220..bcdba81dd1 100644 --- a/src/Avalonia.Visuals/Rendering/IRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/IRenderer.cs @@ -62,6 +62,8 @@ namespace Avalonia.Rendering /// The dirty rectangle. void Paint(Rect rect); + void RepaintLastFrameIfExists(); + /// /// Starts the renderer. /// diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs index 21129e38af..b7d79d66aa 100644 --- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs @@ -88,6 +88,11 @@ namespace Avalonia.Rendering SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect)); } + public void RepaintLastFrameIfExists() + { + // No-op, we don't have a saved frame + } + /// public void Resized(Size size) { diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index bc7fc1c9fa..a770089694 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1235,23 +1235,31 @@ namespace Avalonia.Win32.Interop [StructLayout(LayoutKind.Sequential)] public struct WINDOWPOS { - public IntPtr hwnd; - public IntPtr hwndInsertAfter; - public int x; - public int y; - public int cx; - public int cy; + public IntPtr hWndInsertAfter; + public IntPtr hWnd; + public int X; + public int Y; + public int CX; + public int CY; public uint flags; } [StructLayout(LayoutKind.Sequential)] - public struct NCCALCSIZE_PARAMS + public struct NCCALSIZEPARAMS_ARGS { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - public RECT[] rgrc; - public WINDOWPOS lppos; + public RECT NewRect; + public RECT OldRect; + public RECT OldClientRect; + public WINDOWPOS WindowPos; } - + + public struct NCCALSIZEPARAMS_RETURN + { + public RECT NewClientRect; + public RECT DstRect; + public RECT SrcRect; + } + public struct TRACKMOUSEEVENT { public int cbSize; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 475f7da6b7..98b6547fae 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -8,6 +8,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reactive.Disposables; using System.Runtime.InteropServices; using Avalonia.Controls; +using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.OpenGL; @@ -20,7 +21,8 @@ using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia.Win32 { - public class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo + public unsafe class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo, + ITopLevelWithWin32JitterHacks { private static readonly List s_instances = new List(); @@ -84,7 +86,10 @@ namespace Avalonia.Win32 public Action Input { get; set; } + public Action Paint { get; set; } + + public Action Win32JitterLastFrameRepaint { get; set; } public Action Resized { get; set; } @@ -459,12 +464,26 @@ namespace Avalonia.Win32 return IntPtr.Zero; + case WindowsMessage.WM_NCCALCSIZE: - if (ToInt32(wParam) == 1 && !_decorated) + if (ToInt32(wParam) == 1) { - return IntPtr.Zero; + var ncargs = *(NCCALSIZEPARAMS_ARGS*)lParam; + var rv = (NCCALSIZEPARAMS_RETURN*)lParam; + + if (_decorated) + DefWindowProc(_hwnd, msg, wParam, lParam); + else + { + var x = rv->NewClientRect.left; + var y = rv->NewClientRect.top; + rv->DstRect = rv->SrcRect = new RECT {left = x, top = y, right = x + 1, bottom = y + 1}; + } + + return new IntPtr(0x0400); } break; + case UnmanagedMethods.WindowsMessage.WM_CLOSE: bool? preventClosing = Closing?.Invoke(); @@ -661,6 +680,11 @@ namespace Avalonia.Win32 return IntPtr.Zero; } break; + case WindowsMessage.WM_ERASEBKGND: + + if (_decorated) + return new IntPtr(1); + break; case WindowsMessage.WM_NCACTIVATE: if (!_decorated) @@ -690,6 +714,11 @@ namespace Avalonia.Win32 { // Do nothing here, just block until the pending frame render is completed on the render thread } + + // This is needed to fool Windows™®Ⓒ to think that we've reacted to resize in time, + // so it won't do atrocities to our window + Win32JitterLastFrameRepaint?.Invoke(); + var size = (UnmanagedMethods.SizeCommand)wParam; if (Resized != null && @@ -697,6 +726,7 @@ namespace Avalonia.Win32 size == UnmanagedMethods.SizeCommand.Maximized)) { var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16); + Resized(clientSize / Scaling); } @@ -782,7 +812,7 @@ namespace Avalonia.Win32 UnmanagedMethods.WNDCLASSEX wndClassEx = new UnmanagedMethods.WNDCLASSEX { cbSize = Marshal.SizeOf(), - style = (int)(ClassStyles.CS_OWNDC | ClassStyles.CS_HREDRAW | ClassStyles.CS_VREDRAW), // Unique DC helps with performance when using Gpu based rendering + style = (int)(ClassStyles.CS_OWNDC),// | ClassStyles.CS_HREDRAW | ClassStyles.CS_VREDRAW), // Unique DC helps with performance when using Gpu based rendering lpfnWndProc = _wndProcDelegate, hInstance = UnmanagedMethods.GetModuleHandle(null), hCursor = DefaultCursor,