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

feat(android): GLCanvasElement #18216

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
4 changes: 2 additions & 2 deletions doc/articles/controls/GLCanvasElement.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ uid: Uno.Controls.GLCanvasElement
## Uno.WinUI.Graphics3D.GLCanvasElement

> [!IMPORTANT]
> This functionality is only available on WinUI and Skia Desktop (`netX.0-desktop`) targets that are running with Desktop OpenGL (not GLES) hardware acceleration. This is also not available on MacOS.
> This functionality is only available on WinUI, Android and Skia Desktop (`netX.0-desktop`) and targets that are running on platforms with OpenGL support. This is also not available on MacOS.

`GLCanvasElement` is a `Grid` for drawing 3D graphics with OpenGL. This class comes as a part of the `Uno.WinUI.Graphics3D` package.

Expand All @@ -31,7 +31,7 @@ The `OnDestroy` method is the complement of `Init` and is used to clean up any a

The `RenderOverride` is the main render-loop function. When adding your drawing logic in `RenderOverride`, you can assume that the OpenGL viewport rectangle is already set and its dimensions are equal to the `resolution` parameter provided to the `GLCanvasElement` constructor.

To learn more about using Silk.NET as a C# binding for OpenGL, see the examples in the Silk.NET repository [here](https://github.com/dotnet/Silk.NET/tree/main/examples/CSharp). Note that the windowing and inputs APIs in Silk.NET are not relevant to `GLCanvasElement`, since we only use Silk.NET as an OpenGL binding library, not a windowing library.
To learn more about using [Silk.NET](https://www.nuget.org/packages/Silk.NET.OpenGL/) as a C# binding for OpenGL, see the examples in the Silk.NET repository [here](https://github.com/dotnet/Silk.NET/tree/main/examples/CSharp). Note that the windowing and inputs APIs in Silk.NET are not relevant to `GLCanvasElement`, since we only use Silk.NET as an OpenGL binding library, not a windowing library.

Additionally, `GLCanvasElement` has an `Invalidate` method that requests a redrawing of the `GLCanvasElement`, calling `RenderOverride` in the process. Note that `RenderOverride` will only be called once per `Invalidate` call and the output will be saved to be used in future frames. To update the output, you must call `Invalidate`. If you need to continuously update the output (e.g. in an animation), you can add an `Invalidate` call inside `RenderOverride`.

Expand Down
109 changes: 109 additions & 0 deletions src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.Android.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#if ANDROID

using System;
using Android.Opengl;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Xaml;
using Silk.NET.Core.Contexts;
using Silk.NET.OpenGL;
using Uno.Disposables;
using Uno.Foundation.Extensibility;
using Uno.Graphics;
using Uno.Extensions;
using Uno.Logging;

namespace Uno.WinUI.Graphics3DGL;

public abstract partial class GLCanvasElement
{
static GLCanvasElement()
{
ApiExtensibility.Register(typeof(INativeOpenGLWrapper), _ => new AndroidNativeOpenGLWrapper());
}

private class AndroidNativeOpenGLWrapper : INativeOpenGLWrapper
{
private EGLDisplay? _eglDisplay;
private EGLSurface? _pBufferSurface;
private EGLContext? _glContext;

public void CreateContext(UIElement element)
{
_eglDisplay = EGL14.EglGetDisplay(EGL14.EglDefaultDisplay);
int[] pi32ConfigAttribs =
{
EGL14.EglRedSize, 8,
EGL14.EglGreenSize, 8,
EGL14.EglBlueSize, 8,
EGL14.EglAlphaSize, 8,
EGL14.EglDepthSize, 8,
EGL14.EglStencilSize, 1,
EGL14.EglSampleBuffers, 1,
EGL14.EglNone
};
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
var success = EGL14.EglChooseConfig(_eglDisplay, pi32ConfigAttribs, 0, configs, 0, configs.Length, numConfigs, 0);
if (!success)
{
throw new InvalidOperationException($"{nameof(EGL14.EglChooseConfig)} failed.");
}

// over 95% of android phone have >= OpenGL ES 3.0 support https://developer.android.com/about/dashboards#OpenGL
_glContext = EGL14.EglCreateContext(_eglDisplay, configs[0], EGL14.EglNoContext, new[] { EGL14.EglContextClientVersion, 2, EGL14.EglNone }, 0);
_pBufferSurface = EGL14.EglCreatePbufferSurface(_eglDisplay, configs[0], new[] { EGL14.EglNone }, 0);

if (_glContext is null)
{
throw new InvalidOperationException($"OpenGL context creation failed");
}
if (_pBufferSurface is null)
{
throw new InvalidOperationException($"EGL pbuffer surface creation failed");
}
}

public object CreateGLSilkNETHandle() => GL.GetApi(new DefaultNativeContext("libGLESv2.so"));

public void DestroyContext()
{
if (_eglDisplay is { } && _pBufferSurface is { })
{
EGL14.EglDestroySurface(_eglDisplay, _pBufferSurface);
}
if (_eglDisplay is { } && _glContext is { })
{
EGL14.EglDestroyContext(_eglDisplay, _glContext);
}

_pBufferSurface = null;
_glContext = null;
_eglDisplay = null;
}
public IDisposable MakeCurrent()
{
var glContext = EGL14.EglGetCurrentContext();
var display = EGL14.EglGetCurrentDisplay();
var readSurface = EGL14.EglGetCurrentSurface(EGL14.EglRead);
var drawSurface = EGL14.EglGetCurrentSurface(EGL14.EglDraw);
if (!EGL14.EglMakeCurrent(_eglDisplay, _pBufferSurface, _pBufferSurface, _glContext))
{
if (this.Log().IsEnabled(LogLevel.Error))
{
this.Log().Error($"{nameof(EGL14.EglMakeCurrent)} failed.");
}
}
return Disposable.Create(() =>
{
if (!EGL14.EglMakeCurrent(display, drawSurface, readSurface, glContext))
{
if (this.Log().IsEnabled(LogLevel.Error))
{
this.Log().Error($"{nameof(EGL14.EglMakeCurrent)} failed.");
}
}
});
}
}
}
#endif
21 changes: 14 additions & 7 deletions src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System;
using System.Diagnostics;
using Silk.NET.OpenGL;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Silk.NET.OpenGL;
using Window = Microsoft.UI.Xaml.Window;

#if WINAPPSDK
using System.Runtime.InteropServices;
Expand All @@ -20,6 +20,7 @@
using Buffer = Windows.Storage.Streams.Buffer;
#endif


namespace Uno.WinUI.Graphics3DGL;

/// <summary>
Expand Down Expand Up @@ -174,7 +175,7 @@ private unsafe void OnLoaded(object sender, RoutedEventArgs routedEventArgs)

private void OnUnloaded(object sender, RoutedEventArgs routedEventArgs)
{
Debug.Assert(_gl is not null); // because OnLoaded creates _gl
global::System.Diagnostics.Debug.Assert(_gl is not null); // because OnLoaded creates _gl

#if WINAPPSDK
Marshal.FreeHGlobal(_pixels);
Expand Down Expand Up @@ -215,7 +216,7 @@ private unsafe void Render()
return;
}

Debug.Assert(_gl is not null); // because _gl exists if loaded
global::System.Diagnostics.Debug.Assert(_gl is not null); // because _gl exists if loaded

using var _ = new GLStateDisposable(this);

Expand All @@ -236,7 +237,13 @@ private unsafe void Render()
#else
Buffer.Cast(_backBuffer.PixelBuffer).ApplyActionOnRawBufferPtr(ptr =>
{
_gl.ReadPixels(0, 0, _width, _height, GLEnum.Bgra, GLEnum.UnsignedByte, (void*)ptr);
_gl.ReadPixels(0, 0, _width, _height,
#if ANDROID
GLEnum.Rgba,
#else
GLEnum.Bgra,
#endif
GLEnum.UnsignedByte, (void*)ptr);
});
_backBuffer.PixelBuffer.Length = _width * _height * BytesPerPixel;
#endif
Expand All @@ -253,15 +260,15 @@ public GLStateDisposable(GLCanvasElement glCanvasElement)
{
_glCanvasElement = glCanvasElement;
var gl = _glCanvasElement._gl;
Debug.Assert(gl is not null);
global::System.Diagnostics.Debug.Assert(gl is not null);

_contextDisposable = _glCanvasElement._nativeOpenGlWrapper.MakeCurrent();
}

public void Dispose()
{
var gl = _glCanvasElement._gl;
Debug.Assert(gl is not null);
global::System.Diagnostics.Debug.Assert(gl is not null);

_contextDisposable.Dispose();
}
Expand Down
30 changes: 20 additions & 10 deletions src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetSkiaPreviousAndCurrent)</TargetFrameworks>
<TargetFrameworks Condition="$(BuildGraphics3DGLForWindows) != ''">$(NetUWPOrWinUI)</TargetFrameworks>
<TargetFrameworks>$(NetPrevious)</TargetFrameworks>
<TargetFrameworks Condition="'$(BuildGraphics3DGLForWindows)' != ''">$(NetUWPOrWinUI)</TargetFrameworks>

