Browse Source

Merge pull request #1172 from kekekeks/dispatcher-priority

Dispatcher priority and support for DeferredRenderer in GTK3 backend
repro/dynamic-resource-bug
Nikita Tsukanov 9 years ago
committed by GitHub
parent
commit
c8276106af
  1. 1
      samples/ControlCatalog/MainWindow.xaml.cs
  2. 7
      src/Android/Avalonia.Android/AndroidThreadingInterface.cs
  3. 5
      src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs
  4. 4
      src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
  5. 4
      src/Avalonia.Base/Threading/Dispatcher.cs
  6. 24
      src/Avalonia.Base/Threading/DispatcherPriority.cs
  7. 47
      src/Avalonia.Base/Threading/JobRunner.cs
  8. 60
      src/Avalonia.Base/Threading/SingleThreadDispatcher.cs
  9. 1
      src/Avalonia.Base/Utilities/WeakTimer.cs
  10. 2
      src/Avalonia.Controls/Primitives/Track.cs
  11. 2
      src/Avalonia.Layout/LayoutManager.cs
  12. 8
      src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs
  13. 29
      src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj
  14. 10
      src/Gtk/Avalonia.Gtk3/FramebufferManager.cs
  15. 32
      src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs
  16. 9
      src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs
  17. 109
      src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs
  18. 1
      src/Gtk/Avalonia.Gtk3/Interop/CairoSurface.cs
  19. 48
      src/Gtk/Avalonia.Gtk3/Interop/GlibPriority.cs
  20. 10
      src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs
  21. 62
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  22. 12
      src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs
  23. 4
      src/Gtk/Avalonia.Gtk3/ScreenImpl.cs
  24. 90
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  25. 7
      src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs
  26. 7
      src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs
  27. 7
      src/Windows/Avalonia.Win32/Win32Platform.cs
  28. 1
      src/Windows/Avalonia.Win32/WindowImpl.cs
  29. 7
      src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs
  30. 5
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs
  31. 6
      tests/Avalonia.RenderTests/TestBase.cs

1
samples/ControlCatalog/MainWindow.xaml.cs

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

7
src/Android/Avalonia.Android/AndroidThreadingInterface.cs

@ -12,6 +12,7 @@ using Android.Runtime;
using Android.Views;
using Android.Widget;
using Avalonia.Platform;
using Avalonia.Threading;
namespace Avalonia.Android
{
@ -78,13 +79,13 @@ namespace Avalonia.Android
private void EnsureInvokeOnMainThread(Action action) => _handler.Post(action);
public void Signal()
public void Signal(DispatcherPriority prio)
{
EnsureInvokeOnMainThread(() => Signaled?.Invoke());
EnsureInvokeOnMainThread(() => Signaled?.Invoke(null));
}
public bool CurrentThreadIsLoopThread => Looper.MainLooper.Thread.Equals(Java.Lang.Thread.CurrentThread());
public event Action Signaled;
public event Action<DispatcherPriority?> Signaled;
}
}

5
src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs

@ -3,6 +3,7 @@
using System;
using System.Threading;
using Avalonia.Threading;
namespace Avalonia.Platform
{
@ -21,10 +22,10 @@ namespace Avalonia.Platform
/// <returns>An <see cref="IDisposable"/> used to stop the timer.</returns>
IDisposable StartTimer(TimeSpan interval, Action tick);
void Signal();
void Signal(DispatcherPriority priority);
bool CurrentThreadIsLoopThread { get; }
event Action Signaled;
event Action<DispatcherPriority?> Signaled;
}
}

4
src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs

@ -36,7 +36,7 @@ namespace Avalonia.Threading
/// <inheritdoc/>
public override void Post(SendOrPostCallback d, object state)
{
Dispatcher.UIThread.InvokeAsync(() => d(state));
Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send);
}
/// <inheritdoc/>
@ -45,7 +45,7 @@ namespace Avalonia.Threading
if (Dispatcher.UIThread.CheckAccess())
d(state);
else
Dispatcher.UIThread.InvokeTaskAsync(() => d(state)).Wait();
Dispatcher.UIThread.InvokeTaskAsync(() => d(state), DispatcherPriority.Send).Wait();
}
}
}

4
src/Avalonia.Base/Threading/Dispatcher.cs

@ -60,7 +60,7 @@ namespace Avalonia.Threading
public void MainLoop(CancellationToken cancellationToken)
{
var platform = AvaloniaLocator.Current.GetService<IPlatformThreadingInterface>();
cancellationToken.Register(platform.Signal);
cancellationToken.Register(() => platform.Signal(DispatcherPriority.Send));
platform.RunLoop(cancellationToken);
}
@ -69,7 +69,7 @@ namespace Avalonia.Threading
/// </summary>
public void RunJobs()
{
_jobRunner?.RunJobs();
_jobRunner?.RunJobs(null);
}
/// <inheritdoc/>

