Browse Source

Merge remote-tracking branch 'origin/master' into fixes/dc-render-layers

 Conflicts:
	src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
	src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs
Steven Kirk 8 years ago
parent
commit
e0a6771296
40 changed files with 420 additions and 85 deletions
  1. 15 0
      src/Avalonia.Controls/MenuItem.cs
  2. 9 0
      src/Avalonia.Controls/StackPanel.cs
  3. 1 3
      src/Avalonia.Controls/TreeView.cs
  4. 5 10
      src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs
  5. 26 0
      src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs
  6. 1 4
      src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
  7. 1 1
      src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs
  8. 4 7
      src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs
  9. 1 4
      src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs
  10. 2 3
      src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs
  11. 1 4
      src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
  12. 1 4
      src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs
  13. 32 0
      src/Gtk/Avalonia.Gtk3/FramebufferManager.cs
  14. 5 0
      src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs
  15. 1 1
      src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs
  16. 7 4
      src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs
  17. 4 0
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  18. 3 2
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  19. 54 0
      src/Gtk/Avalonia.Gtk3/X11.cs
  20. 55 0
      src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs
  21. 1 0
      src/Shared/PlatformSupport/StandardRuntimePlatform.cs
  22. 14 8
      src/Skia/Avalonia.Skia/BitmapImpl.cs
  23. 6 1
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  24. 2 0
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  25. 84 0
      src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
  26. 9 9
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs
  27. 1 1
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
  28. 1 1
      tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs
  29. 2 2
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
  30. 1 1
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs
  31. 4 4
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  32. 1 1
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
  33. 2 2
      tests/Avalonia.Controls.UnitTests/StackPanelTests.cs
  34. 1 1
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs
  35. 2 2
      tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs
  36. 6 5
      tests/Avalonia.RenderTests/Media/BitmapTests.cs
  37. 55 0
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs
  38. BIN
      tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png
  39. BIN
      tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png
  40. BIN
      tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgba8888.expected.png

+ 15 - 0
src/Avalonia.Controls/MenuItem.cs

@@ -307,6 +307,21 @@ namespace Avalonia.Controls
                     () => IsSubMenuOpen = true,
                     TimeSpan.FromMilliseconds(400));
             }
+            else
+            {
+                var parentItem = Parent as MenuItem;
+                if (parentItem != null)
+                {
+                    foreach (var sibling in parentItem.Items
+                        .OfType<MenuItem>()
+                        .Where(x => x != this && x.IsSubMenuOpen))
+                    {
+                        sibling.CloseSubmenus();
+                        sibling.IsSubMenuOpen = false;
+                        sibling.IsSelected = false;
+                    }
+                }
+            }
         }
 
         /// <summary>

+ 9 - 0
src/Avalonia.Controls/StackPanel.cs

@@ -170,6 +170,15 @@ namespace Avalonia.Controls
                 }
             }
 
+            if (Orientation == Orientation.Vertical)
+            {
+                measuredHeight -= gap;
+            }
+            else
+            {
+                measuredWidth -= gap;
+            }
+
             return new Size(measuredWidth, measuredHeight);
         }
 

+ 1 - 3
src/Avalonia.Controls/TreeView.cs

@@ -253,9 +253,7 @@ namespace Avalonia.Controls
 
                         if (AutoScrollToSelectedItem)
                         {
-                            DispatcherTimer.RunOnce(
-                                container.ContainerControl.BringIntoView,
-                                TimeSpan.Zero);
+                            Dispatcher.UIThread.InvokeAsync(container.ContainerControl.BringIntoView);
                         }
 
                         break;

+ 5 - 10
src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs

@@ -4,7 +4,6 @@
 using System;
 using System.Collections.Generic;
 using Avalonia.Media;
-using Avalonia.Platform;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Rendering.SceneGraph
@@ -12,20 +11,16 @@ namespace Avalonia.Rendering.SceneGraph
     /// <summary>
     /// Base class for draw operations that can use a brush.
     /// </summary>
-    internal abstract class BrushDrawOperation : IDrawOperation
+    internal abstract class BrushDrawOperation : DrawOperation
     {
-        /// <inheritdoc/>
-        public abstract Rect Bounds { get; }
-
-        /// <inheritdoc/>
-        public abstract bool HitTest(Point p);
+        public BrushDrawOperation(Rect bounds, Matrix transform, Pen pen)
+            : base(bounds, transform, pen)
+        {
+        }
 
         /// <summary>
         /// Gets a collection of child scenes that are needed to draw visual brushes.
         /// </summary>
         public abstract IDictionary<IVisual, Scene> ChildScenes { get; }
-
-        /// <inheritdoc/>
-        public abstract void Render(IDrawingContextImpl context);
     }
 }

+ 26 - 0
src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs

