Browse Source

Various fixes for GTK and DeferredRenderer support

Nikita Tsukanov 8 years ago
parent
commit
70c24908a9

+ 1 - 0
samples/ControlCatalog/MainWindow.xaml.cs

@@ -11,6 +11,7 @@ namespace ControlCatalog
         {
             this.InitializeComponent();
             this.AttachDevTools();
+            //Renderer.DrawFps = true;
             //Renderer.DrawDirtyRects = Renderer.DrawFps = true;
         }
 

+ 10 - 5
src/Avalonia.Base/Threading/DispatcherPriority.cs

@@ -44,21 +44,26 @@ namespace Avalonia.Threading
         /// The job will be processed with the same priority as render.
         /// </summary>
         Render = 7,
-
+        
+        /// <summary>
+        /// The job will be processed with the same priority as render.
+        /// </summary>
+        Layout = 8,
+        
         /// <summary>
         /// The job will be processed with the same priority as data binding.
         /// </summary>
-        DataBind = 8,
+        DataBind = 9,
 
         /// <summary>
         /// The job will be processed with normal priority.
         /// </summary>
-        Normal = 9,
+        Normal = 10,
 
         /// <summary>
         /// The job will be processed before other asynchronous operations.
         /// </summary>
-        Send = 10,
-        MaxValue = 10
+        Send = 11,
+        MaxValue = 11
     }
 }

+ 1 - 1
src/Avalonia.Layout/LayoutManager.cs

@@ -203,7 +203,7 @@ namespace Avalonia.Layout
         {
             if (!_queued && !_running)
             {
-                Dispatcher.UIThread.InvokeAsync(ExecuteLayoutPass, DispatcherPriority.Render);
+                Dispatcher.UIThread.InvokeAsync(ExecuteLayoutPass, DispatcherPriority.Layout);
                 _queued = true;
             }
         }

+ 4 - 4
src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs

@@ -15,7 +15,7 @@ namespace Avalonia.Rendering
     /// </remarks>
     public class DefaultRenderLoop : IRenderLoop
     {
-        private IPlatformThreadingInterface _threading;
+        private IRuntimePlatform _runtime;
         private int _subscriberCount;
         private EventHandler<EventArgs> _tick;
         private IDisposable _subscription;
@@ -78,12 +78,12 @@ namespace Avalonia.Rendering
         /// </remarks>
         protected virtual IDisposable StartCore(Action tick)
         {
-            if (_threading == null)
+            if (_runtime == null)
             {
-                _threading = AvaloniaLocator.Current.GetService<IPlatformThreadingInterface>();
+                _runtime = AvaloniaLocator.Current.GetService<IRuntimePlatform>();
             }
 
-            return _threading.StartTimer(TimeSpan.FromSeconds(1.0 / FramesPerSecond), tick);
+            return _runtime.StartSystemTimer(TimeSpan.FromSeconds(1.0 / FramesPerSecond), tick);
         }
 
         /// <summary>

+ 5 - 5
src/Gtk/Avalonia.Gtk3/FramebufferManager.cs

@@ -23,11 +23,11 @@ namespace Avalonia.Gtk3
 
         public ILockedFramebuffer Lock()
         {
-            if(_window.CurrentCairoContext == IntPtr.Zero)
-                throw new InvalidOperationException("Window is not in drawing state");
-            var width = (int) _window.ClientSize.Width;
-            var height = (int) _window.ClientSize.Height;
-            return new ImageSurfaceFramebuffer(_window.CurrentCairoContext, _window.GtkWidget, width, height);
+            // This method may be called from non-UI thread, don't touch anything that calls back to GTK/GDK
+            var s = _window.ClientSize;
+            var width = (int) s.Width;
+            var height = (int) s.Height;
+            return new ImageSurfaceFramebuffer(_window, width, height);
         }
     }
 }

+ 10 - 4
src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs

@@ -22,6 +22,7 @@ namespace Avalonia.Gtk3
         internal static readonly MouseDevice Mouse = new MouseDevice();
         internal static readonly KeyboardDevice Keyboard = new KeyboardDevice();
         internal static IntPtr App { get; set; }