24
src/Avalonia.Base/Threading/DispatcherPriority.cs

@ -10,10 +10,10 @@ namespace Avalonia.Threading
public enum DispatcherPriority
{
/// <summary>
/// The job will not be processed.
/// Minimum possible priority
/// </summary>
Inactive = 0,
MinValue = 1,
/// <summary>
/// The job will be processed when the system is idle.
/// </summary>
@ -48,20 +48,30 @@ 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,
Send = 11,
/// <summary>
/// Maximum possible priority
/// </summary>
MaxValue = 11
}
}

47
src/Avalonia.Base/Threading/JobRunner.cs

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Platform;
@ -13,29 +14,45 @@ namespace Avalonia.Threading
/// </summary>
internal class JobRunner
{
private readonly Queue<Job> _queue = new Queue<Job>();
private IPlatformThreadingInterface _platform;
private Queue<Job>[] _queues = Enumerable.Range(0, (int) DispatcherPriority.MaxValue + 1)
.Select(_ => new Queue<Job>()).ToArray();
public JobRunner(IPlatformThreadingInterface platform)
{
_platform = platform;
}
Job GetNextJob(DispatcherPriority minimumPriority)
{
for (int c = (int) DispatcherPriority.MaxValue; c >= (int) minimumPriority; c--)
{
var q = _queues[c];
lock (q)
{
if (q.Count > 0)
return q.Dequeue();
}
}
return null;
}
/// <summary>
/// Runs continuations pushed on the loop.
/// </summary>
public void RunJobs()
/// <param name="priority">Priority to execute jobs for. Pass null if platform doesn't have internal priority system</param>
public void RunJobs(DispatcherPriority? priority)
{
var minimumPriority = priority ?? DispatcherPriority.MinValue;
while (true)
{
Job job;
lock (_queue)
{
if (_queue.Count == 0)
return;
job = _queue.Dequeue();
}
var job = GetNextJob(minimumPriority);
if (job == null)
return;
if (job.TaskCompletionSource == null)
{
@ -77,7 +94,6 @@ namespace Avalonia.Threading
/// <param name="priority">The priority with which to invoke the method.</param>
internal void Post(Action action, DispatcherPriority priority)
{
// TODO: Respect priority.
AddJob(new Job(action, priority, true));
}
@ -92,13 +108,14 @@ namespace Avalonia.Threading
private void AddJob(Job job)
{
var needWake = false;
lock (_queue)
var queue = _queues[(int) job.Priority];
lock (queue)
{
needWake = _queue.Count == 0;
_queue.Enqueue(job);
needWake = queue.Count == 0;
queue.Enqueue(job);
}
if (needWake)
_platform?.Signal();
_platform?.Signal(job.Priority);
}
/// <summary>

60
src/Avalonia.Base/Threading/SingleThreadDispatcher.cs

@ -1,60 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Platform;
namespace Avalonia.Threading
{
public class SingleThreadDispatcher : Dispatcher
{
class ThreadingInterface : IPlatformThreadingInterface
{
private readonly AutoResetEvent _evnt = new AutoResetEvent(false);
private readonly JobRunner _timerJobRunner;
public ThreadingInterface()
{
_timerJobRunner = new JobRunner(this);
}
public void RunLoop(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
_evnt.WaitOne();
if (cancellationToken.IsCancellationRequested)
return;
Signaled?.Invoke();
_timerJobRunner.RunJobs();
}
}
public IDisposable StartTimer(TimeSpan interval, Action tick)
=> AvaloniaLocator.Current.GetService<IRuntimePlatform>().StartSystemTimer(interval,
() => _timerJobRunner.Post(tick, DispatcherPriority.Normal));
public void Signal() => _evnt.Set();
//TODO: Actually perform a check
public bool CurrentThreadIsLoopThread => true;
public event Action Signaled;
}
public SingleThreadDispatcher() : base(new ThreadingInterface())
{
}
public static Dispatcher StartNew(CancellationToken token)
{
var dispatcher = new SingleThreadDispatcher();
AvaloniaLocator.Current.GetService<IRuntimePlatform>().PostThreadPoolItem(() =>
{
dispatcher.MainLoop(token);
});
return dispatcher;
}
}
}

1
src/Avalonia.Base/Utilities/WeakTimer.cs

@ -23,7 +23,6 @@ namespace Avalonia.Utilities
_timer = new DispatcherTimer();
_timer.Tick += delegate { OnTick(); };
_timer.Start();
}
private void OnTick()

2
src/Avalonia.Controls/Primitives/Track.cs

@ -165,7 +165,7 @@ namespace Avalonia.Controls.Primitives
if (increaseButton != null)
{
increaseButton.Arrange(new Rect(0, firstHeight + thumbHeight, finalSize.Width, remaining - firstHeight));
increaseButton.Arrange(new Rect(0, firstHeight + thumbHeight, finalSize.Width, Math.Max(remaining - firstHeight, 0)));
}
}

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

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

29
src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj

@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<EnableDefaultCompileItems>False</EnableDefaultCompileItems>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
@ -25,34 +24,6 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\Shared\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
<Compile Include="KeyTransform.cs" />
<Compile Include="Interop\CairoSurface.cs" />
<Compile Include="ClipboardImpl.cs" />
<Compile Include="CursorFactory.cs" />
<Compile Include="FramebufferManager.cs" />
<Compile Include="GdkCursor.cs" />
<Compile Include="GdkKey.cs" />
<Compile Include="Gtk3Platform.cs" />
<Compile Include="Interop\GException.cs" />
<Compile Include="Interop\GObject.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\Pixbuf.cs" />
<Compile Include="Interop\Resolver.cs" />
<Compile Include="Interop\Signal.cs" />
<Compile Include="ImageSurfaceFramebuffer.cs" />
<Compile Include="PlatformIconLoader.cs" />
<Compile Include="PopupImpl.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SystemDialogs.cs" />
<Compile Include="WindowBaseImpl.cs" />
<Compile Include="Interop\Utf8Buffer.cs" />
<Compile Include="WindowImpl.cs" />
<Compile Include="ScreenImpl.cs" />
<Compile Include="GtkScreen.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />

10
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);
}
}
}

