From 88dd17083add2b1134334ad8a16ca5f5d3f20702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Wed, 10 Feb 2021 01:49:23 +0000 Subject: [PATCH 1/3] Use deferred renderer on Android. --- .../Avalonia.Android/ActivityTracker.cs | 47 ------------ .../Avalonia.Android/AndroidPlatform.cs | 8 +- .../Avalonia.Android/AvaloniaActivity.cs | 3 +- src/Android/Avalonia.Android/AvaloniaView.cs | 27 ++++++- .../Avalonia.Android/ChoreographerTimer.cs | 74 +++++++++++++++++++ .../OpenGL/GlPlatformSurface.cs | 6 +- .../Avalonia.Android/OpenGL/GlRenderTarget.cs | 13 +++- .../Platform/SkiaPlatform/TopLevelImpl.cs | 8 +- 8 files changed, 122 insertions(+), 64 deletions(-) delete mode 100644 src/Android/Avalonia.Android/ActivityTracker.cs create mode 100644 src/Android/Avalonia.Android/ChoreographerTimer.cs diff --git a/src/Android/Avalonia.Android/ActivityTracker.cs b/src/Android/Avalonia.Android/ActivityTracker.cs deleted file mode 100644 index 2ad1d9e361..0000000000 --- a/src/Android/Avalonia.Android/ActivityTracker.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Android.App; -using Android.OS; - -namespace Avalonia.Android -{ - internal class ActivityTracker : Java.Lang.Object, global::Android.App.Application.IActivityLifecycleCallbacks - { - public static Activity Current { get; private set; } - public void OnActivityCreated(Activity activity, Bundle savedInstanceState) - { - Current = activity; - } - - public void OnActivityDestroyed(Activity activity) - { - if (Current == activity) - Current = null; - } - - public void OnActivityPaused(Activity activity) - { - if (Current == activity) - Current = null; - } - - public void OnActivityResumed(Activity activity) - { - Current = activity; - } - - public void OnActivitySaveInstanceState(Activity activity, Bundle outState) - { - Current = activity; - } - - public void OnActivityStarted(Activity activity) - { - Current = activity; - } - - public void OnActivityStopped(Activity activity) - { - if (Current == activity) - Current = null; - } - } -} \ No newline at end of file diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index e0ceb0c8b7..043af6a8df 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -32,6 +32,7 @@ namespace Avalonia.Android class AndroidPlatform : IPlatformSettings, IWindowingPlatform { public static readonly AndroidPlatform Instance = new AndroidPlatform(); + public static AndroidPlatformOptions Options { get; private set; } public Size DoubleClickSize => new Size(4, 4); public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(200); public double RenderScalingFactor => _scalingFactor; @@ -46,6 +47,8 @@ namespace Avalonia.Android public static void Initialize(Type appType, AndroidPlatformOptions options) { + Options = options; + AvaloniaLocator.CurrentMutable .Bind().ToTransient() .Bind().ToTransient() @@ -55,14 +58,12 @@ namespace Avalonia.Android .Bind().ToTransient() .Bind().ToConstant(Instance) .Bind().ToSingleton() - .Bind().ToConstant(new DefaultRenderTimer(60)) + .Bind().ToConstant(new ChoreographerTimer()) .Bind().ToConstant(new RenderLoop()) .Bind().ToSingleton() .Bind().ToConstant(new AssetLoader(appType.Assembly)); SkiaPlatform.Initialize(); - ((global::Android.App.Application) global::Android.App.Application.Context.ApplicationContext) - .RegisterActivityLifecycleCallbacks(new ActivityTracker()); if (options.UseGpu) { @@ -83,6 +84,7 @@ namespace Avalonia.Android public sealed class AndroidPlatformOptions { + public bool UseDeferredRendering { get; set; } = true; public bool UseGpu { get; set; } = true; } } diff --git a/src/Android/Avalonia.Android/AvaloniaActivity.cs b/src/Android/Avalonia.Android/AvaloniaActivity.cs index d3696aa41d..52b68f8e2f 100644 --- a/src/Android/Avalonia.Android/AvaloniaActivity.cs +++ b/src/Android/Avalonia.Android/AvaloniaActivity.cs @@ -13,9 +13,8 @@ namespace Avalonia.Android protected override void OnCreate(Bundle savedInstanceState) { - RequestWindowFeature(WindowFeatures.NoTitle); View = new AvaloniaView(this); - if(_content != null) + if (_content != null) View.Content = _content; SetContentView(View); TakeKeyEvents(true); diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs index 72732a1f95..a60a17d08e 100644 --- a/src/Android/Avalonia.Android/AvaloniaView.cs +++ b/src/Android/Avalonia.Android/AvaloniaView.cs @@ -1,11 +1,12 @@ using System; using Android.Content; +using Android.Runtime; using Android.Views; using Android.Widget; using Avalonia.Android.Platform.SkiaPlatform; using Avalonia.Controls; using Avalonia.Controls.Embedding; -using Avalonia.Platform; +using Avalonia.Rendering; namespace Avalonia.Android { @@ -33,6 +34,30 @@ namespace Avalonia.Android return _view.View.DispatchKeyEvent(e); } + public override void OnVisibilityAggregated(bool isVisible) + { + base.OnVisibilityAggregated(isVisible); + OnVisibilityChanged(isVisible); + } + + protected override void OnVisibilityChanged(View changedView, [GeneratedEnum] ViewStates visibility) + { + base.OnVisibilityChanged(changedView, visibility); + OnVisibilityChanged(visibility == ViewStates.Visible); + } + + private void OnVisibilityChanged(bool isVisible) + { + if (isVisible) + { + _root.Renderer.Start(); + } + else + { + _root.Renderer.Stop(); + } + } + class ViewImpl : TopLevelImpl { public ViewImpl(Context context) : base(context) diff --git a/src/Android/Avalonia.Android/ChoreographerTimer.cs b/src/Android/Avalonia.Android/ChoreographerTimer.cs new file mode 100644 index 0000000000..12961fec83 --- /dev/null +++ b/src/Android/Avalonia.Android/ChoreographerTimer.cs @@ -0,0 +1,74 @@ +using System; +using System.Threading.Tasks; + +using Android.OS; +using Android.Views; + +using Avalonia.Rendering; + +using Java.Lang; + +namespace Avalonia.Android +{ + internal sealed class ChoreographerTimer : Java.Lang.Object, IRenderTimer, Choreographer.IFrameCallback + { + private readonly object _lock = new object(); + + private readonly Thread _thread; + private readonly TaskCompletionSource _choreographer = new TaskCompletionSource(); + + private Action _tick; + private int _count; + + public ChoreographerTimer() + { + _thread = new Thread(Loop); + _thread.Start(); + } + + public event Action Tick + { + add + { + lock (_lock) + { + _tick += value; + _count++; + + if (_count == 1) + { + _choreographer.Task.Result.PostFrameCallback(this); + } + } + } + remove + { + lock (_lock) + { + _tick -= value; + _count--; + } + } + } + + private void Loop() + { + Looper.Prepare(); + _choreographer.SetResult(Choreographer.Instance); + Looper.Loop(); + } + + public void DoFrame(long frameTimeNanos) + { + _tick?.Invoke(TimeSpan.FromTicks(frameTimeNanos / 100)); + + lock (_lock) + { + if (_count > 0) + { + Choreographer.Instance.PostFrameCallback(this); + } + } + } + } +} diff --git a/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs b/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs index 4f4c03fe77..a9710039f8 100644 --- a/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs +++ b/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs @@ -1,6 +1,4 @@ -using System.Linq; - -using Avalonia.OpenGL.Egl; +using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Surfaces; namespace Avalonia.Android.OpenGL @@ -17,7 +15,7 @@ namespace Avalonia.Android.OpenGL } public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() => - new GlRenderTarget(_egl, _info, _egl.CreateWindowSurface(_info.Handle)); + new GlRenderTarget(_egl, _info, _egl.CreateWindowSurface(_info.Handle), _info.Handle); public static GlPlatformSurface TryCreate(IEglWindowGlPlatformSurfaceInfo info) { diff --git a/src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs b/src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs index 75bbd15e3e..f9071d9b27 100644 --- a/src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs +++ b/src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs @@ -1,23 +1,30 @@ -using Avalonia.OpenGL.Egl; +using System; + +using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Surfaces; namespace Avalonia.Android.OpenGL { - internal sealed class GlRenderTarget : EglPlatformSurfaceRenderTargetBase + internal sealed class GlRenderTarget : EglPlatformSurfaceRenderTargetBase, IGlPlatformSurfaceRenderTargetWithCorruptionInfo { private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info; private readonly EglSurface _surface; + private readonly IntPtr _handle; public GlRenderTarget( EglPlatformOpenGlInterface egl, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info, - EglSurface surface) + EglSurface surface, + IntPtr handle) : base(egl) { _info = info; _surface = surface; + _handle = handle; } + public bool IsCorrupted => _handle != _info.Handle; + public override IGlPlatformSurfaceRenderingSession BeginDraw() => BeginDraw(_surface, _info); } } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index a8c7f7af9b..a71b574198 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -98,10 +98,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform public IEnumerable Surfaces => new object[] { _gl, _framebuffer }; - public IRenderer CreateRenderer(IRenderRoot root) - { - return new ImmediateRenderer(root); - } + public IRenderer CreateRenderer(IRenderRoot root) => + AndroidPlatform.Options.UseDeferredRendering + ? new DeferredRenderer(root, AvaloniaLocator.Current.GetService()) { RenderOnlyOnRenderThread = true } + : new ImmediateRenderer(root); public virtual void Hide() { From b9a2f76cf0bcbb49e9bab5a50f00de8789c2e9e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Wed, 10 Feb 2021 23:58:51 +0000 Subject: [PATCH 2/3] Added timer subscriptions for views. --- src/Android/Avalonia.Android/AvaloniaView.cs | 8 +++++ .../Avalonia.Android/ChoreographerTimer.cs | 29 ++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs index a60a17d08e..8de3657283 100644 --- a/src/Android/Avalonia.Android/AvaloniaView.cs +++ b/src/Android/Avalonia.Android/AvaloniaView.cs @@ -15,6 +15,8 @@ namespace Avalonia.Android private readonly EmbeddableControlRoot _root; private readonly ViewImpl _view; + private IDisposable? _timerSubscription; + public AvaloniaView(Context context) : base(context) { _view = new ViewImpl(context); @@ -50,11 +52,17 @@ namespace Avalonia.Android { if (isVisible) { + if (AvaloniaLocator.Current.GetService() is ChoreographerTimer timer) + { + _timerSubscription = timer.SubscribeView(this); + } + _root.Renderer.Start(); } else { _root.Renderer.Stop(); + _timerSubscription?.Dispose(); } } diff --git a/src/Android/Avalonia.Android/ChoreographerTimer.cs b/src/Android/Avalonia.Android/ChoreographerTimer.cs index 12961fec83..1d898261a3 100644 --- a/src/Android/Avalonia.Android/ChoreographerTimer.cs +++ b/src/Android/Avalonia.Android/ChoreographerTimer.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Reactive.Disposables; using System.Threading.Tasks; using Android.OS; @@ -17,6 +19,8 @@ namespace Avalonia.Android private readonly Thread _thread; private readonly TaskCompletionSource _choreographer = new TaskCompletionSource(); + private readonly ISet _views = new HashSet(); + private Action _tick; private int _count; @@ -51,6 +55,29 @@ namespace Avalonia.Android } } + internal IDisposable SubscribeView(AvaloniaView view) + { + lock (_lock) + { + _views.Add(view); + + if (_views.Count == 1) + { + _choreographer.Task.Result.PostFrameCallback(this); + } + } + + return Disposable.Create( + () => + { + lock (_lock) + { + _views.Remove(view); + } + } + ); + } + private void Loop() { Looper.Prepare(); @@ -64,7 +91,7 @@ namespace Avalonia.Android lock (_lock) { - if (_count > 0) + if (_count > 0 && _views.Count > 0) { Choreographer.Instance.PostFrameCallback(this); } From e1bfe10f434e61fc4eaa36b8b2ed39c7cf6a0464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Thu, 11 Feb 2021 00:02:10 +0000 Subject: [PATCH 3/3] Fixed CursorFactory. --- src/Android/Avalonia.Android/AndroidPlatform.cs | 2 +- src/Android/Avalonia.Android/CursorFactory.cs | 17 +++++++++++++---- .../Platform/SkiaPlatform/TopLevelImpl.cs | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 043af6a8df..e0ed9b1fda 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -51,7 +51,7 @@ namespace Avalonia.Android AvaloniaLocator.CurrentMutable .Bind().ToTransient() - .Bind().ToTransient() + .Bind().ToTransient() .Bind().ToSingleton() .Bind().ToConstant(Instance) .Bind().ToConstant(new AndroidThreadingInterface()) diff --git a/src/Android/Avalonia.Android/CursorFactory.cs b/src/Android/Avalonia.Android/CursorFactory.cs index 9eb28c67f9..6293637d4e 100644 --- a/src/Android/Avalonia.Android/CursorFactory.cs +++ b/src/Android/Avalonia.Android/CursorFactory.cs @@ -1,12 +1,21 @@ -using System; using Avalonia.Input; using Avalonia.Platform; namespace Avalonia.Android { - internal class CursorFactory : IStandardCursorFactory + internal class CursorFactory : ICursorFactory { - public IPlatformHandle GetCursor(StandardCursorType cursorType) - => new PlatformHandle(IntPtr.Zero, "ZeroCursor"); + public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => CursorImpl.ZeroCursor; + + public ICursorImpl GetCursor(StandardCursorType cursorType) => CursorImpl.ZeroCursor; + + private sealed class CursorImpl : ICursorImpl + { + public static CursorImpl ZeroCursor { get; } = new CursorImpl(); + + private CursorImpl() { } + + public void Dispose() { } + } } } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index a71b574198..10bf414f25 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -123,7 +123,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform return PixelPoint.FromPoint(point, 1); } - public void SetCursor(IPlatformHandle cursor) + public void SetCursor(ICursorImpl cursor) { //still not implemented }