From 81bd7df8d7eebc85a7210fcbc73c286adcb5d41d Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 21 Oct 2017 01:12:02 +0300 Subject: [PATCH 1/5] [MONOMAC] Initial implementation of deferred rendering support --- .../Avalonia.MonoMac/EmulatedFramebuffer.cs | 98 ++++++++++++++++--- src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs | 43 +++++++- src/OSX/Avalonia.MonoMac/RenderLoop.cs | 33 +++++++ src/OSX/Avalonia.MonoMac/ThreadingUtils.cs | 36 +++++++ src/OSX/Avalonia.MonoMac/TopLevelImpl.cs | 61 +++++++++++- 5 files changed, 252 insertions(+), 19 deletions(-) create mode 100644 src/OSX/Avalonia.MonoMac/RenderLoop.cs create mode 100644 src/OSX/Avalonia.MonoMac/ThreadingUtils.cs diff --git a/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs b/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs index 1ecfa0eb7d..02ead65281 100644 --- a/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs +++ b/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs @@ -2,17 +2,20 @@ using Avalonia.Platform; using MonoMac.AppKit; using System.Runtime.InteropServices; +using Avalonia.Threading; using MonoMac.CoreGraphics; namespace Avalonia.MonoMac { class EmulatedFramebuffer : ILockedFramebuffer { + private readonly TopLevelImpl.TopLevelView _view; private readonly CGSize _logicalSize; - public EmulatedFramebuffer(NSView view) + + public EmulatedFramebuffer(TopLevelImpl.TopLevelView view, CGSize logicalSize, CGSize pixelSize) { - _logicalSize = view.Frame.Size; - var pixelSize = view.ConvertSizeToBacking(_logicalSize); + _view = view; + _logicalSize = logicalSize; Width = (int)pixelSize.Width; Height = (int)pixelSize.Height; RowBytes = Width * 4; @@ -21,25 +24,78 @@ namespace Avalonia.MonoMac Address = Marshal.AllocHGlobal(Height * RowBytes); } + class DeferredRenderingHelper : IDisposable + { + private readonly NSView _view; + public void Dispose() + { + _view.NonUIUnlockFocus(); + } + + public DeferredRenderingHelper(NSView view) + { + _view = view; + } + } + + DeferredRenderingHelper SetupWindowContext() + { + if (!_view.NonUILockFocusIfCanDraw()) + return null; + return new DeferredRenderingHelper(_view); + } + public void Dispose() { if (Address == IntPtr.Zero) return; var nfo = (int) CGBitmapFlags.ByteOrder32Big | (int) CGImageAlphaInfo.PremultipliedLast; - using (var colorSpace = CGColorSpace.CreateDeviceRGB()) - using (var bContext = new CGBitmapContext(Address, Width, Height, 8, Width * 4, - colorSpace, (CGImageAlphaInfo) nfo)) - using (var image = bContext.ToImage()) - using (var nscontext = NSGraphicsContext.CurrentContext) - using (var context = nscontext.GraphicsPort) + DeferredRenderingHelper deferred = null; + var isDeferred = !Dispatcher.UIThread.CheckAccess(); + if (!isDeferred || (deferred = SetupWindowContext()) != null) { - context.SetFillColor(255, 255, 255, 255); - context.FillRect(new CGRect(default(CGPoint), _logicalSize)); - context.DrawImage(new CGRect(default(CGPoint), _logicalSize), image); + CGImage image = null; + try + { + lock (_view.SyncRoot) + { + using (deferred) + using (var colorSpace = CGColorSpace.CreateDeviceRGB()) + using (var bContext = new CGBitmapContext(Address, Width, Height, 8, Width * 4, + colorSpace, (CGImageAlphaInfo) nfo)) + { + if (!isDeferred || deferred != null) + { + using (var nscontext = NSGraphicsContext.CurrentContext) + using (var context = nscontext.GraphicsPort) + { + image = bContext.ToImage(); + context.SetFillColor(255, 255, 255, 255); + context.FillRect(new CGRect(default(CGPoint), _view.LogicalSize)); + context.TranslateCTM(0, _view.LogicalSize.Height - _logicalSize.Height); + context.DrawImage(new CGRect(default(CGPoint), _logicalSize), image); + context.Flush(); + nscontext.FlushGraphics(); + } + } + } + } + } + finally + { + if (image != null) + { + if (deferred == null) + image.Dispose(); + else + _view.SetBackBufferImage(new SavedImage(image, _logicalSize)); + } + } } Marshal.FreeHGlobal(Address); Address = IntPtr.Zero; + } public IntPtr Address { get; private set; } @@ -49,4 +105,22 @@ namespace Avalonia.MonoMac public Vector Dpi { get; } public PixelFormat Format { get; } } + + class SavedImage : IDisposable + { + public CGImage Image { get; private set; } + public CGSize LogicalSize { get; } + + public SavedImage(CGImage image, CGSize logicalSize) + { + Image = image; + LogicalSize = logicalSize; + } + + public void Dispose() + { + Image?.Dispose(); + Image = null; + } + } } \ No newline at end of file diff --git a/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs b/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs index 0deea7fb44..c11ac0d6ee 100644 --- a/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs +++ b/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs @@ -1,10 +1,13 @@ using System; +using System.Threading; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Platform; using Avalonia.Rendering; using MonoMac.AppKit; +using MonoMac.Foundation; +using MonoMac.ObjCRuntime; namespace Avalonia.MonoMac { @@ -16,9 +19,11 @@ namespace Avalonia.MonoMac internal static NSApplication App; private static bool s_monoMacInitialized; private static bool s_showInDock = true; + private static IRenderLoop s_renderLoop; void DoInitialize() { + InitializeMonoMac(); AvaloniaLocator.CurrentMutable .Bind().ToTransient() .Bind().ToSingleton() @@ -27,9 +32,8 @@ namespace Avalonia.MonoMac .Bind().ToConstant(this) .Bind().ToConstant(this) .Bind().ToSingleton() + .Bind().ToConstant(s_renderLoop) .Bind().ToConstant(PlatformThreadingInterface.Instance); - - InitializeMonoMac(); } public static void Initialize() @@ -39,13 +43,43 @@ namespace Avalonia.MonoMac } + + /// + /// See "Using POSIX Threads in a Cocoa Application" section here: + /// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html#//apple_ref/doc/uid/20000738-125024 + /// + class ThreadHelper : NSObject + { + private readonly AutoResetEvent _event = new AutoResetEvent(false); + [Export("doNothing")] + public void DoNothing() + { + _event.Set(); + } + + public static void InitializeCocoaThreadingLocks() + { + var helper = new ThreadHelper(); + var thread = new NSThread(helper, Selector.FromHandle(Selector.GetHandle("doNothing")), new NSObject()); + thread.Start(); + helper._event.WaitOne(); + helper._event.Dispose(); + if (!NSThread.IsMultiThreaded) + { + throw new Exception("Unable to initialize Cocoa threading"); + } + } + } + void InitializeMonoMac() { if(s_monoMacInitialized) return; NSApplication.Init(); + ThreadHelper.InitializeCocoaThreadingLocks(); App = NSApplication.SharedApplication; UpdateActivationPolicy(); + s_renderLoop = new RenderLoop(); //TODO: use CVDisplayLink s_monoMacInitialized = true; } @@ -64,6 +98,7 @@ namespace Avalonia.MonoMac } } + public static bool UseDeferredRendering { get; set; } = true; public Size DoubleClickSize => new Size(4, 4); public TimeSpan DoubleClickTime => TimeSpan.FromSeconds(NSEvent.DoubleClickInterval); @@ -87,8 +122,10 @@ namespace Avalonia { public static class MonoMacPlatformExtensions { - public static T UseMonoMac(this T builder) where T : AppBuilderBase, new() + public static T UseMonoMac(this T builder, bool? useDeferredRendering = null) where T : AppBuilderBase, new() { + if (useDeferredRendering.HasValue) + MonoMac.MonoMacPlatform.UseDeferredRendering = useDeferredRendering.Value; return builder.UseWindowingSubsystem(MonoMac.MonoMacPlatform.Initialize); } } diff --git a/src/OSX/Avalonia.MonoMac/RenderLoop.cs b/src/OSX/Avalonia.MonoMac/RenderLoop.cs new file mode 100644 index 0000000000..a6f3e9f493 --- /dev/null +++ b/src/OSX/Avalonia.MonoMac/RenderLoop.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Platform; +using Avalonia.Rendering; +using MonoMac.Foundation; + +namespace Avalonia.MonoMac +{ + //TODO: Switch to using CVDisplayLink + public class RenderLoop : IRenderLoop + { + private readonly object _lock = new object(); + private readonly IDisposable _timer; + + public RenderLoop() + { + _timer = AvaloniaLocator.Current.GetService().StartSystemTimer(new TimeSpan(0, 0, 0, 0, 1000 / 60), + () => + { + lock (_lock) + { + using (new NSAutoreleasePool()) + { + Tick?.Invoke(this, EventArgs.Empty); + } + } + }); + } + + public event EventHandler Tick; + } +} diff --git a/src/OSX/Avalonia.MonoMac/ThreadingUtils.cs b/src/OSX/Avalonia.MonoMac/ThreadingUtils.cs new file mode 100644 index 0000000000..7762d86764 --- /dev/null +++ b/src/OSX/Avalonia.MonoMac/ThreadingUtils.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Text; +using MonoMac.AppKit; +using MonoMac.ObjCRuntime; + +namespace Avalonia.MonoMac +{ + public static class ThreadingUtils + { + private static readonly IntPtr selUnlockFocusHandle = Selector.GetHandle("unlockFocus"); + private static readonly IntPtr selLockFocusIfCanDrawHandle = Selector.GetHandle("lockFocusIfCanDraw"); + private static readonly IntPtr selWindowHandle = Selector.GetHandle("window"); + private static readonly IntPtr selCanDrawHandle = Selector.GetHandle("canDraw"); + + public static bool NonUILockFocusIfCanDraw(this NSView view) + { + return Messaging.bool_objc_msgSend(view.Handle, selLockFocusIfCanDrawHandle); + } + + public static void NonUIUnlockFocus(this NSView view) + { + Messaging.void_objc_msgSend(view.Handle, selUnlockFocusHandle); + } + + public static NSWindow NonUIGetWindow(this NSView view) + { + return (NSWindow) Runtime.GetNSObject(Messaging.IntPtr_objc_msgSendSuper(view.Handle, selWindowHandle)); + } + + public static bool BaseCanDraw(this NSView view) + { + return Messaging.bool_objc_msgSendSuper(view.SuperHandle, selCanDrawHandle); + } + } +} diff --git a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs index 2744474214..ce30deea88 100644 --- a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs +++ b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs @@ -5,6 +5,7 @@ using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Rendering; +using Avalonia.Threading; using MonoMac.AppKit; using MonoMac.CoreGraphics; @@ -37,6 +38,13 @@ namespace Avalonia.MonoMac private NSTrackingArea _area; private NSCursor _cursor; + public CGSize PixelSize { get; set; } + + public CGSize LogicalSize { get; set; } + + private SavedImage _backBuffer; + public object SyncRoot { get; } = new object(); + public TopLevelView(TopLevelImpl tl) { _tl = tl; @@ -44,17 +52,50 @@ namespace Avalonia.MonoMac _keyboard = AvaloniaLocator.Current.GetService(); } + protected override void Dispose(bool disposing) + { + if (disposing) + SetBackBufferImage(null); + base.Dispose(disposing); + } + public override bool ConformsToProtocol(IntPtr protocol) { var rv = base.ConformsToProtocol(protocol); return rv; } + public override bool IsOpaque => false; + public override void DrawRect(CGRect dirtyRect) { + lock (SyncRoot) + { + if (_backBuffer != null) + { + using (var context = NSGraphicsContext.CurrentContext.GraphicsPort) + { + context.SetFillColor(255, 255, 255, 255); + context.FillRect(new CGRect(default(CGPoint), LogicalSize)); + context.TranslateCTM(0, LogicalSize.Height - _backBuffer.LogicalSize.Height); + context.DrawImage(new CGRect(default(CGPoint), _backBuffer.LogicalSize), _backBuffer.Image); + context.Flush(); + NSGraphicsContext.CurrentContext.FlushGraphics(); + } + } + } _tl.Paint?.Invoke(dirtyRect.ToAvaloniaRect()); } + public void SetBackBufferImage(SavedImage image) + { + lock (SyncRoot) + { + _backBuffer?.Dispose(); + _backBuffer = image; + } + } + [Export("viewDidChangeBackingProperties:")] public void ViewDidChangeBackingProperties() { @@ -78,7 +119,12 @@ namespace Avalonia.MonoMac public override void SetFrameSize(CGSize newSize) { - base.SetFrameSize(newSize); + lock (SyncRoot) + { + base.SetFrameSize(newSize); + LogicalSize = Frame.Size; + PixelSize = ConvertSizeToBacking(LogicalSize); + } if (_area != null) { @@ -348,9 +394,16 @@ namespace Avalonia.MonoMac View.Dispose(); } - public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root); + public IRenderer CreateRenderer(IRenderRoot root) => + MonoMacPlatform.UseDeferredRendering + ? new DeferredRenderer(root, AvaloniaLocator.Current.GetService()) + : (IRenderer) new ImmediateRenderer(root); - public void Invalidate(Rect rect) => View.SetNeedsDisplayInRect(View.Frame); + public void Invalidate(Rect rect) + { + if (!MonoMacPlatform.UseDeferredRendering) + View.SetNeedsDisplayInRect(View.Frame); + } public abstract Point PointToClient(Point point); @@ -360,6 +413,6 @@ namespace Avalonia.MonoMac public void SetInputRoot(IInputRoot inputRoot) => InputRoot = inputRoot; - public ILockedFramebuffer Lock() => new EmulatedFramebuffer(View); + public ILockedFramebuffer Lock() => new EmulatedFramebuffer(View, View.LogicalSize, View.PixelSize); } } \ No newline at end of file From 0909a613f2be4aa005152265651721392b2faccb Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 21 Oct 2017 17:36:02 +0300 Subject: [PATCH 2/5] [MONOMAC] Fixed rendering artifacts on resize with DeferredRenderer --- .../Avalonia.MonoMac/EmulatedFramebuffer.cs | 87 ++++++++++--------- src/OSX/Avalonia.MonoMac/TopLevelImpl.cs | 2 +- 2 files changed, 46 insertions(+), 43 deletions(-) diff --git a/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs b/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs index 02ead65281..713d4b0d18 100644 --- a/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs +++ b/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs @@ -11,20 +11,29 @@ namespace Avalonia.MonoMac { private readonly TopLevelImpl.TopLevelView _view; private readonly CGSize _logicalSize; + private bool _isDeferred; - public EmulatedFramebuffer(TopLevelImpl.TopLevelView view, CGSize logicalSize, CGSize pixelSize) + [DllImport("libc")] + static extern void memset(IntPtr p, int c, IntPtr size); + + public EmulatedFramebuffer(TopLevelImpl.TopLevelView view) { _view = view; - _logicalSize = logicalSize; + + _isDeferred = !Dispatcher.UIThread.CheckAccess(); + _logicalSize = _view.LogicalSize; + var pixelSize = _view.PixelSize; Width = (int)pixelSize.Width; Height = (int)pixelSize.Height; RowBytes = Width * 4; Dpi = new Vector(96 * pixelSize.Width / _logicalSize.Width, 96 * pixelSize.Height / _logicalSize.Height); Format = PixelFormat.Rgba8888; - Address = Marshal.AllocHGlobal(Height * RowBytes); + var size = Height * RowBytes; + Address = Marshal.AllocHGlobal(size); + memset(Address, 0, new IntPtr(size)); } - class DeferredRenderingHelper : IDisposable + class CocoaDrawLock : IDisposable { private readonly NSView _view; public void Dispose() @@ -32,17 +41,17 @@ namespace Avalonia.MonoMac _view.NonUIUnlockFocus(); } - public DeferredRenderingHelper(NSView view) + public CocoaDrawLock(NSView view) { _view = view; } } - DeferredRenderingHelper SetupWindowContext() + CocoaDrawLock LockCocoaDrawing() { if (!_view.NonUILockFocusIfCanDraw()) return null; - return new DeferredRenderingHelper(_view); + return new CocoaDrawLock(_view); } public void Dispose() @@ -50,51 +59,45 @@ namespace Avalonia.MonoMac if (Address == IntPtr.Zero) return; var nfo = (int) CGBitmapFlags.ByteOrder32Big | (int) CGImageAlphaInfo.PremultipliedLast; - - DeferredRenderingHelper deferred = null; - var isDeferred = !Dispatcher.UIThread.CheckAccess(); - if (!isDeferred || (deferred = SetupWindowContext()) != null) + IDisposable drawLock = LockCocoaDrawing(); + CGImage image = null; + try { - CGImage image = null; - try + using (var colorSpace = CGColorSpace.CreateDeviceRGB()) + using (var bContext = new CGBitmapContext(Address, Width, Height, 8, Width * 4, + colorSpace, (CGImageAlphaInfo)nfo)) + image = bContext.ToImage(); + lock (_view.SyncRoot) { - lock (_view.SyncRoot) + if (!_isDeferred || drawLock != null) { - using (deferred) - using (var colorSpace = CGColorSpace.CreateDeviceRGB()) - using (var bContext = new CGBitmapContext(Address, Width, Height, 8, Width * 4, - colorSpace, (CGImageAlphaInfo) nfo)) + using (var nscontext = NSGraphicsContext.CurrentContext) + using (var context = nscontext.GraphicsPort) { - if (!isDeferred || deferred != null) - { - using (var nscontext = NSGraphicsContext.CurrentContext) - using (var context = nscontext.GraphicsPort) - { - image = bContext.ToImage(); - context.SetFillColor(255, 255, 255, 255); - context.FillRect(new CGRect(default(CGPoint), _view.LogicalSize)); - context.TranslateCTM(0, _view.LogicalSize.Height - _logicalSize.Height); - context.DrawImage(new CGRect(default(CGPoint), _logicalSize), image); - context.Flush(); - nscontext.FlushGraphics(); - } - } + context.SetFillColor(255, 255, 255, 255); + context.FillRect(new CGRect(default(CGPoint), _view.LogicalSize)); + context.TranslateCTM(0, _view.LogicalSize.Height - _logicalSize.Height); + context.DrawImage(new CGRect(default(CGPoint), _logicalSize), image); + context.Flush(); + nscontext.FlushGraphics(); } } } - finally + } + finally + { + if (image != null) { - if (image != null) - { - if (deferred == null) - image.Dispose(); - else - _view.SetBackBufferImage(new SavedImage(image, _logicalSize)); - } + if (!_isDeferred) + image.Dispose(); + else + _view.SetBackBufferImage(new SavedImage(image, _logicalSize)); } + Marshal.FreeHGlobal(Address); + Address = IntPtr.Zero; + drawLock?.Dispose(); } - Marshal.FreeHGlobal(Address); - Address = IntPtr.Zero; + } diff --git a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs index ce30deea88..37b6b16793 100644 --- a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs +++ b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs @@ -413,6 +413,6 @@ namespace Avalonia.MonoMac public void SetInputRoot(IInputRoot inputRoot) => InputRoot = inputRoot; - public ILockedFramebuffer Lock() => new EmulatedFramebuffer(View, View.LogicalSize, View.PixelSize); + public ILockedFramebuffer Lock() => new EmulatedFramebuffer(View); } } \ No newline at end of file From a0c4ec9039ecf4bdb650299073061a4c4123a643 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 21 Oct 2017 20:54:21 +0300 Subject: [PATCH 3/5] [MONOMAC] Pass frames back to UI thread instead of using lockFocusIfCanDraw --- .../Avalonia.MonoMac/EmulatedFramebuffer.cs | 29 ++---------------- src/OSX/Avalonia.MonoMac/TopLevelImpl.cs | 30 +++++++++++++++++-- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs b/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs index 713d4b0d18..935ab53432 100644 --- a/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs +++ b/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs @@ -11,7 +11,7 @@ namespace Avalonia.MonoMac { private readonly TopLevelImpl.TopLevelView _view; private readonly CGSize _logicalSize; - private bool _isDeferred; + private readonly bool _isDeferred; [DllImport("libc")] static extern void memset(IntPtr p, int c, IntPtr size); @@ -32,34 +32,12 @@ namespace Avalonia.MonoMac Address = Marshal.AllocHGlobal(size); memset(Address, 0, new IntPtr(size)); } - - class CocoaDrawLock : IDisposable - { - private readonly NSView _view; - public void Dispose() - { - _view.NonUIUnlockFocus(); - } - - public CocoaDrawLock(NSView view) - { - _view = view; - } - } - - CocoaDrawLock LockCocoaDrawing() - { - if (!_view.NonUILockFocusIfCanDraw()) - return null; - return new CocoaDrawLock(_view); - } - + public void Dispose() { if (Address == IntPtr.Zero) return; var nfo = (int) CGBitmapFlags.ByteOrder32Big | (int) CGImageAlphaInfo.PremultipliedLast; - IDisposable drawLock = LockCocoaDrawing(); CGImage image = null; try { @@ -69,7 +47,7 @@ namespace Avalonia.MonoMac image = bContext.ToImage(); lock (_view.SyncRoot) { - if (!_isDeferred || drawLock != null) + if(!_isDeferred) { using (var nscontext = NSGraphicsContext.CurrentContext) using (var context = nscontext.GraphicsPort) @@ -95,7 +73,6 @@ namespace Avalonia.MonoMac } Marshal.FreeHGlobal(Address); Address = IntPtr.Zero; - drawLock?.Dispose(); } diff --git a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs index 37b6b16793..563dee74c0 100644 --- a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs +++ b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs @@ -7,7 +7,7 @@ using Avalonia.Controls.Platform.Surfaces; using Avalonia.Rendering; using Avalonia.Threading; using MonoMac.AppKit; - +using MonoMac.CoreFoundation; using MonoMac.CoreGraphics; using MonoMac.Foundation; using MonoMac.ObjCRuntime; @@ -37,6 +37,7 @@ namespace Avalonia.MonoMac private readonly IKeyboardDevice _keyboard; private NSTrackingArea _area; private NSCursor _cursor; + private bool _nonUiRedrawQueued; public CGSize PixelSize { get; set; } @@ -55,7 +56,10 @@ namespace Avalonia.MonoMac protected override void Dispose(bool disposing) { if (disposing) - SetBackBufferImage(null); + { + _backBuffer?.Dispose(); + _backBuffer = null; + } base.Dispose(disposing); } @@ -69,6 +73,8 @@ namespace Avalonia.MonoMac public override void DrawRect(CGRect dirtyRect) { + lock (SyncRoot) + _nonUiRedrawQueued = false; lock (SyncRoot) { if (_backBuffer != null) @@ -93,6 +99,25 @@ namespace Avalonia.MonoMac { _backBuffer?.Dispose(); _backBuffer = image; + if (image == null) + return; + + if (_nonUiRedrawQueued) + return; + _nonUiRedrawQueued = true; + Dispatcher.UIThread.InvokeAsync( + () => + { + lock (SyncRoot) + { + if (!_nonUiRedrawQueued) + return; + _nonUiRedrawQueued = false; + } + SetNeedsDisplayInRect(Frame); + Display(); + }, DispatcherPriority.Render); + } } @@ -138,6 +163,7 @@ namespace Avalonia.MonoMac AddTrackingArea(_area); UpdateCursor(); _tl?.Resized?.Invoke(_tl.ClientSize); + Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout); } InputModifiers GetModifiers(NSEventModifierMask mod) From 5027248bd43fe1654528cb51a3b18a24ba2821a5 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 21 Oct 2017 20:54:39 +0300 Subject: [PATCH 4/5] [MONOMAC] Dispatcher changes --- src/Avalonia.Base/Threading/Dispatcher.cs | 6 ++++ .../PlatformThreadingInterface.cs | 32 +++++++++++------ src/OSX/Avalonia.MonoMac/ThreadingUtils.cs | 36 ------------------- src/OSX/Avalonia.MonoMac/TopLevelImpl.cs | 2 ++ 4 files changed, 30 insertions(+), 46 deletions(-) delete mode 100644 src/OSX/Avalonia.MonoMac/ThreadingUtils.cs diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs index a4b1aaafbc..4a096fc326 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.cs @@ -72,6 +72,12 @@ namespace Avalonia.Threading _jobRunner?.RunJobs(null); } + /// + /// Use this method to ensure that more prioritized tasks are executed + /// + /// + public void RunJobs(DispatcherPriority minimumPriority) => _jobRunner.RunJobs(minimumPriority); + /// public Task InvokeTaskAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) { diff --git a/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs b/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs index 184416e77a..88189ba12c 100644 --- a/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs +++ b/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs @@ -3,14 +3,18 @@ using System.Threading; using Avalonia.Platform; using Avalonia.Threading; using MonoMac.AppKit; +using MonoMac.CoreFoundation; using MonoMac.CoreGraphics; using MonoMac.Foundation; +using MonoMac.ObjCRuntime; namespace Avalonia.MonoMac { - class PlatformThreadingInterface : IPlatformThreadingInterface + class PlatformThreadingInterface : NSObject, IPlatformThreadingInterface { private bool _signaled; + private const string SignaledSelectorName = "avaloniauiSignaled"; + private readonly Selector _signaledSelector = new Selector(SignaledSelectorName); public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface(); public bool CurrentThreadIsLoopThread => NSThread.Current.IsMainThread; @@ -27,18 +31,25 @@ namespace Avalonia.MonoMac return; _signaled = true; } - NSApplication.SharedApplication.BeginInvokeOnMainThread(() => - { - lock (this) + PerformSelector(_signaledSelector, NSThread.MainThread, this, false, + new[] { - if (!_signaled) - return; - _signaled = false; - } - Signaled?.Invoke(null); - }); + NSRunLoop.NSDefaultRunLoopMode, NSRunLoop.NSRunLoopEventTracking, NSRunLoop.NSRunLoopModalPanelMode, + NSRunLoop.NSRunLoopCommonModes, NSRunLoop.NSRunLoopConnectionReplyMode + }); } + [Export(SignaledSelectorName)] + public void CallSignaled() + { + lock (this) + { + if (!_signaled) + return; + _signaled = false; + } + Signaled?.Invoke(null); + } public void RunLoop(CancellationToken cancellationToken) @@ -55,6 +66,7 @@ namespace Avalonia.MonoMac var ev = app.NextEvent(NSEventMask.AnyEvent, NSDate.DistantFuture, NSRunLoop.NSDefaultRunLoopMode, true); if (ev != null) { + Console.WriteLine("NSEVENT"); app.SendEvent(ev); ev.Dispose(); } diff --git a/src/OSX/Avalonia.MonoMac/ThreadingUtils.cs b/src/OSX/Avalonia.MonoMac/ThreadingUtils.cs deleted file mode 100644 index 7762d86764..0000000000 --- a/src/OSX/Avalonia.MonoMac/ThreadingUtils.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using MonoMac.AppKit; -using MonoMac.ObjCRuntime; - -namespace Avalonia.MonoMac -{ - public static class ThreadingUtils - { - private static readonly IntPtr selUnlockFocusHandle = Selector.GetHandle("unlockFocus"); - private static readonly IntPtr selLockFocusIfCanDrawHandle = Selector.GetHandle("lockFocusIfCanDraw"); - private static readonly IntPtr selWindowHandle = Selector.GetHandle("window"); - private static readonly IntPtr selCanDrawHandle = Selector.GetHandle("canDraw"); - - public static bool NonUILockFocusIfCanDraw(this NSView view) - { - return Messaging.bool_objc_msgSend(view.Handle, selLockFocusIfCanDrawHandle); - } - - public static void NonUIUnlockFocus(this NSView view) - { - Messaging.void_objc_msgSend(view.Handle, selUnlockFocusHandle); - } - - public static NSWindow NonUIGetWindow(this NSView view) - { - return (NSWindow) Runtime.GetNSObject(Messaging.IntPtr_objc_msgSendSuper(view.Handle, selWindowHandle)); - } - - public static bool BaseCanDraw(this NSView view) - { - return Messaging.bool_objc_msgSendSuper(view.SuperHandle, selCanDrawHandle); - } - } -} diff --git a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs index 563dee74c0..5ea7972871 100644 --- a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs +++ b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs @@ -25,6 +25,7 @@ namespace Avalonia.MonoMac protected virtual void OnInput(RawInputEventArgs args) { + Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1); Input?.Invoke(args); } @@ -75,6 +76,7 @@ namespace Avalonia.MonoMac { lock (SyncRoot) _nonUiRedrawQueued = false; + Dispatcher.UIThread.RunJobs(DispatcherPriority.Render); lock (SyncRoot) { if (_backBuffer != null) From bb838f2af92bfe55c67737868a7a2d53e799fbe8 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 25 Oct 2017 17:13:48 +0300 Subject: [PATCH 5/5] [MONO] Added names to MonoMacPlatform.cs --- src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs b/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs index c11ac0d6ee..a6b1f1d5b4 100644 --- a/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs +++ b/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs @@ -51,7 +51,8 @@ namespace Avalonia.MonoMac class ThreadHelper : NSObject { private readonly AutoResetEvent _event = new AutoResetEvent(false); - [Export("doNothing")] + private const string InitThreadingName = "initThreading"; + [Export(InitThreadingName)] public void DoNothing() { _event.Set(); @@ -60,7 +61,7 @@ namespace Avalonia.MonoMac public static void InitializeCocoaThreadingLocks() { var helper = new ThreadHelper(); - var thread = new NSThread(helper, Selector.FromHandle(Selector.GetHandle("doNothing")), new NSObject()); + var thread = new NSThread(helper, Selector.FromHandle(Selector.GetHandle(InitThreadingName)), new NSObject()); thread.Start(); helper._event.WaitOne(); helper._event.Dispose(); @@ -126,7 +127,7 @@ namespace Avalonia { if (useDeferredRendering.HasValue) MonoMac.MonoMacPlatform.UseDeferredRendering = useDeferredRendering.Value; - return builder.UseWindowingSubsystem(MonoMac.MonoMacPlatform.Initialize); + return builder.UseWindowingSubsystem(MonoMac.MonoMacPlatform.Initialize, "MonoMac"); } } } \ No newline at end of file