From 79535609b69cb71d02ef2c7e79b509d6fe932337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Fri, 29 Sep 2017 17:34:28 +0200 Subject: [PATCH 01/26] Use DynamicResource instead of StyleResource Fixes #1165 --- samples/ControlCatalog/Pages/ToolTipPage.xaml | 4 ++-- src/Avalonia.Themes.Default/RepeatButton.xaml | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/samples/ControlCatalog/Pages/ToolTipPage.xaml b/samples/ControlCatalog/Pages/ToolTipPage.xaml index 5cf7fee4d1..aa7d60bd11 100644 --- a/samples/ControlCatalog/Pages/ToolTipPage.xaml +++ b/samples/ControlCatalog/Pages/ToolTipPage.xaml @@ -10,7 +10,7 @@ HorizontalAlignment="Center"> @@ -24,7 +24,7 @@ diff --git a/src/Avalonia.Themes.Default/RepeatButton.xaml b/src/Avalonia.Themes.Default/RepeatButton.xaml index 557e2d3b03..1caaa266de 100644 --- a/src/Avalonia.Themes.Default/RepeatButton.xaml +++ b/src/Avalonia.Themes.Default/RepeatButton.xaml @@ -1,13 +1,13 @@ \ No newline at end of file From c7262e76d2bcab22c9458d6f05304b7f486aadbc Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 3 Oct 2017 08:44:11 +0300 Subject: [PATCH 02/26] [SKIA] Fixed DrawImage opacity --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 4feb910deb..eadcc05744 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/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); } From 6514723fb7edb0e629f86d6a2e80fd9a6fc3d4c6 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 3 Oct 2017 08:46:31 +0300 Subject: [PATCH 03/26] [GTK3] Priority and rendering fixes --- src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index a3b5d57fdc..009ef6763c 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -54,11 +54,6 @@ namespace Avalonia.Gtk3 Connect("destroy", OnDestroy); Native.GtkWidgetRealize(gtkWidget); _lastSize = ClientSize; - GlibTimeout.Add(0, 16, () => - { - Invalidate(default(Rect)); - return true; - }); if (Gtk3Platform.UseDeferredRendering) { Native.GtkWidgetSetDoubleBuffered(gtkWidget, false); @@ -138,7 +133,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 +161,7 @@ namespace Avalonia.Gtk3 _inputRoot, RawMouseEventType.Move, position, GetModifierKeys(evnt->state)); - Input(e); + OnInput(e); return true; } @@ -195,7 +190,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 +205,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 +213,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 +223,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; } @@ -338,6 +333,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; From 080cdb5089b95abe180953e0e71dc8ae5a906d4b Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 3 Oct 2017 09:36:47 +0300 Subject: [PATCH 04/26] Linked screenshot to a video of control catalog --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 9f16405726..9e6d7588f1 100644 --- a/readme.md +++ b/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: From 82b2028d8e2bb8bdb8c04c90c9263c02e6c93bf6 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 3 Oct 2017 09:41:47 +0300 Subject: [PATCH 05/26] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 9e6d7588f1..3f4840fce2 100644 --- a/readme.md +++ b/readme.md @@ -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 From 088fc6fc565471ab57889fd81d10e574b90e2f39 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 3 Oct 2017 13:09:29 +0300 Subject: [PATCH 06/26] [GTK3] Fixed DPI support for DeferredRenderer --- src/Gtk/Avalonia.Gtk3/FramebufferManager.cs | 2 +- .../Avalonia.Gtk3/ImageSurfaceFramebuffer.cs | 26 ++++++++----------- src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 6 ++++- src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs | 3 ++- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs b/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs index 00d1ec05f3..b0cc668569 100644 --- a/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs +++ b/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs @@ -27,7 +27,7 @@ namespace Avalonia.Gtk3 var s = _window.ClientSize; var width = (int) s.Width; var height = (int) s.Height; - return new ImageSurfaceFramebuffer(_window, width, height); + return new ImageSurfaceFramebuffer(_window, width, height, _window.LastKnownScaleFactor); } } } diff --git a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs index 34a95df47e..53771dd369 100644 --- a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs +++ b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs @@ -19,11 +19,11 @@ namespace Avalonia.Gtk3 private CairoSurface _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); @@ -32,23 +32,21 @@ namespace Avalonia.Gtk3 Height = height; Address = Native.CairoImageSurfaceGetData(_surface); RowBytes = Native.CairoImageSurfaceGetStride(_surface); + Native.CairoSurfaceSetDeviceScale(_surface, factor, factor); Native.CairoSurfaceFlush(_surface); } - static void Draw(IntPtr context, CairoSurface surface, double factor) + static void Draw(IntPtr context, CairoSurface surface) { - Native.CairoSurfaceMarkDirty(surface); - Native.CairoScale(context, 1d / factor, 1d / factor); Native.CairoSetSourceSurface(context, surface, 0, 0); Native.CairoPaint(context); - } /* static Stopwatch St =Stopwatch.StartNew(); private static int _frames; private static int _fps;*/ - static void DrawToWidget(GtkWidget widget, CairoSurface surface, int width, int height, double factor) + static void DrawToWidget(GtkWidget widget, CairoSurface surface, int width, int height) { if(surface == null || widget.IsClosed) return; @@ -58,7 +56,7 @@ namespace Avalonia.Gtk3 var rc = new GdkRectangle {Width = width, Height = height}; Native.GdkWindowBeginPaintRect(window, ref rc); var context = Native.GdkCairoCreate(window); - Draw(context, surface, factor); + Draw(context, surface); /* _frames++; var el = St.Elapsed; @@ -84,15 +82,13 @@ namespace Avalonia.Gtk3 { private readonly GtkWidget _widget; private CairoSurface _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, CairoSurface _surface, int width, int height) { _widget = widget; this._surface = _surface; - _factor = factor; _width = width; _height = height; } @@ -105,7 +101,7 @@ namespace Avalonia.Gtk3 public void RenderNow() { - DrawToWidget(_widget, _surface, _width, _height, _factor); + DrawToWidget(_widget, _surface, _width, _height); } } @@ -116,13 +112,13 @@ namespace Avalonia.Gtk3 if (Dispatcher.UIThread.CheckAccess()) { if (_impl.CurrentCairoContext != IntPtr.Zero) - Draw(_impl.CurrentCairoContext, _surface, _factor); + Draw(_impl.CurrentCairoContext, _surface); else - DrawToWidget(_widget, _surface, Width, Height, _factor); + DrawToWidget(_widget, _surface, Width, Height); _surface.Dispose(); } else - _impl.SetNextRenderOperation(new RenderOp(_widget, _surface, _factor, Width, Height)); + _impl.SetNextRenderOperation(new RenderOp(_widget, _surface, Width, Height)); _surface = null; } } diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index a6a08c3614..3c6c60ba10 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -178,7 +178,10 @@ 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_surface_set_device_scale(CairoSurface surface, double sx, double sy); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] public delegate void cairo_set_source_surface(IntPtr cr, CairoSurface surface, double x, double y); @@ -465,6 +468,7 @@ namespace Avalonia.Gtk3.Interop public static D.cairo_surface_write_to_png CairoSurfaceWriteToPng; public static D.cairo_surface_flush CairoSurfaceFlush; public static D.cairo_surface_destroy CairoSurfaceDestroy; + public static D.cairo_surface_set_device_scale CairoSurfaceSetDeviceScale; public static D.cairo_set_source_surface CairoSetSourceSurface; public static D.cairo_set_source_rgba CairoSetSourceRgba; public static D.cairo_scale CairoScale; diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index 009ef6763c..bfec3db756 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -306,7 +306,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; @@ -387,6 +387,7 @@ namespace Avalonia.Gtk3 public Size ClientSize { get; private set; } + public int LastKnownScaleFactor { get; private set; } public void Resize(Size value) { From e877f6e86468b7f9476e9f770321b0d389cb0b58 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 3 Oct 2017 16:09:35 +0300 Subject: [PATCH 07/26] [GTK3] Revert to manually scaling the surface, cairo_surface_set_device_scale is not supported on Ubuntu 14.04 --- .../Avalonia.Gtk3/ImageSurfaceFramebuffer.cs | 22 +++++++++++-------- src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 4 ---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs index 53771dd369..182da7df58 100644 --- a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs +++ b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs @@ -32,21 +32,23 @@ namespace Avalonia.Gtk3 Height = height; Address = Native.CairoImageSurfaceGetData(_surface); RowBytes = Native.CairoImageSurfaceGetStride(_surface); - Native.CairoSurfaceSetDeviceScale(_surface, factor, factor); Native.CairoSurfaceFlush(_surface); } - static void Draw(IntPtr context, CairoSurface surface) + static void Draw(IntPtr context, CairoSurface surface, double factor) { + Native.CairoSurfaceMarkDirty(surface); + Native.CairoScale(context, 1d / factor, 1d / factor); Native.CairoSetSourceSurface(context, surface, 0, 0); Native.CairoPaint(context); + } /* static Stopwatch St =Stopwatch.StartNew(); private static int _frames; private static int _fps;*/ - static void DrawToWidget(GtkWidget widget, CairoSurface surface, int width, int height) + static void DrawToWidget(GtkWidget widget, CairoSurface surface, int width, int height, double factor) { if(surface == null || widget.IsClosed) return; @@ -56,7 +58,7 @@ namespace Avalonia.Gtk3 var rc = new GdkRectangle {Width = width, Height = height}; Native.GdkWindowBeginPaintRect(window, ref rc); var context = Native.GdkCairoCreate(window); - Draw(context, surface); + Draw(context, surface, factor); /* _frames++; var el = St.Elapsed; @@ -82,13 +84,15 @@ namespace Avalonia.Gtk3 { private readonly GtkWidget _widget; private CairoSurface _surface; + private readonly double _factor; private readonly int _width; private readonly int _height; - public RenderOp(GtkWidget widget, CairoSurface _surface, int width, int height) + public RenderOp(GtkWidget widget, CairoSurface _surface, double factor, int width, int height) { _widget = widget; this._surface = _surface; + _factor = factor; _width = width; _height = height; } @@ -101,7 +105,7 @@ namespace Avalonia.Gtk3 public void RenderNow() { - DrawToWidget(_widget, _surface, _width, _height); + DrawToWidget(_widget, _surface, _width, _height, _factor); } } @@ -112,13 +116,13 @@ namespace Avalonia.Gtk3 if (Dispatcher.UIThread.CheckAccess()) { if (_impl.CurrentCairoContext != IntPtr.Zero) - Draw(_impl.CurrentCairoContext, _surface); + Draw(_impl.CurrentCairoContext, _surface, _factor); else - DrawToWidget(_widget, _surface, Width, Height); + DrawToWidget(_widget, _surface, Width, Height, _factor); _surface.Dispose(); } else - _impl.SetNextRenderOperation(new RenderOp(_widget, _surface, Width, Height)); + _impl.SetNextRenderOperation(new RenderOp(_widget, _surface, _factor, Width, Height)); _surface = null; } } diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index 3c6c60ba10..14b498077c 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -179,9 +179,6 @@ 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_surface_set_device_scale(CairoSurface surface, double sx, double sy); - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] public delegate void cairo_set_source_surface(IntPtr cr, CairoSurface surface, double x, double y); @@ -468,7 +465,6 @@ namespace Avalonia.Gtk3.Interop public static D.cairo_surface_write_to_png CairoSurfaceWriteToPng; public static D.cairo_surface_flush CairoSurfaceFlush; public static D.cairo_surface_destroy CairoSurfaceDestroy; - public static D.cairo_surface_set_device_scale CairoSurfaceSetDeviceScale; public static D.cairo_set_source_surface CairoSetSourceSurface; public static D.cairo_set_source_rgba CairoSetSourceRgba; public static D.cairo_scale CairoScale; From 1380436b76a165c06a395e64124f0c2d438fb964 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 3 Oct 2017 16:41:47 +0300 Subject: [PATCH 08/26] [GTK3] Use GTK window state management functions instead of GDK --- src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 24 ++++++++++++------------ src/Gtk/Avalonia.Gtk3/WindowImpl.cs | 9 ++++----- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index 14b498077c..c96fb23366 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -236,17 +236,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); @@ -437,10 +437,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; diff --git a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs index a4bc45c0a1..c586661a7a 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs +++ b/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); } } } From 7a01250352dae7a8d2128508007e1c4576c5acf5 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 3 Oct 2017 18:51:17 +0300 Subject: [PATCH 09/26] [GTK#] Burn in fire, fire, fire, you're no longer required --- appveyor.yml | 3 --- docs/guidelines/build.md | 6 ------ 2 files changed, 9 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index cef8c4a2cc..aa8c19ace4 100644 --- a/appveyor.yml +++ b/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 diff --git a/docs/guidelines/build.md b/docs/guidelines/build.md index 828a4ddab3..559790b197 100644 --- a/docs/guidelines/build.md +++ b/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 ``` From 097716141399b0a27a34887e109981a7ff1e9b6c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 11:23:48 +0300 Subject: [PATCH 10/26] Use AppDomain.GetLoadedAssemblies() for all platforms --- build/NetCore.props | 2 - .../NetCoreRuntimePlatform.cs | 42 ------------------- .../StandardRuntimePlatform.cs | 7 +--- 3 files changed, 1 insertion(+), 50 deletions(-) delete mode 100644 src/Avalonia.DotNetCoreRuntime/NetCoreRuntimePlatform.cs diff --git a/build/NetCore.props b/build/NetCore.props index cebb02c7f5..b9cde28015 100644 --- a/build/NetCore.props +++ b/build/NetCore.props @@ -1,6 +1,4 @@  - - diff --git a/src/Avalonia.DotNetCoreRuntime/NetCoreRuntimePlatform.cs b/src/Avalonia.DotNetCoreRuntime/NetCoreRuntimePlatform.cs deleted file mode 100644 index 6aab8e0243..0000000000 --- a/src/Avalonia.DotNetCoreRuntime/NetCoreRuntimePlatform.cs +++ /dev/null @@ -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 Assemblies = new Lazy(LoadAssemblies); - public Assembly[] GetLoadedAssemblies() => Assemblies.Value; - - static Assembly[] LoadAssemblies() - { - var assemblies = new List(); - // 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(); - } - } -} diff --git a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs index 092910a08f..757cf52853 100644 --- a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs +++ b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs @@ -11,13 +11,8 @@ 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); From b05f913e2501c47f6a42f8b9ddead69cb076723e Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 12:15:22 +0300 Subject: [PATCH 11/26] [GTK3] Normal priority for timers --- src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs index be886ea1c7..5253be5afb 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs +++ b/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) From f6a60e27592d89e76129a5f47157d62a2981b34d Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 12:16:29 +0300 Subject: [PATCH 12/26] [GTK3] Block in SetNextRenderOperation until previous frame is dequeued --- src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index bfec3db756..8771106895 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/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,6 +30,7 @@ namespace Avalonia.Gtk3 private GCHandle _gcHandle; private object _lock = new object(); private IDeferredRenderOperation _nextRenderOperation; + private readonly AutoResetEvent _canSetNextOperation = new AutoResetEvent(true); public WindowBaseImpl(GtkWindow gtkWidget) { @@ -255,11 +257,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() @@ -272,6 +282,7 @@ namespace Avalonia.Gtk3 op = _nextRenderOperation; _nextRenderOperation = null; } + _canSetNextOperation.Set(); } if (op != null) { From 5fd6f5ac6d315bc48ab15a9d25f65c0c7cbcc891 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 4 Oct 2017 13:22:59 +0200 Subject: [PATCH 13/26] Snap bounding boxes to pixels. --- .../SceneGraph/BrushDrawOperation.cs | 15 ++--- .../Rendering/SceneGraph/DrawOperation.cs | 26 +++++++++ .../Rendering/SceneGraph/GeometryNode.cs | 5 +- .../Rendering/SceneGraph/IDrawOperation.cs | 2 +- .../Rendering/SceneGraph/ImageNode.cs | 11 ++-- .../Rendering/SceneGraph/LineNode.cs | 5 +- .../Rendering/SceneGraph/OpacityMaskNode.cs | 5 +- .../Rendering/SceneGraph/RectangleNode.cs | 5 +- .../Rendering/SceneGraph/TextNode.cs | 5 +- .../SceneGraph/DrawOperationTests.cs | 55 +++++++++++++++++++ 10 files changed, 97 insertions(+), 37 deletions(-) create mode 100644 src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs create mode 100644 tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs index 59a895a22f..f56e7448a7 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs +++ b/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 /// /// Base class for draw operations that can use a brush. /// - internal abstract class BrushDrawOperation : IDrawOperation + internal abstract class BrushDrawOperation : DrawOperation { - /// - public abstract Rect Bounds { get; } - - /// - public abstract bool HitTest(Point p); + public BrushDrawOperation(Rect bounds, Matrix transform, Pen pen) + : base(bounds, transform, pen) + { + } /// /// Gets a collection of child scenes that are needed to draw visual brushes. /// public abstract IDictionary ChildScenes { get; } - - /// - public abstract void Render(IDrawingContextImpl context); } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs new file mode 100644 index 0000000000..4c6ed189ff --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs @@ -0,0 +1,26 @@ +using System; +using Avalonia.Media; +using Avalonia.Platform; + +namespace Avalonia.Rendering.SceneGraph +{ + /// + /// Base class for draw operations that have bounds. + /// + 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); + } +} diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs index b884c42d99..6310122183 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs @@ -28,8 +28,8 @@ namespace Avalonia.Rendering.SceneGraph Pen pen, IGeometryImpl geometry, IDictionary 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; } - /// - public override Rect Bounds { get; } - /// /// Gets the transform with which the node will be drawn. /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs index 8c3bb72463..839fd9b0e5 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs @@ -12,7 +12,7 @@ namespace Avalonia.Rendering.SceneGraph public interface IDrawOperation { /// - /// Gets the bounds of the visible content in the node. + /// Gets the bounds of the visible content in the node in global coordinates. /// Rect Bounds { get; } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs index 4a50f12095..8291d1c0bb 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs @@ -9,7 +9,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// A node in the scene graph which represents an image draw. /// - internal class ImageNode : IDrawOperation + internal class ImageNode : DrawOperation { /// /// Initializes a new instance of the class. @@ -20,8 +20,8 @@ namespace Avalonia.Rendering.SceneGraph /// The source rect. /// The destination rect. 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; } - /// - public Rect Bounds { get; } - /// /// Gets the transform with which the node will be drawn. /// @@ -80,7 +77,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - 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 } /// - public bool HitTest(Point p) => Bounds.Contains(p); + public override bool HitTest(Point p) => Bounds.Contains(p); } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs index e39335b5b6..d3df478a63 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs @@ -28,8 +28,8 @@ namespace Avalonia.Rendering.SceneGraph Point p1, Point p2, IDictionary 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; } - /// - public override Rect Bounds { get; } - /// /// Gets the transform with which the node will be drawn. /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs index c40869724f..28b8f53e26 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs @@ -19,6 +19,7 @@ namespace Avalonia.Rendering.SceneGraph /// The bounds of the mask. /// Child scenes for drawing visual brushes. public OpacityMaskNode(IBrush mask, Rect bounds, IDictionary childScenes = null) + : base(Rect.Empty, Matrix.Identity, null) { Mask = mask?.ToImmutable(); MaskBounds = bounds; @@ -30,12 +31,10 @@ namespace Avalonia.Rendering.SceneGraph /// opacity mask pop. /// public OpacityMaskNode() + : base(Rect.Empty, Matrix.Identity, null) { } - /// - public override Rect Bounds => Rect.Empty; - /// /// Gets the mask to be pushed or null if the operation represents a pop. /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs index 2affc454b5..1730621c55 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs @@ -30,8 +30,8 @@ namespace Avalonia.Rendering.SceneGraph Rect rect, float cornerRadius, IDictionary 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; } - /// - public override Rect Bounds { get; } - /// /// Gets the transform with which the node will be drawn. /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs index 058f3b1c22..6328d7dd14 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs @@ -28,8 +28,8 @@ namespace Avalonia.Rendering.SceneGraph Point origin, IFormattedTextImpl text, IDictionary 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; } - /// - public override Rect Bounds { get; } - /// /// Gets the transform with which the node will be drawn. /// diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs new file mode 100644 index 0000000000..76fe103c1b --- /dev/null +++ b/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) { } + } + } +} From eb5a354d624f49f4a82f0574a9d4b604db0acf4f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 4 Oct 2017 13:13:07 +0100 Subject: [PATCH 14/26] [StackPanel] Gap value is not added to the measured width or height after the last child. --- src/Avalonia.Controls/StackPanel.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Avalonia.Controls/StackPanel.cs b/src/Avalonia.Controls/StackPanel.cs index 0e12fb3283..a6fe35d668 100644 --- a/src/Avalonia.Controls/StackPanel.cs +++ b/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); } From 29791fa241976caa9de681eddc6791669c48aa39 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 15:27:18 +0300 Subject: [PATCH 15/26] Manual native memory management for leak detection --- .../Platform/IRuntimePlatform.cs | 9 ++ .../Avalonia.DotNetCoreRuntime.csproj | 3 +- .../Avalonia.Gtk3/ImageSurfaceFramebuffer.cs | 20 +-- .../Interop/ManagedCairoSurface.cs | 38 +++++ src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 4 + .../StandardRuntimePlatform.cs | 132 +++++++++++++++++- src/Skia/Avalonia.Skia/BitmapImpl.cs | 13 ++ 7 files changed, 207 insertions(+), 12 deletions(-) create mode 100644 src/Gtk/Avalonia.Gtk3/Interop/ManagedCairoSurface.cs diff --git a/src/Avalonia.Base/Platform/IRuntimePlatform.cs b/src/Avalonia.Base/Platform/IRuntimePlatform.cs index e1a09f094d..66253dc5b2 100644 --- a/src/Avalonia.Base/Platform/IRuntimePlatform.cs +++ b/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 diff --git a/src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj b/src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj index eade213c4c..53b2c997d0 100644 --- a/src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj +++ b/src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj @@ -2,6 +2,7 @@ netcoreapp2.0 false + $(DefineConstants);DOTNETCORE bin\$(Configuration)\Avalonia.DotNetCoreRuntime.XML @@ -21,5 +22,5 @@ - + \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs index 182da7df58..4ebb080dd0 100644 --- a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs +++ b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs @@ -16,7 +16,7 @@ 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, int factor) @@ -26,13 +26,13 @@ namespace Avalonia.Gtk3 _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,12 +83,12 @@ 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; @@ -105,7 +105,7 @@ namespace Avalonia.Gtk3 public void RenderNow() { - DrawToWidget(_widget, _surface, _width, _height, _factor); + DrawToWidget(_widget, _surface.Surface, _width, _height, _factor); } } @@ -116,9 +116,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 diff --git a/src/Gtk/Avalonia.Gtk3/Interop/ManagedCairoSurface.cs b/src/Gtk/Avalonia.Gtk3/Interop/ManagedCairoSurface.cs new file mode 100644 index 0000000000..2cde99afa5 --- /dev/null +++ b/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(); + 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; + } + } + + } +} \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index c96fb23366..433094e0ae 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/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); @@ -459,6 +462,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; diff --git a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs index 757cf52853..d648b630bd 100644 --- a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs +++ b/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; @@ -18,8 +21,135 @@ namespace Avalonia.Shared.PlatformSupport 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 Backtraces = new List(); + 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) + { + Backtraces.Remove(_backtrace); + _plat.Free(Address, Size); + GC.RemoveMemoryPressure(Size); + IsDisposed = true; + Address = IntPtr.Zero; + } + } + + 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 } } \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia/BitmapImpl.cs b/src/Skia/Avalonia.Skia/BitmapImpl.cs index ce3efded11..d13190deff 100644 --- a/src/Skia/Avalonia.Skia/BitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/BitmapImpl.cs @@ -20,6 +20,13 @@ 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; @@ -29,6 +36,12 @@ namespace Avalonia.Skia var runtime = AvaloniaLocator.Current?.GetService()?.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(); + var blob = plat.AllocBlob(nfo.BytesSize); + Bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, null, ReleaseDelegate, blob); Bitmap = new SKBitmap(width, height, colorType, SKAlphaType.Premul); Bitmap.Erase(SKColor.Empty); } From 18e556a1b11c46a5f2061b736913f30174812b16 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 4 Oct 2017 13:37:01 +0100 Subject: [PATCH 16/26] correct stack panel unit tests. --- tests/Avalonia.Controls.UnitTests/StackPanelTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs b/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs index f75c126ff1..56412d732b 100644 --- a/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs +++ b/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); From 835bf9a7e5054a2078d01655c1546215d37cdb34 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 15:45:37 +0300 Subject: [PATCH 17/26] Fixed release build --- src/Shared/PlatformSupport/StandardRuntimePlatform.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs index d648b630bd..fafa17a810 100644 --- a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs +++ b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs @@ -69,7 +69,9 @@ namespace Avalonia.Shared.PlatformSupport { if (!IsDisposed) { +#if DEBUG Backtraces.Remove(_backtrace); +#endif _plat.Free(Address, Size); GC.RemoveMemoryPressure(Size); IsDisposed = true; From 38053170425ea333c70b79128eeae7ec064da696 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 17:31:22 +0300 Subject: [PATCH 18/26] [SKIA] Fall back to direct SKBitmap call for unit tests --- src/Skia/Avalonia.Skia/BitmapImpl.cs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/Skia/Avalonia.Skia/BitmapImpl.cs b/src/Skia/Avalonia.Skia/BitmapImpl.cs index d13190deff..49d7d355ec 100644 --- a/src/Skia/Avalonia.Skia/BitmapImpl.cs +++ b/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()?.GetRuntimeInfo(); + var runtimePlatform = AvaloniaLocator.Current?.GetService() + 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(); - 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(); + 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); } From 004f8a4cdde88168c6b4ebcb3a6a8b9e123d19f4 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 17:32:53 +0300 Subject: [PATCH 19/26] Fall back to direct SKBitmap --- src/Skia/Avalonia.Skia/BitmapImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/BitmapImpl.cs b/src/Skia/Avalonia.Skia/BitmapImpl.cs index 49d7d355ec..00ab770e01 100644 --- a/src/Skia/Avalonia.Skia/BitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/BitmapImpl.cs @@ -33,7 +33,7 @@ namespace Avalonia.Skia PixelWidth = width; _dpi = dpi; var colorType = fmt?.ToSkColorType() ?? SKImageInfo.PlatformColorType; - var runtimePlatform = AvaloniaLocator.Current?.GetService() + var runtimePlatform = AvaloniaLocator.Current?.GetService(); var runtime = runtimePlatform?.GetRuntimeInfo(); if (runtime?.IsDesktop == true && runtime?.OperatingSystem == OperatingSystemType.Linux) colorType = SKColorType.Bgra8888; From 372ae88ba926268fe6cdb1e363506ed1baee9364 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 17:50:31 +0300 Subject: [PATCH 20/26] Set NativeBlob.Size to zero on dispose --- src/Shared/PlatformSupport/StandardRuntimePlatform.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs index fafa17a810..b777736f06 100644 --- a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs +++ b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs @@ -76,6 +76,7 @@ namespace Avalonia.Shared.PlatformSupport GC.RemoveMemoryPressure(Size); IsDisposed = true; Address = IntPtr.Zero; + Size = 0; } } From 1993942e9e0ed329f6c3c40174f9f7a6e01122de Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 18:25:10 +0300 Subject: [PATCH 21/26] [SKIA] Properly dispose intermediate bitmaps --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index eadcc05744..e549f06bd5 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/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 From 3882cf6cf35b80e320f54d82dbe4d4d7d7f8061d Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 18:30:54 +0300 Subject: [PATCH 22/26] [GTK3] Allow to render IDeferredRenderOperation to existing context --- src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs | 2 +- src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs | 11 +++++++---- src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs b/src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs index 8c1456726c..e16463a2ef 100644 --- a/src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs +++ b/src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs @@ -4,6 +4,6 @@ namespace Avalonia.Gtk3 { public interface IDeferredRenderOperation : IDisposable { - void RenderNow(); + void RenderNow(IntPtr? ctx); } } \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs index 4ebb080dd0..efda21c753 100644 --- a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs +++ b/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); } } diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index 8771106895..d12ef23623 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -286,7 +286,7 @@ namespace Avalonia.Gtk3 } if (op != null) { - op?.RenderNow(); + op?.RenderNow(null); op?.Dispose(); } } From e3b3e05a0cf6455717a67921f8ca786f92a67076 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 22:23:59 +0300 Subject: [PATCH 23/26] [GTK3] Use XPutImage on Linux when possible --- src/Gtk/Avalonia.Gtk3/FramebufferManager.cs | 32 ++++++++++++ src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 5 ++ src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 4 ++ src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs | 3 +- src/Gtk/Avalonia.Gtk3/X11.cs | 54 ++++++++++++++++++++ src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs | 55 +++++++++++++++++++++ 6 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 src/Gtk/Avalonia.Gtk3/X11.cs create mode 100644 src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs diff --git a/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs b/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs index b0cc668569..455b63b89e 100644 --- a/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs +++ b/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); + } } } diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index b36a1cda91..10405037ab 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/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 diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index 433094e0ae..15b3a11fbb 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/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; diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index d12ef23623..e14ed77877 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/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("destroy", OnDestroy); Native.GtkWidgetRealize(gtkWidget); + GdkWindowHandle = this.Handle.Handle; _lastSize = ClientSize; if (Gtk3Platform.UseDeferredRendering) { diff --git a/src/Gtk/Avalonia.Gtk3/X11.cs b/src/Gtk/Avalonia.Gtk3/X11.cs new file mode 100644 index 0000000000..6708ece17b --- /dev/null +++ b/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]; + } + + + } +} \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs b/src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs new file mode 100644 index 0000000000..2bf08bddf1 --- /dev/null +++ b/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().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; } + } +} \ No newline at end of file From c8e6bd58090763e2f6c48601f7469108a4bd1443 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 6 Oct 2017 12:41:48 +0100 Subject: [PATCH 24/26] Fix using dispatcher timer with 0 time interval causing exception. --- src/Avalonia.Controls/TreeView.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 5d1b9a1462..079e571d29 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/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; From cc7098583e420ca70198fda7bf916eb74c1731af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Fri, 6 Oct 2017 16:57:32 +0100 Subject: [PATCH 25/26] Fixed Assert.Null warnings. --- .../Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs | 2 +- .../Generators/ItemContainerGeneratorTests.cs | 2 +- .../Presenters/ContentPresenterTests_InTemplate.cs | 4 ++-- .../Presenters/ContentPresenterTests_Standalone.cs | 2 +- .../Primitives/SelectingItemsControlTests.cs | 8 ++++---- .../Primitives/SelectingItemsControlTests_Multiple.cs | 2 +- .../Data/BindingTests_RelativeSource.cs | 2 +- .../Templates/MemberSelectorTests.cs | 4 ++-- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs index 05339c43b0..e9cb2bf450 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs +++ b/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] diff --git a/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs b/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs index 01b550fb3b..9b4be59647 100644 --- a/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs +++ b/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)); } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs index e32c703409..a524ca3e89 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs +++ b/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()); } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs index 589b1d67d2..032928d673 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs +++ b/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(); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 7ee9fbbf52..a60074fa43 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/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); } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index d8600f472d..642f594e4d 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -76,7 +76,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.SelectedItems = new AvaloniaList(); Assert.Equal(-1, target.SelectedIndex); - Assert.Equal(null, target.SelectedItem); + Assert.Null(target.SelectedItem); } [Fact] diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs index e912770470..c46fb6fce2 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs +++ b/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] diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs index 49a88e8fae..aa1e56f2a5 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs +++ b/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] From 3e09457e10e058aed23db2090afaafaa79b0d1b5 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 6 Oct 2017 20:22:52 +0300 Subject: [PATCH 26/26] [D2D1] Implement support for IFramebufferPlatformSurface --- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 2 + .../FramebufferShimRenderTarget.cs | 84 ++++++++++++++++++ .../Media/Imaging/RenderTargetBitmapImpl.cs | 13 ++- .../Media/Imaging/WicBitmapImpl.cs | 1 + .../Avalonia.RenderTests/Media/BitmapTests.cs | 11 +-- ...ouldBeUsableAsBitmap_Bgra8888.expected.png | Bin 0 -> 800 bytes ...ShouldBeUsableAsBitmap_Rgb565.expected.png | Bin 0 -> 786 bytes ...ouldBeUsableAsBitmap_Rgba8888.expected.png | Bin 0 -> 800 bytes 8 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs create mode 100644 tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgba8888.expected.png diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index fd8364c03b..90409bb208 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/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"); } diff --git a/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs new file mode 100644 index 0000000000..d465af5322 --- /dev/null +++ b/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(); + + }); + } + } + + } +} diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs index 33736b02cb..0d6ed9f39f 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs +++ b/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); } } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs index e817dd4812..bcce2496cd 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs +++ b/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)) { diff --git a/tests/Avalonia.RenderTests/Media/BitmapTests.cs b/tests/Avalonia.RenderTests/Media/BitmapTests.cs index f01f78ae94..a7cd06a894 100644 --- a/tests/Avalonia.RenderTests/Media/BitmapTests.cs +++ b/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); diff --git a/tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png b/tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..19686464c546d3f56a55e15fc4df8bc574ecf13e GIT binary patch literal 800 zcmeAS@N?(olHy`uVBq!ia0vp^DIm0?E|u$lp4Sznk+pCI zt8&hw6|IZJx}ppYsSSFePk-;<{yg#k=k4>?e|`Nut9(N0JB8DC9H;GU)+uI> ze8#uwjBLtG>k~#GiPBBIAg^g;9psn|@-5J#JsUblxX|hXn_`Q3mNMsM`qKH18@E56 ndHCa*hZWCgD4b?9|371-;u7va=|XQ{s%G$X^>bP0l+XkKg^yP@ literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png b/tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..f3d20008a1bde3d523b8be3c2dc29af79b52303f GIT binary patch literal 786 zcmeAS@N?(olHy`uVBq!ia0vp^DIm_*Z1E)2cG}@ZrO^~MPgl128YzTqBOD= zu3%NpSu{k1j{g3hz18}JQTc?_cM7NPI8NKytW(S$`HXMV8QGMXK#BYB6l%hUjCbf5 z@>(+IW%|o_YA=nTHk63T%ok=2_BESn~aE`Pr+P_hqe@Y6hlb22WQ%mvv4F FO#n8jN}B)x literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgba8888.expected.png b/tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgba8888.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..19686464c546d3f56a55e15fc4df8bc574ecf13e GIT binary patch literal 800 zcmeAS@N?(olHy`uVBq!ia0vp^DIm0?E|u$lp4Sznk+pCI zt8&hw6|IZJx}ppYsSSFePk-;<{yg#k=k4>?e|`Nut9(N0JB8DC9H;GU)+uI> ze8#uwjBLtG>k~#GiPBBIAg^g;9psn|@-5J#JsUblxX|hXn_`Q3mNMsM`qKH18@E56 ndHCa*hZWCgD4b?9|371-;u7va=|XQ{s%G$X^>bP0l+XkKg^yP@ literal 0 HcmV?d00001