32
src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs

@ -12,6 +12,7 @@ using Avalonia.Input.Platform;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Gtk3;
using Avalonia.Threading;
namespace Avalonia.Gtk3
{
@ -21,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();
@ -65,37 +67,42 @@ 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 = false;
private bool[] _signaled = new bool[(int) DispatcherPriority.MaxValue + 1];
object _lock = new object();
public void Signal()
public void Signal(DispatcherPriority prio)
{
var idx = (int) prio;
lock(_lock)
if (!_signaled)
if (!_signaled[idx])
{
_signaled = true;
GlibTimeout.Add(0, () =>
_signaled[idx] = true;
GlibTimeout.Add(GlibPriority.FromDispatcherPriority(prio), 0, () =>
{
lock (_lock)
{
_signaled = false;
_signaled[idx] = false;
}
Signaled?.Invoke();
Signaled?.Invoke(prio);
return false;
});
}
}
public event Action Signaled;
public event Action<DispatcherPriority?> Signaled;
[ThreadStatic]
private static bool s_tlsMarker;
public bool CurrentThreadIsLoopThread => s_tlsMarker;
}
}
@ -103,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
src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs

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

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

48
src/Gtk/Avalonia.Gtk3/Interop/GlibPriority.cs

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Threading;
namespace Avalonia.Gtk3.Interop
{
static class GlibPriority
{
public static int High = -100;
public static int Default = 0;
public static int HighIdle = 100;
public static int GtkResize = HighIdle + 10;
public static int GtkPaint = HighIdle + 20;
public static int DefaultIdle = 200;
public static int Low = 300;
public static int GdkEvents = Default;
public static int GdkRedraw = HighIdle + 20;
public static int FromDispatcherPriority(DispatcherPriority prio)
{
if (prio == DispatcherPriority.Send)
return High;
if (prio == DispatcherPriority.Normal)
return Default;
if (prio == DispatcherPriority.DataBind)
return Default + 1;
if (prio == DispatcherPriority.Layout)
return Default + 2;
if (prio == DispatcherPriority.Render)
return Default + 3;
if (prio == DispatcherPriority.Loaded)
return GtkPaint + 20;
if (prio == DispatcherPriority.Input)
return GtkPaint + 21;
if (prio == DispatcherPriority.Background)
return DefaultIdle + 1;
if (prio == DispatcherPriority.ContextIdle)
return DefaultIdle + 2;
if (prio == DispatcherPriority.ApplicationIdle)
return DefaultIdle + 3;
if (prio == DispatcherPriority.SystemIdle)
return DefaultIdle + 4;
throw new ArgumentException("Unknown priority");
}
}
}

