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/EmulatedFramebuffer.cs b/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs
index 1ecfa0eb7d..935ab53432 100644
--- a/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs
+++ b/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs
@@ -2,44 +2,80 @@
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)
+ private readonly bool _isDeferred;
+
+ [DllImport("libc")]
+ static extern void memset(IntPtr p, int c, IntPtr size);
+
+ public EmulatedFramebuffer(TopLevelImpl.TopLevelView view)
{
- _logicalSize = view.Frame.Size;
- var pixelSize = view.ConvertSizeToBacking(_logicalSize);
+ _view = view;
+
+ _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));
}
-
+
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)
+ CGImage image = null;
+ try
{
- context.SetFillColor(255, 255, 255, 255);
- context.FillRect(new CGRect(default(CGPoint), _logicalSize));
- context.DrawImage(new CGRect(default(CGPoint), _logicalSize), image);
+ 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)
+ {
+ if(!_isDeferred)
+ {
+ using (var nscontext = NSGraphicsContext.CurrentContext)
+ using (var context = nscontext.GraphicsPort)
+ {
+ 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();
+ }
+ }
+ }
}
- Marshal.FreeHGlobal(Address);
- Address = IntPtr.Zero;
+ finally
+ {
+ if (image != null)
+ {
+ if (!_isDeferred)
+ image.Dispose();
+ else
+ _view.SetBackBufferImage(new SavedImage(image, _logicalSize));
+ }
+ Marshal.FreeHGlobal(Address);
+ Address = IntPtr.Zero;
+ }
+
+
}
public IntPtr Address { get; private set; }
@@ -49,4 +85,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..a6b1f1d5b4 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,44 @@ 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);
+ private const string InitThreadingName = "initThreading";
+ [Export(InitThreadingName)]
+ public void DoNothing()
+ {
+ _event.Set();
+ }
+
+ public static void InitializeCocoaThreadingLocks()
+ {
+ var helper = new ThreadHelper();
+ var thread = new NSThread(helper, Selector.FromHandle(Selector.GetHandle(InitThreadingName)), 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 +99,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,9 +123,11 @@ 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()
{
- return builder.UseWindowingSubsystem(MonoMac.MonoMacPlatform.Initialize);
+ if (useDeferredRendering.HasValue)
+ MonoMac.MonoMacPlatform.UseDeferredRendering = useDeferredRendering.Value;
+ return builder.UseWindowingSubsystem(MonoMac.MonoMacPlatform.Initialize, "MonoMac");
}
}
}
\ No newline at end of file
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/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/TopLevelImpl.cs b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs
index 2744474214..5ea7972871 100644
--- a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs
+++ b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs
@@ -5,8 +5,9 @@ using Avalonia.Input.Raw;
using Avalonia.Platform;
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;
@@ -24,6 +25,7 @@ namespace Avalonia.MonoMac
protected virtual void OnInput(RawInputEventArgs args)
{
+ Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Input?.Invoke(args);
}
@@ -36,6 +38,14 @@ namespace Avalonia.MonoMac
private readonly IKeyboardDevice _keyboard;
private NSTrackingArea _area;
private NSCursor _cursor;
+ private bool _nonUiRedrawQueued;
+
+ public CGSize PixelSize { get; set; }
+
+ public CGSize LogicalSize { get; set; }
+
+ private SavedImage _backBuffer;
+ public object SyncRoot { get; } = new object();
public TopLevelView(TopLevelImpl tl)
{
@@ -44,17 +54,75 @@ namespace Avalonia.MonoMac
_keyboard = AvaloniaLocator.Current.GetService();
}
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _backBuffer?.Dispose();
+ _backBuffer = 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)
+ _nonUiRedrawQueued = false;
+ Dispatcher.UIThread.RunJobs(DispatcherPriority.Render);
+ 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;
+ if (image == null)
+ return;
+
+ if (_nonUiRedrawQueued)
+ return;
+ _nonUiRedrawQueued = true;
+ Dispatcher.UIThread.InvokeAsync(
+ () =>
+ {
+ lock (SyncRoot)
+ {
+ if (!_nonUiRedrawQueued)
+ return;
+ _nonUiRedrawQueued = false;
+ }
+ SetNeedsDisplayInRect(Frame);
+ Display();
+ }, DispatcherPriority.Render);
+
+ }
+ }
+
[Export("viewDidChangeBackingProperties:")]
public void ViewDidChangeBackingProperties()
{
@@ -78,7 +146,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)
{
@@ -92,6 +165,7 @@ namespace Avalonia.MonoMac
AddTrackingArea(_area);
UpdateCursor();
_tl?.Resized?.Invoke(_tl.ClientSize);
+ Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
}
InputModifiers GetModifiers(NSEventModifierMask mod)
@@ -348,9 +422,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);