@@ -0,0 +1,26 @@
+using System;
+using Avalonia.Media;
+using Avalonia.Platform;
+
+namespace Avalonia.Rendering.SceneGraph
+{
+    /// <summary>
+    /// Base class for draw operations that have bounds.
+    /// </summary>
+    internal abstract class DrawOperation : IDrawOperation
+    {
+        public DrawOperation(Rect bounds, Matrix transform, Pen pen)
+        {
+            bounds = bounds.Inflate((pen?.Thickness ?? 0) / 2).TransformToAABB(transform);
+            Bounds = new Rect(
+                new Point(Math.Floor(bounds.X), Math.Floor(bounds.Y)),
+                new Point(Math.Ceiling(bounds.Right), Math.Ceiling(bounds.Bottom)));
+        }
+
+        public Rect Bounds { get; }
+
+        public abstract bool HitTest(Point p);
+
+        public abstract void Render(IDrawingContextImpl context);
+    }
+}

+ 1 - 4
src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs

@@ -28,8 +28,8 @@ namespace Avalonia.Rendering.SceneGraph
             Pen pen,
             IGeometryImpl geometry,
             IDictionary<IVisual, Scene> childScenes = null)
+            : base(geometry.GetRenderBounds(pen?.Thickness ?? 0), transform, null)
         {
-            Bounds = geometry.GetRenderBounds(pen?.Thickness ?? 0).TransformToAABB(transform);
             Transform = transform;
             Brush = brush?.ToImmutable();
             Pen = pen?.ToImmutable();
@@ -37,9 +37,6 @@ namespace Avalonia.Rendering.SceneGraph
             ChildScenes = childScenes;
         }
 
-        /// <inheritdoc/>
-        public override Rect Bounds { get; }
-
         /// <summary>
         /// Gets the transform with which the node will be drawn.
         /// </summary>

+ 1 - 1
src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs

@@ -12,7 +12,7 @@ namespace Avalonia.Rendering.SceneGraph
     public interface IDrawOperation
     {
         /// <summary>
-        /// Gets the bounds of the visible content in the node.
+        /// Gets the bounds of the visible content in the node in global coordinates.
         /// </summary>
         Rect Bounds { get; }
 

+ 4 - 7
src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs

@@ -9,7 +9,7 @@ namespace Avalonia.Rendering.SceneGraph
     /// <summary>
     /// A node in the scene graph which represents an image draw.
     /// </summary>
-    internal class ImageNode : IDrawOperation
+    internal class ImageNode : DrawOperation
     {
         /// <summary>
         /// Initializes a new instance of the <see cref="ImageNode"/> class.
@@ -20,8 +20,8 @@ namespace Avalonia.Rendering.SceneGraph
         /// <param name="sourceRect">The source rect.</param>
         /// <param name="destRect">The destination rect.</param>
         public ImageNode(Matrix transform, IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect)
+            : base(destRect, transform, null)
         {
-            Bounds = destRect.TransformToAABB(transform);
             Transform = transform;
             Source = source;
             Opacity = opacity;
@@ -29,9 +29,6 @@ namespace Avalonia.Rendering.SceneGraph
             DestRect = destRect;
         }
 
-        /// <inheritdoc/>
-        public Rect Bounds { get; }
-
         /// <summary>
         /// Gets the transform with which the node will be drawn.
         /// </summary>
@@ -80,7 +77,7 @@ namespace Avalonia.Rendering.SceneGraph
         }
 
         /// <inheritdoc/>
-        public void Render(IDrawingContextImpl context)
+        public override void Render(IDrawingContextImpl context)
         {
             // TODO: Probably need to introduce some kind of locking mechanism in the case of
             // WriteableBitmap.
@@ -89,6 +86,6 @@ namespace Avalonia.Rendering.SceneGraph
         }
 
         /// <inheritdoc/>
-        public bool HitTest(Point p) => Bounds.Contains(p);
+        public override bool HitTest(Point p) => Bounds.Contains(p);
     }
 }

+ 1 - 4
src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs

@@ -28,8 +28,8 @@ namespace Avalonia.Rendering.SceneGraph
             Point p1,
             Point p2,
             IDictionary<IVisual, Scene> childScenes = null)
+            : base(new Rect(p1, p2), transform, pen)
         {
-            Bounds = new Rect(p1, p2).TransformToAABB(transform).Inflate(pen?.Thickness ?? 0);
             Transform = transform;
             Pen = pen?.ToImmutable();
             P1 = p1;
@@ -37,9 +37,6 @@ namespace Avalonia.Rendering.SceneGraph
             ChildScenes = childScenes;
         }
 
-        /// <inheritdoc/>
-        public override Rect Bounds { get; }
-
         /// <summary>
         /// Gets the transform with which the node will be drawn.
         /// </summary>

+ 2 - 3
src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs

@@ -19,6 +19,7 @@ namespace Avalonia.Rendering.SceneGraph
         /// <param name="bounds">The bounds of the mask.</param>
         /// <param name="childScenes">Child scenes for drawing visual brushes.</param>
         public OpacityMaskNode(IBrush mask, Rect bounds, IDictionary<IVisual, Scene> childScenes = null)
+            : base(Rect.Empty, Matrix.Identity, null)
         {
             Mask = mask?.ToImmutable();
             MaskBounds = bounds;
@@ -30,12 +31,10 @@ namespace Avalonia.Rendering.SceneGraph
         /// opacity mask pop.
         /// </summary>
         public OpacityMaskNode()
+            : base(Rect.Empty, Matrix.Identity, null)
         {
         }
 
-        /// <inheritdoc/>
-        public override Rect Bounds => Rect.Empty;
-
         /// <summary>
         /// Gets the mask to be pushed or null if the operation represents a pop.
         /// </summary>

+ 1 - 4
src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs

@@ -30,8 +30,8 @@ namespace Avalonia.Rendering.SceneGraph
             Rect rect,
             float cornerRadius,
             IDictionary<IVisual, Scene> childScenes = null)
+            : base(rect, transform, pen)
         {
-            Bounds = rect.TransformToAABB(transform).Inflate(pen?.Thickness ?? 0);
             Transform = transform;
             Brush = brush?.ToImmutable();
             Pen = pen?.ToImmutable();
@@ -40,9 +40,6 @@ namespace Avalonia.Rendering.SceneGraph
             ChildScenes = childScenes;
         }
 
-        /// <inheritdoc/>
-        public override Rect Bounds { get; }
-
         /// <summary>
         /// Gets the transform with which the node will be drawn.
         /// </summary>

+ 1 - 4
src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs

@@ -28,8 +28,8 @@ namespace Avalonia.Rendering.SceneGraph
             Point origin,
             IFormattedTextImpl text,
             IDictionary<IVisual, Scene> childScenes = null)
+            : base(new Rect(origin, text.Size), transform, null)
         {
-            Bounds = new Rect(origin, text.Size).TransformToAABB(transform);
             Transform = transform;
             Foreground = foreground?.ToImmutable();
             Origin = origin;
@@ -37,9 +37,6 @@ namespace Avalonia.Rendering.SceneGraph
             ChildScenes = childScenes;
         }
 
-        /// <inheritdoc/>
-        public override Rect Bounds { get; }
-
         /// <summary>
         /// Gets the transform with which the node will be drawn.
         /// </summary>

+ 32 - 0
src/Gtk/Avalonia.Gtk3/FramebufferManager.cs

@@ -5,6 +5,7 @@ using System.Text;
 using System.Threading.Tasks;
 using Avalonia.Controls.Platform.Surfaces;
 using Avalonia.Platform;
+using Avalonia.Threading;
 
 namespace Avalonia.Gtk3
 {
@@ -27,7 +28,38 @@ namespace Avalonia.Gtk3
             var s = _window.ClientSize;
             var width = (int) s.Width;
             var height = (int) s.Height;
+            
+            if (!Dispatcher.UIThread.CheckAccess() && Gtk3Platform.DisplayClassName.ToLower().Contains("x11"))
+            {
+                var x11 = LockX11Framebuffer(width, height);
+                if (x11 != null)
+                    return x11;
+            }
+            
+
             return new ImageSurfaceFramebuffer(_window, width, height, _window.LastKnownScaleFactor);
         }
+
+        private static int X11ErrorHandler(IntPtr d, IntPtr e)
+        {
+            return 0;
+        }
+
+        private static X11.XErrorHandler X11ErrorHandlerDelegate = X11ErrorHandler;
+        
+        private static IntPtr X11Display;
+        private ILockedFramebuffer LockX11Framebuffer(int width, int height)
+        {
+            if (!_window.GdkWindowHandle.HasValue)
+                return null;
+            if (X11Display == IntPtr.Zero)
+            {
+                X11Display = X11.XOpenDisplay(IntPtr.Zero);
+                if (X11Display == IntPtr.Zero)
+                    return null;
+                X11.XSetErrorHandler(X11ErrorHandlerDelegate);
+            }
+            return new X11Framebuffer(X11Display, _window.GdkWindowHandle.Value, width, height, _window.LastKnownScaleFactor);
+        }
     }
 }

+ 5 - 0
src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Runtime.InteropServices;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
@@ -22,11 +23,15 @@ namespace Avalonia.Gtk3
         internal static readonly MouseDevice Mouse = new MouseDevice();
         internal static readonly KeyboardDevice Keyboard = new KeyboardDevice();
         internal static IntPtr App { get; set; }