<RootNamespace>Uno.WinUI.Graphics3DGL</RootNamespace>
<GeneratePackageOnBuild Condition="'$(Configuration)'=='Release'">true</GeneratePackageOnBuild>
Expand All @@ -18,8 +18,25 @@
<PackageReference Include="Uno.Core.Extensions.Logging.Singleton" Version="4.0.1" />
</ItemGroup>

<ItemGroup Condition="!$(TargetFramework.Contains('windows10'))">
<ProjectReference Include="..\..\Uno.UI\Uno.UI.Reference.csproj" PrivateAssets="all" />
</ItemGroup>

<Choose>
<When Condition="$(TargetFramework) == $(NetUWPOrWinUI)">
<!-- Android -->
<When Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))=='android'">
<PropertyGroup>
<DefineConstants>$(DefineConstants);ANDROID</DefineConstants>
</PropertyGroup>
</When>
<!-- Skia Desktop -->
<When Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))==''">
<PropertyGroup>
<DefineConstants>$(DefineConstants);CROSSRUNTIME</DefineConstants>
</PropertyGroup>
</When>
<!-- WinUI -->
<When Condition="$(TargetFramework.Contains('windows10'))">
<PropertyGroup>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>

Expand All @@ -39,13 +56,6 @@
<Compile Include="..\..\Uno.UI\Graphics\INativeOpenGLWrapper.cs" />
</ItemGroup>
</When>
<Otherwise>
<ItemGroup>
<ProjectReference Include="..\..\Uno.Foundation\Uno.Foundation.Skia.csproj" />
<ProjectReference Include="..\..\Uno.UI\Uno.UI.Skia.csproj" />
<ProjectReference Include="..\..\Uno.UWP\Uno.Skia.csproj" />
</ItemGroup>
</Otherwise>
</Choose>

