Browse Source

[MONOMAC] Initial implementation of deferred rendering support

pull/1240/head
Nikita Tsukanov 8 years ago
parent
commit
81bd7df8d7
  1. 98
      src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs
  2. 43
      src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs
  3. 33
      src/OSX/Avalonia.MonoMac/RenderLoop.cs
  4. 36
      src/OSX/Avalonia.MonoMac/ThreadingUtils.cs
  5. 61
      src/OSX/Avalonia.MonoMac/TopLevelImpl.cs

98
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;
}
}
}

43
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<IStandardCursorFactory>().ToTransient<CursorFactoryStub>()
.Bind<IPlatformIconLoader>().ToSingleton<IconLoader>()
@ -27,9 +32,8 @@ namespace Avalonia.MonoMac
.Bind<IPlatformSettings>().ToConstant(this)
.Bind<IWindowingPlatform>().ToConstant(this)
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialogsImpl>()
.Bind<IRenderLoop>().ToConstant(s_renderLoop)
.Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance);
InitializeMonoMac();
}
public static void Initialize()
@ -39,13 +43,43 @@ namespace Avalonia.MonoMac
}
/// <summary>
/// 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
/// </summary>
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<T>(this T builder) where T : AppBuilderBase<T>, new()
public static T UseMonoMac<T>(this T builder, bool? useDeferredRendering = null) where T : AppBuilderBase<T>, new()
{
if (useDeferredRendering.HasValue)
MonoMac.MonoMacPlatform.UseDeferredRendering = useDeferredRendering.Value;
return builder.UseWindowingSubsystem(MonoMac.MonoMacPlatform.Initialize);
}
}

33
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<IRuntimePlatform>().StartSystemTimer(new TimeSpan(0, 0, 0, 0, 1000 / 60),
() =>
{
lock (_lock)
{
using (new NSAutoreleasePool())
{
Tick?.Invoke(this, EventArgs.Empty);
}
}
});
}
public event EventHandler<EventArgs> Tick;
}
}

36
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);
}
}
}

61
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<IKeyboardDevice>();
}
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<IRenderLoop>())
: (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);
}
}
Loading…
Cancel
Save