Browse Source

Implemented framebuffer and smarter dll search logic

Nikita Tsukanov 8 years ago
parent
commit
5bb9e6913e

+ 1 - 1
samples/ControlCatalog.Desktop/Program.cs

@@ -18,7 +18,7 @@ namespace ControlCatalog
             // TODO: Make this work with GTK/Skia/Cairo depending on command-line args
             // again.
             AppBuilder.Configure<App>()
-                .UseDirect2D1()
+                .UseSkia()
                 .UseGtk3()
                 .Start<MainWindow>();
         }

+ 3 - 0
src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj

@@ -41,13 +41,16 @@
     <None Include="project.json" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="FramebufferManager.cs" />
     <Compile Include="Gtk3Platform.cs" />
+    <Compile Include="Interop\ICustomGtk3NativeLibraryResolver.cs" />
     <Compile Include="Interop\DynLoader.cs" />
     <Compile Include="Interop\GlibTimeout.cs" />
     <Compile Include="Interop\Native.cs" />
     <Compile Include="Interop\NativeException.cs" />
     <Compile Include="Interop\Resolver.cs" />
     <Compile Include="Interop\Signal.cs" />
+    <Compile Include="ImageSurfaceFramebuffer.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Stubs.cs" />
     <Compile Include="TopLevelImpl.cs" />

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

@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Controls.Platform.Surfaces;
+
+namespace Avalonia.Gtk3
+{
+    class FramebufferManager : IFramebufferPlatformSurface, IDisposable
+    {
+        private readonly TopLevelImpl _window;
+        private ImageSurfaceFramebuffer _fb;
+        public FramebufferManager(TopLevelImpl window)
+        {
+            _window = window;
+        }
+
+        public void Dispose()
+        {
+            _fb?.Deallocate();
+        }
+
+        public ILockedFramebuffer Lock()
+        {
+            if(_window.CurrentCairoContext == IntPtr.Zero)
+                throw new InvalidOperationException("Window is not in drawing state");
+
+            var ctx = _window.CurrentCairoContext;
+            var width = (int) _window.ClientSize.Width;
+            var height = (int) _window.ClientSize.Height;
+            if (_fb == null || _fb.Width != width ||
+                _fb.Height != height)
+            {
+                _fb?.Dispose();
+                _fb = new ImageSurfaceFramebuffer(width, height);
+            }
+            _fb.Prepare(ctx);
+            return _fb;
+        }
+    }
+}

+ 6 - 2
src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs

@@ -104,7 +104,11 @@ namespace Avalonia.Gtk3
 
     public static class Gtk3AppBuilderExtensions
     {
-        public static T UseGtk3<T>(this AppBuilderBase<T> builder) where T : AppBuilderBase<T>, new()
-        => builder.UseWindowingSubsystem(Gtk3Platform.Initialize, "GTK3");
+        public static T UseGtk3<T>(this AppBuilderBase<T> builder, ICustomGtk3NativeLibraryResolver resolver = null) 
+            where T : AppBuilderBase<T>, new()
+        {
+            Resolver.Custom = resolver;
+            return builder.UseWindowingSubsystem(Gtk3Platform.Initialize, "GTK3");
+        }
     }
 }

+ 71 - 0
src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs

@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Controls.Platform.Surfaces;
+using Avalonia.Gtk3.Interop;
+using Avalonia.Platform;
+
+
+namespace Avalonia.Gtk3
+{
+    class ImageSurfaceFramebuffer : ILockedFramebuffer
+    {
+        private IntPtr _context;
+        private IntPtr _surface;
+
+        public ImageSurfaceFramebuffer(int width, int height)
+        {
+            _surface = Native.CairoImageSurfaceCreate(1, width, height);
+            Width = width;
+            Height = height;
+            Address = Native.CairoImageSurfaceGetData(_surface);
+            RowBytes = Native.CairoImageSurfaceGetStride(_surface);
+            Native.CairoSurfaceFlush(_surface);
+        }
+
+        public void Prepare(IntPtr context)
+        {
+            _context = context;
+        }
+
+        public void Deallocate()
+        {
+            Native.CairoSurfaceDestroy(_surface);
+            _surface = IntPtr.Zero;
+        }
+
+        public void Dispose()
+        {
+            if(_context == IntPtr.Zero || _surface == IntPtr.Zero)
+                return;
+            Native.CairoSurfaceMarkDirty(_surface);
+            Native.CairoSetSourceSurface(_context, _surface, 0, 0);
+            Native.CairoPaint(_context);
+            _context = IntPtr.Zero;
+
+        }
+
+        public IntPtr Address { get; }
+        public int Width  { get; }
+        public int Height { get; }
+        public int RowBytes { get; }
+
+        //TODO: Proper DPI detect
+        public Size Dpi => new Size(96, 96);
+        public PixelFormat Format
+        {
+            get
+            {
+                if (AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetRuntimeInfo().OperatingSystem ==
+                    OperatingSystemType.WinNT)
+                    return PixelFormat.Bgra8888;
+                return PixelFormat.Rgba8888;
+            }
+        }
+    }
+}
+
+
+

