Browse Source

Merge remote-tracking branch 'origin/master' into pr/1161-fixed-226

Steven Kirk 8 years ago
parent
commit
b133cb8588
52 changed files with 668 additions and 192 deletions
  1. 0 3
      appveyor.yml
  2. 0 2
      build/NetCore.props
  3. 0 6
      docs/guidelines/build.md
  4. 2 2
      readme.md
  5. 2 2
      samples/ControlCatalog/Pages/ToolTipPage.xaml
  6. 9 0
      src/Avalonia.Base/Platform/IRuntimePlatform.cs
  7. 9 0
      src/Avalonia.Controls/StackPanel.cs
  8. 1 3
      src/Avalonia.Controls/TreeView.cs
  9. 2 1
      src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj
  10. 0 42
      src/Avalonia.DotNetCoreRuntime/NetCoreRuntimePlatform.cs
  11. 7 7
      src/Avalonia.Themes.Default/RepeatButton.xaml
  12. 5 10
      src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs
  13. 26 0
      src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs
  14. 1 4
      src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
  15. 1 1
      src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs
  16. 4 7
      src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs
  17. 1 4
      src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs
  18. 2 3
      src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs
  19. 1 4
      src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
  20. 1 4
      src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs
  21. 33 1
      src/Gtk/Avalonia.Gtk3/FramebufferManager.cs
  22. 5 0
      src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs
  23. 1 1
      src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs
  24. 17 14
      src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs
  25. 1 1
      src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs
  26. 38 0
      src/Gtk/Avalonia.Gtk3/Interop/ManagedCairoSurface.cs
  27. 21 13
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  28. 30 17
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  29. 4 5
      src/Gtk/Avalonia.Gtk3/WindowImpl.cs
  30. 54 0
      src/Gtk/Avalonia.Gtk3/X11.cs
  31. 55 0
      src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs
  32. 135 7
      src/Shared/PlatformSupport/StandardRuntimePlatform.cs
  33. 21 2
      src/Skia/Avalonia.Skia/BitmapImpl.cs
  34. 7 2
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  35. 2 0
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  36. 84 0
      src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
  37. 9 4
      src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs
  38. 1 0
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  39. 1 1
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
  40. 1 1
      tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs
  41. 2 2
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
  42. 1 1
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs
  43. 4 4
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  44. 1 1
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
  45. 2 2
      tests/Avalonia.Controls.UnitTests/StackPanelTests.cs
  46. 1 1
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs
  47. 2 2
      tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs
  48. 6 5
      tests/Avalonia.RenderTests/Media/BitmapTests.cs
  49. 55 0
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs
  50. BIN
      tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png
  51. BIN
      tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png
  52. BIN
      tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgba8888.expected.png

+ 0 - 3
appveyor.yml

@@ -16,9 +16,7 @@ environment:
 init:
 - ps: if (Test-Path env:nuget_address) {[System.IO.File]::AppendAllText("C:\Windows\System32\drivers\etc\hosts", "`n$($env:nuget_address)`tapi.nuget.org")}
 install:
-  - if not exist gtk-sharp-2.12.26.msi appveyor DownloadFile http://download.xamarin.com/GTKforWindows/Windows/gtk-sharp-2.12.26.msi
   - if not exist dotnet-2.0.0.exe appveyor DownloadFile https://download.microsoft.com/download/0/F/D/0FD852A4-7EA1-4E2A-983A-0484AC19B92C/dotnet-sdk-2.0.0-win-x64.exe -FileName "dotnet-2.0.0.exe"
-  - ps: Start-Process -FilePath "msiexec" -ArgumentList "/i gtk-sharp-2.12.26.msi /quiet /qn /norestart" -Wait
   - ps: Start-Process -FilePath "dotnet-2.0.0.exe" -ArgumentList "/quiet" -Wait
   - cmd: set PATH=%programfiles(x86)%\GtkSharp\2.12\bin\;%PATH%
 before_build:
@@ -36,5 +34,4 @@ artifacts:
   - path: artifacts\zip\*.zip
   - path: artifacts\inspectcode.xml
 cache:
-  - gtk-sharp-2.12.26.msi
   - dotnet-2.0.0.exe

+ 0 - 2
build/NetCore.props

