Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use deferred renderer on Android #5337

Merged
merged 5 commits into from
Mar 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 0 additions & 47 deletions src/Android/Avalonia.Android/ActivityTracker.cs

This file was deleted.

10 changes: 6 additions & 4 deletions src/Android/Avalonia.Android/AndroidPlatform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -46,23 +47,23 @@ public AndroidPlatform()

public static void Initialize(Type appType, AndroidPlatformOptions options)
{
Options = options;

AvaloniaLocator.CurrentMutable
.Bind<IClipboard>().ToTransient<ClipboardImpl>()
.Bind<IStandardCursorFactory>().ToTransient<CursorFactory>()
.Bind<ICursorFactory>().ToTransient<CursorFactory>()
.Bind<IKeyboardDevice>().ToSingleton<AndroidKeyboardDevice>()
.Bind<IPlatformSettings>().ToConstant(Instance)
.Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface())
.Bind<ISystemDialogImpl>().ToTransient<SystemDialogImpl>()
.Bind<IWindowingPlatform>().ToConstant(Instance)
.Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoader>()
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<IRenderTimer>().ToConstant(new ChoreographerTimer())
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
.Bind<IAssetLoader>().ToConstant(new AssetLoader(appType.Assembly));

SkiaPlatform.Initialize();
((global::Android.App.Application) global::Android.App.Application.Context.ApplicationContext)
.RegisterActivityLifecycleCallbacks(new ActivityTracker());

if (options.UseGpu)
{
Expand All @@ -83,6 +84,7 @@ public IWindowImpl CreateEmbeddableWindow()

public sealed class AndroidPlatformOptions
{
public bool UseDeferredRendering { get; set; } = true;
public bool UseGpu { get; set; } = true;
}
}
3 changes: 1 addition & 2 deletions src/Android/Avalonia.Android/AvaloniaActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ public abstract class AvaloniaActivity : Activity

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);
Expand Down
35 changes: 34 additions & 1 deletion src/Android/Avalonia.Android/AvaloniaView.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -14,6 +15,8 @@ public class AvaloniaView : FrameLayout
private readonly EmbeddableControlRoot _root;
private readonly ViewImpl _view;

private IDisposable? _timerSubscription;

public AvaloniaView(Context context) : base(context)
{
_view = new ViewImpl(context);
Expand All @@ -33,6 +36,36 @@ public override bool DispatchKeyEvent(KeyEvent e)
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)
{
if (AvaloniaLocator.Current.GetService<IRenderTimer>() is ChoreographerTimer timer)
{
_timerSubscription = timer.SubscribeView(this);
}

_root.Renderer.Start();
}
else
{
_root.Renderer.Stop();
_timerSubscription?.Dispose();
}
}

class ViewImpl : TopLevelImpl
{
public ViewImpl(Context context) : base(context)
Expand Down
101 changes: 101 additions & 0 deletions src/Android/Avalonia.Android/ChoreographerTimer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
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> _choreographer = new TaskCompletionSource<Choreographer>();

private readonly ISet<AvaloniaView> _views = new HashSet<AvaloniaView>();

private Action<TimeSpan> _tick;
private int _count;

public ChoreographerTimer()
{
_thread = new Thread(Loop);
_thread.Start();
}

public event Action<TimeSpan> Tick
{
add
{
lock (_lock)
{
_tick += value;
_count++;

if (_count == 1)
{
_choreographer.Task.Result.PostFrameCallback(this);
}
}
}
remove
{
lock (_lock)
{
_tick -= value;
_count--;
}
}
}

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();
_choreographer.SetResult(Choreographer.Instance);
Looper.Loop();
}

public void DoFrame(long frameTimeNanos)
{
_tick?.Invoke(TimeSpan.FromTicks(frameTimeNanos / 100));

lock (_lock)
{
if (_count > 0 && _views.Count > 0)
{
Choreographer.Instance.PostFrameCallback(this);
}
}
}
}
}
17 changes: 13 additions & 4 deletions src/Android/Avalonia.Android/CursorFactory.cs
Original file line number Diff line number Diff line change
@@ -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() { }
}
}
}
6 changes: 2 additions & 4 deletions src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Linq;

using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;

namespace Avalonia.Android.OpenGL
Expand All @@ -17,7 +15,7 @@ private GlPlatformSurface(EglPlatformOpenGlInterface egl, IEglWindowGlPlatformSu
}

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)
{
Expand Down
13 changes: 10 additions & 3 deletions src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,10 @@ public virtual Size ClientSize

public IEnumerable<object> 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<IRenderLoop>()) { RenderOnlyOnRenderThread = true }
: new ImmediateRenderer(root);

public virtual void Hide()
{
Expand All @@ -123,7 +123,7 @@ public PixelPoint PointToScreen(Point point)
return PixelPoint.FromPoint(point, 1);
}

public void SetCursor(IPlatformHandle cursor)
public void SetCursor(ICursorImpl cursor)
{
//still not implemented
}
Expand Down