Browse Source

Use NDK instead of C#-JNI for choreographer

pull/19045/head
Max Katz 8 months ago
parent
commit
f33dca36a0
  1. 71
      src/Android/Avalonia.Android/ChoreographerTimer.cs
  2. 18
      src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs

71
src/Android/Avalonia.Android/ChoreographerTimer.cs

@ -1,39 +1,42 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Android.OS;
using Android.Views;
using Avalonia.Reactive;
using Avalonia.Rendering;
using ThreadPriority = System.Threading.ThreadPriority;
using static Avalonia.Android.Platform.SkiaPlatform.AndroidFramebuffer;
using Looper = Android.OS.Looper;
namespace Avalonia.Android
{
internal sealed class ChoreographerTimer : Java.Lang.Object, IRenderTimer, Choreographer.IFrameCallback
internal sealed class ChoreographerTimer : IRenderTimer
{
private static readonly bool s_supports64Callback = OperatingSystem.IsAndroidVersionAtLeast(10);
private readonly object _lock = new();
private AutoResetEvent _event = new(false);
private long _lastTime;
private readonly TaskCompletionSource<Choreographer> _choreographer = new();
private readonly ISet<AvaloniaView> _views = new HashSet<AvaloniaView>();
private readonly TaskCompletionSource<IntPtr> _choreographer = new();
private readonly AutoResetEvent _event = new(false);
private readonly GCHandle _timerHandle;
private readonly HashSet<AvaloniaView> _views = new();
private Action<TimeSpan>? _tick;
private long _lastTime;
private int _count;
public ChoreographerTimer()
{
_timerHandle = GCHandle.Alloc(this);
new Thread(Loop)
{
Priority = ThreadPriority.AboveNormal
Priority = ThreadPriority.AboveNormal,
Name = "Choreographer Thread"
}.Start();
new Thread(RenderLoop)
{
Priority = ThreadPriority.AboveNormal
Priority = ThreadPriority.AboveNormal,
Name = "Render Thread"
}.Start();
}
@ -50,7 +53,7 @@ namespace Avalonia.Android
if (_count == 1)
{
_choreographer.Task.Result.PostFrameCallback(this);
PostFrameCallback(_choreographer.Task.Result, GCHandle.ToIntPtr(_timerHandle));
}
}
}
@ -72,7 +75,7 @@ namespace Avalonia.Android
if (_views.Count == 1)
{
_choreographer.Task.Result.PostFrameCallback(this);
PostFrameCallback(_choreographer.Task.Result, GCHandle.ToIntPtr(_timerHandle));
}
}
@ -90,10 +93,10 @@ namespace Avalonia.Android
private void Loop()
{
Looper.Prepare();
_choreographer.SetResult(Choreographer.Instance!);
_choreographer.SetResult(AChoreographer_getInstance());
Looper.Loop();
}
private void RenderLoop()
{
while (true)
@ -108,18 +111,46 @@ namespace Avalonia.Android
}
}
public void DoFrame(long frameTimeNanos)
private void DoFrameCallback(long frameTimeNanos, IntPtr data)
{
lock (_lock)
{
if (_count > 0 && _views.Count > 0)
{
Choreographer.Instance!.PostFrameCallback(this);
PostFrameCallback(_choreographer.Task.Result, data);
}
_lastTime = frameTimeNanos;
_event.Set();
}
}
private static unsafe void PostFrameCallback(IntPtr choreographer, IntPtr data)
{
// AChoreographer_postFrameCallback is deprecated on 10.0+.
if (s_supports64Callback)
{
AChoreographer_postFrameCallback64(choreographer, &FrameCallback64, data);
}
else
{
AChoreographer_postFrameCallback(choreographer, &FrameCallback, data);
}
return;
[UnmanagedCallersOnly]
static void FrameCallback(int frameTimeNanos, IntPtr data)
{
var timer = (ChoreographerTimer)GCHandle.FromIntPtr(data).Target!;
timer.DoFrameCallback(frameTimeNanos, data);
}
[UnmanagedCallersOnly]
static void FrameCallback64(long frameTimeNanos, IntPtr data)
{
var timer = (ChoreographerTimer)GCHandle.FromIntPtr(data).Target!;
timer.DoFrameCallback(frameTimeNanos, data);
}
}
}
}

18
src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs

@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using Android.Runtime;
using Android.Views;
using Avalonia.Platform;
@ -60,6 +61,19 @@ namespace Avalonia.Android.Platform.SkiaPlatform
[DllImport("android")]
internal static extern void ANativeWindow_unlockAndPost(IntPtr window);
[DllImport("android")]
internal static extern IntPtr AChoreographer_getInstance();
[DllImport("android")]
[UnsupportedOSPlatform("android10.0")]
internal static extern void AChoreographer_postFrameCallback(
IntPtr choreographer, delegate* unmanaged<int, IntPtr, void> callback, IntPtr data);
[DllImport("android")]
[SupportedOSPlatform("android10.0")]
internal static extern void AChoreographer_postFrameCallback64(
IntPtr choreographer, delegate* unmanaged<long, IntPtr, void> callback, IntPtr data);
[DllImport("android")]
internal static extern int ANativeWindow_lock(IntPtr window, ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds);
public enum AndroidPixelFormat
@ -69,6 +83,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
WINDOW_FORMAT_RGB_565 = 4,
}
[StructLayout(LayoutKind.Sequential)]
internal struct ARect
{
public int left;
@ -76,7 +91,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public int right;
public int bottom;
}
[StructLayout(LayoutKind.Sequential)]
internal struct ANativeWindow_Buffer
{
// The number of pixels that are shown horizontally.

Loading…
Cancel
Save