@@ -1,6 +1,4 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="System.Threading.ThreadPool" Version="4.3.0" />
-    <PackageReference Include="Microsoft.Extensions.DependencyModel" Version="1.1.0" />
   </ItemGroup>
 </Project>

+ 0 - 6
docs/guidelines/build.md

@@ -4,12 +4,6 @@
 
 Avalonia requires at least Visual Studio 2017 and .NET Core SDK 2.0 to build on Windows.
 
-### Install GTK Sharp
-
-For the moment under windows, you must have [gtk-sharp](http://www.mono-project.com/download/#download-win)
-installed. Note that after installing the package your machine may require a restart before GTK# is
-added to your path. We hope to remove or make this dependency optional at some point in the future.
-
 ### Clone the Avalonia repository
 
 ```

+ 2 - 2
readme.md

@@ -7,7 +7,7 @@
 
 A multi-platform .NET UI framework. It can run on Windows, Linux, Mac OS X, iOS and Android.
 
-![](docs/images/screen.png)
+[![](docs/images/screen.png)](https://youtu.be/wHcB3sGLVYg)
 
 Desktop platforms:
 
@@ -36,7 +36,7 @@ Try out the ControlCatalog to give it a quick demo.
 
 Avalonia is a multi-platform windowing toolkit - somewhat like WPF - that is intended to be multi-
 platform. It supports XAML, lookless controls and a flexible styling system, and runs on Windows
-using Direct2D and other operating systems using Gtk & Cairo.
+using Direct2D and other operating systems using Skia and OS-specific windowing backend (GTK, Cocoa, etc).
 
 ## Current Status
 

+ 2 - 2
samples/ControlCatalog/Pages/ToolTipPage.xaml

@@ -10,7 +10,7 @@
               HorizontalAlignment="Center">
             <Border Grid.Column="0"
                     Grid.Row="1"
-                    Background="{StyleResource ThemeAccentBrush}"
+                    Background="{DynamicResource ThemeAccentBrush}"
                     Margin="5"
                     Padding="50"
                     ToolTip.Tip="This is a ToolTip">
@@ -24,7 +24,7 @@
             <Border Name="Border"
                     Grid.Column="1"
                     Grid.Row="1"
-                    Background="{StyleResource ThemeAccentBrush}"
+                    Background="{DynamicResource ThemeAccentBrush}"
                     Margin="5"
                     Padding="50"
                     ToolTip.Placement="Bottom">

+ 9 - 0
src/Avalonia.Base/Platform/IRuntimePlatform.cs

@@ -14,6 +14,15 @@ namespace Avalonia.Platform
         IDisposable StartSystemTimer(TimeSpan interval, Action tick);
         string GetStackTrace();
         RuntimePlatformInfo GetRuntimeInfo();
+        IUnmanagedBlob AllocBlob(int size);
+    }
+
+    public interface IUnmanagedBlob : IDisposable
+    {
+        IntPtr Address { get; }
+        int Size { get; }
+        bool IsDisposed { get; }
+        
     }
 
     public struct RuntimePlatformInfo

+ 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;

+ 2 - 1
src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj

@@ -2,6 +2,7 @@
   <PropertyGroup>
     <TargetFramework>netcoreapp2.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+    <DefineConstants>$(DefineConstants);DOTNETCORE</DefineConstants>
   </PropertyGroup>
   <PropertyGroup>
     <DocumentationFile>bin\$(Configuration)\Avalonia.DotNetCoreRuntime.XML</DocumentationFile>
@@ -21,5 +22,5 @@
     <ProjectReference Include="..\Windows\Avalonia.Win32.NetStandard\Avalonia.Win32.NetStandard.csproj" />
   </ItemGroup>
   <Import Project="..\..\build\NetCore.props" />
-  <Import Project="..\Shared\PlatformSupport\PlatformSupport.projitems" Label="Shared" />
+  <Import Project="..\Shared\PlatformSupport\PlatformSupport.projitems" />
 </Project>

+ 0 - 42
src/Avalonia.DotNetCoreRuntime/NetCoreRuntimePlatform.cs

@@ -1,42 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-using System.Threading.Tasks;
-using Microsoft.DotNet.PlatformAbstractions;
-using Microsoft.Extensions.DependencyModel;
-
-namespace Avalonia.Shared.PlatformSupport
-{
-    internal partial class StandardRuntimePlatform
-    {
-        private static readonly Lazy<Assembly[]> Assemblies = new Lazy<Assembly[]>(LoadAssemblies);
-        public Assembly[] GetLoadedAssemblies() => Assemblies.Value;
-
-        static Assembly[] LoadAssemblies()
-        {
-            var assemblies = new List<Assembly>();
-            // Mostly copy-pasted from (MIT):
-            // https://github.com/StefH/System.AppDomain.Core/blob/0b35e676c2721aa367b96e62eb52c97ee0b43a70/src/System.AppDomain.NetCoreApp/AppDomain.cs
-
-            foreach (var assemblyName in
-                DependencyContext.Default.GetRuntimeAssemblyNames(RuntimeEnvironment.GetRuntimeIdentifier()))
-            {
-                try
-                {
-                    var assembly = Assembly.Load(assemblyName);
-                    // just load all types and skip this assembly if one or more types cannot be resolved
-                    assembly.DefinedTypes.ToArray();
-                    assemblies.Add(assembly);
-                }
-                catch (Exception ex)
-                {
-                    Debug.Write(ex.Message);
-                }
-            }
-            return assemblies.ToArray();
-        }
-    }
-}

+ 7 - 7
src/Avalonia.Themes.Default/RepeatButton.xaml

@@ -1,13 +1,13 @@
 <Styles xmlns="https://github.com/avaloniaui">
     <Style Selector="RepeatButton">
         <Setter Property="Background"
-                Value="{StyleResource ThemeControlMidBrush}" />
+                Value="{DynamicResource ThemeControlMidBrush}" />
         <Setter Property="BorderBrush"
-                Value="{StyleResource ThemeBorderLightBrush}" />
+                Value="{DynamicResource ThemeBorderLightBrush}" />
         <Setter Property="BorderThickness"
-                Value="{StyleResource ThemeBorderThickness}" />
+                Value="{DynamicResource ThemeBorderThickness}" />
         <Setter Property="Foreground"
-                Value="{StyleResource ThemeForegroundBrush}" />
+                Value="{DynamicResource ThemeForegroundBrush}" />
         <Setter Property="HorizontalContentAlignment"
                 Value="Center" />
         <Setter Property="VerticalContentAlignment"
@@ -31,14 +31,14 @@
     </Style>
     <Style Selector="RepeatButton:pointerover /template/ ContentPresenter">
         <Setter Property="BorderBrush"
-                Value="{StyleResource ThemeBorderMidBrush}" />
+                Value="{DynamicResource ThemeBorderMidBrush}" />
     </Style>
     <Style Selector="RepeatButton:pressed  /template/ ContentPresenter">
         <Setter Property="Background"
-                Value="{StyleResource ThemeControlDarkBrush}" />
+                Value="{DynamicResource ThemeControlDarkBrush}" />
     </Style>
     <Style Selector="RepeatButton:disabled">
         <Setter Property="Opacity"
-                Value="{StyleResource ThemeDisabledOpacity}" />
+                Value="{DynamicResource ThemeDisabledOpacity}" />
     </Style>
 </Styles>

+ 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>

+ 33 - 1
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;
-            return new ImageSurfaceFramebuffer(_window, width, 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);
     }
 }

+ 17 - 14
src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs

@@ -16,23 +16,23 @@ namespace Avalonia.Gtk3
     {
         private readonly WindowBaseImpl _impl;
         private readonly GtkWidget _widget;
-        private CairoSurface _surface;
+        private ManagedCairoSurface _surface;
         private int _factor;
         private object _lock = new object();
-        public ImageSurfaceFramebuffer(WindowBaseImpl impl, int width, int height)
+        public ImageSurfaceFramebuffer(WindowBaseImpl impl, int width, int height, int factor)
         {
             _impl = impl;
             _widget = impl.GtkWidget;
-            _factor = (int)(Native.GtkWidgetGetScaleFactor?.Invoke(_widget) ?? 1u);
+            _factor = factor;
             width *= _factor;
             height *= _factor;
-            _surface = Native.CairoImageSurfaceCreate(1, width, height);
+            _surface = new ManagedCairoSurface(width, height);
             
             Width = width;
             Height = height;
-            Address = Native.CairoImageSurfaceGetData(_surface);
-            RowBytes = Native.CairoImageSurfaceGetStride(_surface);
-            Native.CairoSurfaceFlush(_surface);
+            Address = _surface.Buffer;
+            RowBytes = _surface.Stride;
+            Native.CairoSurfaceFlush(_surface.Surface);
         }
 
         static void Draw(IntPtr context, CairoSurface surface, double factor)
@@ -83,15 +83,15 @@ namespace Avalonia.Gtk3
         class RenderOp : IDeferredRenderOperation
         {
             private readonly GtkWidget _widget;
-            private CairoSurface _surface;
+            private ManagedCairoSurface _surface;
             private readonly double _factor;
             private readonly int _width;
             private readonly int _height;
 
-            public RenderOp(GtkWidget widget, CairoSurface _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, _width, _height, _factor);
+                if(ctx.HasValue)
+                    Draw(ctx.Value, _surface.Surface, _factor);
+                else
+                    DrawToWidget(_widget, _surface.Surface, _width, _height, _factor);
             }
         }
         
@@ -116,9 +119,9 @@ namespace Avalonia.Gtk3
                 if (Dispatcher.UIThread.CheckAccess())
                 {
                     if (_impl.CurrentCairoContext != IntPtr.Zero)
-                        Draw(_impl.CurrentCairoContext, _surface, _factor);
+                        Draw(_impl.CurrentCairoContext, _surface.Surface, _factor);
                     else
-                        DrawToWidget(_widget, _surface, Width, Height, _factor);
+                        DrawToWidget(_widget, _surface.Surface, Width, Height, _factor);
                     _surface.Dispose();
                 }
                 else

+ 1 - 1
src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs

@@ -53,7 +53,7 @@ namespace Avalonia.Gtk3.Interop
             if (interval == 0)
                 throw new ArgumentException("Don't know how to create a timer with zero or negative interval");
             var timer = new Timer ();
-            GlibTimeout.Add(GlibPriority.FromDispatcherPriority(DispatcherPriority.Background), interval,
+            GlibTimeout.Add(GlibPriority.FromDispatcherPriority(DispatcherPriority.Normal), interval,
                 () =>
                 {
                     if (timer.Stopped)

+ 38 - 0
src/Gtk/Avalonia.Gtk3/Interop/ManagedCairoSurface.cs

@@ -0,0 +1,38 @@
+using System;
+using System.Runtime.InteropServices;
+using Avalonia.Platform;
+
+namespace Avalonia.Gtk3.Interop
+{
+    class ManagedCairoSurface : IDisposable
+    {
+        public IntPtr Buffer { get; private set; }
+        public CairoSurface Surface { get; private set; }
+        public int Stride { get; private set; }
+        private int _size;
+        private IRuntimePlatform _plat;
+        private IUnmanagedBlob _blob;
+
+        public ManagedCairoSurface(int width, int height)
+        {
+            _plat = AvaloniaLocator.Current.GetService<IRuntimePlatform>();
+            Stride = width * 4;
+            _size = height * Stride;
+            _blob = _plat.AllocBlob(_size * 2);
+            Buffer = _blob.Address;
+            Surface = Native.CairoImageSurfaceCreateForData(Buffer, 1, width, height, Stride);
+        }
+        
+        public void Dispose()
+        {
+            
+            if (Buffer != IntPtr.Zero)
+            {
+                Surface.Dispose();
+                _blob.Dispose();
+                Buffer = IntPtr.Zero;
+            }
+        }
+
+    }
+}

+ 21 - 13
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@@ -160,6 +160,9 @@ namespace Avalonia.Gtk3.Interop
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
             public delegate CairoSurface cairo_image_surface_create(int format, int width, int height);
+            
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
+            public delegate CairoSurface cairo_image_surface_create_for_data(IntPtr data, int format, int width, int height, int stride);
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
             public delegate IntPtr cairo_image_surface_get_data(CairoSurface surface);
@@ -178,7 +181,7 @@ namespace Avalonia.Gtk3.Interop
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
             public delegate void cairo_surface_destroy(IntPtr surface);
-
+            
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
             public delegate void cairo_set_source_surface(IntPtr cr, CairoSurface surface, double x, double y);
             
@@ -236,17 +239,17 @@ namespace Avalonia.Gtk3.Interop
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
             public delegate GdkWindowState gdk_window_get_state(IntPtr window);
 
-            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
-            public delegate void gdk_window_iconify(IntPtr window);
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
+            public delegate void gtk_window_iconify(GtkWindow window);
 
-            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
-            public delegate void gdk_window_deiconify(IntPtr window);
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
+            public delegate void gtk_window_deiconify(GtkWindow window);
 
-            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
-            public delegate void gdk_window_maximize(IntPtr window);
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
+            public delegate void gtk_window_maximize(GtkWindow window);
 
-            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
-            public delegate void gdk_window_unmaximize(IntPtr window);
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
+            public delegate void gtk_window_unmaximize(GtkWindow window);
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
             public delegate void gtk_window_set_geometry_hints(GtkWindow window, IntPtr geometry_widget, ref GdkGeometry geometry, GdkWindowHints geom_mask);
@@ -315,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);
             
@@ -407,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;
@@ -437,10 +444,10 @@ namespace Avalonia.Gtk3.Interop
         public static D.gdk_window_get_origin GdkWindowGetOrigin;
         public static D.gdk_window_get_pointer GdkWindowGetPointer;
         public static D.gdk_window_get_state GdkWindowGetState;
-        public static D.gdk_window_iconify GdkWindowIconify;
-        public static D.gdk_window_deiconify GdkWindowDeiconify;
-        public static D.gdk_window_maximize GdkWindowMaximize;
-        public static D.gdk_window_unmaximize GdkWindowUnmaximize;
+        public static D.gtk_window_iconify GtkWindowIconify;
+        public static D.gtk_window_deiconify GtkWindowDeiconify;
+        public static D.gtk_window_maximize GtkWindowMaximize;
+        public static D.gtk_window_unmaximize GtkWindowUnmaximize;
         public static D.gdk_window_begin_move_drag GdkWindowBeginMoveDrag;
         public static D.gdk_window_begin_resize_drag GdkWindowBeginResizeDrag;
         public static D.gdk_event_request_motions GdkEventRequestMotions;
@@ -459,6 +466,7 @@ namespace Avalonia.Gtk3.Interop
         public static D.gdk_cairo_create GdkCairoCreate;
         
         public static D.cairo_image_surface_create CairoImageSurfaceCreate;
+        public static D.cairo_image_surface_create_for_data CairoImageSurfaceCreateForData;
         public static D.cairo_image_surface_get_data CairoImageSurfaceGetData;
         public static D.cairo_image_surface_get_stride CairoImageSurfaceGetStride;
         public static D.cairo_surface_mark_dirty CairoSurfaceMarkDirty;

+ 30 - 17
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@@ -4,6 +4,7 @@ using System.Diagnostics;
 using System.Linq;
 using System.Runtime.InteropServices;
 using System.Text;
+using System.Threading;
 using Avalonia.Controls;
 using Avalonia.Gtk3.Interop;
 using Avalonia.Input;
@@ -29,7 +30,8 @@ namespace Avalonia.Gtk3
         private GCHandle _gcHandle;
         private object _lock = new object();
         private IDeferredRenderOperation _nextRenderOperation;
-
+        private readonly AutoResetEvent _canSetNextOperation = new AutoResetEvent(true);
+        internal IntPtr? GdkWindowHandle;
         public WindowBaseImpl(GtkWindow gtkWidget)
         {
             
@@ -53,12 +55,8 @@ namespace Avalonia.Gtk3
             ConnectEvent("leave-notify-event", OnLeaveNotifyEvent);
             Connect<Native.D.signal_generic>("destroy", OnDestroy);
             Native.GtkWidgetRealize(gtkWidget);
+            GdkWindowHandle = this.Handle.Handle;
             _lastSize = ClientSize;
-            GlibTimeout.Add(0, 16, () =>
-            {
-                Invalidate(default(Rect));
-                return true;
-            });
             if (Gtk3Platform.UseDeferredRendering)
             {
                 Native.GtkWidgetSetDoubleBuffered(gtkWidget, false);
@@ -138,7 +136,7 @@ namespace Avalonia.Gtk3
                         ? RawMouseEventType.LeftButtonDown
                         : evnt->button == 3 ? RawMouseEventType.RightButtonDown : RawMouseEventType.MiddleButtonDown,
                 new Point(evnt->x, evnt->y), GetModifierKeys(evnt->state));
-            Input?.Invoke(e);
+            OnInput(e);
             return true;
         }
 
@@ -166,7 +164,7 @@ namespace Avalonia.Gtk3
                 _inputRoot,
                 RawMouseEventType.Move,
                 position, GetModifierKeys(evnt->state));
-            Input(e);
+            OnInput(e);
             
             return true;
         }
@@ -195,7 +193,7 @@ namespace Avalonia.Gtk3
             }
             var e = new RawMouseWheelEventArgs(Gtk3Platform.Mouse, evnt->time, _inputRoot,
                 new Point(evnt->x, evnt->y), delta, GetModifierKeys(evnt->state));
-            Input(e);
+            OnInput(e);
             return true;
         }
 
@@ -210,7 +208,7 @@ namespace Avalonia.Gtk3
                 evnt->time,
                 evnt->type == GdkEventType.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
                 Avalonia.Gtk.Common.KeyTransform.ConvertKey((GdkKey)evnt->keyval), GetModifierKeys((GdkModifierType)evnt->state));
-            Input(e);
+            OnInput(e);
             return true;
         }
 
@@ -218,7 +216,7 @@ namespace Avalonia.Gtk3
         {
             var evnt = (GdkEventCrossing*) pev;
             var position = new Point(evnt->x, evnt->y);
-            Input(new RawMouseEventArgs(Gtk3Platform.Mouse,
+            OnInput(new RawMouseEventArgs(Gtk3Platform.Mouse,
                 evnt->time,
                 _inputRoot,
                 RawMouseEventType.Move,
@@ -228,7 +226,7 @@ namespace Avalonia.Gtk3
 
         private unsafe bool OnCommit(IntPtr gtkwidget, IntPtr utf8string, IntPtr userdata)
         {
-            Input(new RawTextInputEventArgs(Gtk3Platform.Keyboard, _lastKbdEvent, Utf8Buffer.StringFromPtr(utf8string)));
+            OnInput(new RawTextInputEventArgs(Gtk3Platform.Keyboard, _lastKbdEvent, Utf8Buffer.StringFromPtr(utf8string)));
             return true;
         }
 
@@ -260,11 +258,19 @@ namespace Avalonia.Gtk3
 
         public void SetNextRenderOperation(IDeferredRenderOperation op)
         {
-            lock (_lock)
+            while (true)
             {
-                _nextRenderOperation?.Dispose();
-                _nextRenderOperation = op;
+                lock (_lock)
+                {
+                    if (_nextRenderOperation == null)
+                    {
+                        _nextRenderOperation = op;
+                        return;
+                    }
+                }
+                _canSetNextOperation.WaitOne();
             }
+            
         }
 
         private void OnRenderTick()
@@ -277,10 +283,11 @@ namespace Avalonia.Gtk3
                     op = _nextRenderOperation;
                     _nextRenderOperation = null;
                 }
+                _canSetNextOperation.Set();
             }
             if (op != null)
             {
-                op?.RenderNow();
+                op?.RenderNow(null);
                 op?.Dispose();
             }
         }
@@ -311,7 +318,7 @@ namespace Avalonia.Gtk3
 
         public IMouseDevice MouseDevice => Gtk3Platform.Mouse;
 
-        public double Scaling => (double) 1 / (Native.GtkWidgetGetScaleFactor?.Invoke(GtkWidget) ?? 1);
+        public double Scaling => LastKnownScaleFactor = (int) (Native.GtkWidgetGetScaleFactor?.Invoke(GtkWidget) ?? 1);
 
         public IPlatformHandle Handle => this;
 
@@ -338,6 +345,11 @@ namespace Avalonia.Gtk3
 
         public void SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot;
 
+        void OnInput(RawInputEventArgs args)
+        {
+            Dispatcher.UIThread.InvokeAsync(() => Input?.Invoke(args), DispatcherPriority.Input);
+        }
+
         public Point PointToClient(Point point)
         {
             int x, y;
@@ -387,6 +399,7 @@ namespace Avalonia.Gtk3
 
 
         public Size ClientSize { get; private set; }
+        public int LastKnownScaleFactor { get; private set; }
 
         public void Resize(Size value)
         {

+ 4 - 5
src/Gtk/Avalonia.Gtk3/WindowImpl.cs

@@ -32,15 +32,14 @@ namespace Avalonia.Gtk3
             }
             set
             {
-                var w = Native.GtkWidgetGetWindow(GtkWidget);
                 if (value == WindowState.Minimized)
-                    Native.GdkWindowIconify(w);
+                    Native.GtkWindowIconify(GtkWidget);
                 else if (value == WindowState.Maximized)
-                    Native.GdkWindowMaximize(w);
+                    Native.GtkWindowMaximize(GtkWidget);
                 else
                 {
-                    Native.GdkWindowUnmaximize(w);
-                    Native.GdkWindowDeiconify(w);
+                    Native.GtkWindowUnmaximize(GtkWidget);
+                    Native.GtkWindowDeiconify(GtkWidget);
                 }
             }
         }

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

+ 135 - 7
src/Shared/PlatformSupport/StandardRuntimePlatform.cs

@@ -2,8 +2,11 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.Collections.Generic;
 using System.Reflection;
 using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
 using System.Threading;
 using Avalonia.Platform;
 
@@ -11,20 +14,145 @@ namespace Avalonia.Shared.PlatformSupport
 {
     internal partial class StandardRuntimePlatform : IRuntimePlatform
     {
-
-#if NETCOREAPP2_0
-        public void PostThreadPoolItem(Action cb) =>  ThreadPool.QueueUserWorkItem(_ => cb(), null);
-#else
-        public Assembly[] GetLoadedAssemblies() => AppDomain.CurrentDomain.GetAssemblies();
         public void PostThreadPoolItem(Action cb) => ThreadPool.UnsafeQueueUserWorkItem(_ => cb(), null);
-#endif
+        public Assembly[] GetLoadedAssemblies() => AppDomain.CurrentDomain.GetAssemblies();
         public IDisposable StartSystemTimer(TimeSpan interval, Action tick)
         {
             return new Timer(_ => tick(), null, interval, interval);
         }
 
+        public string GetStackTrace() => Environment.StackTrace;
+
+        public IUnmanagedBlob AllocBlob(int size) => new UnmanagedBlob(this, size);
+        
+        class UnmanagedBlob : IUnmanagedBlob
+        {
+            private readonly StandardRuntimePlatform _plat;
+#if DEBUG
+            private static readonly List<string> Backtraces = new List<string>();
+            private static Thread GCThread;
+            private readonly string _backtrace;
 
 
-        public string GetStackTrace() => Environment.StackTrace;
+            class GCThreadDetector
+            {
+                ~GCThreadDetector()
+                {
+                    GCThread = Thread.CurrentThread;
+                }
+            }
+
+            [MethodImpl(MethodImplOptions.NoInlining)]
+            static void Spawn() => new GCThreadDetector();
+            
+            static UnmanagedBlob()
+            {
+                Spawn();
+                GC.WaitForPendingFinalizers();
+            }
+            
+#endif
+            
+            public UnmanagedBlob(StandardRuntimePlatform plat, int size)
+            {
+                _plat = plat;
+                Address = plat.Alloc(size);
+                GC.AddMemoryPressure(size);
+                Size = size;
+#if DEBUG
+                _backtrace = Environment.StackTrace;
+                Backtraces.Add(_backtrace);
+#endif
+            }
+
+            void DoDispose()
+            {
+                if (!IsDisposed)
+                {
+#if DEBUG
+                    Backtraces.Remove(_backtrace);
+#endif
+                    _plat.Free(Address, Size);
+                    GC.RemoveMemoryPressure(Size);
+                    IsDisposed = true;
+                    Address = IntPtr.Zero;
+                    Size = 0;
+                }
+            }
+
+            public void Dispose()
+            {
+#if DEBUG
+                if (Thread.CurrentThread.ManagedThreadId == GCThread?.ManagedThreadId)
+                {
+                    Console.Error.WriteLine("Native blob disposal from finalizer thread\nBacktrace: "
+                                            + Environment.StackTrace
+                                            + "\n\nBlob created by " + _backtrace);
+                }
+#endif
+                DoDispose();
+                GC.SuppressFinalize(this);
+            }
+
+            ~UnmanagedBlob()
+            {
+#if DEBUG
+                Console.Error.WriteLine("Undisposed native blob created by " + _backtrace);
+#endif
+                DoDispose();
+            }
+
+            public IntPtr Address { get; private set; }
+            public int Size { get; private set; }
+            public bool IsDisposed { get; private set; }
+        }
+        
+        
+        
+#if FULLDOTNET || DOTNETCORE
+        [DllImport("libc", SetLastError = true)]
+        private static extern IntPtr mmap(IntPtr addr, IntPtr length, int prot, int flags, int fd, IntPtr offset);
+        [DllImport("libc", SetLastError = true)]
+        private static extern int munmap(IntPtr addr, IntPtr length);
+        [DllImport("libc", SetLastError = true)]
+        private static extern long sysconf(int name);
+
+        private bool? _useMmap;
+        private bool UseMmap 
+            => _useMmap ?? ((_useMmap = GetRuntimeInfo().OperatingSystem == OperatingSystemType.Linux)).Value;
+        
+        IntPtr Alloc(int size)
+        {
+            if (UseMmap)
+            {
+                var rv = mmap(IntPtr.Zero, new IntPtr(size), 3, 0x22, -1, IntPtr.Zero);
+                if (rv.ToInt64() == -1 || (ulong) rv.ToInt64() == 0xffffffff)
+                {
+                    var errno = Marshal.GetLastWin32Error();
+                    throw new Exception("Unable to allocate memory: " + errno);
+                }
+                return rv;
+            }
+            else
+                return Marshal.AllocHGlobal(size);
+        }
+
+        void Free(IntPtr ptr, int len)
+        {
+            if (UseMmap)
+            {
+                if (munmap(ptr, new IntPtr(len)) == -1)
+                {
+                    var errno = Marshal.GetLastWin32Error();
+                    throw new Exception("Unable to free memory: " + errno);
+                }
+            }
+            else
+                Marshal.FreeHGlobal(ptr);
+        }
+#else
+        IntPtr Alloc(int size) => Marshal.AllocHGlobal(size);
+        void Free(IntPtr ptr, int len) => Marshal.FreeHGlobal(ptr);
+#endif
     }
 }

+ 21 - 2
src/Skia/Avalonia.Skia/BitmapImpl.cs

@@ -20,16 +20,35 @@ namespace Avalonia.Skia
             _dpi = new Vector(96, 96);
         }
 
+        static void ReleaseProc(IntPtr address, object ctx)
+        {
+            ((IUnmanagedBlob) ctx).Dispose();
+        }
+
+        private static readonly SKBitmapReleaseDelegate ReleaseDelegate = ReleaseProc;
+        
         public BitmapImpl(int width, int height, Vector dpi, PixelFormat? fmt = null)
         {
             PixelHeight = height;
             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(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);
         }
 

+ 7 - 2
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@@ -45,7 +45,7 @@ namespace Avalonia.Skia
             var s = sourceRect.ToSKRect();
             var d = destRect.ToSKRect();
             using (var paint = new SKPaint()
-                    { Color = new SKColor(255, 255, 255, (byte)(255 * opacity)) })
+                    { Color = new SKColor(255, 255, 255, (byte)(255 * opacity * _currentOpacity)) })
             {
                 Canvas.DrawBitmap(impl.Bitmap, s, d, paint);
             }
@@ -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);
+                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 : RenderTargetBitmapImpl
+        {
+            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 - 4
src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.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,9 +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, _target, _dwriteFactory);
+            return new DrawingContextImpl(visualBrushRenderer, _target, _dwriteFactory,
+                finishedCallback: finishedCallback);
         }
     }
 }

+ 1 - 0
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs

@@ -74,6 +74,7 @@ namespace Avalonia.Direct2D1.Media
         public WicBitmapImpl(ImagingFactory factory, Platform.PixelFormat format, IntPtr data, int width, int height, int stride)
         {
             WicImpl = new Bitmap(factory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand);
+            _factory = factory;
             PixelFormat = format;
             using (var l = WicImpl.Lock(BitmapLockFlags.Write))
             {

+ 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