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.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();
}
/// <summary>
/// Handles a closed notification from <see cref="ITopLevelImpl.Closed"/>.
/// </summary>

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

@ -31,6 +31,7 @@ namespace Avalonia.Rendering
private volatile IRef<Scene> _scene;
private DirtyVisuals _dirty;
private IRef<IRenderTargetBitmapImpl> _overlay;
private IRef<Scene> _lastRenderedScene;
private int _lastSceneId = -1;
private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects();
private IRef<IDrawOperation> _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);
}
/// <inheritdoc/>
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();

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

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

5
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
}
/// <inheritdoc/>
public void Resized(Size size)
{

30
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;

38
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<WindowImpl> s_instances = new List<WindowImpl>();
@ -84,7 +86,10 @@ namespace Avalonia.Win32
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action Win32JitterLastFrameRepaint { get; set; }
public Action<Size> 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<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,
hInstance = UnmanagedMethods.GetModuleHandle(null),
hCursor = DefaultCursor,

Loading…
Cancel
Save