+        internal static string DisplayClassName;
         public static bool UseDeferredRendering = true;
         public static void Initialize()
         {
             Resolver.Resolve();
             Native.GtkInit(0, IntPtr.Zero);
+            var disp = Native.GdkGetDefaultDisplay();
+            DisplayClassName = Utf8Buffer.StringFromPtr(Native.GTypeName(Marshal.ReadIntPtr(Marshal.ReadIntPtr(disp))));
+            
             using (var utf = new Utf8Buffer("avalonia.app." + Guid.NewGuid()))
                 App = Native.GtkApplicationNew(utf, 0);
             //Mark current thread as UI thread

+ 1 - 1
src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs

@@ -4,6 +4,6 @@ namespace Avalonia.Gtk3
 {
     public interface IDeferredRenderOperation : IDisposable
     {
-        void RenderNow();
+        void RenderNow(IntPtr? ctx);
     }
 }

+ 7 - 4
src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs

@@ -88,10 +88,10 @@ namespace Avalonia.Gtk3
             private readonly int _width;
             private readonly int _height;
 
-            public RenderOp(GtkWidget widget, ManagedCairoSurface _surface, double factor, int width, int height)
+            public RenderOp(GtkWidget widget, ManagedCairoSurface surface, double factor, int width, int height)
             {
                 _widget = widget;
-                this._surface = _surface;
+                _surface = surface ?? throw new ArgumentNullException();
                 _factor = factor;
                 _width = width;
                 _height = height;
@@ -103,9 +103,12 @@ namespace Avalonia.Gtk3
                 _surface = null;
             }
 
-            public void RenderNow()
+            public void RenderNow(IntPtr? ctx)
             {
-                DrawToWidget(_widget, _surface.Surface, _width, _height, _factor);
+                if(ctx.HasValue)
+                    Draw(ctx.Value, _surface.Surface, _factor);
+                else
+                    DrawToWidget(_widget, _surface.Surface, _width, _height, _factor);
             }
         }
         

+ 4 - 0
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@@ -318,6 +318,9 @@ namespace Avalonia.Gtk3.Interop
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)]
             public delegate void g_object_ref(GObject instance);
             
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)]
+            public delegate IntPtr g_type_name(IntPtr instance);
+            
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)]
             public delegate ulong g_signal_connect_object(GObject instance, Utf8Buffer signal, IntPtr handler, IntPtr userData, int flags);
             
@@ -410,6 +413,7 @@ namespace Avalonia.Gtk3.Interop
         public static D.gtk_dialog_add_button GtkDialogAddButton;
         public static D.g_object_unref GObjectUnref;
         public static D.g_object_ref GObjectRef;
+        public static D.g_type_name GTypeName;
         public static D.g_signal_connect_object GSignalConnectObject;
         public static D.g_signal_handler_disconnect GSignalHandlerDisconnect;
         public static D.g_timeout_add GTimeoutAdd;

+ 3 - 2
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@@ -31,7 +31,7 @@ namespace Avalonia.Gtk3
         private object _lock = new object();
         private IDeferredRenderOperation _nextRenderOperation;
         private readonly AutoResetEvent _canSetNextOperation = new AutoResetEvent(true);
-
+        internal IntPtr? GdkWindowHandle;
         public WindowBaseImpl(GtkWindow gtkWidget)
         {
             
@@ -55,6 +55,7 @@ namespace Avalonia.Gtk3
             ConnectEvent("leave-notify-event", OnLeaveNotifyEvent);
             Connect<Native.D.signal_generic>("destroy", OnDestroy);
             Native.GtkWidgetRealize(gtkWidget);
+            GdkWindowHandle = this.Handle.Handle;
             _lastSize = ClientSize;
             if (Gtk3Platform.UseDeferredRendering)
             {
@@ -286,7 +287,7 @@ namespace Avalonia.Gtk3
             }
             if (op != null)
             {
-                op?.RenderNow();
+                op?.RenderNow(null);
                 op?.Dispose();
             }
         }

+ 54 - 0
src/Gtk/Avalonia.Gtk3/X11.cs

@@ -0,0 +1,54 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Avalonia.Gtk3
+{
+    class X11
+    {
+        [DllImport("libX11.so.6")]
+        public static extern IntPtr XOpenDisplay(IntPtr name);
+        
+        [DllImport("libX11.so.6")]
+        public static extern IntPtr XFreeGC(IntPtr display, IntPtr gc);
+        
+        [DllImport("libX11.so.6")]
+        public static extern IntPtr XCreateGC(IntPtr display, IntPtr drawable, ulong valuemask, IntPtr values);
+        
+        [DllImport("libX11.so.6")]
+        public static extern int XInitImage(ref XImage image);
+        
+        [DllImport("libX11.so.6")]
+        public static extern int XDestroyImage(ref XImage image);
+        
+        [DllImport("libX11.so.6")]
+        public static extern IntPtr XSetErrorHandler(XErrorHandler handler);
+
+        public delegate int XErrorHandler(IntPtr display, IntPtr error);
+
+        [DllImport("libX11.so.6")]
+        public static extern int XPutImage(IntPtr display, IntPtr drawable, IntPtr gc, ref XImage image,
+            int srcx, int srcy, int destx, int desty, uint width, uint height);
+
+        
+        public unsafe struct XImage
+        {
+            public int width, height; /* size of image */
+            public int xoffset; /* number of pixels offset in X direction */
+            public int format; /* XYBitmap, XYPixmap, ZPixmap */
+            public IntPtr data; /* pointer to image data */
+            public int byte_order; /* data byte order, LSBFirst, MSBFirst */
+            public int bitmap_unit; /* quant. of scanline 8, 16, 32 */
+            public int bitmap_bit_order; /* LSBFirst, MSBFirst */
+            public int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */
+            public int depth; /* depth of image */
+            public int bytes_per_line; /* accelerator to next scanline */
+            public int bits_per_pixel; /* bits per pixel (ZPixmap) */
+            public ulong red_mask; /* bits in z arrangement */
+            public ulong green_mask;
+            public ulong blue_mask;
+            private fixed byte funcs[128];
+        }
+        
+        
+    }
+}