+        public static bool UseDeferredRendering = true;
         public static void Initialize()
         {
             Resolver.Resolve();
@@ -66,12 +67,17 @@ namespace Avalonia.Gtk3
 
         public IDisposable StartTimer(TimeSpan interval, Action tick)
         {
-            return GlibTimeout.StarTimer((uint) interval.TotalMilliseconds, tick);
+            var msec = interval.TotalMilliseconds;
+            if (msec <= 0)
+                throw new ArgumentException("Don't know how to create a timer with zero or negative interval");
+            var imsec = (uint) msec;
+            if (imsec == 0)
+                imsec = 1;
+            return GlibTimeout.StarTimer(imsec, tick);
         }
 
         private bool[] _signaled = new bool[(int) DispatcherPriority.MaxValue + 1];
         object _lock = new object();
-
         public void Signal(DispatcherPriority prio)
         {
             var idx = (int) prio;
@@ -97,7 +103,6 @@ namespace Avalonia.Gtk3
         private static bool s_tlsMarker;
 
         public bool CurrentThreadIsLoopThread => s_tlsMarker;
-
     }
 }
 
@@ -105,10 +110,11 @@ namespace Avalonia
 {
     public static class Gtk3AppBuilderExtensions
     {
-        public static T UseGtk3<T>(this AppBuilderBase<T> builder, ICustomGtk3NativeLibraryResolver resolver = null) 
+        public static T UseGtk3<T>(this AppBuilderBase<T> builder, bool deferredRendering = true, ICustomGtk3NativeLibraryResolver resolver = null) 
             where T : AppBuilderBase<T>, new()
         {
             Resolver.Custom = resolver;
+            Gtk3Platform.UseDeferredRendering = deferredRendering;
             return builder.UseWindowingSubsystem(Gtk3Platform.Initialize, "GTK3");
         }
     }

+ 9 - 0
src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs

@@ -0,0 +1,9 @@
+using System;
+
+namespace Avalonia.Gtk3
+{
+    public interface IDeferredRenderOperation : IDisposable
+    {
+        void RenderNow();
+    }
+}

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

@@ -1,26 +1,28 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using Avalonia.Controls.Platform.Surfaces;
 using Avalonia.Gtk3.Interop;
 using Avalonia.Platform;
+using Avalonia.Threading;
 
 
 namespace Avalonia.Gtk3
 {
     class ImageSurfaceFramebuffer : ILockedFramebuffer
     {
-        private IntPtr _context;
+        private readonly WindowBaseImpl _impl;
         private readonly GtkWidget _widget;
         private CairoSurface _surface;
         private int _factor;
-
-        public ImageSurfaceFramebuffer(IntPtr context, GtkWidget widget, int width, int height)
+        private object _lock = new object();
+        public ImageSurfaceFramebuffer(WindowBaseImpl impl, int width, int height)
         {
-            _context = context;
-            _widget = widget;
+            _impl = impl;
+            _widget = impl.GtkWidget;
             _factor = (int)(Native.GtkWidgetGetScaleFactor?.Invoke(_widget) ?? 1u);
             width *= _factor;
             height *= _factor;
@@ -32,18 +34,97 @@ namespace Avalonia.Gtk3
             RowBytes = Native.CairoImageSurfaceGetStride(_surface);
             Native.CairoSurfaceFlush(_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, double factor)
+        {
+            if(surface == null || widget.IsClosed)
+                return;
+            var window = Native.GtkWidgetGetWindow(widget);
+            if(window == IntPtr.Zero)
+                return;
+            var rc = new GdkRectangle {Width = width, Height = height};
+            Native.GdkWindowBeginPaintRect(window, ref rc);
+            var context = Native.GdkCairoCreate(window);
+            Draw(context, surface, factor);
+            /*
+            _frames++;
+            var el = St.Elapsed;
+            if (el.TotalSeconds > 1)
+            {
+                _fps = (int) (_frames / el.TotalSeconds);
+                _frames = 0;
+                St = Stopwatch.StartNew();
+            }
+            
+            Native.CairoSetSourceRgba(context, 1, 0, 0, 1);
+            Native.CairoMoveTo(context, 20, 20);
+            Native.CairoSetFontSize(context, 30);
+            using (var txt = new Utf8Buffer("FPS: " + _fps))
+                Native.CairoShowText(context, txt);
+            */
+            
+            Native.CairoDestroy(context);
+            Native.GdkWindowEndPaint(window);
+        }
+        
+        class RenderOp : IDeferredRenderOperation
+        {
+            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)
+            {
+                _widget = widget;
+                this._surface = _surface;
+                _factor = factor;
+                _width = width;
+                _height = height;
+            }
+
+            public void Dispose()
+            {
+                _surface?.Dispose();
+                _surface = null;
+            }
+
+            public void RenderNow()
+            {
+                DrawToWidget(_widget, _surface, _width, _height, _factor);
+            }
+        }
         
         public void Dispose()
         {
-            if(_context == IntPtr.Zero || _surface == null)
-                return;
-            Native.CairoSurfaceMarkDirty(_surface);
-            Native.CairoScale(_context, 1d / _factor, 1d / _factor);
-            Native.CairoSetSourceSurface(_context, _surface, 0, 0);
-            Native.CairoPaint(_context);
-            _context = IntPtr.Zero;
-            _surface.Dispose();
-            _surface = null;
+            lock (_lock)
+            {
+                if (Dispatcher.UIThread.CheckAccess())
+                {
+                    if (_impl.CurrentCairoContext != IntPtr.Zero)
+                        Draw(_impl.CurrentCairoContext, _surface, _factor);
+                    else
+                        DrawToWidget(_widget, _surface, Width, Height, _factor);
+                    _surface.Dispose();
+                }
+                else
+                    _impl.SetNextRenderOperation(new RenderOp(_widget, _surface, _factor, Width, Height));
+                _surface = null;
+            }
         }
 
         public IntPtr Address { get; }

+ 1 - 0
src/Gtk/Avalonia.Gtk3/Interop/CairoSurface.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.Runtime.InteropServices;
 
 namespace Avalonia.Gtk3.Interop

+ 11 - 9
src/Gtk/Avalonia.Gtk3/Interop/GlibPriority.cs

@@ -22,23 +22,25 @@ namespace Avalonia.Gtk3.Interop
             if (prio == DispatcherPriority.Send)
                 return High;
             if (prio == DispatcherPriority.Normal)
-                return Default - 4;
+                return Default;
             if (prio == DispatcherPriority.DataBind)
-                return Default - 3;
+                return Default + 1;
+            if (prio == DispatcherPriority.Layout)
+                return Default + 2;
             if (prio == DispatcherPriority.Render)
-                return Default -2;
+                return Default + 3;
             if (prio == DispatcherPriority.Loaded)
-                return Default - 1;
+                return GtkPaint + 20;
             if (prio == DispatcherPriority.Input)
-                return Default;
+                return GtkPaint + 21;
             if (prio == DispatcherPriority.Background)
-                return DefaultIdle;
+                return DefaultIdle + 1;
             if (prio == DispatcherPriority.ContextIdle)
-                return DefaultIdle + 100;
+                return DefaultIdle + 2;
             if (prio == DispatcherPriority.ApplicationIdle)
-                return DefaultIdle + 200;
+                return DefaultIdle + 3;
             if (prio == DispatcherPriority.SystemIdle)
-                return DefaultIdle + 200;
+                return DefaultIdle + 4;
             throw new ArgumentException("Unknown priority");
 
         }

+ 6 - 2
src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs

@@ -4,6 +4,7 @@ using System.Linq;
 using System.Runtime.InteropServices;
 using System.Text;
 using System.Threading.Tasks;
+using Avalonia.Threading;
 
 namespace Avalonia.Gtk3.Interop
 {
@@ -33,7 +34,8 @@ namespace Avalonia.Gtk3.Interop
         public static void Add(int priority, uint interval, Func<bool> callback)
         {
             var handle = GCHandle.Alloc(callback);
-            Native.GTimeoutAdd(interval, PinnedHandler, GCHandle.ToIntPtr(handle));
+            //Native.GTimeoutAdd(interval, PinnedHandler, GCHandle.ToIntPtr(handle));
+            Native.GTimeoutAddFull(priority, interval, PinnedHandler, GCHandle.ToIntPtr(handle), IntPtr.Zero);
         }
 
         class Timer : IDisposable
@@ -48,8 +50,10 @@ namespace Avalonia.Gtk3.Interop
 
         public static IDisposable StarTimer(uint interval, Action tick)
         {
+            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(101, interval,
+            GlibTimeout.Add(GlibPriority.FromDispatcherPriority(DispatcherPriority.Background), interval,
                 () =>
                 {
                     if (timer.Stopped)

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

@@ -169,6 +169,9 @@ namespace Avalonia.Gtk3.Interop
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
             public delegate void cairo_surface_mark_dirty(CairoSurface surface);
+            
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
+            public delegate void cairo_surface_write_to_png(CairoSurface surface, Utf8Buffer path);
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
             public delegate void cairo_surface_flush(CairoSurface surface);
@@ -178,15 +181,36 @@ namespace Avalonia.Gtk3.Interop
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
             public delegate void cairo_set_source_surface(IntPtr cr, CairoSurface surface, double x, double y);
+            
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
+            public delegate void cairo_set_source_rgba(IntPtr cr, double r, double g, double b, double a);
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
             public delegate void cairo_scale(IntPtr context, double sx, double sy);
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
             public delegate void cairo_paint(IntPtr context);
+            
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
+            public delegate void cairo_show_text(IntPtr context, Utf8Buffer text);   
+            
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
+            public delegate void cairo_set_font_size(IntPtr context, double size);  
+            
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
+            public delegate void cairo_select_font_face(IntPtr context, Utf8Buffer face, int slant, int weight);
+            
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
+            public delegate void cairo_move_to(IntPtr context, double x, double y);   
+            
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
+            public delegate void cairo_destroy(IntPtr context);
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
             public delegate void gtk_widget_queue_draw_area(GtkWidget widget, int x, int y, int width, int height);
+            
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
+            public delegate void gtk_widget_add_tick_callback(GtkWidget widget, TickCallback callback, IntPtr userData, IntPtr destroy);
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
             public delegate GtkImContext gtk_im_multicontext_new();
@@ -238,6 +262,12 @@ namespace Avalonia.Gtk3.Interop
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
             public delegate void gdk_window_process_updates(IntPtr window, bool updateChildren);
+            
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
+            public delegate void gdk_window_begin_paint_rect(IntPtr window, ref GdkRectangle rect);
+            
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
+            public delegate void gdk_window_end_paint(IntPtr window);
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
             public delegate void gdk_event_request_motions(IntPtr ev);
@@ -276,6 +306,9 @@ namespace Avalonia.Gtk3.Interop
             public delegate bool gdk_pixbuf_save_to_bufferv(Pixbuf pixbuf, out IntPtr buffer, out IntPtr buffer_size,
                             Utf8Buffer type, IntPtr option_keys, IntPtr option_values, out IntPtr error);
 
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
+            public delegate IntPtr gdk_cairo_create(IntPtr window);
+
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)]
             public delegate void g_object_unref(IntPtr instance);
             
@@ -326,6 +359,11 @@ namespace Avalonia.Gtk3.Interop
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
             public delegate void GtkClipboardTextReceivedFunc(IntPtr clipboard, IntPtr utf8string, IntPtr userdata);
+
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+            public delegate bool TickCallback(IntPtr widget, IntPtr clock, IntPtr userdata);
+
+
         }
 
         public static D.gdk_display_get_n_screens GdkDisplayGetNScreens;
@@ -380,6 +418,7 @@ namespace Avalonia.Gtk3.Interop
         public static D.gtk_widget_set_events GtkWidgetSetEvents;
         public static D.gdk_window_invalidate_rect GdkWindowInvalidateRect;
         public static D.gtk_widget_queue_draw_area GtkWidgetQueueDrawArea;
+        public static D.gtk_widget_add_tick_callback GtkWidgetAddTickCallback;
         public static D.gtk_widget_activate GtkWidgetActivate;
         public static D.gtk_clipboard_get_for_display GtkClipboardGetForDisplay;
         public static D.gtk_clipboard_request_text GtkClipboardRequestText;
@@ -406,6 +445,9 @@ namespace Avalonia.Gtk3.Interop
         public static D.gdk_window_begin_resize_drag GdkWindowBeginResizeDrag;
         public static D.gdk_event_request_motions GdkEventRequestMotions;
         public static D.gdk_window_process_updates GdkWindowProcessUpdates;
+        public static D.gdk_window_begin_paint_rect GdkWindowBeginPaintRect;
+        public static D.gdk_window_end_paint GdkWindowEndPaint;
+        
 
         public static D.gdk_pixbuf_new_from_file GdkPixbufNewFromFile;
         public static D.gtk_icon_theme_get_default GtkIconThemeGetDefault;
@@ -414,16 +456,24 @@ namespace Avalonia.Gtk3.Interop
         public static D.gdk_window_set_cursor GdkWindowSetCursor;
         public static D.gdk_pixbuf_new_from_stream GdkPixbufNewFromStream;
         public static D.gdk_pixbuf_save_to_bufferv GdkPixbufSaveToBufferv;
-
+        public static D.gdk_cairo_create GdkCairoCreate;
+        
         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_write_to_png CairoSurfaceWriteToPng;
         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_set_source_rgba CairoSetSourceRgba;
         public static D.cairo_scale CairoScale;
         public static D.cairo_paint CairoPaint;
+        public static D.cairo_show_text CairoShowText;
+        public static D.cairo_select_font_face CairoSelectFontFace;
+        public static D.cairo_set_font_size CairoSetFontSize;
+        public static D.cairo_move_to CairoMoveTo;
+        public static D.cairo_destroy CairoDestroy;
     }
 
     public enum GtkWindowType

+ 2 - 2
src/Gtk/Avalonia.Gtk3/ScreenImpl.cs

@@ -10,7 +10,7 @@ namespace Avalonia.Gtk3
     {
         public int ScreenCount
         {
-            get => _allScreens.Length;
+            get => AllScreens.Length;
         }
         
         private Screen[] _allScreens;
@@ -23,7 +23,7 @@ namespace Avalonia.Gtk3
                     IntPtr display = Native.GdkGetDefaultDisplay();
                     GdkScreen screen = Native.GdkDisplayGetDefaultScreen(display);
                     short primary = Native.GdkScreenGetPrimaryMonitor(screen);
-                    Screen[] screens = new Screen[ScreenCount];
+                    Screen[] screens = new Screen[Native.GdkScreenGetNMonitors(screen)];
                     for (short i = 0; i < screens.Length; i++)
                     {
                         GdkRectangle workArea = new GdkRectangle(), geometry = new GdkRectangle();

+ 72 - 32
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@@ -26,7 +26,9 @@ namespace Avalonia.Gtk3
         private double _lastScaling;
         private uint _lastKbdEvent;
         private uint _lastSmoothScrollEvent;
-        private bool _hasDirtyRects;
+        private GCHandle _gcHandle;
+        private object _lock = new object();
+        private IDeferredRenderOperation _nextRenderOperation;
 
         public WindowBaseImpl(GtkWindow gtkWidget)
         {
@@ -52,11 +54,25 @@ namespace Avalonia.Gtk3
             Connect<Native.D.signal_generic>("destroy", OnDestroy);
             Native.GtkWidgetRealize(gtkWidget);
             _lastSize = ClientSize;
+            GlibTimeout.Add(0, 16, () =>
+            {
+                Invalidate(default(Rect));
+                return true;
+            });
+            if (Gtk3Platform.UseDeferredRendering)
+            {
+                Native.GtkWidgetSetDoubleBuffered(gtkWidget, false);
+                _gcHandle = GCHandle.Alloc(this);
+                Native.GtkWidgetAddTickCallback(GtkWidget, PinnedStaticCallback, GCHandle.ToIntPtr(_gcHandle), IntPtr.Zero);
+                
+            }
         }
 
         private bool OnConfigured(IntPtr gtkwidget, IntPtr ev, IntPtr userdata)
         {
-            var size = ClientSize;
+            int w, h;
+            Native.GtkWindowGetSize(GtkWidget, out w, out h);
+            var size = ClientSize = new Size(w, h);
             if (_lastSize != size)
             {
                 Resized?.Invoke(size);
@@ -224,13 +240,52 @@ namespace Avalonia.Gtk3
 
         private bool OnDraw(IntPtr gtkwidget, IntPtr cairocontext, IntPtr userdata)
         {
-            _hasDirtyRects = false;
-            CurrentCairoContext = cairocontext;
-            Paint?.Invoke(new Rect(ClientSize));
-            CurrentCairoContext = IntPtr.Zero;
+            if (!Gtk3Platform.UseDeferredRendering)
+            {
+                CurrentCairoContext = cairocontext;
+                Paint?.Invoke(new Rect(ClientSize));
+                CurrentCairoContext = IntPtr.Zero;
+            }
+            return true;
+        }
+
+        private static Native.D.TickCallback PinnedStaticCallback = StaticTickCallback;
+
+        static bool StaticTickCallback(IntPtr widget, IntPtr clock, IntPtr userData)
+        {
+            var impl = (WindowBaseImpl) GCHandle.FromIntPtr(userData).Target;
+            impl.OnRenderTick();
             return true;
         }
 
+        public void SetNextRenderOperation(IDeferredRenderOperation op)
+        {
+            lock (_lock)
+            {
+                _nextRenderOperation?.Dispose();
+                _nextRenderOperation = op;
+            }
+        }
+
+        private void OnRenderTick()
+        {
+            IDeferredRenderOperation op = null;
+            lock (_lock)
+            {
+                if (_nextRenderOperation != null)
+                {
+                    op = _nextRenderOperation;
+                    _nextRenderOperation = null;
+                }
+            }
+            if (op != null)
+            {
+                op?.RenderNow();
+                op?.Dispose();
+            }
+        }
+
+
         public void Dispose()
         {
             //We are calling it here, since signal handler will be detached
@@ -239,6 +294,10 @@ namespace Avalonia.Gtk3
             foreach(var d in Disposables.AsEnumerable().Reverse())
                 d.Dispose();
             Disposables.Clear();
+            if (_gcHandle.IsAllocated)
+            {
+                _gcHandle.Free();
+            }
         }
 
         public Size MaxClientSize
@@ -273,20 +332,8 @@ namespace Avalonia.Gtk3
         {
             if(GtkWidget.IsClosed)
                 return;
-            Native.GtkWidgetQueueDrawArea(GtkWidget, (int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height);
-            if (!_hasDirtyRects)
-            {
-                _hasDirtyRects = true;
-                Dispatcher.UIThread.InvokeAsync(() =>
-                {
-                    if (GtkWidget.IsClosed)
-                        return;
-                    var window = Native.GtkWidgetGetWindow(GtkWidget);
-                    if (window == IntPtr.Zero)
-                        return;
-                    Native.GdkWindowProcessUpdates(window, false);
-                }, DispatcherPriority.Render);
-            }
+            var s = ClientSize;
+            Native.GtkWidgetQueueDrawArea(GtkWidget, 0, 0, (int) s.Width, (int) s.Height);
         }
 
         public void SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot;
@@ -339,17 +386,7 @@ namespace Avalonia.Gtk3
         }
 
 
-        public Size ClientSize
-        {
-            get
-            {
-                if (GtkWidget.IsClosed)
-                    return new Size();
-                int w, h;
-                Native.GtkWindowGetSize(GtkWidget, out w, out h);
-                return new Size(w, h);
-            }
-        }
+        public Size ClientSize { get; private set; }
 
         public void Resize(Size value)
         {
@@ -376,7 +413,10 @@ namespace Avalonia.Gtk3
 
         public IRenderer CreateRenderer(IRenderRoot root)
         {
-            return new ImmediateRenderer(root);
+            var loop = AvaloniaLocator.Current.GetService<IRenderLoop>();
+            return Gtk3Platform.UseDeferredRendering
+                ? (IRenderer) new DeferredRenderer(root, loop)
+                : new ImmediateRenderer(root);
         }
     }
 }