Browse Source

Some hacks to reduce left/top resize jitter

win32-jitter-hack
Nikita Tsukanov 7 years ago
parent
commit
43ab079260
  1. 13
      src/Avalonia.Controls/Platform/ITopLevelWithWin32JitterHacks.cs
  2. 9
      src/Avalonia.Controls/TopLevel.cs
  3. 18
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  4. 2
      src/Avalonia.Visuals/Rendering/IRenderer.cs
  5. 5
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  6. 30
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  7. 38
      src/Windows/Avalonia.Win32/WindowImpl.cs

13
src/Avalonia.Controls/Platform/ITopLevelWithWin32JitterHacks.cs

@ -0,0 +1,13 @@
using System;
namespace Avalonia.Controls.Platform
{
/// <summary>
/// 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
/// </summary>
public interface ITopLevelWithWin32JitterHacks
{
Action Win32JitterLastFrameRepaint { get; set; }
}
}

9
src/Avalonia.Controls/TopLevel.cs

@ -5,6 +5,7 @@ using System;
using System.Linq; using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Controls.Notifications; using Avalonia.Controls.Notifications;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
@ -114,6 +115,9 @@ namespace Avalonia.Controls
impl.Resized = HandleResized; impl.Resized = HandleResized;
impl.ScalingChanged = HandleScalingChanged; impl.ScalingChanged = HandleScalingChanged;
if (impl is ITopLevelWithWin32JitterHacks jitterHacks)
jitterHacks.Win32JitterLastFrameRepaint += HandleWin32JitterRepaint;
_keyboardNavigationHandler?.SetOwner(this); _keyboardNavigationHandler?.SetOwner(this);
_accessKeyHandler?.SetOwner(this); _accessKeyHandler?.SetOwner(this);
styler?.ApplyStyles(this); styler?.ApplyStyles(this);
@ -271,6 +275,11 @@ namespace Avalonia.Controls
Renderer?.Paint(rect); Renderer?.Paint(rect);
} }
protected virtual void HandleWin32JitterRepaint()
{
Renderer?.RepaintLastFrameIfExists();
}
/// <summary> /// <summary>
/// Handles a closed notification from <see cref="ITopLevelImpl.Closed"/>. /// Handles a closed notification from <see cref="ITopLevelImpl.Closed"/>.
/// </summary> /// </summary>