+ 55 - 0
src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs

@@ -0,0 +1,55 @@
+using System;
+using System.Runtime.InteropServices;
+using Avalonia.Platform;
+
+namespace Avalonia.Gtk3
+{
+    class X11Framebuffer : ILockedFramebuffer
+    {
+        private readonly IntPtr _display;
+        private readonly IntPtr _xid;
+        private IUnmanagedBlob _blob;
+
+        public X11Framebuffer(IntPtr display, IntPtr xid, int width, int height, int factor)
+        {
+            _display = display;
+            _xid = xid;
+            Width = width*factor;
+            Height = height*factor;
+            RowBytes = Width * 4;
+            Dpi = new Vector(96, 96) * factor;
+            Format = PixelFormat.Bgra8888;
+            _blob = AvaloniaLocator.Current.GetService<IRuntimePlatform>().AllocBlob(RowBytes * Height);
+            Address = _blob.Address;
+        }
+        
+        public void Dispose()
+        {
+            var image = new X11.XImage();
+            int bitsPerPixel = 32;
+            image.width = Width;
+            image.height = Height;
+            image.format = 2; //ZPixmap;
+            image.data = Address;
+            image.byte_order = 0;// LSBFirst;
+            image.bitmap_unit = bitsPerPixel;
+            image.bitmap_bit_order = 0;// LSBFirst;
+            image.bitmap_pad = bitsPerPixel;
+            image.depth = 24;
+            image.bytes_per_line = RowBytes - Width * 4;
+            image.bits_per_pixel = bitsPerPixel;
+            X11.XInitImage(ref image);
+            var gc = X11.XCreateGC(_display, _xid, 0, IntPtr.Zero);
+            X11.XPutImage(_display, _xid, gc, ref image, 0, 0, 0, 0, (uint) Width, (uint) Height);
+            X11.XFreeGC(_display, gc);
+            _blob.Dispose();
+        }
+
+        public IntPtr Address { get; }
+        public int Width { get; }
+        public int Height { get; }
+        public int RowBytes { get; }
+        public Vector Dpi { get; }
+        public PixelFormat Format { get; }
+    }
+}

+ 1 - 0
src/Shared/PlatformSupport/StandardRuntimePlatform.cs

@@ -76,6 +76,7 @@ namespace Avalonia.Shared.PlatformSupport
                     GC.RemoveMemoryPressure(Size);
                     IsDisposed = true;
                     Address = IntPtr.Zero;
+                    Size = 0;
                 }
             }
 

+ 14 - 8
src/Skia/Avalonia.Skia/BitmapImpl.cs

@@ -33,16 +33,22 @@ namespace Avalonia.Skia
             PixelWidth = width;
             _dpi = dpi;
             var colorType = fmt?.ToSkColorType() ?? SKImageInfo.PlatformColorType;
-            var runtime = AvaloniaLocator.Current?.GetService<IRuntimePlatform>()?.GetRuntimeInfo();
+            var runtimePlatform = AvaloniaLocator.Current?.GetService<IRuntimePlatform>();
+            var runtime = runtimePlatform?.GetRuntimeInfo();
             if (runtime?.IsDesktop == true && runtime?.OperatingSystem == OperatingSystemType.Linux)
                 colorType = SKColorType.Bgra8888;
-            
-            Bitmap = new SKBitmap();
-            var nfo = new SKImageInfo(width, height, colorType, SKAlphaType.Premul);
-            var plat = AvaloniaLocator.Current.GetService<IRuntimePlatform>();
-            var blob = plat.AllocBlob(nfo.BytesSize);
-            Bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, null, ReleaseDelegate, blob);
-            Bitmap = new SKBitmap(width, height, colorType, SKAlphaType.Premul);
+
+            if (runtimePlatform != null)
+            {
+                Bitmap = new SKBitmap();
+                var nfo = new SKImageInfo(width, height, colorType, SKAlphaType.Premul);
+                var plat = AvaloniaLocator.Current.GetService<IRuntimePlatform>();
+                var blob = plat.AllocBlob(nfo.BytesSize);
+                Bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, null, ReleaseDelegate, blob);
+                
+            }
+            else 
+                Bitmap =  new SKBitmap(width, height, colorType, SKAlphaType.Premul);
             Bitmap.Erase(SKColor.Empty);
         }
 