10
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
{
@ -30,10 +31,11 @@ namespace Avalonia.Gtk3.Interop
}
public static void Add(uint interval, Func<bool> callback)
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(interval,
GlibTimeout.Add(GlibPriority.FromDispatcherPriority(DispatcherPriority.Background), interval,
() =>
{
if (timer.Stopped)

62
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();
@ -236,6 +260,15 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate void gdk_window_begin_resize_drag(IntPtr window, WindowEdge edge, gint button, gint root_x, gint root_y, guint32 timestamp);
[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);
@ -273,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);
@ -287,7 +323,10 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)]
public delegate ulong g_timeout_add(uint interval, timeout_callback callback, IntPtr data);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)]
public delegate ulong g_timeout_add_full(int prio, uint interval, timeout_callback callback, IntPtr data, IntPtr destroy);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)]
public delegate ulong g_free(IntPtr data);
@ -320,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;
@ -366,6 +410,7 @@ namespace Avalonia.Gtk3.Interop
public static D.g_signal_connect_object GSignalConnectObject;
public static D.g_signal_handler_disconnect GSignalHandlerDisconnect;
public static D.g_timeout_add GTimeoutAdd;
public static D.g_timeout_add_full GTimeoutAddFull;
public static D.g_free GFree;
public static D.g_slist_free GSlistFree;
public static D.g_memory_input_stream_new_from_data GMemoryInputStreamNewFromData;
@ -373,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;
@ -398,6 +444,10 @@ namespace Avalonia.Gtk3.Interop
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;
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;
@ -406,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

12
src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs

@ -140,13 +140,11 @@ namespace Avalonia.Gtk3.Interop
var nativeHandleNames = new[] { "gdk_win32_window_get_handle", "gdk_x11_window_get_xid", "gdk_quartz_window_get_nswindow" };
foreach (var name in nativeHandleNames)
{
try
{
Native.GetNativeGdkWindowHandle = (Native.D.gdk_get_native_handle)Marshal
.GetDelegateForFunctionPointer(loader.GetProcAddress(dlls[GtkDll.Gdk], name, false), typeof(Native.D.gdk_get_native_handle));
break;
}
catch { }
var ptr = loader.GetProcAddress(dlls[GtkDll.Gdk], name, true);
if (ptr == IntPtr.Zero)
continue;
Native.GetNativeGdkWindowHandle = (Native.D.gdk_get_native_handle) Marshal
.GetDelegateForFunctionPointer(ptr, typeof(Native.D.gdk_get_native_handle));
}
if (Native.GetNativeGdkWindowHandle == null)
throw new Exception($"Unable to locate any of [{string.Join(", ", nativeHandleNames)}] in libgdk");

4
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();

90
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@ -10,6 +10,7 @@ using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
namespace Avalonia.Gtk3
{
@ -25,6 +26,9 @@ namespace Avalonia.Gtk3
private double _lastScaling;
private uint _lastKbdEvent;
private uint _lastSmoothScrollEvent;
private GCHandle _gcHandle;
private object _lock = new object();
private IDeferredRenderOperation _nextRenderOperation;
public WindowBaseImpl(GtkWindow gtkWidget)
{
@ -50,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);
@ -222,12 +240,52 @@ namespace Avalonia.Gtk3
private bool OnDraw(IntPtr gtkwidget, IntPtr cairocontext, IntPtr userdata)
{
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
@ -236,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
@ -270,7 +332,8 @@ namespace Avalonia.Gtk3
{
if(GtkWidget.IsClosed)
return;
Native.GtkWidgetQueueDrawArea(GtkWidget, (int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height);
var s = ClientSize;
Native.GtkWidgetQueueDrawArea(GtkWidget, 0, 0, (int) s.Width, (int) s.Height);
}
public void SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot;
@ -323,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)
{
@ -363,7 +416,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);
}
}
}

7
src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs

@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
namespace Avalonia.LinuxFramebuffer
{
@ -30,7 +31,7 @@ namespace Avalonia.LinuxFramebuffer
while (true)
{
if (0 == WaitHandle.WaitAny(handles))
Signaled?.Invoke();
Signaled?.Invoke(null);
else
{
while (true)
@ -97,7 +98,7 @@ namespace Avalonia.LinuxFramebuffer
}
public void Signal()
public void Signal(DispatcherPriority prio)
{
_signaled.Set();
}
@ -105,7 +106,7 @@ namespace Avalonia.LinuxFramebuffer
[ThreadStatic] private static bool TlsCurrentThreadIsLoopThread;
public bool CurrentThreadIsLoopThread => TlsCurrentThreadIsLoopThread;
public event Action Signaled;
public event Action<DispatcherPriority?> Signaled;
public event EventHandler<EventArgs> Tick;
}

7
src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs

@ -1,6 +1,7 @@
using System;
using System.Threading;
using Avalonia.Platform;
using Avalonia.Threading;
using MonoMac.AppKit;
using MonoMac.CoreGraphics;
using MonoMac.Foundation;
@ -13,12 +14,12 @@ namespace Avalonia.MonoMac
public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface();
public bool CurrentThreadIsLoopThread => NSThread.Current.IsMainThread;
public event Action Signaled;
public event Action<DispatcherPriority?> Signaled;
public IDisposable StartTimer(TimeSpan interval, Action tick)
=> NSTimer.CreateRepeatingScheduledTimer(interval, () => tick());
public void Signal()
public void Signal(DispatcherPriority prio)
{
lock (this)
{
@ -34,7 +35,7 @@ namespace Avalonia.MonoMac
return;
_signaled = false;
}
Signaled?.Invoke();
Signaled?.Invoke(null);
});
}