18
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -31,6 +31,7 @@ namespace Avalonia.Rendering
private volatile IRef<Scene> _scene; private volatile IRef<Scene> _scene;
private DirtyVisuals _dirty; private DirtyVisuals _dirty;
private IRef<IRenderTargetBitmapImpl> _overlay; private IRef<IRenderTargetBitmapImpl> _overlay;
private IRef<Scene> _lastRenderedScene;
private int _lastSceneId = -1; private int _lastSceneId = -1;
private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects(); private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects();
private IRef<IDrawOperation> _currentDraw; private IRef<IDrawOperation> _currentDraw;
@ -129,6 +130,9 @@ namespace Avalonia.Rendering
var scene = _scene; var scene = _scene;
_scene = null; _scene = null;
scene?.Dispose(); scene?.Dispose();
scene = _lastRenderedScene;
_lastRenderedScene = null;
scene?.Dispose();
} }
Stop(); Stop();
@ -176,6 +180,11 @@ namespace Avalonia.Rendering
Render(true); Render(true);
} }
public void RepaintLastFrameIfExists()
{
Render(true, true);
}
/// <inheritdoc/> /// <inheritdoc/>
public void Resized(Size size) public void Resized(Size size)
{ {
@ -229,7 +238,7 @@ namespace Avalonia.Rendering
internal void UnitTestRender() => Render(false); internal void UnitTestRender() => Render(false);
private void Render(bool forceComposite) private void Render(bool forceComposite, bool doNotUpdateScene = false)
{ {
using (var l = _lock.TryLock()) 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) using (scene)
{ {
if (scene?.Item != null) if (scene?.Item != null)
{ {
_lastRenderedScene?.Dispose();
_lastRenderedScene = scene.Clone();
var overlay = DrawDirtyRects || DrawFps; var overlay = DrawDirtyRects || DrawFps;
if (DrawDirtyRects) if (DrawDirtyRects)
_dirtyRectsDisplay.Tick(); _dirtyRectsDisplay.Tick();

2
src/Avalonia.Visuals/Rendering/IRenderer.cs

@ -62,6 +62,8 @@ namespace Avalonia.Rendering
/// <param name="rect">The dirty rectangle.</param> /// <param name="rect">The dirty rectangle.</param>
void Paint(Rect rect); void Paint(Rect rect);
void RepaintLastFrameIfExists();
/// <summary> /// <summary>
/// Starts the renderer. /// Starts the renderer.
/// </summary> /// </summary>

5
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@ -88,6 +88,11 @@ namespace Avalonia.Rendering
SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect)); SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect));
} }
public void RepaintLastFrameIfExists()
{
// No-op, we don't have a saved frame
}
/// <inheritdoc/> /// <inheritdoc/>
public void Resized(Size size) public void Resized(Size size)
{ {

30
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -1235,23 +1235,31 @@ namespace Avalonia.Win32.Interop
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public struct WINDOWPOS public struct WINDOWPOS
{ {
public IntPtr hwnd; public IntPtr hWndInsertAfter;
public IntPtr hwndInsertAfter; public IntPtr hWnd;
public int x; public int X;
public int y; public int Y;
public int cx; public int CX;
public int cy; public int CY;
public uint flags; public uint flags;
} }
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public struct NCCALCSIZE_PARAMS public struct NCCALSIZEPARAMS_ARGS
{ {
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public RECT NewRect;
public RECT[] rgrc; public RECT OldRect;
public WINDOWPOS lppos; public RECT OldClientRect;
public WINDOWPOS WindowPos;
} }
public struct NCCALSIZEPARAMS_RETURN
{
public RECT NewClientRect;
public RECT DstRect;
public RECT SrcRect;
}
public struct TRACKMOUSEEVENT public struct TRACKMOUSEEVENT
{ {
public int cbSize; public int cbSize;

38
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -8,6 +8,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.OpenGL; using Avalonia.OpenGL;
@ -20,7 +21,8 @@ using static Avalonia.Win32.Interop.UnmanagedMethods;
namespace Avalonia.Win32 namespace Avalonia.Win32
{ {
public class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo public unsafe class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo,
ITopLevelWithWin32JitterHacks
{ {
private static readonly List<WindowImpl> s_instances = new List<WindowImpl>(); private static readonly List<WindowImpl> s_instances = new List<WindowImpl>();
@ -84,7 +86,10 @@ namespace Avalonia.Win32
public Action<RawInputEventArgs> Input { get; set; } public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; } public Action<Rect> Paint { get; set; }
public Action Win32JitterLastFrameRepaint { get; set; }
public Action<Size> Resized { get; set; } public Action<Size> Resized { get; set; }
@ -459,12 +464,26 @@ namespace Avalonia.Win32
return IntPtr.Zero; return IntPtr.Zero;
case WindowsMessage.WM_NCCALCSIZE: 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; break;
case UnmanagedMethods.WindowsMessage.WM_CLOSE: case UnmanagedMethods.WindowsMessage.WM_CLOSE:
bool? preventClosing = Closing?.Invoke(); bool? preventClosing = Closing?.Invoke();
@ -661,6 +680,11 @@ namespace Avalonia.Win32
return IntPtr.Zero; return IntPtr.Zero;
} }
break; break;
case WindowsMessage.WM_ERASEBKGND:
if (_decorated)
return new IntPtr(1);
break;
case WindowsMessage.WM_NCACTIVATE: case WindowsMessage.WM_NCACTIVATE:
if (!_decorated) 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 // 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; var size = (UnmanagedMethods.SizeCommand)wParam;
if (Resized != null && if (Resized != null &&
@ -697,6 +726,7 @@ namespace Avalonia.Win32
size == UnmanagedMethods.SizeCommand.Maximized)) size == UnmanagedMethods.SizeCommand.Maximized))
{ {
var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16); var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16);
Resized(clientSize / Scaling); Resized(clientSize / Scaling);
} }
@ -782,7 +812,7 @@ namespace Avalonia.Win32
UnmanagedMethods.WNDCLASSEX wndClassEx = new UnmanagedMethods.WNDCLASSEX UnmanagedMethods.WNDCLASSEX wndClassEx = new UnmanagedMethods.WNDCLASSEX
{ {
cbSize = Marshal.SizeOf<UnmanagedMethods.WNDCLASSEX>(), cbSize = Marshal.SizeOf<UnmanagedMethods.WNDCLASSEX>(),
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, lpfnWndProc = _wndProcDelegate,
hInstance = UnmanagedMethods.GetModuleHandle(null), hInstance = UnmanagedMethods.GetModuleHandle(null),
hCursor = DefaultCursor, hCursor = DefaultCursor,

Loading…
Cancel
Save