+ 6 - 1
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@@ -112,6 +112,7 @@ namespace Avalonia.Skia
             public readonly SKPaint Paint;
 
             private IDisposable _disposable1;
+            private IDisposable _disposable2;
 
             public IDisposable ApplyTo(SKPaint paint)
             {
@@ -127,6 +128,8 @@ namespace Avalonia.Skia
             {
                 if (_disposable1 == null)
                     _disposable1 = disposable;
+                else if (_disposable2 == null)
+                    _disposable2 = disposable;
                 else
                     throw new InvalidOperationException();
             }
@@ -135,12 +138,14 @@ namespace Avalonia.Skia
             {
                 Paint = paint;
                 _disposable1 = null;
+                _disposable2 = null;
             }
 
             public void Dispose()
             {
                 Paint?.Dispose();
                 _disposable1?.Dispose();
+                _disposable2?.Dispose();
             }
         }
 
@@ -221,8 +226,8 @@ namespace Avalonia.Skia
                             _visualBrushRenderer.RenderVisualBrush(ctx, visualBrush);
                         }
 
-                        rv.AddDisposable(tileBrushImage);
                         tileBrushImage = intermediate;
+                        rv.AddDisposable(tileBrushImage);
                     }
                 }
                 else

+ 2 - 0
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@@ -146,6 +146,8 @@ namespace Avalonia.Direct2D1
                 }
                 if (s is IExternalDirect2DRenderTargetSurface external)
                     return new ExternalRenderTarget(external, s_dwfactory, s_imagingFactory);
+                if (s is IFramebufferPlatformSurface fb)
+                    return new FramebufferShimRenderTarget(fb, s_imagingFactory, s_d2D1Factory, s_dwfactory);
             }
             throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces");
         }

+ 84 - 0
src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs

@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Avalonia.Controls.Platform.Surfaces;
+using Avalonia.Direct2D1.Media;
+using Avalonia.Direct2D1.Media.Imaging;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using Avalonia.Win32.Interop;
+using SharpDX.Direct2D1;
+using SharpDX.WIC;
+using PixelFormat = Avalonia.Platform.PixelFormat;
+
+namespace Avalonia.Direct2D1
+{
+    class FramebufferShimRenderTarget : IRenderTarget
+    {
+        private readonly IFramebufferPlatformSurface _surface;
+        private readonly ImagingFactory _imagingFactory;
+        private readonly Factory _d2DFactory;
+        private readonly SharpDX.DirectWrite.Factory _dwriteFactory;
+
+        public FramebufferShimRenderTarget(IFramebufferPlatformSurface surface,
+            ImagingFactory imagingFactory, Factory d2dFactory, SharpDX.DirectWrite.Factory dwriteFactory)
+        {
+            _surface = surface;
+            _imagingFactory = imagingFactory;
+            _d2DFactory = d2dFactory;
+            _dwriteFactory = dwriteFactory;
+        }
+
+        public void Dispose()
+        {
+            
+        }
+
+        public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
+        {
+            var locked = _surface.Lock();
+            if (locked.Format == PixelFormat.Rgb565)
+            {
+                locked.Dispose();
+                throw new ArgumentException("Unsupported pixel format: " + locked.Format);
+            }
+
+            return new FramebufferShim(locked, _imagingFactory, _d2DFactory, _dwriteFactory)
+                .CreateDrawingContext(visualBrushRenderer);
+        }
+
+        class FramebufferShim : WicRenderTargetBitmapImpl
+        {
+            private readonly ILockedFramebuffer _target;
+
+            public FramebufferShim(ILockedFramebuffer target,
+                ImagingFactory imagingFactory, Factory d2dFactory, SharpDX.DirectWrite.Factory dwriteFactory
+                ) : base(imagingFactory, d2dFactory, dwriteFactory,
+                    target.Width, target.Height, target.Dpi.X, target.Dpi.Y, target.Format)
+            {
+                _target = target;
+            }
+            
+            public override IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
+            {
+                return base.CreateDrawingContext(visualBrushRenderer, () =>
+                {
+                    using (var l = WicImpl.Lock(BitmapLockFlags.Read))
+                    {
+                        for (var y = 0; y < _target.Height; y++)
+                        {
+                            UnmanagedMethods.CopyMemory(
+                                _target.Address + _target.RowBytes * y,
+                                l.Data.DataPointer + l.Stride * y,
+                                (uint) Math.Min(l.Stride, _target.RowBytes));
+                        }
+                    }
+                    Dispose();
+                    _target.Dispose();
+
+                });
+            }
+        }
+
+    }
+}