7
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -15,6 +15,7 @@ using Avalonia.Win32.Input;
using Avalonia.Win32.Interop;
using Avalonia.Controls;
using Avalonia.Rendering;
using Avalonia.Threading;
#if NETSTANDARD
using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception;
#else
@ -137,7 +138,7 @@ namespace Avalonia.Win32
private static readonly int SignalW = unchecked((int) 0xdeadbeaf);
private static readonly int SignalL = unchecked((int)0x12345678);
public void Signal()
public void Signal(DispatcherPriority prio)
{
UnmanagedMethods.PostMessage(
_hwnd,
@ -148,14 +149,14 @@ namespace Avalonia.Win32
public bool CurrentThreadIsLoopThread => _uiThread == UnmanagedMethods.GetCurrentThreadId();
public event Action Signaled;
public event Action<DispatcherPriority?> Signaled;
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")]
private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
if (msg == (int) UnmanagedMethods.WindowsMessage.WM_DISPATCH_WORK_ITEM && wParam.ToInt64() == SignalW && lParam.ToInt64() == SignalL)
{
Signaled?.Invoke();
Signaled?.Invoke(null);
}
return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam);
}

1
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -16,6 +16,7 @@ using Avalonia.Win32.Input;
using Avalonia.Win32.Interop;
using static Avalonia.Win32.Interop.UnmanagedMethods;
using Avalonia.Rendering;
using Avalonia.Threading;
#if NETSTANDARD
using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception;
#endif

7
src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs

@ -9,6 +9,7 @@ using CoreAnimation;
using Foundation;
using Avalonia.Platform;
using Avalonia.Shared.PlatformSupport;
using Avalonia.Threading;
namespace Avalonia.iOS
{
@ -18,7 +19,7 @@ namespace Avalonia.iOS
public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface();
public bool CurrentThreadIsLoopThread => NSThread.Current.IsMainThread;
public event Action Signaled;
public event Action<DispatcherPriority?> Signaled;
public void RunLoop(CancellationToken cancellationToken)
{
//Mobile platforms are using external main loop
@ -53,7 +54,7 @@ namespace Avalonia.iOS
public IDisposable StartTimer(TimeSpan interval, Action tick)
=> NSTimer.CreateRepeatingScheduledTimer(interval, _ => tick());
public void Signal()
public void Signal(DispatcherPriority prio)
{
lock (this)
{
@ -65,7 +66,7 @@ namespace Avalonia.iOS
{
lock (this)
_signaled = false;
Signaled?.Invoke();
Signaled?.Invoke(null);
});
}
}

5
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs

@ -6,6 +6,7 @@ using System.Reactive.Subjects;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Platform;
using Avalonia.Threading;
using Avalonia.UnitTests;
using Xunit;
@ -147,14 +148,14 @@ namespace Avalonia.Base.UnitTests
public bool CurrentThreadIsLoopThread { get; set; }
public event Action Signaled;
public event Action<DispatcherPriority?> Signaled;
public void RunLoop(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public void Signal()
public void Signal(DispatcherPriority prio)
{
throw new NotImplementedException();
}

6
tests/Avalonia.RenderTests/TestBase.cs

@ -13,7 +13,7 @@ using Avalonia.Platform;
using System.Threading.Tasks;
using System;
using System.Threading;
using Avalonia.Threading;
#if AVALONIA_SKIA
using Avalonia.Skia;
#else
@ -149,14 +149,14 @@ namespace Avalonia.Direct2D1.RenderTests
public Thread MainThread { get; set; }
public event Action Signaled;
public event Action<DispatcherPriority?> Signaled;
public void RunLoop(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public void Signal()
public void Signal(DispatcherPriority prio)
{
throw new NotImplementedException();
}

Loading…
Cancel
Save