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.InitializeComponent();
this.AttachDevTools(); this.AttachDevTools();
//Renderer.DrawFps = true;
//Renderer.DrawDirtyRects = 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.Views;
using Android.Widget; using Android.Widget;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Threading;
namespace Avalonia.Android namespace Avalonia.Android
{ {
@ -78,13 +79,13 @@ namespace Avalonia.Android
private void EnsureInvokeOnMainThread(Action action) => _handler.Post(action); 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 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;
using System.Threading; using System.Threading;
using Avalonia.Threading;
namespace Avalonia.Platform namespace Avalonia.Platform
{ {
@ -21,10 +22,10 @@ namespace Avalonia.Platform
/// <returns>An <see cref="IDisposable"/> used to stop the timer.</returns> /// <returns>An <see cref="IDisposable"/> used to stop the timer.</returns>
IDisposable StartTimer(TimeSpan interval, Action tick); IDisposable StartTimer(TimeSpan interval, Action tick);
void Signal(); void Signal(DispatcherPriority priority);
bool CurrentThreadIsLoopThread { get; } 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/> /// <inheritdoc/>
public override void Post(SendOrPostCallback d, object state) public override void Post(SendOrPostCallback d, object state)
{ {
Dispatcher.UIThread.InvokeAsync(() => d(state)); Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -45,7 +45,7 @@ namespace Avalonia.Threading
if (Dispatcher.UIThread.CheckAccess()) if (Dispatcher.UIThread.CheckAccess())
d(state); d(state);
else 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) public void MainLoop(CancellationToken cancellationToken)
{ {
var platform = AvaloniaLocator.Current.GetService<IPlatformThreadingInterface>(); var platform = AvaloniaLocator.Current.GetService<IPlatformThreadingInterface>();
cancellationToken.Register(platform.Signal); cancellationToken.Register(() => platform.Signal(DispatcherPriority.Send));
platform.RunLoop(cancellationToken); platform.RunLoop(cancellationToken);
} }
@ -69,7 +69,7 @@ namespace Avalonia.Threading
/// </summary> /// </summary>
public void RunJobs() public void RunJobs()
{ {
_jobRunner?.RunJobs(); _jobRunner?.RunJobs(null);
} }
/// <inheritdoc/> /// <inheritdoc/>

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

@ -10,10 +10,10 @@ namespace Avalonia.Threading
public enum DispatcherPriority public enum DispatcherPriority
{ {
/// <summary> /// <summary>
/// The job will not be processed. /// Minimum possible priority
/// </summary> /// </summary>
Inactive = 0, MinValue = 1,
/// <summary> /// <summary>
/// The job will be processed when the system is idle. /// The job will be processed when the system is idle.
/// </summary> /// </summary>
@ -48,20 +48,30 @@ namespace Avalonia.Threading
/// The job will be processed with the same priority as render. /// The job will be processed with the same priority as render.
/// </summary> /// </summary>
Render = 7, Render = 7,
/// <summary>
/// The job will be processed with the same priority as render.
/// </summary>
Layout = 8,
/// <summary> /// <summary>
/// The job will be processed with the same priority as data binding. /// The job will be processed with the same priority as data binding.
/// </summary> /// </summary>
DataBind = 8, DataBind = 9,
/// <summary> /// <summary>
/// The job will be processed with normal priority. /// The job will be processed with normal priority.
/// </summary> /// </summary>
Normal = 9, Normal = 10,
/// <summary> /// <summary>
/// The job will be processed before other asynchronous operations. /// The job will be processed before other asynchronous operations.
/// </summary> /// </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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Platform; using Avalonia.Platform;
@ -13,29 +14,45 @@ namespace Avalonia.Threading
/// </summary> /// </summary>
internal class JobRunner internal class JobRunner
{ {
private readonly Queue<Job> _queue = new Queue<Job>();
private IPlatformThreadingInterface _platform; private IPlatformThreadingInterface _platform;
private Queue<Job>[] _queues = Enumerable.Range(0, (int) DispatcherPriority.MaxValue + 1)
.Select(_ => new Queue<Job>()).ToArray();
public JobRunner(IPlatformThreadingInterface platform) public JobRunner(IPlatformThreadingInterface platform)
{ {
_platform = 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> /// <summary>
/// Runs continuations pushed on the loop. /// Runs continuations pushed on the loop.
/// </summary> /// </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) while (true)
{ {
Job job; var job = GetNextJob(minimumPriority);
if (job == null)
lock (_queue) return;
{
if (_queue.Count == 0)
return;
job = _queue.Dequeue();
}
if (job.TaskCompletionSource == null) if (job.TaskCompletionSource == null)
{ {
@ -77,7 +94,6 @@ namespace Avalonia.Threading
/// <param name="priority">The priority with which to invoke the method.</param> /// <param name="priority">The priority with which to invoke the method.</param>
internal void Post(Action action, DispatcherPriority priority) internal void Post(Action action, DispatcherPriority priority)
{ {
// TODO: Respect priority.
AddJob(new Job(action, priority, true)); AddJob(new Job(action, priority, true));
} }
@ -92,13 +108,14 @@ namespace Avalonia.Threading
private void AddJob(Job job) private void AddJob(Job job)
{ {
var needWake = false; var needWake = false;
lock (_queue) var queue = _queues[(int) job.Priority];
lock (queue)
{ {
needWake = _queue.Count == 0; needWake = queue.Count == 0;
_queue.Enqueue(job); queue.Enqueue(job);
} }
if (needWake) if (needWake)
_platform?.Signal(); _platform?.Signal(job.Priority);
} }
/// <summary> /// <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 = new DispatcherTimer();
_timer.Tick += delegate { OnTick(); }; _timer.Tick += delegate { OnTick(); };
_timer.Start();
} }
private void OnTick() private void OnTick()

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

@ -165,7 +165,7 @@ namespace Avalonia.Controls.Primitives
if (increaseButton != null) 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) if (!_queued && !_running)
{ {
Dispatcher.UIThread.InvokeAsync(ExecuteLayoutPass, DispatcherPriority.Render); Dispatcher.UIThread.InvokeAsync(ExecuteLayoutPass, DispatcherPriority.Layout);
_queued = true; _queued = true;
} }
} }

8
src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs

@ -15,7 +15,7 @@ namespace Avalonia.Rendering
/// </remarks> /// </remarks>
public class DefaultRenderLoop : IRenderLoop public class DefaultRenderLoop : IRenderLoop
{ {
private IPlatformThreadingInterface _threading; private IRuntimePlatform _runtime;
private int _subscriberCount; private int _subscriberCount;
private EventHandler<EventArgs> _tick; private EventHandler<EventArgs> _tick;
private IDisposable _subscription; private IDisposable _subscription;
@ -78,12 +78,12 @@ namespace Avalonia.Rendering
/// </remarks> /// </remarks>
protected virtual IDisposable StartCore(Action tick) 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> /// <summary>

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

@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<EnableDefaultCompileItems>False</EnableDefaultCompileItems>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
@ -25,34 +24,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\..\Shared\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" /> <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>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" /> <ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />

10
src/Gtk/Avalonia.Gtk3/FramebufferManager.cs

@ -23,11 +23,11 @@ namespace Avalonia.Gtk3
public ILockedFramebuffer Lock() public ILockedFramebuffer Lock()
{ {
if(_window.CurrentCairoContext == IntPtr.Zero) // This method may be called from non-UI thread, don't touch anything that calls back to GTK/GDK
throw new InvalidOperationException("Window is not in drawing state"); var s = _window.ClientSize;
var width = (int) _window.ClientSize.Width; var width = (int) s.Width;
var height = (int) _window.ClientSize.Height; var height = (int) s.Height;
return new ImageSurfaceFramebuffer(_window.CurrentCairoContext, _window.GtkWidget, width, 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.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Gtk3; using Avalonia.Gtk3;
using Avalonia.Threading;
namespace Avalonia.Gtk3 namespace Avalonia.Gtk3
{ {
@ -21,6 +22,7 @@ namespace Avalonia.Gtk3
internal static readonly MouseDevice Mouse = new MouseDevice(); internal static readonly MouseDevice Mouse = new MouseDevice();
internal static readonly KeyboardDevice Keyboard = new KeyboardDevice(); internal static readonly KeyboardDevice Keyboard = new KeyboardDevice();
internal static IntPtr App { get; set; } internal static IntPtr App { get; set; }
public static bool UseDeferredRendering = true;
public static void Initialize() public static void Initialize()
{ {
Resolver.Resolve(); Resolver.Resolve();
@ -65,37 +67,42 @@ namespace Avalonia.Gtk3
public IDisposable StartTimer(TimeSpan interval, Action tick) 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(); object _lock = new object();
public void Signal(DispatcherPriority prio)
public void Signal()
{ {
var idx = (int) prio;
lock(_lock) lock(_lock)
if (!_signaled) if (!_signaled[idx])
{ {
_signaled = true; _signaled[idx] = true;
GlibTimeout.Add(0, () => GlibTimeout.Add(GlibPriority.FromDispatcherPriority(prio), 0, () =>
{ {
lock (_lock) lock (_lock)
{ {
_signaled = false; _signaled[idx] = false;
} }
Signaled?.Invoke(); Signaled?.Invoke(prio);
return false; return false;
}); });
} }
} }
public event Action Signaled; public event Action<DispatcherPriority?> Signaled;
[ThreadStatic] [ThreadStatic]
private static bool s_tlsMarker; private static bool s_tlsMarker;
public bool CurrentThreadIsLoopThread => s_tlsMarker; public bool CurrentThreadIsLoopThread => s_tlsMarker;
} }
} }
@ -103,10 +110,11 @@ namespace Avalonia
{ {
public static class Gtk3AppBuilderExtensions 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() where T : AppBuilderBase<T>, new()
{ {
Resolver.Custom = resolver; Resolver.Custom = resolver;
Gtk3Platform.UseDeferredRendering = deferredRendering;
return builder.UseWindowingSubsystem(Gtk3Platform.Initialize, "GTK3"); 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls.Platform.Surfaces; using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Gtk3.Interop; using Avalonia.Gtk3.Interop;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Threading;
namespace Avalonia.Gtk3 namespace Avalonia.Gtk3
{ {
class ImageSurfaceFramebuffer : ILockedFramebuffer class ImageSurfaceFramebuffer : ILockedFramebuffer
{ {
private IntPtr _context; private readonly WindowBaseImpl _impl;
private readonly GtkWidget _widget; private readonly GtkWidget _widget;
private CairoSurface _surface; private CairoSurface _surface;
private int _factor; private int _factor;
private object _lock = new object();
public ImageSurfaceFramebuffer(IntPtr context, GtkWidget widget, int width, int height) public ImageSurfaceFramebuffer(WindowBaseImpl impl, int width, int height)
{ {
_context = context; _impl = impl;
_widget = widget; _widget = impl.GtkWidget;
_factor = (int)(Native.GtkWidgetGetScaleFactor?.Invoke(_widget) ?? 1u); _factor = (int)(Native.GtkWidgetGetScaleFactor?.Invoke(_widget) ?? 1u);
width *= _factor; width *= _factor;
height *= _factor; height *= _factor;
@ -32,18 +34,97 @@ namespace Avalonia.Gtk3
RowBytes = Native.CairoImageSurfaceGetStride(_surface); RowBytes = Native.CairoImageSurfaceGetStride(_surface);
Native.CairoSurfaceFlush(_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() public void Dispose()
{ {
if(_context == IntPtr.Zero || _surface == null) lock (_lock)
return; {
Native.CairoSurfaceMarkDirty(_surface); if (Dispatcher.UIThread.CheckAccess())
Native.CairoScale(_context, 1d / _factor, 1d / _factor); {
Native.CairoSetSourceSurface(_context, _surface, 0, 0); if (_impl.CurrentCairoContext != IntPtr.Zero)
Native.CairoPaint(_context); Draw(_impl.CurrentCairoContext, _surface, _factor);
_context = IntPtr.Zero; else
_surface.Dispose(); DrawToWidget(_widget, _surface, Width, Height, _factor);
_surface = null; _surface.Dispose();
}
else
_impl.SetNextRenderOperation(new RenderOp(_widget, _surface, _factor, Width, Height));
_surface = null;
}
} }
public IntPtr Address { get; } public IntPtr Address { get; }

1
src/Gtk/Avalonia.Gtk3/Interop/CairoSurface.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Avalonia.Gtk3.Interop 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.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Threading;
namespace Avalonia.Gtk3.Interop 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); 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 class Timer : IDisposable
@ -48,8 +50,10 @@ namespace Avalonia.Gtk3.Interop
public static IDisposable StarTimer(uint interval, Action tick) 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 (); var timer = new Timer ();
GlibTimeout.Add(interval, GlibTimeout.Add(GlibPriority.FromDispatcherPriority(DispatcherPriority.Background), interval,
() => () =>
{ {
if (timer.Stopped) 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)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
public delegate void cairo_surface_mark_dirty(CairoSurface surface); 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)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
public delegate void cairo_surface_flush(CairoSurface surface); public delegate void cairo_surface_flush(CairoSurface surface);
@ -178,15 +181,36 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
public delegate void cairo_set_source_surface(IntPtr cr, CairoSurface surface, double x, double y); 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)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
public delegate void cairo_scale(IntPtr context, double sx, double sy); public delegate void cairo_scale(IntPtr context, double sx, double sy);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
public delegate void cairo_paint(IntPtr context); 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)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_widget_queue_draw_area(GtkWidget widget, int x, int y, int width, int height); 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)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate GtkImContext gtk_im_multicontext_new(); public delegate GtkImContext gtk_im_multicontext_new();
@ -236,6 +260,15 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] [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); 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)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate void gdk_event_request_motions(IntPtr ev); 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, 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); 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)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)]
public delegate void g_object_unref(IntPtr instance); public delegate void g_object_unref(IntPtr instance);
@ -287,7 +323,10 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)]
public delegate ulong g_timeout_add(uint interval, timeout_callback callback, IntPtr data); 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)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)]
public delegate ulong g_free(IntPtr data); public delegate ulong g_free(IntPtr data);
@ -320,6 +359,11 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void GtkClipboardTextReceivedFunc(IntPtr clipboard, IntPtr utf8string, IntPtr userdata); 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; 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_connect_object GSignalConnectObject;
public static D.g_signal_handler_disconnect GSignalHandlerDisconnect; public static D.g_signal_handler_disconnect GSignalHandlerDisconnect;
public static D.g_timeout_add GTimeoutAdd; 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_free GFree;
public static D.g_slist_free GSlistFree; public static D.g_slist_free GSlistFree;
public static D.g_memory_input_stream_new_from_data GMemoryInputStreamNewFromData; 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.gtk_widget_set_events GtkWidgetSetEvents;
public static D.gdk_window_invalidate_rect GdkWindowInvalidateRect; public static D.gdk_window_invalidate_rect GdkWindowInvalidateRect;
public static D.gtk_widget_queue_draw_area GtkWidgetQueueDrawArea; 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_widget_activate GtkWidgetActivate;
public static D.gtk_clipboard_get_for_display GtkClipboardGetForDisplay; public static D.gtk_clipboard_get_for_display GtkClipboardGetForDisplay;
public static D.gtk_clipboard_request_text GtkClipboardRequestText; 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_move_drag GdkWindowBeginMoveDrag;
public static D.gdk_window_begin_resize_drag GdkWindowBeginResizeDrag; public static D.gdk_window_begin_resize_drag GdkWindowBeginResizeDrag;
public static D.gdk_event_request_motions GdkEventRequestMotions; 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.gdk_pixbuf_new_from_file GdkPixbufNewFromFile;
public static D.gtk_icon_theme_get_default GtkIconThemeGetDefault; 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_window_set_cursor GdkWindowSetCursor;
public static D.gdk_pixbuf_new_from_stream GdkPixbufNewFromStream; public static D.gdk_pixbuf_new_from_stream GdkPixbufNewFromStream;
public static D.gdk_pixbuf_save_to_bufferv GdkPixbufSaveToBufferv; 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_create CairoImageSurfaceCreate;
public static D.cairo_image_surface_get_data CairoImageSurfaceGetData; public static D.cairo_image_surface_get_data CairoImageSurfaceGetData;
public static D.cairo_image_surface_get_stride CairoImageSurfaceGetStride; public static D.cairo_image_surface_get_stride CairoImageSurfaceGetStride;
public static D.cairo_surface_mark_dirty CairoSurfaceMarkDirty; 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_flush CairoSurfaceFlush;
public static D.cairo_surface_destroy CairoSurfaceDestroy; public static D.cairo_surface_destroy CairoSurfaceDestroy;
public static D.cairo_set_source_surface CairoSetSourceSurface; 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_scale CairoScale;
public static D.cairo_paint CairoPaint; 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 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" }; var nativeHandleNames = new[] { "gdk_win32_window_get_handle", "gdk_x11_window_get_xid", "gdk_quartz_window_get_nswindow" };
foreach (var name in nativeHandleNames) foreach (var name in nativeHandleNames)
{ {
try var ptr = loader.GetProcAddress(dlls[GtkDll.Gdk], name, true);
{ if (ptr == IntPtr.Zero)
Native.GetNativeGdkWindowHandle = (Native.D.gdk_get_native_handle)Marshal continue;
.GetDelegateForFunctionPointer(loader.GetProcAddress(dlls[GtkDll.Gdk], name, false), typeof(Native.D.gdk_get_native_handle)); Native.GetNativeGdkWindowHandle = (Native.D.gdk_get_native_handle) Marshal
break; .GetDelegateForFunctionPointer(ptr, typeof(Native.D.gdk_get_native_handle));
}
catch { }
} }
if (Native.GetNativeGdkWindowHandle == null) if (Native.GetNativeGdkWindowHandle == null)
throw new Exception($"Unable to locate any of [{string.Join(", ", nativeHandleNames)}] in libgdk"); 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 public int ScreenCount
{ {
get => _allScreens.Length; get => AllScreens.Length;
} }
private Screen[] _allScreens; private Screen[] _allScreens;
@ -23,7 +23,7 @@ namespace Avalonia.Gtk3
IntPtr display = Native.GdkGetDefaultDisplay(); IntPtr display = Native.GdkGetDefaultDisplay();
GdkScreen screen = Native.GdkDisplayGetDefaultScreen(display); GdkScreen screen = Native.GdkDisplayGetDefaultScreen(display);
short primary = Native.GdkScreenGetPrimaryMonitor(screen); 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++) for (short i = 0; i < screens.Length; i++)
{ {
GdkRectangle workArea = new GdkRectangle(), geometry = new GdkRectangle(); 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.Input.Raw;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Threading;
namespace Avalonia.Gtk3 namespace Avalonia.Gtk3
{ {
@ -25,6 +26,9 @@ namespace Avalonia.Gtk3
private double _lastScaling; private double _lastScaling;
private uint _lastKbdEvent; private uint _lastKbdEvent;
private uint _lastSmoothScrollEvent; private uint _lastSmoothScrollEvent;
private GCHandle _gcHandle;
private object _lock = new object();
private IDeferredRenderOperation _nextRenderOperation;
public WindowBaseImpl(GtkWindow gtkWidget) public WindowBaseImpl(GtkWindow gtkWidget)
{ {
@ -50,11 +54,25 @@ namespace Avalonia.Gtk3
Connect<Native.D.signal_generic>("destroy", OnDestroy); Connect<Native.D.signal_generic>("destroy", OnDestroy);
Native.GtkWidgetRealize(gtkWidget); Native.GtkWidgetRealize(gtkWidget);
_lastSize = ClientSize; _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) 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) if (_lastSize != size)
{ {
Resized?.Invoke(size); Resized?.Invoke(size);
@ -222,12 +240,52 @@ namespace Avalonia.Gtk3
private bool OnDraw(IntPtr gtkwidget, IntPtr cairocontext, IntPtr userdata) private bool OnDraw(IntPtr gtkwidget, IntPtr cairocontext, IntPtr userdata)
{ {
CurrentCairoContext = cairocontext; if (!Gtk3Platform.UseDeferredRendering)
Paint?.Invoke(new Rect(ClientSize)); {
CurrentCairoContext = IntPtr.Zero; 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; 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() public void Dispose()
{ {
//We are calling it here, since signal handler will be detached //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()) foreach(var d in Disposables.AsEnumerable().Reverse())
d.Dispose(); d.Dispose();
Disposables.Clear(); Disposables.Clear();
if (_gcHandle.IsAllocated)
{
_gcHandle.Free();
}
} }
public Size MaxClientSize public Size MaxClientSize
@ -270,7 +332,8 @@ namespace Avalonia.Gtk3
{ {
if(GtkWidget.IsClosed) if(GtkWidget.IsClosed)
return; 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; public void SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot;
@ -323,17 +386,7 @@ namespace Avalonia.Gtk3
} }
public Size ClientSize public Size ClientSize { get; private set; }
{
get
{
if (GtkWidget.IsClosed)
return new Size();
int w, h;
Native.GtkWindowGetSize(GtkWidget, out w, out h);
return new Size(w, h);
}
}
public void Resize(Size value) public void Resize(Size value)
{ {
@ -363,7 +416,10 @@ namespace Avalonia.Gtk3
public IRenderer CreateRenderer(IRenderRoot root) 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 System.Threading.Tasks;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Threading;
namespace Avalonia.LinuxFramebuffer namespace Avalonia.LinuxFramebuffer
{ {
@ -30,7 +31,7 @@ namespace Avalonia.LinuxFramebuffer
while (true) while (true)
{ {
if (0 == WaitHandle.WaitAny(handles)) if (0 == WaitHandle.WaitAny(handles))
Signaled?.Invoke(); Signaled?.Invoke(null);
else else
{ {
while (true) while (true)
@ -97,7 +98,7 @@ namespace Avalonia.LinuxFramebuffer
} }
public void Signal() public void Signal(DispatcherPriority prio)
{ {
_signaled.Set(); _signaled.Set();
} }
@ -105,7 +106,7 @@ namespace Avalonia.LinuxFramebuffer
[ThreadStatic] private static bool TlsCurrentThreadIsLoopThread; [ThreadStatic] private static bool TlsCurrentThreadIsLoopThread;
public bool CurrentThreadIsLoopThread => TlsCurrentThreadIsLoopThread; public bool CurrentThreadIsLoopThread => TlsCurrentThreadIsLoopThread;
public event Action Signaled; public event Action<DispatcherPriority?> Signaled;
public event EventHandler<EventArgs> Tick; public event EventHandler<EventArgs> Tick;
} }

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

@ -1,6 +1,7 @@
using System; using System;
using System.Threading; using System.Threading;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Threading;
using MonoMac.AppKit; using MonoMac.AppKit;
using MonoMac.CoreGraphics; using MonoMac.CoreGraphics;
using MonoMac.Foundation; using MonoMac.Foundation;
@ -13,12 +14,12 @@ namespace Avalonia.MonoMac
public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface(); public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface();
public bool CurrentThreadIsLoopThread => NSThread.Current.IsMainThread; public bool CurrentThreadIsLoopThread => NSThread.Current.IsMainThread;
public event Action Signaled; public event Action<DispatcherPriority?> Signaled;
public IDisposable StartTimer(TimeSpan interval, Action tick) public IDisposable StartTimer(TimeSpan interval, Action tick)
=> NSTimer.CreateRepeatingScheduledTimer(interval, () => tick()); => NSTimer.CreateRepeatingScheduledTimer(interval, () => tick());
public void Signal() public void Signal(DispatcherPriority prio)
{ {
lock (this) lock (this)
{ {
@ -34,7 +35,7 @@ namespace Avalonia.MonoMac
return; return;
_signaled = false; _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.Win32.Interop;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Threading;
#if NETSTANDARD #if NETSTANDARD
using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception; using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception;
#else #else
@ -137,7 +138,7 @@ namespace Avalonia.Win32
private static readonly int SignalW = unchecked((int) 0xdeadbeaf); private static readonly int SignalW = unchecked((int) 0xdeadbeaf);
private static readonly int SignalL = unchecked((int)0x12345678); private static readonly int SignalL = unchecked((int)0x12345678);
public void Signal() public void Signal(DispatcherPriority prio)
{ {
UnmanagedMethods.PostMessage( UnmanagedMethods.PostMessage(
_hwnd, _hwnd,
@ -148,14 +149,14 @@ namespace Avalonia.Win32
public bool CurrentThreadIsLoopThread => _uiThread == UnmanagedMethods.GetCurrentThreadId(); 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.")] [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")]
private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) 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) 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); 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 Avalonia.Win32.Interop;
using static Avalonia.Win32.Interop.UnmanagedMethods; using static Avalonia.Win32.Interop.UnmanagedMethods;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Threading;
#if NETSTANDARD #if NETSTANDARD
using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception; using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception;
#endif #endif

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

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

6
tests/Avalonia.RenderTests/TestBase.cs

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

Loading…
Cancel
Save