+ 9 - 9
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs

@@ -22,8 +22,9 @@ namespace Avalonia.Direct2D1.Media
             int width,
             int height,
             double dpiX,
-            double dpiY)
-            : base(imagingFactory, width, height)
+            double dpiY,
+            Platform.PixelFormat? pixelFormat = null)
+            : base(imagingFactory, width, height, pixelFormat)
         {
             var props = new RenderTargetProperties
             {
@@ -45,14 +46,13 @@ namespace Avalonia.Direct2D1.Media
             base.Dispose();
         }
 
-        public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
+        public virtual IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
+            => CreateDrawingContext(visualBrushRenderer, null);
+
+        public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer, Action finishedCallback)
         {
-            return new DrawingContextImpl(
-                visualBrushRenderer,
-                null,
-                _target,
-                _dwriteFactory,
-                WicImagingFactory);
+            return new DrawingContextImpl(visualBrushRenderer, null, _target, _dwriteFactory, WicImagingFactory,
+                finishedCallback: finishedCallback);
         }
     }
 }

+ 1 - 1
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs

@@ -187,7 +187,7 @@ namespace Avalonia.Base.UnitTests
 
             source.OnNext(45);
 
-            Assert.Equal(null, target.Foo);
+            Assert.Null(target.Foo);
         }
 
         [Fact]

+ 1 - 1
tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs

@@ -66,7 +66,7 @@ namespace Avalonia.Controls.UnitTests.Generators
             target.Dematerialize(1, 1);
 
             Assert.Equal(containers[0].ContainerControl, target.ContainerFromIndex(0));
-            Assert.Equal(null, target.ContainerFromIndex(1));
+            Assert.Null(target.ContainerFromIndex(1));
             Assert.Equal(containers[2].ContainerControl, target.ContainerFromIndex(2));
         }
 

+ 2 - 2
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs

@@ -107,7 +107,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
             target.Content = child;
             target.Content = null;
 
-            Assert.Equal(null, child.GetLogicalParent());
+            Assert.Null(child.GetLogicalParent());
             Assert.Empty(target.GetLogicalChildren());
         }
 
@@ -120,7 +120,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
             target.Content = child;
             target.Content = null;
 
-            Assert.Equal(null, child.GetVisualParent());
+            Assert.Null(child.GetVisualParent());
             Assert.Empty(target.GetVisualChildren());
         }
 

+ 1 - 1
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs

@@ -191,7 +191,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
             target.Content = "bar";
             target.UpdateChild();
 
-            Assert.Equal(null, foo.Parent);
+            Assert.Null(foo.Parent);
 
             logicalChildren = target.GetLogicalChildren();
 

+ 4 - 4
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@@ -171,7 +171,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
 
             target.SelectedItem = new Item();
 
-            Assert.Equal(null, target.SelectedItem);
+            Assert.Null(target.SelectedItem);
             Assert.Equal(-1, target.SelectedIndex);
         }
 
@@ -278,7 +278,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
 
             target.Items = null;
 
-            Assert.Equal(null, target.SelectedItem);
+            Assert.Null(target.SelectedItem);
             Assert.Equal(-1, target.SelectedIndex);
         }
 
@@ -305,7 +305,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
 
             items.RemoveAt(1);
 
-            Assert.Equal(null, target.SelectedItem);
+            Assert.Null(target.SelectedItem);
             Assert.Equal(-1, target.SelectedIndex);
         }
 
@@ -334,7 +334,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
 
             items.Clear();
 
-            Assert.Equal(null, target.SelectedItem);
+            Assert.Null(target.SelectedItem);
             Assert.Equal(-1, target.SelectedIndex);
         }
 

+ 1 - 1
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs

@@ -76,7 +76,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
             target.SelectedItems = new AvaloniaList<object>();
 
             Assert.Equal(-1, target.SelectedIndex);
-            Assert.Equal(null, target.SelectedItem);
+            Assert.Null(target.SelectedItem);
         }
 
         [Fact]

+ 2 - 2
tests/Avalonia.Controls.UnitTests/StackPanelTests.cs

@@ -70,7 +70,7 @@ namespace Avalonia.Controls.UnitTests
             target.Measure(Size.Infinity);
             target.Arrange(new Rect(target.DesiredSize));
 
-            Assert.Equal(new Size(120, 130), target.Bounds.Size);
+            Assert.Equal(new Size(120, 120), target.Bounds.Size);
             Assert.Equal(new Rect(0, 0, 120, 20), target.Children[0].Bounds);
             Assert.Equal(new Rect(0, 30, 120, 30), target.Children[1].Bounds);
             Assert.Equal(new Rect(0, 70, 120, 50), target.Children[2].Bounds);