+ 10 - 23
src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.Runtime.InteropServices;
 
 /*
@@ -11,7 +12,7 @@ namespace Avalonia.Gtk3.Interop
 {
     internal interface IDynLoader
     {
-        IntPtr LoadLibrary(string basePath, string dll);
+        IntPtr LoadLibrary(string dll);
         IntPtr GetProcAddress(IntPtr dll, string proc);
 
     }
@@ -21,7 +22,6 @@ namespace Avalonia.Gtk3.Interop
         // ReSharper disable InconsistentNaming
         static class LinuxImports
         {
-            
             [DllImport("libdl.so.2")]
             private static extern IntPtr dlopen(string path, int flags);
 
@@ -36,7 +36,6 @@ namespace Avalonia.Gtk3.Interop
                 DlOpen = dlopen;
                 DlSym = dlsym;
                 DlError = dlerror;
-                Postfix = ".so.0";
             }
         }
         static class OsXImports
@@ -57,7 +56,6 @@ namespace Avalonia.Gtk3.Interop
                 DlOpen = dlopen;
                 DlSym = dlsym;
                 DlError = dlerror;
-                Postfix = ".dylib"; //TODO
             }
             
         }
@@ -66,8 +64,6 @@ namespace Avalonia.Gtk3.Interop
         [DllImport("libc")]
         static extern int uname(IntPtr buf);
 
-        
-
         static UnixLoader()
         {
             var buffer = Marshal.AllocHGlobal(0x1000);
@@ -83,20 +79,12 @@ namespace Avalonia.Gtk3.Interop
         private static Func<string, int, IntPtr> DlOpen;
         private static Func<IntPtr, string, IntPtr> DlSym;
         private static Func<IntPtr> DlError;
-        private static string Postfix;
         // ReSharper restore InconsistentNaming
 
-        static string DlErrorString()
-        {
-            
-            return Marshal.PtrToStringAnsi(DlError());
-        }
+        static string DlErrorString() => Marshal.PtrToStringAnsi(DlError());
 
-        public IntPtr LoadLibrary(string basePath, string dll)
+        public IntPtr LoadLibrary(string dll)
         {
-            dll += Postfix;
-            if (basePath != null)
-                dll = System.IO.Path.Combine(basePath, dll);
             var handle = DlOpen(dll, 1);
             if (handle == IntPtr.Zero)
                 throw new NativeException(DlErrorString());
@@ -120,15 +108,14 @@ namespace Avalonia.Gtk3.Interop
         [DllImport("kernel32", EntryPoint = "LoadLibraryW", SetLastError = true, CharSet = CharSet.Unicode)]
         private static extern IntPtr LoadLibrary(string lpszLib);
 
-        IntPtr IDynLoader.LoadLibrary(string basePath, string dll)
+        IntPtr IDynLoader.LoadLibrary(string dll)
         {
-            dll += "-0.dll";
-            if (basePath != null)
-                dll = System.IO.Path.Combine(basePath, dll);
             var handle = LoadLibrary(dll);
-            if (handle == IntPtr.Zero)
-                throw new NativeException("Error " + Marshal.GetLastWin32Error());
-            return handle;
+            if (handle != IntPtr.Zero)
+                return handle;
+            var err = Marshal.GetLastWin32Error();
+
+            throw new NativeException("Error loading " + dll + " error " + err);
         }
 
         IntPtr IDynLoader.GetProcAddress(IntPtr dll, string proc)

+ 17 - 0
src/Gtk/Avalonia.Gtk3/Interop/ICustomGtk3NativeLibraryResolver.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Gtk3.Interop;
+
+namespace Avalonia.Gtk3
+{
+    public interface ICustomGtk3NativeLibraryResolver
+    {
+        string GetName(GtkDll dll);
+        string BasePath { get; }
+        bool TrySystemFirst { get; }
+        string Lookup(GtkDll dll);
+    }
+}

+ 35 - 1
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@@ -80,6 +80,33 @@ namespace Avalonia.Gtk3.Interop
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
             public delegate void gtk_window_move(IntPtr gtkWindow, int x, int y);
 
+
+
+
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
+            public delegate IntPtr cairo_image_surface_create(int format, int width, int height);
+
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
+            public delegate IntPtr cairo_image_surface_get_data(IntPtr surface);
+
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
+            public delegate int cairo_image_surface_get_stride(IntPtr surface);
+
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
+            public delegate void cairo_surface_mark_dirty(IntPtr surface);
+
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
+            public delegate void cairo_surface_flush(IntPtr surface);
+
+            [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, IntPtr surface, double x, double y);
+
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
+            public delegate void cairo_paint(IntPtr context);
+
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
             public delegate void gtk_widget_queue_draw_area(IntPtr gtkWindow, int x, int y, int width, int height);
 
@@ -130,7 +157,14 @@ namespace Avalonia.Gtk3.Interop
         public static D.gdk_screen_get_width GdkScreenGetWidth;
         public static D.gdk_window_get_origin GdkWindowGetOrigin;
 
-
+        public static D.cairo_image_surface_create CairoImageSurfaceCreate;
+        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;
+        public static D.cairo_surface_flush CairoSurfaceFlush;
+        public static D.cairo_surface_destroy CairoSurfaceDestroy;
+        public static D.cairo_set_source_surface CairoSetSourceSurface;
+        public static D.cairo_paint CairoPaint;
     }
 
     public enum GtkWindowType

+ 80 - 33
src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs

@@ -1,10 +1,12 @@
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using System.Reflection;
 using System.Runtime.InteropServices;
 using System.Text;
 using System.Threading.Tasks;
+using Avalonia.Platform;
 
 namespace Avalonia.Gtk3.Interop
 {
@@ -22,64 +24,108 @@ namespace Avalonia.Gtk3.Interop
         }
     }
 
-    internal enum GtkDll
+    public enum GtkDll
     {
         Gdk,
         Gtk,
         Glib,
         Gio,
-        Gobject
+        Gobject,
+        Cairo
     }
 
     static class Resolver
     {
-        [DllImport("kernel32.dll")]
-        static extern int GetVersion();
+        private static Lazy<OperatingSystemType> Platform =
+            new Lazy<OperatingSystemType>(
+                () => AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetRuntimeInfo().OperatingSystem);
 
-        static bool IsWin32()
+        public static ICustomGtk3NativeLibraryResolver Custom { get; set; }
+
+
+        static string FormatName(string name, int version = 0)
+        {
+            if (Platform.Value == OperatingSystemType.WinNT)
+                return "lib" + name + "-" + version + ".dll";
+            if (Platform.Value == OperatingSystemType.Linux)
+                return "lib" + name + ".so" + "." + version;
+            if (Platform.Value == OperatingSystemType.OSX)
+                return "lib" + name + "." + version + ".dylib";
+            throw new Exception("Unknown platform, use custom name resolver");
+        }
+
+        
+
+        static string GetDllName(GtkDll dll)
         {
+            var name = Custom?.GetName(dll);
+            if (name != null)
+                return name;
+
+            switch (dll)
+            {
+                case GtkDll.Cairo:
+                    return FormatName("cairo", 2);
+                case GtkDll.Gdk:
+                    return FormatName("gdk-3");
+                case GtkDll.Glib:
+                    return FormatName("glib-2.0");
+                case GtkDll.Gio:
+                    return FormatName("gio-2.0");
+                case GtkDll.Gtk:
+                    return FormatName("gtk-3");
+                case GtkDll.Gobject:
+                    return FormatName("gobject-2.0");
+                default:
+                    throw new ArgumentException("Unknown lib: " + dll);
+            }
+        }
+
+        static IntPtr LoadDll(IDynLoader  loader, GtkDll dll)
+        {
+            
+            var exceptions = new List<Exception>();
+
+            var name = GetDllName(dll);
+            if (Custom?.TrySystemFirst != false)
+            {
+                try
+                {
+                    return loader.LoadLibrary(name);
+                }
+                catch (Exception e)
+                {
+                    exceptions.Add(e);
+                }
+            }
+            var path = Custom?.Lookup(dll);
+            if (path == null && Custom?.BasePath != null)
+                path = Path.Combine(Custom.BasePath, name);
+
             try
             {
-                GetVersion();
-                return true;
+                return loader.LoadLibrary(path);
             }
-            catch
+            catch (Exception e)
             {
-                return false;
+                exceptions.Add(e);
             }
+            throw new AggregateException("Unable to load " + dll, exceptions);
         }
 
-        
-
         public static void Resolve(string basePath = null)
         {
-            var loader = IsWin32() ? (IDynLoader)new Win32Loader() : new UnixLoader();
-            
+            var loader = Platform.Value == OperatingSystemType.WinNT ? (IDynLoader)new Win32Loader() : new UnixLoader();
 
-            var gdk = loader.LoadLibrary(basePath, "libgdk-3");
-            var gtk = loader.LoadLibrary(basePath, "libgtk-3");
-            var gio = loader.LoadLibrary(basePath, "libgio-2.0");
-            var glib = loader.LoadLibrary(basePath, "libglib-2.0");
-            var gobject = loader.LoadLibrary(basePath, "libgobject-2.0");
+            var dlls = Enum.GetValues(typeof(GtkDll)).Cast<GtkDll>().ToDictionary(x => x, x => LoadDll(loader, x));
+            
             foreach (var fieldInfo in typeof(Native).GetTypeInfo().DeclaredFields)
             {
                 var import = fieldInfo.FieldType.GetTypeInfo().GetCustomAttributes(typeof(GtkImportAttribute), true).Cast<GtkImportAttribute>().FirstOrDefault();
                 if(import == null)
                     continue;
-                IntPtr lib;
-                if (import.Dll == GtkDll.Gtk)
-                    lib = gtk;
-                else if (import.Dll == GtkDll.Gdk)
-                    lib = gdk;
-                else if (import.Dll == GtkDll.Gio)
-                    lib = gio;
-                else if (import.Dll == GtkDll.Glib)
-                    lib = glib;
-                else if (import.Dll == GtkDll.Gobject)
-                    lib = gobject;
-                else
-                    throw new ArgumentException("Invalid GtkImportAttribute for " + fieldInfo.FieldType);
-
+                IntPtr lib = dlls[import.Dll];
+                
                 var funcPtr = loader.GetProcAddress(lib, import.Name ?? fieldInfo.FieldType.Name);
                 fieldInfo.SetValue(null, Marshal.GetDelegateForFunctionPointer(funcPtr, fieldInfo.FieldType));
             }
@@ -90,7 +136,7 @@ namespace Avalonia.Gtk3.Interop
                 try
                 {
                     Native.GetNativeGdkWindowHandle = (Native.D.gdk_get_native_handle)Marshal
-                        .GetDelegateForFunctionPointer(loader.GetProcAddress(gdk, name), typeof(Native.D.gdk_get_native_handle));
+                        .GetDelegateForFunctionPointer(loader.GetProcAddress(dlls[GtkDll.Gdk], name), typeof(Native.D.gdk_get_native_handle));
                     break;
                 }
                 catch { }
@@ -103,3 +149,4 @@ namespace Avalonia.Gtk3.Interop
 
     }
 }
+

+ 7 - 1
src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs

@@ -13,11 +13,13 @@ namespace Avalonia.Gtk3
     {
         protected readonly IntPtr GtkWidget;
         private IInputRoot _inputRoot;
+        private readonly FramebufferManager _framebuffer;
         protected readonly List<IDisposable> _disposables = new List<IDisposable>();
 
         public TopLevelImpl(IntPtr gtkWidget)
         {
             GtkWidget = gtkWidget;
+            _framebuffer = new FramebufferManager(this);
             Native.GtkWidgetSetEvents(gtkWidget, uint.MaxValue);
             Native.GtkWidgetRealize(gtkWidget);
             Connect<Native.D.signal_widget_draw>("draw", OnDraw);
@@ -126,9 +128,13 @@ namespace Avalonia.Gtk3
 
         void Connect<T>(string name, T handler) => _disposables.Add(Signal.Connect<T>(GtkWidget, name, handler));
 
+        internal IntPtr CurrentCairoContext { get; private set; }
+
         private bool OnDraw(IntPtr gtkwidget, IntPtr cairocontext, IntPtr userdata)
         {
+            CurrentCairoContext = cairocontext;
             Paint?.Invoke(new Rect(ClientSize));
+            CurrentCairoContext = IntPtr.Zero;
             return true;
         }
 
@@ -234,6 +240,6 @@ namespace Avalonia.Gtk3
         }
 
         IntPtr IPlatformHandle.Handle => Native.GetNativeGdkWindowHandle(Native.GtkWidgetGetWindow(GtkWidget));
-        public IEnumerable<object> Surfaces => new object[] {Handle};
+        public IEnumerable<object> Surfaces => new object[] {Handle, _framebuffer};
     }
 }