<ItemGroup>
Expand Down
9 changes: 4 additions & 5 deletions src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\AddIns\Uno.WinUI.Graphics3DGL\Uno.WinUI.Graphics3DGL.csproj" />
<ProjectReference Include="..\..\AddIns\Uno.WinUI.Graphics2DSK\Uno.WinUI.Graphics2DSK.csproj" />
<ProjectReference Include="..\..\AddIns\Uno.UI.Lottie\Uno.UI.Lottie.Skia.csproj" />
<ProjectReference Include="..\..\AddIns\Uno.UI.MSAL\Uno.UI.MSAL.Skia.csproj" />
<ProjectReference Include="..\..\AddIns\Uno.UI.Svg\Uno.UI.Svg.Skia.csproj" />
Expand All @@ -26,16 +28,13 @@
<ProjectReference Include="..\..\Uno.UI.RemoteControl\Uno.UI.RemoteControl.Skia.csproj" />
<ProjectReference Include="..\..\Uno.UI.RuntimeTests\Uno.UI.RuntimeTests.Skia.csproj" />
<ProjectReference Include="..\..\Uno.UI\Uno.UI.Skia.csproj" />
<ProjectReference Include="..\..\Uno.UI.Composition\Uno.UI.Composition.Skia.csproj" />
<ProjectReference Include="..\..\Uno.UI.Dispatching\Uno.UI.Dispatching.Skia.csproj" />
<ProjectReference Include="..\..\Uno.UI.Toolkit\Uno.UI.Toolkit.Skia.csproj" />
<ProjectReference Include="..\..\Uno.UWP\Uno.Skia.csproj" />
<ProjectReference Include="..\..\Uno.UI.Adapter.Microsoft.Extensions.Logging\Uno.UI.Adapter.Microsoft.Extensions.Logging.csproj" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\AddIns\Uno.WinUI.Graphics3DGL\Uno.WinUI.Graphics3DGL.csproj" />
<ProjectReference Include="..\..\AddIns\Uno.WinUI.Graphics2DSK\Uno.WinUI.Graphics2DSK.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Uno.BenchmarkDotNet" Version="0.11.7-develop" />
<PackageReference Include="Uno.BenchmarkDotNet.Annotations" Version="0.11.7-develop" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFrameworks>$(NetMobilePreviousAndCurrent)</TargetFrameworks>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<Import Project="../../targetframework-override.props" />
Expand Down Expand Up @@ -148,6 +149,8 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\AddIns\Uno.WinUI.Graphics3DGL\Uno.WinUI.Graphics3DGL.csproj" />