@@ -94,7 +94,7 @@ namespace Avalonia.Controls.UnitTests
             target.Measure(Size.Infinity);
             target.Arrange(new Rect(target.DesiredSize));
 
-            Assert.Equal(new Size(130, 120), target.Bounds.Size);
+            Assert.Equal(new Size(120, 120), target.Bounds.Size);
             Assert.Equal(new Rect(0, 0, 20, 120), target.Children[0].Bounds);
             Assert.Equal(new Rect(30, 0, 30, 120), target.Children[1].Bounds);
             Assert.Equal(new Rect(70, 0, 50, 120), target.Children[2].Bounds);

+ 1 - 1
tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs

@@ -117,7 +117,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
             };
 
             target.Bind(TextBox.TextProperty, binding);
-            Assert.Equal(null, target.Text);
+            Assert.Null(target.Text);
         }
 
         [Fact]

+ 2 - 2
tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs

@@ -63,7 +63,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Templates
         {
             var selector = new MemberSelector() { MemberName = "StringValue" };
 
-            Assert.Equal(null, selector.Select(null));
+            Assert.Null(selector.Select(null));
         }
 
         [Fact]
@@ -73,7 +73,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Templates
 
             var data = new Item() { StringValue = "Value1" };
 
-            Assert.Same(null, selector.Select(data));
+            Assert.Null(selector.Select(data));
         }
 
         [Fact]

+ 6 - 5
tests/Avalonia.RenderTests/Media/BitmapTests.cs

@@ -64,13 +64,13 @@ namespace Avalonia.Direct2D1.RenderTests.Media
             public void Deallocate() => Marshal.FreeHGlobal(Address);
         }
 
-
-#if AVALONIA_SKIA
+        
         [Theory]
-#else
-        [Theory(Skip = "Framebuffer not supported")]
+        [InlineData(PixelFormat.Rgba8888), InlineData(PixelFormat.Bgra8888),
+#if SKIA
+             InlineData(PixelFormat.Rgb565)
 #endif
-        [InlineData(PixelFormat.Rgba8888), InlineData(PixelFormat.Bgra8888), InlineData(PixelFormat.Rgb565)]
+            ]
         public void FramebufferRenderResultsShouldBeUsableAsBitmap(PixelFormat fmt)
         {
             var testName = nameof(FramebufferRenderResultsShouldBeUsableAsBitmap) + "_" + fmt;
@@ -84,6 +84,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
                 ctx.FillRectangle(Brushes.Chartreuse, new Rect(0, 0, 20, 100));
                 ctx.FillRectangle(Brushes.Crimson, new Rect(20, 0, 20, 100));
                 ctx.FillRectangle(Brushes.Gold, new Rect(40, 0, 20, 100));
+                ctx.PopOpacity();
             }
 
             var bmp = new Bitmap(fmt, fb.Address, fb.Width, fb.Height, fb.RowBytes);

+ 55 - 0
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs

@@ -0,0 +1,55 @@
+using System;
+using Avalonia.Media;
+using Avalonia.Platform;
+using Avalonia.Rendering.SceneGraph;
+using Xunit;
+
+namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
+{
+    public class DrawOperationTests
+    {
+        [Fact]
+        public void Empty_Bounds_Remain_Empty()
+        {
+            var target = new TestDrawOperation(Rect.Empty, Matrix.Identity, null);
+
+            Assert.Equal(Rect.Empty, target.Bounds);
+        }
+
+        [Theory]
+        [InlineData(10, 10, 10, 10, 1, 1, 1, 9, 9, 12, 12)]
+        [InlineData(10, 10, 10, 10, 1, 1, 2, 9, 9, 12, 12)]
+        [InlineData(10, 10, 10, 10, 1.5, 1.5, 1, 14, 14, 17, 17)]
+        public void Rectangle_Bounds_Are_Snapped_To_Pixels(
+            double x,
+            double y,
+            double width,
+            double height,
+            double scaleX,
+            double scaleY,
+            double? penThickness,
+            double expectedX,
+            double expectedY,
+            double expectedWidth,
+            double expectedHeight)
+        {
+            var target = new TestDrawOperation(
+                new Rect(x, y, width, height),
+                Matrix.CreateScale(scaleX, scaleY),
+                penThickness.HasValue ? new Pen(Brushes.Black, penThickness.Value) : null);
+            Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), target.Bounds);
+        }
+
+        private class TestDrawOperation : DrawOperation
+        {
+            public TestDrawOperation(Rect bounds, Matrix transform, Pen pen)
+                :base(bounds, transform, pen)
+            {
+            }
+
+            public override bool HitTest(Point p) => false;
+
+            public override void Render(IDrawingContextImpl context) { }
+        }
+    }
+}

BIN
tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png


BIN
tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png


BIN
tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgba8888.expected.png