diff --git a/src/Avalonia.Native.OSX/window.h b/src/Avalonia.Native.OSX/window.h index 11aea0c528..58dca5bc9a 100644 --- a/src/Avalonia.Native.OSX/window.h +++ b/src/Avalonia.Native.OSX/window.h @@ -15,6 +15,7 @@ class WindowBaseImpl; -(AvnView*) initWithParent: (WindowBaseImpl*) parent; -(NSEvent*) lastMouseDownEvent; -(AvnPoint) translateLocalPoint:(AvnPoint)pt; +-(void) setSwRenderedFrame: (AvnFramebuffer*) fb dispose: (IUnknown*) dispose; @end @interface AvnWindow : NSWindow diff --git a/src/Avalonia.Native.OSX/window.mm b/src/Avalonia.Native.OSX/window.mm index dd2df1be27..608f828e0e 100644 --- a/src/Avalonia.Native.OSX/window.mm +++ b/src/Avalonia.Native.OSX/window.mm @@ -150,7 +150,7 @@ public: } point = ConvertPointY(point); - auto viewPoint = [Window convertPointFromScreen:ToNSPoint(point)]; + auto viewPoint = [Window convertScreenToBase:ToNSPoint(point)]; *ret = [View translateLocalPoint:ToAvnPoint(viewPoint)]; @@ -165,12 +165,18 @@ public: } auto cocoaViewPoint = ToNSPoint([View translateLocalPoint:point]); - auto cocoaScreenPoint = [Window convertPointToScreen:cocoaViewPoint]; + auto cocoaScreenPoint = [Window convertBaseToScreen:cocoaViewPoint]; *ret = ConvertPointY(ToAvnPoint(cocoaScreenPoint)); return S_OK; } + virtual HRESULT ThreadSafeSetSwRenderedFrame(AvnFramebuffer* fb, IUnknown* dispose) + { + [View setSwRenderedFrame: fb dispose: dispose]; + return S_OK; + } + protected: virtual NSWindowStyleMask GetStyle() { @@ -183,9 +189,14 @@ protected: } }; +NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEventTrackingRunLoopMode, NSModalPanelRunLoopMode, NSRunLoopCommonModes, NSConnectionReplyMode, nil]; + @implementation AvnView { ComPtr _parent; + ComPtr _swRenderedFrame; + AvnFramebuffer _swRenderedFrameBuffer; + bool _queuedDisplayFromThread; NSTrackingArea* _area; bool _isLeftPressed, _isMiddlePressed, _isRightPressed, _isMouseOver; NSEvent* _lastMouseDownEvent; @@ -229,18 +240,10 @@ protected: _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}); } -- (void)drawRect:(NSRect)dirtyRect +- (void) drawFb: (AvnFramebuffer*) fb { - auto logicalSize = [self frame].size; - auto pixelSize = [self convertSizeToBacking:logicalSize]; - int w = pixelSize.width; - int h = pixelSize.height; - int stride = w * 4; - void*ptr = malloc(h * stride); - _parent->BaseEvents->SoftwareDraw(ptr, stride, w, h, AvnSize{logicalSize.width, logicalSize.height}); - auto colorSpace = CGColorSpaceCreateDeviceRGB(); - auto bctx = CGBitmapContextCreate(ptr, w, h, 8, stride, colorSpace, kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast); + auto bctx = CGBitmapContextCreate(fb->Data, fb->Width, fb->Height, 8, fb->Stride, colorSpace, kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast); auto image = CGBitmapContextCreateImage(bctx); CGContextRelease(bctx); CGColorSpaceRelease(colorSpace); @@ -250,13 +253,74 @@ protected: [ctx saveGraphicsState]; auto cgc = [ctx CGContext]; - CGContextDrawImage(cgc, CGRect{0,0, logicalSize.width, logicalSize.height}, image); + CGContextDrawImage(cgc, CGRect{0,0, fb->Width/(fb->Dpi.X/96), fb->Height/(fb->Dpi.Y/96)}, image); CGImageRelease(image); [ctx restoreGraphicsState]; + +} + +- (void)drawRect:(NSRect)dirtyRect +{ + _parent->BaseEvents->RunRenderPriorityJobs(); + @synchronized (self) { + if(_swRenderedFrame != NULL) + { + [self drawFb: &_swRenderedFrameBuffer]; + return; + } + } + + auto logicalSize = [self frame].size; + auto pixelSize = [self convertSizeToBacking:logicalSize]; + int w = pixelSize.width; + int h = pixelSize.height; + int stride = w * 4; + void*ptr = malloc(h * stride); + AvnFramebuffer fb = { + .Data = ptr, + .Stride = stride, + .Width = w, + .Height = h, + .PixelFormat = kAvnRgba8888, + .Dpi = AvnVector { .X = w / logicalSize.width * 96, .Y = h / logicalSize.height * 96} + }; + _parent->BaseEvents->SoftwareDraw(&fb); + [self drawFb: &fb]; free(ptr); } +-(void) redrawSelf +{ + @autoreleasepool + { + @synchronized(self) + { + if(!_queuedDisplayFromThread) + return; + _queuedDisplayFromThread = false; + } + [self setNeedsDisplayInRect:[self frame]]; + [self display]; + + } +} + +-(void) setSwRenderedFrame: (AvnFramebuffer*) fb dispose: (IUnknown*) dispose +{ + @autoreleasepool { + @synchronized (self) { + _swRenderedFrame = dispose; + _swRenderedFrameBuffer = *fb; + if(!_queuedDisplayFromThread) + { + _queuedDisplayFromThread = true; + [self performSelector:@selector(redrawSelf) onThread:[NSThread mainThread] withObject:NULL waitUntilDone:false modes: AllLoopModes]; + } + } + } +} + - (AvnPoint) translateLocalPoint:(AvnPoint)pt { pt.Y = [self bounds].size.height - pt.Y; @@ -273,6 +337,12 @@ protected: return result; } +- (void) viewDidChangeBackingProperties +{ + _parent->BaseEvents->ScalingChanged([_parent->Window backingScaleFactor]); + [super viewDidChangeBackingProperties]; +} + - (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type { auto localPoint = [self convertPoint:[event locationInWindow] toView:self]; diff --git a/src/Avalonia.Native/DeferredFramebuffer.cs b/src/Avalonia.Native/DeferredFramebuffer.cs new file mode 100644 index 0000000000..395d9bb0d3 --- /dev/null +++ b/src/Avalonia.Native/DeferredFramebuffer.cs @@ -0,0 +1,77 @@ +using System; +using System.Runtime.InteropServices; +using Avalonia.Native.Interop; +using Avalonia.Platform; +using SharpGen.Runtime; + +namespace Avalonia.Native +{ + public class DeferredFramebuffer : ILockedFramebuffer + { + private readonly Func, bool> _lockWindow; + public DeferredFramebuffer(Func, bool> lockWindow, + int width, int height, Vector dpi) + { + _lockWindow = lockWindow; + Address = Marshal.AllocHGlobal(width * height * 4); + Width = width; + Height = height; + RowBytes = width * 4; + Dpi = dpi; + Format = PixelFormat.Rgba8888; + } + + public IntPtr Address { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public int RowBytes { get; set; } + public Vector Dpi { get; set; } + public PixelFormat Format { get; set; } + + + class Disposer : CallbackBase + { + private IntPtr _ptr; + + public Disposer(IntPtr ptr) + { + _ptr = ptr; + } + + protected override void Destroyed() + { + if(_ptr != IntPtr.Zero) + { + Marshal.FreeHGlobal(_ptr); + _ptr = IntPtr.Zero; + } + } + } + + public void Dispose() + { + if (Address == IntPtr.Zero) + return; + if (!_lockWindow(win => + { + var fb = new AvnFramebuffer + { + Data = Address, + Dpi = new AvnVector + { + X = Dpi.X, + Y = Dpi.Y + }, + Width = Width, + Height = Height, + PixelFormat = (AvnPixelFormat)Format, + Stride = RowBytes + }; + using (var d = new Disposer(Address)) + win.ThreadSafeSetSwRenderedFrame(ref fb, d); + })) + Marshal.FreeHGlobal(Address); + Address = IntPtr.Zero; + } + } +} diff --git a/src/Avalonia.Native/DeferredRendererProxy.cs b/src/Avalonia.Native/DeferredRendererProxy.cs new file mode 100644 index 0000000000..099275eaa6 --- /dev/null +++ b/src/Avalonia.Native/DeferredRendererProxy.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using Avalonia.Platform; +using Avalonia.Rendering; +using Avalonia.VisualTree; + +namespace Avalonia.Native +{ + public class DeferredRendererProxy : IRenderer, IRenderLoopTask, IRenderLoop + { + object _lock = new object(); + void IRenderLoop.Add(IRenderLoopTask i) + { + AvaloniaLocator.Current.GetService().Add(this); + } + + void IRenderLoop.Remove(IRenderLoopTask i) + { + AvaloniaLocator.Current.GetService().Remove(this); + } + + + DeferredRenderer _renderer; + IRenderLoopTask _rendererTask; + public DeferredRendererProxy(IRenderRoot root) + { + _renderer = new DeferredRenderer(root, this); + _rendererTask = (IRenderLoopTask)_renderer; + } + + public bool DrawFps{ + get => _renderer.DrawFps; + set => _renderer.DrawFps = value; + } + public bool DrawDirtyRects + { + get => _renderer.DrawDirtyRects; + set => _renderer.DrawDirtyRects = value; + } + + public bool NeedsUpdate => _rendererTask.NeedsUpdate; + + public void AddDirty(IVisual visual) => _renderer.AddDirty(visual); + + public void Dispose() => _renderer.Dispose(); + + public IEnumerable HitTest(Point p, IVisual root, Func filter) + { + return _renderer.HitTest(p, root, filter); + } + + public void Paint(Rect rect) + { + if (NeedsUpdate) + Update(TimeSpan.FromMilliseconds(Environment.TickCount)); + Render(); + } + + public void Resized(Size size) => _renderer.Resized(size); + + public void Start() => _renderer.Start(); + + public void Stop() => _renderer.Stop(); + + public void Update(TimeSpan time) + { + _rendererTask.Update(time); + } + + public void Render() + { + lock(_lock) + { + _rendererTask.Render(); + } + } + } +} diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 5a37f33cdf..98068773a8 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -15,9 +15,11 @@ namespace Avalonia.Native { IInputRoot _inputRoot; IAvnWindowBase _native; - - private bool _deferredRendering = false; + private object _syncRoot = new object(); + private bool _deferredRendering = true; private readonly IMouseDevice _mouse; + private Size _savedLogicalSize; + private double _savedScaling; public WindowBaseImpl() { @@ -27,6 +29,8 @@ namespace Avalonia.Native protected void Init(IAvnWindowBase window) { _native = window; + _savedLogicalSize = ClientSize; + _savedScaling = Scaling; } public Size ClientSize @@ -42,7 +46,28 @@ namespace Avalonia.Native public IEnumerable Surfaces => new[] { this }; public ILockedFramebuffer Lock() { - return _framebuffer; + if(_deferredRendering) + { + var w = _savedLogicalSize.Width / _savedScaling; + var h = _savedLogicalSize.Height / _savedScaling; + var dpi = _savedScaling * 96; + return new DeferredFramebuffer(cb => + { + lock (_syncRoot) + { + if (_native == null) + return false; + cb(_native); + return true; + } + }, (int)w, (int)h, new Vector(dpi, dpi)); + } + + var fb = _framebuffer; + _framebuffer = null; + if (fb == null) + throw new InvalidOperationException("Lock call without corresponding Paint event"); + return fb; } public Action Paint { get; set; } @@ -80,33 +105,47 @@ namespace Avalonia.Native void IAvnWindowBaseEvents.Deactivated() => _parent.Deactivated?.Invoke(); - void IAvnWindowBaseEvents.SoftwareDraw(IntPtr ptr, int stride, int pixelWidth, int pixelHeight, AvnSize logicalSize) + void IAvnWindowBaseEvents.SoftwareDraw(ref AvnFramebuffer fb) { Dispatcher.UIThread.RunJobs(DispatcherPriority.Render); _parent._framebuffer = new SavedFramebuffer { - Address = ptr, - RowBytes = stride, - Width = pixelWidth, - Height = pixelHeight, - Dpi = new Vector(pixelWidth / logicalSize.Width * 96, pixelHeight / logicalSize.Height * 96) + Address = fb.Data, + RowBytes = fb.Stride, + Width = fb.Width, + Height = fb.Height, + Dpi = new Vector(fb.Dpi.X, fb.Dpi.Y) }; - _parent.Paint?.Invoke(new Rect(0, 0, logicalSize.Width, logicalSize.Height)); + _parent.Paint?.Invoke(new Rect(0, 0, fb.Width / (fb.Dpi.X / 96), fb.Height / (fb.Dpi.Y / 96))); } - void IAvnWindowBaseEvents.Resized(AvnSize size) => _parent.Resized?.Invoke(new Size(size.Width, size.Height)); + void IAvnWindowBaseEvents.Resized(AvnSize size) + { + var s = new Size(size.Width, size.Height); + _parent._savedLogicalSize = s; + _parent.Resized?.Invoke(s); + } void IAvnWindowBaseEvents.RawMouseEvent(AvnRawMouseEventType type, uint timeStamp, AvnInputModifiers modifiers, AvnPoint point, AvnVector delta) { _parent.RawMouseEvent(type, timeStamp, modifiers, point, delta); } - void IAvnWindowBaseEvents.ScalingChanged() + void IAvnWindowBaseEvents.ScalingChanged(double scaling) { - _parent.ScalingChanged?.Invoke(_parent.Scaling); + _parent._savedScaling = scaling; + _parent.ScalingChanged?.Invoke(scaling); + } + + void IAvnWindowBaseEvents.RunRenderPriorityJobs() + { + if (_parent._deferredRendering) + // Hack to trigger Paint event on the renderer + _parent.Paint?.Invoke(new Rect()); + Dispatcher.UIThread.RunJobs(DispatcherPriority.Render); } } @@ -139,8 +178,8 @@ namespace Avalonia.Native public IRenderer CreateRenderer(IRenderRoot root) { - //_deferredRendering = true; - //return new DeferredRenderer(root, AvaloniaLocator.Current.GetService()); + if(_deferredRendering) + return new DeferredRendererProxy(root); return new ImmediateRenderer(root); } @@ -154,7 +193,8 @@ namespace Avalonia.Native public void Invalidate(Rect rect) { - _native.Invalidate(new AvnRect { Height = rect.Height, Width = rect.Width, X = rect.X, Y = rect.Y }); + if (!_deferredRendering) + _native.Invalidate(new AvnRect { Height = rect.Height, Width = rect.Width, X = rect.X, Y = rect.Y }); } public void SetInputRoot(IInputRoot inputRoot) diff --git a/src/headers/avalonia-native.h b/src/headers/avalonia-native.h index 1cc4b92de8..8d637e7f38 100644 --- a/src/headers/avalonia-native.h +++ b/src/headers/avalonia-native.h @@ -30,6 +30,23 @@ struct AvnPoint double X, Y; }; +enum AvnPixelFormat +{ + kAvnRgb565, + kAvnRgba8888, + kAvnBgra8888 +}; + +struct AvnFramebuffer +{ + void* Data; + int Width; + int Height; + int Stride; + AvnVector Dpi; + AvnPixelFormat PixelFormat; +}; + enum AvnRawMouseEventType { LeaveWindow, @@ -82,8 +99,8 @@ AVNCOM(IAvnWindowBase, 02) : virtual IUnknown virtual void SetPosition (AvnPoint point) = 0; virtual HRESULT PointToClient (AvnPoint point, AvnPoint*ret) = 0; virtual HRESULT PointToScreen (AvnPoint point, AvnPoint*ret) = 0; + virtual HRESULT ThreadSafeSetSwRenderedFrame(AvnFramebuffer* fb, IUnknown* dispose) = 0; virtual HRESULT SetTopMost (bool value) = 0; - }; AVNCOM(IAvnPopup, 03) : virtual IAvnWindowBase @@ -99,7 +116,7 @@ AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase AVNCOM(IAvnWindowBaseEvents, 05) : IUnknown { - virtual HRESULT SoftwareDraw(void* ptr, int stride, int pixelWidth, int pixelHeight, const AvnSize& logicalSize) = 0; + virtual HRESULT SoftwareDraw(AvnFramebuffer* fb) = 0; virtual void Closed() = 0; virtual void Activated() = 0; virtual void Deactivated() = 0; @@ -109,8 +126,8 @@ AVNCOM(IAvnWindowBaseEvents, 05) : IUnknown AvnInputModifiers modifiers, AvnPoint point, AvnVector delta) = 0; - - virtual void ScalingChanged () = 0; + virtual void ScalingChanged(double scaling) = 0; + virtual void RunRenderPriorityJobs() = 0; };