<PackageReference Include="Xamarin.Google.Android.Material" Version="1.4.0.4" />
<PackageReference Include="Uno.UniversalImageLoader" Version="1.9.37" />
<PackageReference Include="SkiaSharp.NativeAssets.Android" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,26 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:skia="http://uno.ui/skia#using:UITests.Shared.Windows_UI_Composition"
xmlns:not_skia="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:not_android="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:win="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:android="http://nventive.com/android"
xmlns:not_win="http://uno.ui/not_win"
mc:Ignorable="d skia not_win"
mc:Ignorable="d android skia not_win"
d:DesignHeight="300"
d:DesignWidth="400">

<Grid>
<android:RotatingCubeGlCanvasElement />
<skia:RotatingCubeGlCanvasElement />
<win:Grid>
<local:RotatingCubeGlCanvasElement />
</win:Grid>
<not_win:Grid>
<not_skia:TextBlock Text="This sample is only supported on skia and WinUI." />
<not_android:Grid>
<not_skia:Grid>
<TextBlock Text="This sample is only supported on Skia, Android and WinUI." />
</not_skia:Grid>
</not_android:Grid>
</not_win:Grid>
</Grid>
</UserControl>
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,26 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:skia="http://uno.ui/skia#using:UITests.Shared.Windows_UI_Composition"
xmlns:not_skia="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:not_android="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:win="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:android="http://nventive.com/android"
xmlns:not_win="http://uno.ui/not_win"
mc:Ignorable="d skia not_win"
mc:Ignorable="d android skia not_win"
d:DesignHeight="300"
d:DesignWidth="400">

<Grid>
<android:SimpleTriangleGlCanvasElement />
<skia:SimpleTriangleGlCanvasElement />
<win:Grid>
<local:SimpleTriangleGlCanvasElement />
</win:Grid>
<not_win:Grid>
<not_skia:TextBlock Text="This sample is only supported on skia and WinUI." />
<not_android:Grid>
<not_skia:Grid>
<TextBlock Text="This sample is only supported on Skia, Android and WinUI." />
</not_skia:Grid>
</not_android:Grid>
</not_win:Grid>
</Grid>
</UserControl>
Loading
Loading