From e190cb4e8c0573430de78bf3edfc14951d61de1b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 18 Oct 2016 01:15:06 +0200 Subject: [PATCH] Use multimedia timer on Win32 for render loop. --- .../Rendering/DefaultRenderLoop.cs | 7 ++-- src/Avalonia.SceneGraph/Rendering/Renderer.cs | 12 +++++++ .../Avalonia.Win32/Avalonia.Win32.csproj | 1 + .../Interop/UnmanagedMethods.cs | 10 ++++-- src/Windows/Avalonia.Win32/RenderLoop.cs | 35 +++++++++++++++++++ src/Windows/Avalonia.Win32/Win32Platform.cs | 2 +- 6 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 src/Windows/Avalonia.Win32/RenderLoop.cs diff --git a/src/Avalonia.SceneGraph/Rendering/DefaultRenderLoop.cs b/src/Avalonia.SceneGraph/Rendering/DefaultRenderLoop.cs index 5210b0a98f..a4e8e6f0c2 100644 --- a/src/Avalonia.SceneGraph/Rendering/DefaultRenderLoop.cs +++ b/src/Avalonia.SceneGraph/Rendering/DefaultRenderLoop.cs @@ -65,24 +65,25 @@ namespace Avalonia.Rendering /// protected void Start() { - _subscription = StartCore(); + _subscription = StartCore(InternalTick); } /// /// Provides the implementation of starting the timer. /// + /// The method to call on each tick. /// /// This can be overridden by platform implementations to use a specialized timer /// implementation. /// - protected virtual IDisposable StartCore() + protected virtual IDisposable StartCore(Action tick) { if (_threading == null) { _threading = AvaloniaLocator.Current.GetService(); } - return _threading.StartTimer(TimeSpan.FromSeconds(1.0 / FramesPerSecond), InternalTick); + return _threading.StartTimer(TimeSpan.FromSeconds(1.0 / FramesPerSecond), tick); } /// diff --git a/src/Avalonia.SceneGraph/Rendering/Renderer.cs b/src/Avalonia.SceneGraph/Rendering/Renderer.cs index bacef30e48..121c30fecd 100644 --- a/src/Avalonia.SceneGraph/Rendering/Renderer.cs +++ b/src/Avalonia.SceneGraph/Rendering/Renderer.cs @@ -13,6 +13,7 @@ namespace Avalonia.Rendering private IRenderRoot _root; private IRenderTarget _renderTarget; private bool _dirty; + private bool _rendering; public void Attach(IRenderRoot root, IRenderLoop renderLoop) { @@ -35,6 +36,11 @@ namespace Avalonia.Rendering public void Render(Rect rect) { + if (_rendering) + { + return; + } + if (_renderTarget == null) { _renderTarget = _root.CreateRenderTarget(); @@ -42,6 +48,7 @@ namespace Avalonia.Rendering try { + _rendering = true; _renderTarget.Render(_root); } catch (RenderTargetCorruptedException ex) @@ -50,6 +57,11 @@ namespace Avalonia.Rendering _renderTarget.Dispose(); _renderTarget = null; } + finally + { + _rendering = false; + _dirty = false; + } } private void OnRenderLoopTick(object sender, EventArgs e) diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index c4bb5c8a25..51ad5ce05e 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -70,6 +70,7 @@ + diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index ae030df49a..d115b35a23 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -23,6 +23,8 @@ namespace Avalonia.Win32.Interop public delegate void TimerProc(IntPtr hWnd, uint uMsg, IntPtr nIDEvent, uint dwTime); + public delegate void TimeCallback(uint uTimerID, uint uMsg, UIntPtr dwUser, UIntPtr dw1, UIntPtr dw2); + public delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); public enum Cursor @@ -705,8 +707,6 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern IntPtr LoadCursor(IntPtr hInstance, IntPtr lpCursorName); - - [DllImport("user32.dll")] public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg); @@ -738,6 +738,12 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, ShowWindowCommand nCmdShow); + [DllImport("Winmm.dll")] + public static extern uint timeKillEvent(uint uTimerID); + + [DllImport("Winmm.dll")] + public static extern uint timeSetEvent(uint uDelay, uint uResolution, TimeCallback lpTimeProc, UIntPtr dwUser, uint fuEvent); + [DllImport("user32.dll")] public static extern int ToUnicode( uint virtualKeyCode, diff --git a/src/Windows/Avalonia.Win32/RenderLoop.cs b/src/Windows/Avalonia.Win32/RenderLoop.cs new file mode 100644 index 0000000000..7d7befcc33 --- /dev/null +++ b/src/Windows/Avalonia.Win32/RenderLoop.cs @@ -0,0 +1,35 @@ +using System; +using System.Reactive.Disposables; +using Avalonia.Rendering; +using Avalonia.Win32.Interop; + +namespace Avalonia.Win32 +{ + internal class RenderLoop : DefaultRenderLoop + { + private UnmanagedMethods.TimeCallback timerDelegate; + + public RenderLoop(int framesPerSecond) + : base(framesPerSecond) + { + } + + protected override IDisposable StartCore(Action tick) + { + timerDelegate = (id, uMsg, user, dw1, dw2) => tick(); + + var handle = UnmanagedMethods.timeSetEvent( + (uint)(1000 / FramesPerSecond), + 0, + timerDelegate, + UIntPtr.Zero, + 1); + + return Disposable.Create(() => + { + timerDelegate = null; + UnmanagedMethods.timeKillEvent(handle); + }); + } + } +} diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 9b65e64e00..b437100cee 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -66,7 +66,7 @@ namespace Avalonia.Win32 .Bind().ToConstant(WindowsMouseDevice.Instance) .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) - .Bind().ToConstant(new DefaultRenderLoop(60)) + .Bind().ToConstant(new RenderLoop(60)) .Bind().ToSingleton() .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance);