From 3b9422caa505d7358d1f7a9292bd7c79cf971e45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Fri, 22 Jan 2021 09:36:06 +0000 Subject: [PATCH] Added OpenGL support on Android. --- .../Avalonia.Android/AndroidPlatform.cs | 20 ++++++++++-- .../OpenGL/GlPlatformSurface.cs | 32 +++++++++++++++++++ .../Avalonia.Android/OpenGL/GlRenderTarget.cs | 23 +++++++++++++ .../SkiaPlatform/FramebufferManager.cs | 17 ++++++++++ .../Platform/SkiaPlatform/TopLevelImpl.cs | 30 +++++++++++++---- src/Avalonia.OpenGL/Egl/EglInterface.cs | 4 ++- src/Shared/PlatformSupport/DynLoader.cs | 14 +++++++- 7 files changed, 128 insertions(+), 12 deletions(-) create mode 100644 src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs create mode 100644 src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs create mode 100644 src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 2e6f4a67c393..2fd9c93e04ee 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -1,11 +1,13 @@ using System; + +using Avalonia.Android; using Avalonia.Android.Platform; using Avalonia.Android.Platform.Input; -using Avalonia.Android.Platform.SkiaPlatform; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Platform; +using Avalonia.OpenGL.Egl; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Shared.PlatformSupport; @@ -17,7 +19,8 @@ public static class AndroidApplicationExtensions { public static T UseAndroid(this T builder) where T : AppBuilderBase, new() { - builder.UseWindowingSubsystem(() => Android.AndroidPlatform.Initialize(builder.ApplicationType), "Android"); + var options = AvaloniaLocator.Current.GetService() ?? new AndroidPlatformOptions(); + builder.UseWindowingSubsystem(() => AndroidPlatform.Initialize(builder.ApplicationType, options), "Android"); builder.UseSkia(); return builder; } @@ -29,6 +32,7 @@ namespace Avalonia.Android class AndroidPlatform : IPlatformSettings, IWindowingPlatform { public static readonly AndroidPlatform Instance = new AndroidPlatform(); + public static bool UseGpu { get; set; } = true; public Size DoubleClickSize => new Size(4, 4); public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(200); public double RenderScalingFactor => _scalingFactor; @@ -41,7 +45,7 @@ public AndroidPlatform() _scalingFactor = global::Android.App.Application.Context.Resources.DisplayMetrics.ScaledDensity; } - public static void Initialize(Type appType) + public static void Initialize(Type appType, AndroidPlatformOptions options) { AvaloniaLocator.CurrentMutable .Bind().ToTransient() @@ -60,6 +64,11 @@ public static void Initialize(Type appType) SkiaPlatform.Initialize(); ((global::Android.App.Application) global::Android.App.Application.Context.ApplicationContext) .RegisterActivityLifecycleCallbacks(new ActivityTracker()); + + if (options.UseGpu) + { + EglPlatformOpenGlInterface.TryInitialize(); + } } public IWindowImpl CreateWindow() @@ -72,4 +81,9 @@ public IWindowImpl CreateEmbeddableWindow() throw new NotSupportedException(); } } + + public sealed class AndroidPlatformOptions + { + public bool UseGpu { get; set; } = true; + } } diff --git a/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs b/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs new file mode 100644 index 000000000000..4f4c03fe779c --- /dev/null +++ b/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs @@ -0,0 +1,32 @@ +using System.Linq; + +using Avalonia.OpenGL.Egl; +using Avalonia.OpenGL.Surfaces; + +namespace Avalonia.Android.OpenGL +{ + internal sealed class GlPlatformSurface : EglGlPlatformSurfaceBase + { + private readonly EglPlatformOpenGlInterface _egl; + private readonly IEglWindowGlPlatformSurfaceInfo _info; + + private GlPlatformSurface(EglPlatformOpenGlInterface egl, IEglWindowGlPlatformSurfaceInfo info) + { + _egl = egl; + _info = info; + } + + public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() => + new GlRenderTarget(_egl, _info, _egl.CreateWindowSurface(_info.Handle)); + + public static GlPlatformSurface TryCreate(IEglWindowGlPlatformSurfaceInfo info) + { + if (EglPlatformOpenGlInterface.TryCreate() is EglPlatformOpenGlInterface egl) + { + return new GlPlatformSurface(egl, info); + } + + return null; + } + } +} diff --git a/src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs b/src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs new file mode 100644 index 000000000000..75bbd15e3e19 --- /dev/null +++ b/src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs @@ -0,0 +1,23 @@ +using Avalonia.OpenGL.Egl; +using Avalonia.OpenGL.Surfaces; + +namespace Avalonia.Android.OpenGL +{ + internal sealed class GlRenderTarget : EglPlatformSurfaceRenderTargetBase + { + private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info; + private readonly EglSurface _surface; + + public GlRenderTarget( + EglPlatformOpenGlInterface egl, + EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info, + EglSurface surface) + : base(egl) + { + _info = info; + _surface = surface; + } + + public override IGlPlatformSurfaceRenderingSession BeginDraw() => BeginDraw(_surface, _info); + } +} diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs new file mode 100644 index 000000000000..18c4796fae31 --- /dev/null +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs @@ -0,0 +1,17 @@ +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Platform; + +namespace Avalonia.Android.Platform.SkiaPlatform +{ + internal sealed class FramebufferManager : IFramebufferPlatformSurface + { + private readonly TopLevelImpl _topLevel; + + public FramebufferManager(TopLevelImpl topLevel) + { + _topLevel = topLevel; + } + + public ILockedFramebuffer Lock() => new AndroidFramebuffer(_topLevel.InternalView.Holder.Surface); + } +} diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 360e76b2dc7a..a8c7f7af9be1 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -2,7 +2,10 @@ using System.Collections.Generic; using Android.Content; using Android.Graphics; +using Android.Runtime; using Android.Views; + +using Avalonia.Android.OpenGL; using Avalonia.Android.Platform.Input; using Avalonia.Android.Platform.Specific; using Avalonia.Android.Platform.Specific.Helpers; @@ -10,13 +13,18 @@ using Avalonia.Controls.Platform.Surfaces; using Avalonia.Input; using Avalonia.Input.Raw; +using Avalonia.OpenGL.Egl; +using Avalonia.OpenGL.Surfaces; using Avalonia.Platform; using Avalonia.Rendering; namespace Avalonia.Android.Platform.SkiaPlatform { - class TopLevelImpl : IAndroidView, ITopLevelImpl, IFramebufferPlatformSurface + class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo { + private readonly IGlPlatformSurface _gl; + private readonly IFramebufferPlatformSurface _framebuffer; + private readonly AndroidKeyboardEventsHelper _keyboardHelper; private readonly AndroidTouchEventsHelper _touchHelper; @@ -29,7 +37,8 @@ public TopLevelImpl(Context context, bool placeOnTop = false) _touchHelper = new AndroidTouchEventsHelper(this, () => InputRoot, p => GetAvaloniaPointFromEvent(p)); - Surfaces = new object[] { this }; + _gl = GlPlatformSurface.TryCreate(this); + _framebuffer = new FramebufferManager(this); MaxClientSize = new Size(_view.Resources.DisplayMetrics.WidthPixels, _view.Resources.DisplayMetrics.HeightPixels); @@ -48,7 +57,7 @@ public bool HandleEvents _keyboardHelper.HandleEvents = _handleEvents; } } - + public virtual Point GetAvaloniaPointFromEvent(MotionEvent e) => new Point(e.GetX(), e.GetY()); public IInputRoot InputRoot { get; private set; } @@ -63,7 +72,7 @@ public virtual Size ClientSize } set { - + } } @@ -83,9 +92,11 @@ public virtual Size ClientSize public View View => _view; + internal InvalidationAwareSurfaceView InternalView => _view; + public IPlatformHandle Handle => _view; - public IEnumerable Surfaces { get; } + public IEnumerable Surfaces => new object[] { _gl, _framebuffer }; public IRenderer CreateRenderer(IRenderRoot root) { @@ -96,7 +107,7 @@ public virtual void Hide() { _view.Visibility = ViewStates.Invisible; } - + public void Invalidate(Rect rect) { if (_view.Holder?.Surface?.IsValid == true) _view.Invalidate(); @@ -203,7 +214,12 @@ void ISurfaceHolderCallback.SurfaceChanged(ISurfaceHolder holder, Format format, public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1); - ILockedFramebuffer IFramebufferPlatformSurface.Lock() => new AndroidFramebuffer(_view.Holder.Surface); + IntPtr EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo.Handle => + AndroidFramebuffer.ANativeWindow_fromSurface(JNIEnv.Handle, _view.Holder.Surface.Handle); + + public PixelSize Size => new PixelSize(_view.Holder.SurfaceFrame.Width(), _view.Holder.SurfaceFrame.Height()); + + public double Scaling => RenderScaling; public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { diff --git a/src/Avalonia.OpenGL/Egl/EglInterface.cs b/src/Avalonia.OpenGL/Egl/EglInterface.cs index 80552260428e..cadd7cc1f22a 100644 --- a/src/Avalonia.OpenGL/Egl/EglInterface.cs +++ b/src/Avalonia.OpenGL/Egl/EglInterface.cs @@ -30,8 +30,10 @@ public EglInterface(string library) : base(Load(library)) static Func Load() { var os = AvaloniaLocator.Current.GetService().GetRuntimeInfo().OperatingSystem; - if(os == OperatingSystemType.Linux || os == OperatingSystemType.Android) + if(os == OperatingSystemType.Linux) return Load("libEGL.so.1"); + if (os == OperatingSystemType.Android) + return Load("libEGL.so"); throw new PlatformNotSupportedException(); } diff --git a/src/Shared/PlatformSupport/DynLoader.cs b/src/Shared/PlatformSupport/DynLoader.cs index 5884558baaa7..8fd4b1ad1e87 100644 --- a/src/Shared/PlatformSupport/DynLoader.cs +++ b/src/Shared/PlatformSupport/DynLoader.cs @@ -11,13 +11,25 @@ class UnixLoader : IDynamicLibraryLoader // ReSharper disable InconsistentNaming static class LinuxImports { +#if __ANDROID__ + [DllImport("libdl.so")] +#else [DllImport("libdl.so.2")] +#endif private static extern IntPtr dlopen(string path, int flags); +#if __ANDROID__ + [DllImport("libdl.so")] +#else [DllImport("libdl.so.2")] +#endif private static extern IntPtr dlsym(IntPtr handle, string symbol); +#if __ANDROID__ + [DllImport("libdl.so")] +#else [DllImport("libdl.so.2")] +#endif private static extern IntPtr dlerror(); public static void Init() @@ -27,7 +39,7 @@ public static void Init() DlError = dlerror; } } - + static class OsXImports {