Skip to content

Commit

Permalink
feat(animations): Add compositor thread on Android
Browse files Browse the repository at this point in the history
  • Loading branch information
dr1rrb committed Mar 26, 2021
1 parent 1ecf441 commit 077511b
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 1 deletion.
19 changes: 19 additions & 0 deletions src/Uno.UI/FeatureConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -531,5 +531,24 @@ public static class AppBarButton
public static bool EnableBitmapIconTint { get; set; } = false;
#endif
}

public static class Composition
{
/// <summary>
///
/// </summary>
/// <remarks>This is captured on window creation, this means that you can </remarks>
public static Options Configuration { get; set; } = Options.Disabled;

[Flags]
public enum Options
{
Disabled = 0,

UseCompositorThread = 0x1,

Enabled = UseCompositorThread,
}
}
}
}
14 changes: 13 additions & 1 deletion src/Uno.UI/UI/Xaml/ApplicationActivity.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
namespace Windows.UI.Xaml
{
[Activity(ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode, WindowSoftInputMode = SoftInput.AdjustPan | SoftInput.StateHidden)]
public class ApplicationActivity : Controls.NativePage
public class ApplicationActivity : Controls.NativePage, Uno.UI.Composition.ICompositionRoot
{

/// The windows model implies only one managed activity.
Expand All @@ -28,6 +28,8 @@ public class ApplicationActivity : Controls.NativePage
internal LayoutProvider LayoutProvider { get; private set; }

private InputPane _inputPane;
private View _content;
private Android.Views.Window _window;

public ApplicationActivity(IntPtr ptr, Android.Runtime.JniHandleOwnership owner) : base(ptr, owner)
{
Expand All @@ -48,6 +50,9 @@ private void Initialize()
_inputPane.Hiding += OnInputPaneVisibilityChanged;
}

View Uno.UI.Composition.ICompositionRoot.Content => _content;
Android.Views.Window Uno.UI.Composition.ICompositionRoot.Window => _window ??= base.Window;

public override void OnAttachedToWindow()
{
base.OnAttachedToWindow();
Expand Down Expand Up @@ -117,6 +122,11 @@ private void OnLayoutChanged(Rect statusBar, Rect keyboard, Rect navigationBar)

protected override void OnCreate(Bundle bundle)
{
if (FeatureConfiguration.Composition.Configuration.HasFlag(FeatureConfiguration.Composition.Options.UseCompositorThread))
{
Uno.UI.Composition.CompositorThread.Start(this);
}

base.OnCreate(bundle);

LayoutProvider = new LayoutProvider(this);
Expand All @@ -138,6 +148,8 @@ private void OnInsetsChanged(Thickness insets)

public override void SetContentView(View view)
{
_content = view;

if (view != null)
{
if (view.IsAttachedToWindow)
Expand Down
186 changes: 186 additions & 0 deletions src/Uno.UWP/UI/Composition/Uno/CompositorThread.android.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Windows.Foundation;
using Android.App;
using Android.Graphics;
using Android.OS;
using Android.Views;
using Uno.Extensions;
using Uno.Logging;
using Exception = System.Exception;
using Point = Windows.Foundation.Point;
using Rect = Windows.Foundation.Rect;
using ThreadPriority = System.Threading.ThreadPriority;

namespace Uno.UI.Composition
{
internal interface ICompositionRoot
{
Window Window { get; }

View Content { get; }
}

internal class CompositorThread : Java.Lang.Object, ISurfaceHolderCallback2, Choreographer.IFrameCallback
{
[ThreadStatic] // Static to the related UI Thread !!!
private static CompositorThread? _current;

public static void Start(ICompositionRoot activity)
=> _current ??= new CompositorThread(activity);

//private RenderNode _animationsNode;
//private List<(RenderNode node, double x, double y)> _animatedNodes = new List<(RenderNode node, double x, double y)>();

private readonly HardwareRenderer _hwRender = new HardwareRenderer();
private readonly RenderNode _visualTree = new RenderNode("visual-tree");
private readonly ManualResetEventSlim _frameRendered = new ManualResetEventSlim(false);
private readonly ICompositionRoot _activity;

private Thread? _thread;

public CompositorThread(ICompositionRoot activity)
{
_activity = activity;

_hwRender.SetContentRoot(_visualTree);
_activity.Window!.TakeSurface(this);
}

void ISurfaceHolderCallback.SurfaceCreated(ISurfaceHolder? holder)
{
_hwRender.SetSurface(holder!.Surface);

Start();
}

void ISurfaceHolderCallback.SurfaceChanged(ISurfaceHolder holder, Format format, int width, int height)
{
_visualTree.SetPosition(new Rect(new Point(), new Size(width, height)));
}

void ISurfaceHolderCallback.SurfaceDestroyed(ISurfaceHolder? holder)
{
Stop();

_hwRender?.Stop();
_hwRender?.Destroy(); // Note: This only release the surface, we can restore it by setting a new surface
}

void ISurfaceHolderCallback2.SurfaceRedrawNeeded(ISurfaceHolder? holder)
{
_frameRendered.Reset();
_frameRendered.Wait();
}

void ISurfaceHolderCallback2.SurfaceRedrawNeededAsync(ISurfaceHolder holder, Java.Lang.IRunnable drawingFinished)
{
_frameRendered.Reset();

Task.Run(() => // TODO: Should we invoke drawingFinished on calling / UI thread?
{
try
{
_frameRendered.Wait();
drawingFinished.Run();
}
catch (Exception e)
{
this.Log().Error("Async redraw failed.", e);
}
});
}

private void Start()
{
if (_thread is {})
{
this.Log().Warn("Try to start an already running compositor thread.");
return;
}

_thread = new Thread(() =>
{
_hwRender.Start();
Looper.Prepare(); // Required to get a Choreographer.Instance
Choreographer.Instance!.PostFrameCallback(this);
Looper.Loop(); // Start the looper so the Choreographer.Instance will invoke our callback
})
{
Name = "Compositor",
Priority = ThreadPriority.AboveNormal
};

_thread.Start();
}

private void Stop()
{
try
{
_thread?.Abort();
}
catch (Exception e)
{
this.Log().Warn("Failed to abort compositor thread.", e);
}
_thread = null;
}

void Choreographer.IFrameCallback.DoFrame(long frameTimeNanos)
{
RenderFrame(frameTimeNanos);

Choreographer.Instance!.PostFrameCallback(this);
}

private void RenderFrame(long frameTimeNanos)
{
RecordingCanvas canvas;
try
{
canvas = _visualTree.BeginRecording();
}
catch (Java.Lang.IllegalStateException)
{
// Cannot start recording (most probably recording is already running somehow).
return;
}

try
{
_activity.Content.Draw(canvas);
}
catch (Exception e)
{
this.Log().Error("Failed to render frame.", e);
}
finally
{
_visualTree.EndRecording();

var request = _hwRender.CreateRenderRequest();
var needsSync = !_frameRendered.IsSet;

if (needsSync)
{
request.SetWaitForPresent(true);
}

request.SyncAndDraw();

if (needsSync)
{
_frameRendered.Set();
}
}
}
}
}

0 comments on commit 077511b

Please sign in to comment.