From fbe830af819ece0597b7857dccc58aabd338ff8f Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 12 Sep 2024 14:14:03 +0300 Subject: [PATCH 01/27] chore: get important changes from #18216 --- doc/articles/controls/GLCanvasElement.md | 4 +-- .../Uno.WinUI.Graphics3DGL/GLCanvasElement.cs | 12 ++++---- .../Uno.WinUI.Graphics3DGL.csproj | 30 ++++++++++++------- .../SamplesApp.Skia/SamplesApp.Skia.csproj | 9 +++--- .../RotatingCubeGlCanvasElement.cs | 14 +++++---- .../SimpleTriangleGlCanvasElement.cs | 16 +++++----- src/Uno.Foundation/AssemblyInfo.cs | 1 + src/Uno.UI-Skia-only.slnf | 1 + src/Uno.UI/Graphics/INativeOpenGLWrapper.cs | 9 +++++- src/Uno.UI/XamlMerge.targets | 2 +- 10 files changed, 60 insertions(+), 38 deletions(-) diff --git a/doc/articles/controls/GLCanvasElement.md b/doc/articles/controls/GLCanvasElement.md index 668504fe6059..daf031096401 100644 --- a/doc/articles/controls/GLCanvasElement.md +++ b/doc/articles/controls/GLCanvasElement.md @@ -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 support for hardware acceleration. On Windows and Linux, this means having (desktop) OpenGL support. On Mac OS X, this means having Vulkan support (to be used by ANGLE). 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. @@ -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`. diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs index bdcf860cab63..82794fb816ea 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs @@ -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; @@ -174,7 +174,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); @@ -215,7 +215,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); @@ -253,7 +253,7 @@ 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(); } @@ -261,7 +261,7 @@ public GLStateDisposable(GLCanvasElement glCanvasElement) public void Dispose() { var gl = _glCanvasElement._gl; - Debug.Assert(gl is not null); + global::System.Diagnostics.Debug.Assert(gl is not null); _contextDisposable.Dispose(); } diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj index a7eeb6a451e1..92ed63d4a1dc 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj @@ -1,7 +1,7 @@  - $(NetSkiaPreviousAndCurrent) - $(NetUWPOrWinUI) + $(NetPrevious) + $(NetUWPOrWinUI) Uno.WinUI.Graphics3DGL true @@ -18,8 +18,25 @@ + + + + - + + + + $(DefineConstants);ANDROID + + + + + + $(DefineConstants);CROSSRUNTIME + + + + win-x86;win-x64;win-arm64 @@ -39,13 +56,6 @@ - - - - - - - diff --git a/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj b/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj index f86014c02a32..24c11fada021 100644 --- a/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj +++ b/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj @@ -18,6 +18,8 @@ + + @@ -26,16 +28,13 @@ + + - - - - - diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs index 0ab7ad230cf1..5bba4187e1a7 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs @@ -22,7 +22,7 @@ namespace UITests.Shared.Windows_UI_Composition { #if WINAPPSDK public class RotatingCubeGlCanvasElement() : GLCanvasElement(1200, 800, () => SamplesApp.App.MainWindow) -#elif __SKIA__ +#else public class RotatingCubeGlCanvasElement() : GLCanvasElement(1200, 800, null) #endif { @@ -67,9 +67,8 @@ public class RotatingCubeGlCanvasElement() : GLCanvasElement(1200, 800, null) 0, 4, 5, }; - private readonly string _vertexShaderSource = "#version 330" + Environment.NewLine + + private readonly string _vertexShaderSource = """ - layout(location = 0) in vec3 pos; layout(location = 1) in vec3 vertex_color; @@ -83,9 +82,8 @@ void main() { } """; - private readonly string FragmentShaderSource = "#version 330" + Environment.NewLine + + private readonly string FragmentShaderSource = """ - in vec3 color; out vec4 frag_color; @@ -104,7 +102,11 @@ protected override void Init(GL Gl) _vao.VertexAttributePointer(0, 3, VertexAttribPointerType.Float, 6, 0); _vao.VertexAttributePointer(1, 3, VertexAttribPointerType.Float, 6, 3); - _shader = new Shader(Gl, _vertexShaderSource, FragmentShaderSource); + var slVersion = Gl.GetStringS(StringName.ShadingLanguageVersion); + var versionDef = slVersion.Contains("OpenGL ES", StringComparison.InvariantCultureIgnoreCase) + ? "#version 300 es" + : "#version 330"; + _shader = new Shader(Gl, versionDef + Environment.NewLine + _vertexShaderSource, versionDef + Environment.NewLine + FragmentShaderSource); } protected override void OnDestroy(GL Gl) diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs index 27261b1eba4b..8eb3d274d42d 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs @@ -8,10 +8,10 @@ namespace UITests.Shared.Windows_UI_Composition { // https://learnopengl.com/Getting-started/Hello-Triangle public class SimpleTriangleGlCanvasElement() -#if __SKIA__ - : GLCanvasElement(1200, 800, null) -#elif WINAPPSDK +#if WINAPPSDK : GLCanvasElement(1200, 800, () => SamplesApp.App.MainWindow) +#else + : GLCanvasElement(1200, 800, null) #endif { private uint _vao; @@ -37,10 +37,13 @@ unsafe protected override void Init(GL gl) gl.VertexAttribPointer(0, 3, GLEnum.Float, false, 3 * sizeof(float), (void*)0); gl.EnableVertexAttribArray(0); - // string.Empty is added so that the version line is not interpreted as a preprocessor command + var slVersion = gl.GetStringS(StringName.ShadingLanguageVersion); + var versionDef = slVersion.Contains("OpenGL ES", StringComparison.InvariantCultureIgnoreCase) + ? "#version 300 es" + : "#version 330"; var vertexCode = $$""" - {{string.Empty}}#version 330 + {{versionDef}} layout (location = 0) in vec3 aPosition; out vec4 vertexColor; @@ -52,10 +55,9 @@ void main() } """; - // string.Empty is added so that the version line is not interpreted as a preprocessor command var fragmentCode = $$""" - {{string.Empty}}#version 330 + {{versionDef}} out vec4 out_color; in vec4 vertexColor; diff --git a/src/Uno.Foundation/AssemblyInfo.cs b/src/Uno.Foundation/AssemblyInfo.cs index 78219411a5c2..27f3cf685913 100644 --- a/src/Uno.Foundation/AssemblyInfo.cs +++ b/src/Uno.Foundation/AssemblyInfo.cs @@ -43,4 +43,5 @@ [assembly: InternalsVisibleTo("Uno.UI.Runtime.WebAssembly")] [assembly: InternalsVisibleTo("Uno.UI.Composition")] [assembly: InternalsVisibleTo("Uno.UI.Dispatching")] +[assembly: InternalsVisibleTo("Uno.WinUI.Graphics3DGL")] [assembly: System.Reflection.AssemblyMetadata("IsTrimmable", "True")] diff --git a/src/Uno.UI-Skia-only.slnf b/src/Uno.UI-Skia-only.slnf index 9f8b8e555e01..9372e80dfb51 100644 --- a/src/Uno.UI-Skia-only.slnf +++ b/src/Uno.UI-Skia-only.slnf @@ -54,6 +54,7 @@ "Uno.UI.Toolkit\\Uno.UI.Toolkit.Skia.csproj", "Uno.UI.XamlHost.Skia.Wpf\\Uno.UI.XamlHost.Skia.Wpf.csproj", "Uno.UI.XamlHost\\Uno.UI.XamlHost.Skia.csproj", + "Uno.UI\\Uno.UI.Reference.csproj", "Uno.UI\\Uno.UI.Skia.csproj", "Uno.UWPSyncGenerator.Reference\\Uno.UWPSyncGenerator.Reference.csproj", "Uno.UWPSyncGenerator\\Uno.UWPSyncGenerator.csproj", diff --git a/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs b/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs index e9575a2b2ffb..fbf9cf3e8c60 100644 --- a/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs +++ b/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs @@ -18,7 +18,14 @@ internal interface INativeOpenGLWrapper /// public void CreateContext(UIElement element); - /// This should be cast to a Silk.NET.GL + /// + /// This should be cast to a Silk.NET.OpenGL.GL (even on devices with GLES, not Desktop GL). + /// The Silk.NET.OpenGL.GL API surface is a almost a complete superset of the Silk.NET.OpenGLES.GL API + /// surface (with the exception of BlendBarrier and PrimitiveBoundingBox which are OpenGL ES 3.2-only APIs). + /// So, we always expose an OpenGL.GL object even on GLES devices, with the caveat that the user is only + /// allowed to make calls that are available on this GL (or GLES) version (just like OpenGL 4-only APIs + /// aren't allowed on OpenGL 3 implementations). + /// public object CreateGLSilkNETHandle(); /// diff --git a/src/Uno.UI/XamlMerge.targets b/src/Uno.UI/XamlMerge.targets index d5a88a997369..4d5a017db722 100644 --- a/src/Uno.UI/XamlMerge.targets +++ b/src/Uno.UI/XamlMerge.targets @@ -9,7 +9,7 @@ - $(MSBuildProjectDirectory)\UI\Xaml\Style\mergedstyles.xaml + $(MSBuildProjectDirectory)\obj\$(MSBuildProjectName)\$(Configuration)\$(TargetFramework)\mergedstyles.xaml From 4fc0287f5bd613ec548e501175453a303e5c5260 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 1 Oct 2024 20:57:52 +0300 Subject: [PATCH 02/27] chore: GlCanvasElement on MacOS progress 1 --- src/Uno.UI.Runtime.Skia.MacOS/Uno.UI.Runtime.Skia.MacOS.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/Uno.UI.Runtime.Skia.MacOS.csproj b/src/Uno.UI.Runtime.Skia.MacOS/Uno.UI.Runtime.Skia.MacOS.csproj index 4a0bec49b565..17a92b90ffbb 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/Uno.UI.Runtime.Skia.MacOS.csproj +++ b/src/Uno.UI.Runtime.Skia.MacOS/Uno.UI.Runtime.Skia.MacOS.csproj @@ -31,6 +31,9 @@ + + + From 6ca4af16885e51adc5cc21e629ded0f6cc715274 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 2 Oct 2024 07:15:52 -0400 Subject: [PATCH 03/27] chore: GlCanvasElement on MacOS progress 2 --- .../Uno.UI.Runtime.Skia.MacOS.csproj | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/Uno.UI.Runtime.Skia.MacOS.csproj b/src/Uno.UI.Runtime.Skia.MacOS/Uno.UI.Runtime.Skia.MacOS.csproj index 17a92b90ffbb..460e8e2b56d5 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/Uno.UI.Runtime.Skia.MacOS.csproj +++ b/src/Uno.UI.Runtime.Skia.MacOS/Uno.UI.Runtime.Skia.MacOS.csproj @@ -33,7 +33,6 @@ - @@ -45,11 +44,11 @@ - - + Visible="False" /> + From a4810f38a96a4564dee54e62fcd877ce1d280282 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 2 Oct 2024 15:56:20 +0300 Subject: [PATCH 04/27] chore: GlCanvasElement on MacOS progress 3 --- .../MacOSNativeOpenGLWrapper.cs | 101 ++++++++++++++++++ .../Uno.UI.Runtime.Skia.MacOS.csproj | 1 + 2 files changed, 102 insertions(+) create mode 100644 src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs new file mode 100644 index 000000000000..1c953508f771 --- /dev/null +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs @@ -0,0 +1,101 @@ +using Microsoft.UI.Xaml; +using OpenTK.Graphics.Egl; +using Silk.NET.Core.Contexts; +using Silk.NET.OpenGL; +using Uno.Disposables; +using Uno.Foundation.Logging; +using Uno.Graphics; + +namespace Uno.UI.Runtime.Skia.MacOS; + +// ANGLE implements EGL 1.5 +// https://registry.khronos.org/EGL/api/EGL/egl.h +// Permalink: https://github.com/KhronosGroup/EGL-Registry/blob/29c4314e0ef04c730992d295f91b76635019fbba/api/EGL/egl.h + +public class MacOSNativeOpenGLWrapper : INativeOpenGLWrapper +{ + private const int EGL_DEFAULT_DISPLAY = 0; + private const int EGL_NO_CONTEXT = 0; + + private IntPtr _eglDisplay; + private IntPtr _glContext; + private IntPtr _pBufferSurface; + + public void CreateContext(UIElement element) + { + _eglDisplay = Egl.GetDisplay(EGL_DEFAULT_DISPLAY); + int[] pi32ConfigAttribs = + { + Egl.RED_SIZE, 8, + Egl.GREEN_SIZE, 8, + Egl.BLUE_SIZE, 8, + Egl.ALPHA_SIZE, 8, + Egl.DEPTH_SIZE, 8, + Egl.STENCIL_SIZE, 1, + Egl.SAMPLES, 2, + Egl.SAMPLE_BUFFERS, 1, + Egl.NONE + }; + + var configs = new IntPtr[1]; + var success = Egl.ChooseConfig(_eglDisplay, pi32ConfigAttribs, configs, configs.Length, out var numConfig); + + if (!success) + { + throw new InvalidOperationException($"{nameof(Egl.ChooseConfig)} failed."); + } + + // ANGLE implements GLES 3 + _glContext = Egl.CreateContext(_eglDisplay, configs[0], EGL_NO_CONTEXT, new[] { Egl.CONTEXT_CLIENT_VERSION, 3, Egl.NONE }); + _pBufferSurface = Egl.CreatePbufferSurface(_eglDisplay, configs[0], new[] { Egl.NONE }); + + if (_glContext == IntPtr.Zero) + { + throw new InvalidOperationException($"OpenGL context creation failed"); + } + if (_pBufferSurface == IntPtr.Zero) + { + throw new InvalidOperationException($"EGL pbuffer surface creation failed"); + } + } + public object CreateGLSilkNETHandle() => GL.GetApi(new DefaultNativeContext("libGLESv2.so")); + public void DestroyContext() + { + if (_eglDisplay != IntPtr.Zero && _pBufferSurface != IntPtr.Zero) + { + Egl.DestroySurface(_eglDisplay, _pBufferSurface); + } + if (_eglDisplay != IntPtr.Zero && _glContext != IntPtr.Zero) + { + Egl.DestroyContext(_eglDisplay, _glContext); + } + + _pBufferSurface = IntPtr.Zero; + _glContext = IntPtr.Zero; + _eglDisplay = IntPtr.Zero; + } + public IDisposable MakeCurrent() + { + var glContext = Egl.GetCurrentContext(); + var display = Egl.GetCurrentDisplay(); + var readSurface = Egl.GetCurrentSurface(Egl.READ); + var drawSurface = Egl.GetCurrentSurface(Egl.DRAW); + if (!Egl.MakeCurrent(_eglDisplay, _pBufferSurface, _pBufferSurface, _glContext)) + { + if (this.Log().IsEnabled(LogLevel.Error)) + { + this.Log().Error($"{nameof(Egl.MakeCurrent)} failed."); + } + } + return Disposable.Create(() => + { + if (!Egl.MakeCurrent(display, drawSurface, readSurface, glContext)) + { + if (this.Log().IsEnabled(LogLevel.Error)) + { + this.Log().Error($"{nameof(Egl.MakeCurrent)} failed."); + } + } + }); + } +} diff --git a/src/Uno.UI.Runtime.Skia.MacOS/Uno.UI.Runtime.Skia.MacOS.csproj b/src/Uno.UI.Runtime.Skia.MacOS/Uno.UI.Runtime.Skia.MacOS.csproj index 460e8e2b56d5..47af5ce33ea1 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/Uno.UI.Runtime.Skia.MacOS.csproj +++ b/src/Uno.UI.Runtime.Skia.MacOS/Uno.UI.Runtime.Skia.MacOS.csproj @@ -33,6 +33,7 @@ + From fd42c8cdaf6fd108ce510eefdf3a61a57e214d01 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 2 Oct 2024 16:02:14 +0300 Subject: [PATCH 05/27] chore: GlCanvasElement on MacOS progress 4 --- src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs | 2 ++ src/Uno.UI.Runtime.Skia.MacOS/MacSkiaHost.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs index 1c953508f771..ae5ddf707f5f 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs @@ -3,6 +3,7 @@ using Silk.NET.Core.Contexts; using Silk.NET.OpenGL; using Uno.Disposables; +using Uno.Foundation.Extensibility; using Uno.Foundation.Logging; using Uno.Graphics; @@ -98,4 +99,5 @@ public IDisposable MakeCurrent() } }); } + public static void Register() => ApiExtensibility.Register(typeof(INativeOpenGLWrapper), _ => new MacOSNativeOpenGLWrapper()); } diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacSkiaHost.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacSkiaHost.cs index 94ae216c84e6..c1a90852a4c4 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacSkiaHost.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacSkiaHost.cs @@ -28,6 +28,7 @@ static MacSkiaHost() MacOSNativeWindowFactoryExtension.Register(); MacOSSystemNavigationManagerPreviewExtension.Register(); MacOSSystemThemeHelperExtension.Register(); + MacOSNativeOpenGLWrapper.Register(); } public MacSkiaHost(Func appBuilder) From 3b3cdcb00325023fe0ab5d48ceae7baca98c2255 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 3 Oct 2024 11:48:07 -0400 Subject: [PATCH 06/27] chore: GlCanvasElement on MacOS progress 5 --- .../UnoNativeMac/UnoNativeMac/.gitignore | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/.gitignore b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/.gitignore index f1e5aa5f61a6..81e1dc3fd958 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/.gitignore +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/.gitignore @@ -1,2 +1,3 @@ -SkiaSharp.NativeAssets.macOS.*/ -libSkiaSharp.dylib +skiasharp_*_nativeassets.zip +skiasharp_*_nativeassets/ +libSkiaSharp.dylib \ No newline at end of file From 8266fed9f14ab83ed61f17b7b415108c1634a65b Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 3 Oct 2024 18:17:06 -0400 Subject: [PATCH 07/27] chore: GlCanvasElement on MacOS progress 6 --- .../Uno.WinUI.Graphics3DGL.csproj | 9 + .../Uno.WinUI.Graphics3DGL/build_angle.sh | 57 ++++++ .../SamplesApp.Skia.Generic.csproj | 10 ++ .../MacOSNativeOpenGLWrapper.cs | 162 ++++++++++++++---- .../Uno.UI.Runtime.Skia.MacOS.csproj | 3 +- .../UnoNativeMac/getSkiaSharpDylib.sh | 11 +- 6 files changed, 216 insertions(+), 36 deletions(-) create mode 100755 src/AddIns/Uno.WinUI.Graphics3DGL/build_angle.sh diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj index 92ed63d4a1dc..24005578dbbd 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj @@ -58,7 +58,16 @@ + + + + + + diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/build_angle.sh b/src/AddIns/Uno.WinUI.Graphics3DGL/build_angle.sh new file mode 100755 index 000000000000..7cb43b421f33 --- /dev/null +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/build_angle.sh @@ -0,0 +1,57 @@ +#/usr/bin/env bash +set -euo pipefail + +pushd . > /dev/null + +mkdir -p angle +cd angle + +# It's not enough to check for the existence of depot_tools/.git as the cloning may have been interrupted. +# The git status call makes sure that the repo is not corrupted. +if [ ! -d depot_tools/.git ] || ! git -C depot_tools status; then + rm -rf depot_tools + git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git +fi + +export PATH="$(pwd)/depot_tools:${PATH}" + +if [ ! -f angle_source/angle_fetched_successfully ]; then + rm -rf angle_source + mkdir angle_source + cd angle_source + fetch angle + touch angle_fetched_successfully +else + cd angle_source + echo "Already fetched angle sources. Skipping fetch." +fi + +for arch in arm64 x64 +do + if [ ! -f "out/Release_${arch}/build_succeeded" ]; then + rm -rf "out/Release_${arch}" + mkdir -p "out/Release_${arch}" + + cat << EOF > "out/Release_${arch}/args.gn" +target_cpu = "${arch}" +is_debug = false +angle_assert_always_on = true +EOF + + gn gen "out/Release_${arch}" + autoninja -C "out/Release_${arch}" + touch "out/Release_${arch}/build_succeeded" + else + echo "Already compiled angle for ${arch}. Skipping ${arch} compilation." + fi +done + +popd > /dev/null + +mkdir -p angle_binaries/osx + +# create universal mac binaries +for lib in libEGL.dylib libGLESv2.dylib +do + lipo -create angle/angle_source/out/Release_arm64/${lib} angle/angle_source/out/Release_x64/${lib} -output angle_binaries/osx/${lib} +done \ No newline at end of file diff --git a/src/SamplesApp/SamplesApp.Skia.Generic/SamplesApp.Skia.Generic.csproj b/src/SamplesApp/SamplesApp.Skia.Generic/SamplesApp.Skia.Generic.csproj index b32eea49980a..da265941d944 100644 --- a/src/SamplesApp/SamplesApp.Skia.Generic/SamplesApp.Skia.Generic.csproj +++ b/src/SamplesApp/SamplesApp.Skia.Generic/SamplesApp.Skia.Generic.csproj @@ -97,6 +97,16 @@ DestinationFolder="$(MSBuildThisFileDirectory)bin/$(Configuration)/$(TargetFramework)/runtimes/osx/native/" /> + + + <_libGLESdylibPath Include="$(MSBuildThisFileDirectory)../../AddIns/Uno.WinUI.Graphics3DGL/angle_binaries/**/libEGL.*" /> + <_libGLESdylibPath Include="$(MSBuildThisFileDirectory)../../AddIns/Uno.WinUI.Graphics3DGL/angle_binaries/**/libGLESv2.*" /> + + + + <_validationPath Include="Uno.Fonts.Fluent/Fonts/uno-fluentui-assets.ttf" /> diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs index ae5ddf707f5f..f7b28ee7c15b 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs @@ -1,5 +1,5 @@ +using System.Runtime.InteropServices; using Microsoft.UI.Xaml; -using OpenTK.Graphics.Egl; using Silk.NET.Core.Contexts; using Silk.NET.OpenGL; using Uno.Disposables; @@ -13,10 +13,22 @@ namespace Uno.UI.Runtime.Skia.MacOS; // https://registry.khronos.org/EGL/api/EGL/egl.h // Permalink: https://github.com/KhronosGroup/EGL-Registry/blob/29c4314e0ef04c730992d295f91b76635019fbba/api/EGL/egl.h -public class MacOSNativeOpenGLWrapper : INativeOpenGLWrapper +internal class MacOSNativeOpenGLWrapper : INativeOpenGLWrapper { + private const string libEGL = "libEGL.dylib"; + private const int EGL_DEFAULT_DISPLAY = 0; private const int EGL_NO_CONTEXT = 0; + private const int EGL_ALPHA_SIZE = 0x3021; + private const int EGL_BLUE_SIZE = 0x3022; + private const int EGL_GREEN_SIZE = 0x3023; + private const int EGL_RED_SIZE = 0x3024; + private const int EGL_DEPTH_SIZE = 0x3025; + private const int EGL_STENCIL_SIZE = 0x3026; + private const int EGL_NONE = 0x3038; + private const int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + private const int EGL_DRAW = 0x3059; + private const int EGL_READ = 0x305A; private IntPtr _eglDisplay; private IntPtr _glContext; @@ -24,51 +36,50 @@ public class MacOSNativeOpenGLWrapper : INativeOpenGLWrapper public void CreateContext(UIElement element) { - _eglDisplay = Egl.GetDisplay(EGL_DEFAULT_DISPLAY); + _eglDisplay = EglGetDisplay(EGL_DEFAULT_DISPLAY); + EglInitialize(_eglDisplay, out var major, out var minor); int[] pi32ConfigAttribs = { - Egl.RED_SIZE, 8, - Egl.GREEN_SIZE, 8, - Egl.BLUE_SIZE, 8, - Egl.ALPHA_SIZE, 8, - Egl.DEPTH_SIZE, 8, - Egl.STENCIL_SIZE, 1, - Egl.SAMPLES, 2, - Egl.SAMPLE_BUFFERS, 1, - Egl.NONE + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 8, + EGL_STENCIL_SIZE, 1, + EGL_NONE }; var configs = new IntPtr[1]; - var success = Egl.ChooseConfig(_eglDisplay, pi32ConfigAttribs, configs, configs.Length, out var numConfig); + var success = EglChooseConfig(_eglDisplay, pi32ConfigAttribs, configs, configs.Length, out var numConfig); if (!success) { - throw new InvalidOperationException($"{nameof(Egl.ChooseConfig)} failed."); + throw new InvalidOperationException($"{nameof(EglChooseConfig)} failed: {EglGetError()}"); } // ANGLE implements GLES 3 - _glContext = Egl.CreateContext(_eglDisplay, configs[0], EGL_NO_CONTEXT, new[] { Egl.CONTEXT_CLIENT_VERSION, 3, Egl.NONE }); - _pBufferSurface = Egl.CreatePbufferSurface(_eglDisplay, configs[0], new[] { Egl.NONE }); - + _glContext = EglCreateContext(_eglDisplay, configs[0], EGL_NO_CONTEXT, new[] { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }); if (_glContext == IntPtr.Zero) { - throw new InvalidOperationException($"OpenGL context creation failed"); + throw new InvalidOperationException($"EGL context creation failed: {EglGetError()}"); } + + _pBufferSurface = EglCreatePbufferSurface(_eglDisplay, configs[0], new[] { EGL_NONE }); if (_pBufferSurface == IntPtr.Zero) { - throw new InvalidOperationException($"EGL pbuffer surface creation failed"); + throw new InvalidOperationException($"EGL pbuffer surface creation failed: {EglGetError()}"); } } - public object CreateGLSilkNETHandle() => GL.GetApi(new DefaultNativeContext("libGLESv2.so")); + public object CreateGLSilkNETHandle() => GL.GetApi(new MacOSAngleNativeContext()); public void DestroyContext() { if (_eglDisplay != IntPtr.Zero && _pBufferSurface != IntPtr.Zero) { - Egl.DestroySurface(_eglDisplay, _pBufferSurface); + EglDestroySurface(_eglDisplay, _pBufferSurface); } if (_eglDisplay != IntPtr.Zero && _glContext != IntPtr.Zero) { - Egl.DestroyContext(_eglDisplay, _glContext); + EglDestroyContext(_eglDisplay, _glContext); } _pBufferSurface = IntPtr.Zero; @@ -77,27 +88,118 @@ public void DestroyContext() } public IDisposable MakeCurrent() { - var glContext = Egl.GetCurrentContext(); - var display = Egl.GetCurrentDisplay(); - var readSurface = Egl.GetCurrentSurface(Egl.READ); - var drawSurface = Egl.GetCurrentSurface(Egl.DRAW); - if (!Egl.MakeCurrent(_eglDisplay, _pBufferSurface, _pBufferSurface, _glContext)) + var glContext = EglGetCurrentContext(); + var display = EglGetCurrentDisplay(); + var readSurface = EglGetCurrentSurface(EGL_READ); + var drawSurface = EglGetCurrentSurface(EGL_DRAW); + if (!EglMakeCurrent(_eglDisplay, _pBufferSurface, _pBufferSurface, _glContext)) { if (this.Log().IsEnabled(LogLevel.Error)) { - this.Log().Error($"{nameof(Egl.MakeCurrent)} failed."); + this.Log().Error($"{nameof(EglMakeCurrent)} failed."); } } return Disposable.Create(() => { - if (!Egl.MakeCurrent(display, drawSurface, readSurface, glContext)) + if (!EglMakeCurrent(display, drawSurface, readSurface, glContext)) { if (this.Log().IsEnabled(LogLevel.Error)) { - this.Log().Error($"{nameof(Egl.MakeCurrent)} failed."); + this.Log().Error($"{nameof(EglMakeCurrent)} failed."); } } }); } + public static void Register() => ApiExtensibility.Register(typeof(INativeOpenGLWrapper), _ => new MacOSNativeOpenGLWrapper()); + + private class MacOSAngleNativeContext : INativeContext + { + private readonly IntPtr _handle; + + public MacOSAngleNativeContext() + { + if (!NativeLibrary.TryLoad("libGLESv2.dylib", typeof(MacOSNativeOpenGLWrapper).Assembly, DllImportSearchPath.UserDirectories, out _handle) || _handle == IntPtr.Zero) + { + throw new PlatformNotSupportedException("Unable to load libGLESv2.dylib."); + } + } + + public bool TryGetProcAddress(string proc, out nint addr, int? slot = null) => NativeLibrary.TryGetExport(_handle, proc, out addr); + + public nint GetProcAddress(string proc, int? slot = null) + { + if (TryGetProcAddress(proc, out var address, slot)) + { + return address; + } + + throw new InvalidOperationException($"No function was found with the name '{proc}'."); + } + + public void Dispose() => NativeLibrary.Free(_handle); + } + + // Copyright (c) 2006-2019 Stefanos Apostolopoulos for the Open Toolkit project. + // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + [DllImport(libEGL, EntryPoint = "eglGetDisplay")] + public static extern IntPtr EglGetDisplay(IntPtr display_id); + + [DllImport(libEGL, EntryPoint = "eglInitialize")] + private static extern bool EglInitialize(IntPtr dpy, out int major, out int minor); + + [DllImport(libEGL, EntryPoint = "eglChooseConfig")] + [return: MarshalAs(UnmanagedType.I1)] + private static extern bool EglChooseConfig(IntPtr dpy, int[] attrib_list, [In][Out] IntPtr[] configs, int config_size, out int num_config); + + [DllImport(libEGL, EntryPoint = "eglGetError")] + private static extern ErrorCode EglGetError(); + + [DllImport(libEGL, EntryPoint = "eglCreateContext")] + private static extern IntPtr EglCreateContext(IntPtr dpy, IntPtr config, IntPtr share_context, int[] attrib_list); + + [DllImport(libEGL, EntryPoint = "eglCreatePbufferSurface")] + private static extern IntPtr EglCreatePbufferSurface(IntPtr dpy, IntPtr config, int[] attrib_list); + + [DllImport(libEGL, EntryPoint = "eglDestroySurface")] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool EglDestroySurface(IntPtr dpy, IntPtr surface); + + [DllImport(libEGL, EntryPoint = "eglDestroyContext")] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool EglDestroyContext(IntPtr dpy, IntPtr ctx); + + [DllImport(libEGL, EntryPoint = "eglGetCurrentContext")] + public static extern IntPtr EglGetCurrentContext(); + + [DllImport(libEGL, EntryPoint = "eglGetCurrentDisplay")] + public static extern IntPtr EglGetCurrentDisplay(); + + [DllImport(libEGL, EntryPoint = "eglGetCurrentSurface")] + public static extern IntPtr EglGetCurrentSurface(int readdraw); + + [DllImport(libEGL, EntryPoint = "eglMakeCurrent")] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool EglMakeCurrent(IntPtr dpy, IntPtr draw, IntPtr read, IntPtr ctx); + + private enum ErrorCode + { + SUCCESS = 0x3000, + NOT_INITIALIZED, + BAD_ACCESS, + BAD_ALLOC, + BAD_ATTRIBUTE, + BAD_CONFIG, + BAD_CONTEXT, + BAD_CURRENT_SURFACE, + BAD_DISPLAY, + BAD_MATCH, + BAD_NATIVE_PIXMAP, + BAD_NATIVE_WINDOW, + BAD_PARAMETER, + BAD_SURFACE, + CONTEXT_LOST + } } diff --git a/src/Uno.UI.Runtime.Skia.MacOS/Uno.UI.Runtime.Skia.MacOS.csproj b/src/Uno.UI.Runtime.Skia.MacOS/Uno.UI.Runtime.Skia.MacOS.csproj index 47af5ce33ea1..4d3de656bc73 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/Uno.UI.Runtime.Skia.MacOS.csproj +++ b/src/Uno.UI.Runtime.Skia.MacOS/Uno.UI.Runtime.Skia.MacOS.csproj @@ -33,7 +33,6 @@ - @@ -49,7 +48,7 @@ PackagePath="runtimes/osx/native" Pack="true" Visible="False" /> - + diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/getSkiaSharpDylib.sh b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/getSkiaSharpDylib.sh index e8b4e60d5740..42f526967e6b 100755 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/getSkiaSharpDylib.sh +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/getSkiaSharpDylib.sh @@ -1,7 +1,10 @@ -#!/bin/bash +#!/usr/bin/env bash VERSION=${VERSION:-2.88.7} -nuget install SkiaSharp.NativeAssets.macOS -Version ${VERSION} -cp SkiaSharp.NativeAssets.macOS.${VERSION}/runtimes/osx/native/libSkiaSharp.dylib . -rm -rf SkiaSharp.NativeAssets.macOS.${VERSION}/ +filename="skiasharp_"${VERSION}"_nativeassets" +if [ ! -f "${filename}"/runtimes/osx/native/libSkiaSharp.dylib ]; then + curl -o "${filename}.zip" -L "https://www.nuget.org/api/v2/package/SkiaSharp.NativeAssets.macOS/${VERSION}" + unzip -d "${filename}" "${filename}.zip" +fi +cp "${filename}/runtimes/osx/native/libSkiaSharp.dylib" . From 22b04c7dbda06edcceaf560a9c191b0071a88a39 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Fri, 4 Oct 2024 04:46:17 -0400 Subject: [PATCH 08/27] chore: GlCanvasElement on MacOS progress 7 --- src/AddIns/Uno.WinUI.Graphics3DGL/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/AddIns/Uno.WinUI.Graphics3DGL/.gitignore diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/.gitignore b/src/AddIns/Uno.WinUI.Graphics3DGL/.gitignore new file mode 100644 index 000000000000..8d1f1ac147eb --- /dev/null +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/.gitignore @@ -0,0 +1,2 @@ +angle/ +angle_binaries/ \ No newline at end of file From 363b8df25410de0079f6e598274aca1e972cef84 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Fri, 4 Oct 2024 07:05:32 -0400 Subject: [PATCH 09/27] feat: GlCanvasElement on MacOS --- .../Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj index 24005578dbbd..e81bc81b83bc 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj @@ -58,6 +58,10 @@ + + $(DefaultItemExcludes);angle/** + + From 17b484ced537abab77f3a06742d11144f0caeeef Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Fri, 4 Oct 2024 23:04:37 +0300 Subject: [PATCH 10/27] chore: undo XamlMerge fix --- src/Uno.UI/XamlMerge.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Uno.UI/XamlMerge.targets b/src/Uno.UI/XamlMerge.targets index 4d5a017db722..c48e88e40453 100644 --- a/src/Uno.UI/XamlMerge.targets +++ b/src/Uno.UI/XamlMerge.targets @@ -9,7 +9,7 @@ - $(MSBuildProjectDirectory)\obj\$(MSBuildProjectName)\$(Configuration)\$(TargetFramework)\mergedstyles.xaml + $(MSBuildProjectDirectory)\UI\Xaml\Style\mergedstyles.xaml From 064fce50961f7e1043faba756dd4193e6eaef469 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Fri, 4 Oct 2024 23:12:47 +0300 Subject: [PATCH 11/27] chore: move Graphics3DGL back to reference UNo.UI.Skia.csproj --- src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj index e81bc81b83bc..f5924253668f 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj @@ -19,7 +19,7 @@ - + From 21cd6b0f0d69241d4e31cbec44c98420b95853f4 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Fri, 4 Oct 2024 16:13:55 -0400 Subject: [PATCH 12/27] chore: minor angle build script touches --- .../Uno.WinUI.Graphics3DGL/build_angle.sh | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/build_angle.sh b/src/AddIns/Uno.WinUI.Graphics3DGL/build_angle.sh index 7cb43b421f33..86fc1fb0b753 100755 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/build_angle.sh +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/build_angle.sh @@ -19,7 +19,12 @@ if [ ! -f angle_source/angle_fetched_successfully ]; then rm -rf angle_source mkdir angle_source cd angle_source - fetch angle + fetch --no-history angle + + # remove unnecessary dirs for space + rm -rf third_party/dawn third_party/VK-GL-CTS + find ./tools ./third_party -name "*.git" | xargs rm -rf + touch angle_fetched_successfully else cd angle_source @@ -32,10 +37,18 @@ do rm -rf "out/Release_${arch}" mkdir -p "out/Release_${arch}" + # we remove as much as we can from the build to save space for the space-limited CI machines cat << EOF > "out/Release_${arch}/args.gn" target_cpu = "${arch}" is_debug = false angle_assert_always_on = true +angle_build_tests = false +build_angle_deqp_tests = false +build_angle_perftests = false +angle_enable_vulkan = false +angle_enable_wgpu = false +angle_enable_abseil = false +angle_enable_gl_null = false EOF gn gen "out/Release_${arch}" @@ -51,7 +64,9 @@ popd > /dev/null mkdir -p angle_binaries/osx # create universal mac binaries -for lib in libEGL.dylib libGLESv2.dylib +shopt -s nullglob +for lib in libEGL.* libGLESv2.* do lipo -create angle/angle_source/out/Release_arm64/${lib} angle/angle_source/out/Release_x64/${lib} -output angle_binaries/osx/${lib} -done \ No newline at end of file +done +shopt -u nullglob \ No newline at end of file From 5139d76c766502e70b66120248922ae165825b65 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 7 Oct 2024 15:42:38 +0300 Subject: [PATCH 13/27] chore: refactoring and dropping Silk.NET PrivateAssets dependency --- .../Uno.WinUI.Graphics3DGL/GLCanvasElement.cs | 175 +++++++++++------- .../UnoIslandsSamplesApp.Skia.csproj | 2 - .../MacOSNativeOpenGLWrapper.cs | 81 ++++---- .../Uno.UI.Runtime.Skia.MacOS.csproj | 2 - .../Extensions/WpfExtensionsRegistrar.cs | 2 +- .../Extensions/WpfNativeOpenGLWrapper.cs | 119 ++++++------ .../Uno.UI.Runtime.Skia.Wpf.csproj | 1 - .../Uno.UI.Runtime.Skia.X11.csproj | 2 - .../X11ApplicationHost.cs | 2 +- .../X11NativeOpenGLWrapper.cs | 61 ++++-- src/Uno.UI/Graphics/INativeOpenGLWrapper.cs | 36 +--- 11 files changed, 263 insertions(+), 220 deletions(-) diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs index 82794fb816ea..4b7287269220 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs @@ -1,9 +1,14 @@ using System; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Logging; 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.Core.Contexts; +using Uno.Extensions; +using Uno.Logging; using Window = Microsoft.UI.Xaml.Window; #if WINAPPSDK @@ -29,18 +34,21 @@ namespace Uno.WinUI.Graphics3DGL; /// This is only available on WinUI and on skia-based targets running with hardware acceleration. /// This is currently only available on the WPF and X11 targets (and WinUI). /// -public abstract partial class GLCanvasElement : Grid +public abstract partial class GLCanvasElement : Grid, INativeContext { private const int BytesPerPixel = 4; - - private readonly INativeOpenGLWrapper _nativeOpenGlWrapper; + private static readonly BitmapImage _fallbackImage = new BitmapImage(new Uri("https://uno-assets.platform.uno/logos/uno.png")); + private static bool? _glAvailable; private readonly uint _width; private readonly uint _height; + private readonly Func? _getWindowFunc; private readonly WriteableBitmap _backBuffer; - // These are valid if and only if IsLoaded + private bool _loadedAtleastOnce; + // These are valid if and only if _loadedAtleastOnce and _glAvailable + private INativeOpenGLWrapper? _nativeOpenGlWrapper; private GL? _gl; private uint _framebuffer; private uint _textureColorBuffer; @@ -93,15 +101,7 @@ protected GLCanvasElement(uint width, uint height, Func? getWindowFunc) { _width = width; _height = height; - -#if WINAPPSDK - _nativeOpenGlWrapper = new WinUINativeOpenGLWrapper(getWindowFunc); -#else - if (!ApiExtensibility.CreateInstance(this, out _nativeOpenGlWrapper!)) - { - throw new InvalidOperationException($"Couldn't create a {nameof(INativeOpenGLWrapper)} object for {nameof(GLCanvasElement)}. Make sure you are running on a platform with {nameof(GLCanvasElement)} support."); - } -#endif + _getWindowFunc = getWindowFunc; _backBuffer = new WriteableBitmap((int)width, (int)height); @@ -115,6 +115,31 @@ protected GLCanvasElement(uint width, uint height, Func? getWindowFunc) Unloaded += OnUnloaded; } + private static INativeOpenGLWrapper? CreateNativeOpenGlWrapper(XamlRoot xamlRoot, Func? getWindowFunc) + { + try + { +#if WINAPPSDK + var nativeOpenGlWrapper = new WinUINativeOpenGLWrapper(getWindowFunc); +#else + if (!ApiExtensibility.CreateInstance(xamlRoot, out var nativeOpenGlWrapper)) + { + throw new InvalidOperationException($"Couldn't create a {nameof(INativeOpenGLWrapper)} object. Make sure you are running on a platform with OpenGL support."); + } + xamlRoot.HostWindow!.Closed += (_, _) => { nativeOpenGlWrapper.Dispose(); }; +#endif + return nativeOpenGlWrapper; + } + catch (Exception e) + { + if (typeof(INativeOpenGLWrapper).Log().IsEnabled(LogLevel.Error)) + { + typeof(INativeOpenGLWrapper).Log().Error($"{nameof(INativeOpenGLWrapper)} creation failed.", e); + } + return null; + } + } + /// /// Invalidates the rendering, and queues a call to . /// will only be called once after and the output will @@ -127,46 +152,77 @@ protected GLCanvasElement(uint width, uint height, Func? getWindowFunc) public void Invalidate() => NativeDispatcher.Main.Enqueue(Render, NativeDispatcherPriority.Idle); #endif + private bool? GlAvailable + { + get => _glAvailable; + set + { + if (!value!.Value) + { + ((ImageBrush)Background).ImageSource = _fallbackImage; + } + _glAvailable = value; + } + } + private unsafe void OnLoaded(object sender, RoutedEventArgs routedEventArgs) { - _nativeOpenGlWrapper.CreateContext(this); - _gl = (GL)_nativeOpenGlWrapper.CreateGLSilkNETHandle(); + if (!_loadedAtleastOnce) + { + _loadedAtleastOnce = true; -#if WINAPPSDK - _pixels = Marshal.AllocHGlobal((int)(_width * _height * BytesPerPixel)); -#endif + if ((!GlAvailable ?? false) || CreateNativeOpenGlWrapper(XamlRoot!, _getWindowFunc) is not { } glWrapper) + { + GlAvailable = false; + } + else + { + GlAvailable = true; + _nativeOpenGlWrapper = glWrapper; + } - using (new GLStateDisposable(this)) - { - _framebuffer = _gl.GenBuffer(); - _gl.BindFramebuffer(GLEnum.Framebuffer, _framebuffer); + if (GlAvailable.Value) { - _textureColorBuffer = _gl.GenTexture(); - _gl.BindTexture(GLEnum.Texture2D, _textureColorBuffer); - { - _gl.TexImage2D(GLEnum.Texture2D, 0, InternalFormat.Rgb, _width, _height, 0, GLEnum.Rgb, GLEnum.UnsignedByte, (void*)0); - _gl.TexParameterI(GLEnum.Texture2D, GLEnum.TextureMinFilter, (uint)GLEnum.Linear); - _gl.TexParameterI(GLEnum.Texture2D, GLEnum.TextureMagFilter, (uint)GLEnum.Linear); - _gl.FramebufferTexture2D(GLEnum.Framebuffer, FramebufferAttachment.ColorAttachment0, GLEnum.Texture2D, _textureColorBuffer, 0); - } - _gl.BindTexture(GLEnum.Texture2D, 0); + _gl = GL.GetApi(this); + GlAvailable = true; - _renderBuffer = _gl.GenRenderbuffer(); - _gl.BindRenderbuffer(GLEnum.Renderbuffer, _renderBuffer); - { - _gl.RenderbufferStorage(GLEnum.Renderbuffer, InternalFormat.Depth24Stencil8, _width, _height); - _gl.FramebufferRenderbuffer(GLEnum.Framebuffer, GLEnum.DepthStencilAttachment, GLEnum.Renderbuffer, _renderBuffer); - } - _gl.BindRenderbuffer(GLEnum.Renderbuffer, 0); +#if WINAPPSDK + _pixels = Marshal.AllocHGlobal((int)(_width * _height * BytesPerPixel)); +#endif - if (_gl.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete) + using (_nativeOpenGlWrapper!.MakeCurrent()) { - throw new InvalidOperationException("Offscreen framebuffer is not complete"); + _framebuffer = _gl.GenBuffer(); + _gl.BindFramebuffer(GLEnum.Framebuffer, _framebuffer); + { + _textureColorBuffer = _gl.GenTexture(); + _gl.BindTexture(GLEnum.Texture2D, _textureColorBuffer); + { + _gl.TexImage2D(GLEnum.Texture2D, 0, InternalFormat.Rgb, _width, _height, 0, GLEnum.Rgb, GLEnum.UnsignedByte, (void*)0); + _gl.TexParameterI(GLEnum.Texture2D, GLEnum.TextureMinFilter, (uint)GLEnum.Linear); + _gl.TexParameterI(GLEnum.Texture2D, GLEnum.TextureMagFilter, (uint)GLEnum.Linear); + _gl.FramebufferTexture2D(GLEnum.Framebuffer, FramebufferAttachment.ColorAttachment0, GLEnum.Texture2D, _textureColorBuffer, 0); + } + _gl.BindTexture(GLEnum.Texture2D, 0); + + _renderBuffer = _gl.GenRenderbuffer(); + _gl.BindRenderbuffer(GLEnum.Renderbuffer, _renderBuffer); + { + _gl.RenderbufferStorage(GLEnum.Renderbuffer, InternalFormat.Depth24Stencil8, _width, _height); + _gl.FramebufferRenderbuffer(GLEnum.Framebuffer, GLEnum.DepthStencilAttachment, GLEnum.Renderbuffer, _renderBuffer); + } + _gl.BindRenderbuffer(GLEnum.Renderbuffer, 0); + + if (_gl.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete) + { + throw new InvalidOperationException("Offscreen framebuffer is not complete"); + } + + Init(_gl); + } + _gl.BindFramebuffer(GLEnum.Framebuffer, 0); } - - Init(_gl); } - _gl.BindFramebuffer(GLEnum.Framebuffer, 0); } Invalidate(); @@ -174,13 +230,18 @@ private unsafe void OnLoaded(object sender, RoutedEventArgs routedEventArgs) private void OnUnloaded(object sender, RoutedEventArgs routedEventArgs) { + if (!GlAvailable!.Value) + { + return; + } + global::System.Diagnostics.Debug.Assert(_gl is not null); // because OnLoaded creates _gl #if WINAPPSDK Marshal.FreeHGlobal(_pixels); #endif - using (new GLStateDisposable(this)) + using (_nativeOpenGlWrapper!.MakeCurrent()) { #if WINAPPSDK if (WindowsRenderingNativeMethods.wglGetCurrentContext() == 0) @@ -217,7 +278,7 @@ private unsafe void Render() global::System.Diagnostics.Debug.Assert(_gl is not null); // because _gl exists if loaded - using var _ = new GLStateDisposable(this); + using var _ = _nativeOpenGlWrapper!.MakeCurrent(); _gl!.BindFramebuffer(GLEnum.Framebuffer, _framebuffer); { @@ -244,26 +305,6 @@ private unsafe void Render() } } - private readonly struct GLStateDisposable : IDisposable - { - private readonly GLCanvasElement _glCanvasElement; - private readonly IDisposable _contextDisposable; - - public GLStateDisposable(GLCanvasElement glCanvasElement) - { - _glCanvasElement = glCanvasElement; - var gl = _glCanvasElement._gl; - global::System.Diagnostics.Debug.Assert(gl is not null); - - _contextDisposable = _glCanvasElement._nativeOpenGlWrapper.MakeCurrent(); - } - - public void Dispose() - { - var gl = _glCanvasElement._gl; - global::System.Diagnostics.Debug.Assert(gl is not null); - - _contextDisposable.Dispose(); - } - } + public IntPtr GetProcAddress(string proc, int? slot = null) => _nativeOpenGlWrapper!.GetProcAddress(proc); + public bool TryGetProcAddress(string proc, [UnscopedRef] out IntPtr addr, int? slot = null) => _nativeOpenGlWrapper!.TryGetProcAddress(proc, out addr); } diff --git a/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj b/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj index 8ac3b125cf63..3314889496e9 100644 --- a/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj +++ b/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj @@ -55,8 +55,6 @@ - - diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs index f7b28ee7c15b..5366a416fceb 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs @@ -1,7 +1,5 @@ using System.Runtime.InteropServices; using Microsoft.UI.Xaml; -using Silk.NET.Core.Contexts; -using Silk.NET.OpenGL; using Uno.Disposables; using Uno.Foundation.Extensibility; using Uno.Foundation.Logging; @@ -16,6 +14,17 @@ namespace Uno.UI.Runtime.Skia.MacOS; internal class MacOSNativeOpenGLWrapper : INativeOpenGLWrapper { private const string libEGL = "libEGL.dylib"; + private static readonly Lazy _libGLES = new Lazy(() => + { + if (!NativeLibrary.TryLoad("libGLESv2.dylib", typeof(MacOSNativeOpenGLWrapper).Assembly, DllImportSearchPath.UserDirectories, out var _handle)) + { + if (typeof(MacOSNativeOpenGLWrapper).Log().IsEnabled(LogLevel.Error)) + { + typeof(MacOSNativeOpenGLWrapper).Log().Error("libGLESv2.dylib was not loaded successfully."); + } + } + return _handle; + }); private const int EGL_DEFAULT_DISPLAY = 0; private const int EGL_NO_CONTEXT = 0; @@ -34,11 +43,16 @@ internal class MacOSNativeOpenGLWrapper : INativeOpenGLWrapper private IntPtr _glContext; private IntPtr _pBufferSurface; - public void CreateContext(UIElement element) + public MacOSNativeOpenGLWrapper(XamlRoot xamlRoot) { _eglDisplay = EglGetDisplay(EGL_DEFAULT_DISPLAY); EglInitialize(_eglDisplay, out var major, out var minor); - int[] pi32ConfigAttribs = + if (this.Log().IsEnabled(LogLevel.Information)) + { + this.Log().Info($"Found EGL version {major}.{minor}."); + } + + int[] attribList = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, @@ -50,9 +64,9 @@ public void CreateContext(UIElement element) }; var configs = new IntPtr[1]; - var success = EglChooseConfig(_eglDisplay, pi32ConfigAttribs, configs, configs.Length, out var numConfig); + var success = EglChooseConfig(_eglDisplay, attribList, configs, configs.Length, out var numConfig); - if (!success) + if (!success || numConfig < 1) { throw new InvalidOperationException($"{nameof(EglChooseConfig)} failed: {EglGetError()}"); } @@ -70,9 +84,30 @@ public void CreateContext(UIElement element) throw new InvalidOperationException($"EGL pbuffer surface creation failed: {EglGetError()}"); } } - public object CreateGLSilkNETHandle() => GL.GetApi(new MacOSAngleNativeContext()); - public void DestroyContext() + + public IntPtr GetProcAddress(string proc) + { + if (TryGetProcAddress(proc, out var addr)) + { + return addr; + } + + throw new InvalidOperationException($"A procedure named {proc} was not found in libGLES"); + } + + public bool TryGetProcAddress(string proc, out IntPtr addr) + { + if (_libGLES.Value == IntPtr.Zero) + { + addr = IntPtr.Zero; + return false; + } + return NativeLibrary.TryGetExport(_libGLES.Value, proc, out addr); + } + + public void Dispose() { + using var _ = MakeCurrent(); if (_eglDisplay != IntPtr.Zero && _pBufferSurface != IntPtr.Zero) { EglDestroySurface(_eglDisplay, _pBufferSurface); @@ -86,6 +121,7 @@ public void DestroyContext() _glContext = IntPtr.Zero; _eglDisplay = IntPtr.Zero; } + public IDisposable MakeCurrent() { var glContext = EglGetCurrentContext(); @@ -111,34 +147,7 @@ public IDisposable MakeCurrent() }); } - public static void Register() => ApiExtensibility.Register(typeof(INativeOpenGLWrapper), _ => new MacOSNativeOpenGLWrapper()); - - private class MacOSAngleNativeContext : INativeContext - { - private readonly IntPtr _handle; - - public MacOSAngleNativeContext() - { - if (!NativeLibrary.TryLoad("libGLESv2.dylib", typeof(MacOSNativeOpenGLWrapper).Assembly, DllImportSearchPath.UserDirectories, out _handle) || _handle == IntPtr.Zero) - { - throw new PlatformNotSupportedException("Unable to load libGLESv2.dylib."); - } - } - - public bool TryGetProcAddress(string proc, out nint addr, int? slot = null) => NativeLibrary.TryGetExport(_handle, proc, out addr); - - public nint GetProcAddress(string proc, int? slot = null) - { - if (TryGetProcAddress(proc, out var address, slot)) - { - return address; - } - - throw new InvalidOperationException($"No function was found with the name '{proc}'."); - } - - public void Dispose() => NativeLibrary.Free(_handle); - } + public static void Register() => ApiExtensibility.Register(typeof(INativeOpenGLWrapper), xamlRoot => new MacOSNativeOpenGLWrapper(xamlRoot)); // Copyright (c) 2006-2019 Stefanos Apostolopoulos for the Open Toolkit project. // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/src/Uno.UI.Runtime.Skia.MacOS/Uno.UI.Runtime.Skia.MacOS.csproj b/src/Uno.UI.Runtime.Skia.MacOS/Uno.UI.Runtime.Skia.MacOS.csproj index 4d3de656bc73..57562c5c571a 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/Uno.UI.Runtime.Skia.MacOS.csproj +++ b/src/Uno.UI.Runtime.Skia.MacOS/Uno.UI.Runtime.Skia.MacOS.csproj @@ -31,8 +31,6 @@ - - diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfExtensionsRegistrar.cs b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfExtensionsRegistrar.cs index f79162569d63..88574a5f1c10 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfExtensionsRegistrar.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfExtensionsRegistrar.cs @@ -58,7 +58,7 @@ internal static void Register() ApiExtensibility.Register(typeof(IAnalyticsInfoExtension), o => new AnalyticsInfoExtension()); ApiExtensibility.Register(typeof(ISystemNavigationManagerPreviewExtension), o => new SystemNavigationManagerPreviewExtension()); ApiExtensibility.Register(typeof(INativeWebViewProvider), o => new WpfNativeWebViewProvider(o)); - ApiExtensibility.Register(typeof(INativeOpenGLWrapper), _ => new WpfNativeOpenGLWrapper()); + ApiExtensibility.Register(typeof(INativeOpenGLWrapper), xamlRoot => new WpfNativeOpenGLWrapper(xamlRoot)); _registered = true; } diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs index b0ebe1220714..37257ad8958a 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs @@ -3,9 +3,6 @@ using System; using System.Runtime.InteropServices; -using Silk.NET.Core.Contexts; -using Silk.NET.Core.Loader; -using Silk.NET.OpenGL; #if WINDOWS_UWP || WINAPPSDK using Microsoft.UI.Xaml; @@ -30,23 +27,50 @@ namespace Uno.UI.Runtime.Skia.Wpf.Extensions; #endif #if WINDOWS_UWP || WINAPPSDK -internal class WinUINativeOpenGLWrapper(Func getWindowFunc) +internal class WinUINativeOpenGLWrapper #else internal class WpfNativeOpenGLWrapper #endif : INativeOpenGLWrapper { +#if WINDOWS_UWP || WINAPPSDK + private static readonly Type _type = typeof(WinUINativeOpenGLWrapper); +#else + private static readonly Type _type = typeof(WpfNativeOpenGLWrapper); +#endif + private static readonly Lazy _opengl32 = new Lazy(() => + { + if (!NativeLibrary.TryLoad("opengl32.dll", _type.Assembly, DllImportSearchPath.UserDirectories, out var _handle)) + { + if (_type.Log().IsEnabled(LogLevel.Error)) + { + _type.Log().Error("opengl32.dll was not loaded successfully."); + } + } + return _handle; + }); + +#if WINDOWS_UWP || WINAPPSDK + private readonly Func _getWindowFunc +#endif private nint _hdc; private nint _glContext; - public void CreateContext(UIElement element) +#if WINDOWS_UWP || WINAPPSDK + public WinUINativeOpenGLWrapper(XamlRoot xamlRoot, Func getWindowFunc) : base(xamlRoot) { + _getWindowFunc = getWindowFunc; +#else + public WpfNativeOpenGLWrapper(XamlRoot xamlRoot) + { +#endif + #if WINDOWS_UWP || WINAPPSDK - var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(getWindowFunc()); + var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(_getWindowFunc()); #else - if (element.XamlRoot?.HostWindow?.NativeWindow is not WpfWindow wpfWindow) + if (xamlRoot.HostWindow?.NativeWindow is not WpfWindow wpfWindow) { - throw new InvalidOperationException($"The XamlRoot and its NativeWindow must be initialized on the element before calling {nameof(CreateContext)}."); + throw new InvalidOperationException($"The XamlRoot and its NativeWindow must be initialized on the element before constructing a {_type.Name}."); } var hwnd = new WindowInteropHelper(wpfWindow).Handle; #endif @@ -75,19 +99,11 @@ public void CreateContext(UIElement element) if (pixelFormat == 0) { - if (this.Log().IsEnabled(LogLevel.Error)) - { - this.Log().Error($"ChoosePixelFormat failed"); - } throw new InvalidOperationException("ChoosePixelFormat failed"); } if (WindowsRenderingNativeMethods.SetPixelFormat(_hdc, pixelFormat, ref pfd) == 0) { - if (this.Log().IsEnabled(LogLevel.Error)) - { - this.Log().Error($"SetPixelFormat failed"); - } throw new InvalidOperationException("ChoosePixelFormat failed"); } @@ -95,17 +111,38 @@ public void CreateContext(UIElement element) if (_glContext == IntPtr.Zero) { - if (this.Log().IsEnabled(LogLevel.Error)) - { - this.Log().Error($"wglCreateContext failed"); - } throw new InvalidOperationException("ChoosePixelFormat failed"); } } - public object CreateGLSilkNETHandle() => GL.GetApi(new WindowsGlNativeContext()); + // https://sharovarskyi.com/blog/posts/csharp-win32-opengl-silknet/ + public bool TryGetProcAddress(string proc, out nint addr) + { + if (_opengl32.Value == IntPtr.Zero) + { + addr = IntPtr.Zero; + return false; + } + if (NativeLibrary.TryGetExport(_opengl32.Value, proc, out addr)) + { + return true; + } + + addr = WindowsRenderingNativeMethods.wglGetProcAddress(proc); + return addr != IntPtr.Zero; + } + + public nint GetProcAddress(string proc) + { + if (TryGetProcAddress(proc, out var address)) + { + return address; + } - public void DestroyContext() + throw new InvalidOperationException("No function was found with the name " + proc + "."); + } + + public void Dispose() { if (WindowsRenderingNativeMethods.wglDeleteContext(_glContext) == 0) { @@ -140,42 +177,4 @@ public IDisposable MakeCurrent() } }); } - - // https://sharovarskyi.com/blog/posts/csharp-win32-opengl-silknet/ - private class WindowsGlNativeContext : INativeContext - { - private readonly UnmanagedLibrary _l; - - public WindowsGlNativeContext() - { - _l = new UnmanagedLibrary("opengl32.dll"); - if (_l.Handle == IntPtr.Zero) - { - throw new PlatformNotSupportedException("Unable to load opengl32.dll. Make sure you're running on a system with OpenGL support"); - } - } - - public bool TryGetProcAddress(string proc, out nint addr, int? slot = null) - { - if (_l.TryLoadFunction(proc, out addr)) - { - return true; - } - - addr = WindowsRenderingNativeMethods.wglGetProcAddress(proc); - return addr != IntPtr.Zero; - } - - public nint GetProcAddress(string proc, int? slot = null) - { - if (TryGetProcAddress(proc, out var address, slot)) - { - return address; - } - - throw new InvalidOperationException("No function was found with the name " + proc + "."); - } - - public void Dispose() => _l.Dispose(); - } } diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Uno.UI.Runtime.Skia.Wpf.csproj b/src/Uno.UI.Runtime.Skia.Wpf/Uno.UI.Runtime.Skia.Wpf.csproj index 2ac97c1882cf..704fa9d750ee 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Uno.UI.Runtime.Skia.Wpf.csproj +++ b/src/Uno.UI.Runtime.Skia.Wpf/Uno.UI.Runtime.Skia.Wpf.csproj @@ -33,7 +33,6 @@ - diff --git a/src/Uno.UI.Runtime.Skia.X11/Uno.UI.Runtime.Skia.X11.csproj b/src/Uno.UI.Runtime.Skia.X11/Uno.UI.Runtime.Skia.X11.csproj index a4c40812cb32..9fca805f2504 100644 --- a/src/Uno.UI.Runtime.Skia.X11/Uno.UI.Runtime.Skia.X11.csproj +++ b/src/Uno.UI.Runtime.Skia.X11/Uno.UI.Runtime.Skia.X11.csproj @@ -33,8 +33,6 @@ - - diff --git a/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs b/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs index a6d6393c3436..f40b91496cd6 100644 --- a/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs +++ b/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs @@ -67,7 +67,7 @@ static X11ApplicationHost() ApiExtensibility.Register(typeof(Windows.ApplicationModel.DataTransfer.DragDrop.Core.IDragDropExtension), o => new X11DragDropExtension(o)); - ApiExtensibility.Register(typeof(Uno.Graphics.INativeOpenGLWrapper), _ => new X11NativeOpenGLWrapper()); + ApiExtensibility.Register(typeof(Uno.Graphics.INativeOpenGLWrapper), xamlRoot => new X11NativeOpenGLWrapper(xamlRoot)); } public X11ApplicationHost(Func appBuilder) diff --git a/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs index 05b90001ad7f..6ce745559812 100644 --- a/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs +++ b/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs @@ -1,7 +1,7 @@ using System; using Microsoft.UI.Xaml; -using Silk.NET.OpenGL; using Uno.Disposables; +using Uno.Foundation.Logging; using Uno.Graphics; namespace Uno.WinUI.Runtime.Skia.X11; @@ -12,11 +12,11 @@ internal class X11NativeOpenGLWrapper : INativeOpenGLWrapper private IntPtr _glContext; private IntPtr _pBuffer; - public unsafe void CreateContext(UIElement element) - { - if (element.XamlRoot is null || X11Manager.XamlRootMap.GetHostForRoot(element.XamlRoot) is not X11XamlRootHost xamlRootHost) + public unsafe X11NativeOpenGLWrapper(XamlRoot xamlRoot) + { + if (X11Manager.XamlRootMap.GetHostForRoot(xamlRoot) is not X11XamlRootHost xamlRootHost) { - throw new InvalidOperationException($"The XamlRoot and its XamlRootHost must be initialized on the element before calling {nameof(CreateContext)}."); + throw new InvalidOperationException($"The XamlRoot and its XamlRootHost must be initialized on the element before constructing an {nameof(X11NativeOpenGLWrapper)}."); } _display = xamlRootHost.RootX11Window.Display; @@ -34,41 +34,55 @@ public unsafe void CreateContext(UIElement element) (int)X11Helper.None }; - IntPtr bestFbc = IntPtr.Zero; - XVisualInfo* visual = null; - var ptr = GlxInterface.glXChooseFBConfig(_display, XLib.XDefaultScreen(_display), glxAttribs, out var count); - if (ptr == null || *ptr == IntPtr.Zero) + var fbConfigs = GlxInterface.glXChooseFBConfig(_display, XLib.XDefaultScreen(_display), glxAttribs, out var count); + if (fbConfigs == null || *fbConfigs == IntPtr.Zero) { throw new InvalidOperationException($"{nameof(GlxInterface.glXChooseFBConfig)} failed to retrieve GLX framebuffer configurations."); } + using var fbConfigsDisposable = new DisposableStruct(static aa => { _ = XLib.XFree(aa); }, (IntPtr)fbConfigs); + + IntPtr bestFbc = IntPtr.Zero; for (var c = 0; c < count; c++) { - XVisualInfo* visual_ = GlxInterface.glXGetVisualFromFBConfig(_display, ptr[c]); - if (visual_->depth == 32) // 24bit color + 8bit stencil as requested above + XVisualInfo* visual = GlxInterface.glXGetVisualFromFBConfig(_display, fbConfigs[c]); + using var visualDisposable = new DisposableStruct(static aa => { _ = XLib.XFree(aa); }, (IntPtr)visual); + if (visual->depth == 32) // 24bit color + 8bit stencil as requested above { - bestFbc = ptr[c]; - visual = visual_; + bestFbc = fbConfigs[c]; break; } } - if (visual == null) + if (bestFbc == IntPtr.Zero) { - throw new InvalidOperationException("Could not create correct visual window.\n"); + throw new InvalidOperationException("Could not find a suitable framebuffer config.\n"); } _glContext = GlxInterface.glXCreateNewContext(_display, bestFbc, GlxConsts.GLX_RGBA_TYPE, IntPtr.Zero, /* True */ 1); + if (_glContext == IntPtr.Zero) + { + throw new InvalidOperationException($"{nameof(GlxInterface.glXCreateNewContext)} failed."); + } _pBuffer = GlxInterface.glXCreatePbuffer(_display, bestFbc, new[] { (int)X11Helper.None }); + if (_pBuffer == IntPtr.Zero) + { + throw new InvalidOperationException($"{nameof(GlxInterface.glXCreatePbuffer)} failed."); + } } - public object CreateGLSilkNETHandle() => GL.GetApi(GlxInterface.glXGetProcAddress); - - public void DestroyContext() + public void Dispose() { using var lockDisposable = X11Helper.XLock(_display); + using var _ = MakeCurrent(); - GlxInterface.glXDestroyPbuffer(_display, _pBuffer); - GlxInterface.glXDestroyContext(_display, _glContext); + if (_display != IntPtr.Zero && _pBuffer != IntPtr.Zero) + { + GlxInterface.glXDestroyPbuffer(_display, _pBuffer); + } + if (_display != IntPtr.Zero && _glContext != IntPtr.Zero) + { + GlxInterface.glXDestroyContext(_display, _glContext); + } _display = default; _glContext = default; @@ -82,4 +96,11 @@ public IDisposable MakeCurrent() GlxInterface.glXMakeCurrent(_display, _pBuffer, _glContext); return Disposable.Create(() => GlxInterface.glXMakeCurrent(_display, drawable, glContext)); } + + public IntPtr GetProcAddress(string proc) => GlxInterface.glXGetProcAddress(proc); + public bool TryGetProcAddress(string proc, out IntPtr addr) + { + addr = GlxInterface.glXGetProcAddress(proc); + return addr != IntPtr.Zero; + } } diff --git a/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs b/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs index fbf9cf3e8c60..595fedf061db 100644 --- a/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs +++ b/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs @@ -1,5 +1,9 @@ -using System; +#nullable enable + +using System; using Microsoft.UI.Xaml; +using Uno.Foundation.Extensibility; +using Uno.Foundation.Logging; #if WINAPPSDK || WINDOWS_UWP namespace Uno.WinUI.Graphics3DGL; @@ -7,36 +11,12 @@ namespace Uno.WinUI.Graphics3DGL; namespace Uno.Graphics; #endif -internal interface INativeOpenGLWrapper +internal interface INativeOpenGLWrapper : IDisposable { - public delegate IntPtr GLGetProcAddress(string proc); - - /// - /// Creates an OpenGL context for a native window/surface that the - /// belongs to. The - /// will be associated with this element until a corresponding call to . - /// - public void CreateContext(UIElement element); - - /// - /// This should be cast to a Silk.NET.OpenGL.GL (even on devices with GLES, not Desktop GL). - /// The Silk.NET.OpenGL.GL API surface is a almost a complete superset of the Silk.NET.OpenGLES.GL API - /// surface (with the exception of BlendBarrier and PrimitiveBoundingBox which are OpenGL ES 3.2-only APIs). - /// So, we always expose an OpenGL.GL object even on GLES devices, with the caveat that the user is only - /// allowed to make calls that are available on this GL (or GLES) version (just like OpenGL 4-only APIs - /// aren't allowed on OpenGL 3 implementations). - /// - public object CreateGLSilkNETHandle(); + public IntPtr GetProcAddress(string proc); - /// - /// Destroys the context created in . This is only called if a preceding - /// call to is made (after the last call to ). - /// - public void DestroyContext(); + public bool TryGetProcAddress(string proc, out IntPtr addr); - /// - /// Makes the OpenGL context created in the current context for the thread. - /// /// A disposable that restores the OpenGL context to what it was at the time of this method call. public IDisposable MakeCurrent(); } From 03ae3aa71dd41fc6b047c27f318fea88d6e329e0 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 7 Oct 2024 16:23:51 +0300 Subject: [PATCH 14/27] chore: error image fallback for GLCanvasElement --- .../Uno.WinUI.Graphics3DGL/Assets/error.png | Bin 0 -> 71036 bytes .../Uno.WinUI.Graphics3DGL.csproj | 11 +++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 src/AddIns/Uno.WinUI.Graphics3DGL/Assets/error.png diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/Assets/error.png b/src/AddIns/Uno.WinUI.Graphics3DGL/Assets/error.png new file mode 100644 index 0000000000000000000000000000000000000000..cceacbc6ee7b7e3260c98493db806087c5f06cfe GIT binary patch literal 71036 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

-6808I1$iW%IA8H$-)O`F2(l_G!;0hr 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 Hr-UW|g%{`= literal 0 HcmV?d00001 diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj index f5924253668f..e176af8f4173 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj @@ -68,10 +68,13 @@ - + + + + + PackagePath="runtimes/osx/native" + Pack="true" + Visible="False" /> From 8a031b71f02991d1c54617f8b8a324930032b6c2 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 7 Oct 2024 09:42:37 -0400 Subject: [PATCH 15/27] chore: remove angle build script now that it's ported to Silk.NET's CI. Pull from Silk.NET's github instead. --- .../Uno.WinUI.Graphics3DGL/build_angle.sh | 72 ++----------------- 1 file changed, 4 insertions(+), 68 deletions(-) diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/build_angle.sh b/src/AddIns/Uno.WinUI.Graphics3DGL/build_angle.sh index 86fc1fb0b753..f0002b1fb185 100755 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/build_angle.sh +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/build_angle.sh @@ -1,72 +1,8 @@ #/usr/bin/env bash set -euo pipefail -pushd . > /dev/null - -mkdir -p angle -cd angle - -# It's not enough to check for the existence of depot_tools/.git as the cloning may have been interrupted. -# The git status call makes sure that the repo is not corrupted. -if [ ! -d depot_tools/.git ] || ! git -C depot_tools status; then - rm -rf depot_tools - git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git -fi - -export PATH="$(pwd)/depot_tools:${PATH}" - -if [ ! -f angle_source/angle_fetched_successfully ]; then - rm -rf angle_source - mkdir angle_source - cd angle_source - fetch --no-history angle - - # remove unnecessary dirs for space - rm -rf third_party/dawn third_party/VK-GL-CTS - find ./tools ./third_party -name "*.git" | xargs rm -rf - - touch angle_fetched_successfully -else - cd angle_source - echo "Already fetched angle sources. Skipping fetch." -fi - -for arch in arm64 x64 -do - if [ ! -f "out/Release_${arch}/build_succeeded" ]; then - rm -rf "out/Release_${arch}" - mkdir -p "out/Release_${arch}" - - # we remove as much as we can from the build to save space for the space-limited CI machines - cat << EOF > "out/Release_${arch}/args.gn" -target_cpu = "${arch}" -is_debug = false -angle_assert_always_on = true -angle_build_tests = false -build_angle_deqp_tests = false -build_angle_perftests = false -angle_enable_vulkan = false -angle_enable_wgpu = false -angle_enable_abseil = false -angle_enable_gl_null = false -EOF - - gn gen "out/Release_${arch}" - autoninja -C "out/Release_${arch}" - touch "out/Release_${arch}/build_succeeded" - else - echo "Already compiled angle for ${arch}. Skipping ${arch} compilation." - fi -done - -popd > /dev/null - mkdir -p angle_binaries/osx - -# create universal mac binaries -shopt -s nullglob -for lib in libEGL.* libGLESv2.* -do - lipo -create angle/angle_source/out/Release_arm64/${lib} angle/angle_source/out/Release_x64/${lib} -output angle_binaries/osx/${lib} -done -shopt -u nullglob \ No newline at end of file +pushd angle_binaries/osx +curl -O -L https://github.com/dotnet/Silk.NET/raw/refs/heads/main/src/Native/Silk.NET.OpenGLES.ANGLE.Native/runtimes/osx/native/libEGL.dylib +curl -O -L https://github.com/dotnet/Silk.NET/raw/refs/heads/main/src/Native/Silk.NET.OpenGLES.ANGLE.Native/runtimes/osx/native/libGLESv2.dylib +popd \ No newline at end of file From f6c14876db8e528b43a60a4cd8a4b44993835d78 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 7 Oct 2024 10:17:59 -0400 Subject: [PATCH 16/27] chore: add precision qualifiers for gles shaders --- .../RotatingCubeGlCanvasElement.cs | 4 ++ .../SimpleTriangleGlCanvasElement.cs | 2 + .../MacOSNativeOpenGLWrapper.cs | 6 +-- .../Extensions/WpfNativeOpenGLWrapper.cs | 48 +++++++++---------- .../X11NativeOpenGLWrapper.cs | 4 +- 5 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs index 5bba4187e1a7..2064d2e6278d 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs @@ -69,6 +69,8 @@ public class RotatingCubeGlCanvasElement() : GLCanvasElement(1200, 800, null) private readonly string _vertexShaderSource = """ + precision highp float; + layout(location = 0) in vec3 pos; layout(location = 1) in vec3 vertex_color; @@ -84,6 +86,8 @@ void main() { private readonly string FragmentShaderSource = """ + precision highp float; + in vec3 color; out vec4 frag_color; diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs index 8eb3d274d42d..33f1e414298a 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs @@ -44,6 +44,7 @@ unsafe protected override void Init(GL gl) var vertexCode = $$""" {{versionDef}} + precision highp float; layout (location = 0) in vec3 aPosition; out vec4 vertexColor; @@ -58,6 +59,7 @@ void main() var fragmentCode = $$""" {{versionDef}} + precision highp float; out vec4 out_color; in vec4 vertexColor; diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs index 5366a416fceb..1ba48c61139b 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs @@ -98,10 +98,10 @@ public IntPtr GetProcAddress(string proc) public bool TryGetProcAddress(string proc, out IntPtr addr) { if (_libGLES.Value == IntPtr.Zero) - { + { addr = IntPtr.Zero; - return false; - } + return false; + } return NativeLibrary.TryGetExport(_libGLES.Value, proc, out addr); } diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs index 37257ad8958a..846fdd5b1b38 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs @@ -39,16 +39,16 @@ internal class WpfNativeOpenGLWrapper private static readonly Type _type = typeof(WpfNativeOpenGLWrapper); #endif private static readonly Lazy _opengl32 = new Lazy(() => - { - if (!NativeLibrary.TryLoad("opengl32.dll", _type.Assembly, DllImportSearchPath.UserDirectories, out var _handle)) - { - if (_type.Log().IsEnabled(LogLevel.Error)) - { - _type.Log().Error("opengl32.dll was not loaded successfully."); - } - } - return _handle; - }); + { + if (!NativeLibrary.TryLoad("opengl32.dll", _type.Assembly, DllImportSearchPath.UserDirectories, out var _handle)) + { + if (_type.Log().IsEnabled(LogLevel.Error)) + { + _type.Log().Error("opengl32.dll was not loaded successfully."); + } + } + return _handle; + }); #if WINDOWS_UWP || WINAPPSDK private readonly Func _getWindowFunc @@ -57,7 +57,7 @@ private readonly Func _getWindowFunc private nint _glContext; #if WINDOWS_UWP || WINAPPSDK - public WinUINativeOpenGLWrapper(XamlRoot xamlRoot, Func getWindowFunc) : base(xamlRoot) + public WinUINativeOpenGLWrapper(XamlRoot xamlRoot, Func getWindowFunc) : base(xamlRoot) { _getWindowFunc = getWindowFunc; #else @@ -117,7 +117,7 @@ public WpfNativeOpenGLWrapper(XamlRoot xamlRoot) // https://sharovarskyi.com/blog/posts/csharp-win32-opengl-silknet/ public bool TryGetProcAddress(string proc, out nint addr) - { + { if (_opengl32.Value == IntPtr.Zero) { addr = IntPtr.Zero; @@ -125,22 +125,22 @@ public bool TryGetProcAddress(string proc, out nint addr) } if (NativeLibrary.TryGetExport(_opengl32.Value, proc, out addr)) { - return true; + return true; } - addr = WindowsRenderingNativeMethods.wglGetProcAddress(proc); - return addr != IntPtr.Zero; - } + addr = WindowsRenderingNativeMethods.wglGetProcAddress(proc); + return addr != IntPtr.Zero; + } public nint GetProcAddress(string proc) - { - if (TryGetProcAddress(proc, out var address)) - { - return address; - } - - throw new InvalidOperationException("No function was found with the name " + proc + "."); - } + { + if (TryGetProcAddress(proc, out var address)) + { + return address; + } + + throw new InvalidOperationException("No function was found with the name " + proc + "."); + } public void Dispose() { diff --git a/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs index 6ce745559812..478731979cb0 100644 --- a/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs +++ b/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs @@ -13,7 +13,7 @@ internal class X11NativeOpenGLWrapper : INativeOpenGLWrapper private IntPtr _pBuffer; public unsafe X11NativeOpenGLWrapper(XamlRoot xamlRoot) - { + { if (X11Manager.XamlRootMap.GetHostForRoot(xamlRoot) is not X11XamlRootHost xamlRootHost) { throw new InvalidOperationException($"The XamlRoot and its XamlRootHost must be initialized on the element before constructing an {nameof(X11NativeOpenGLWrapper)}."); @@ -98,7 +98,7 @@ public IDisposable MakeCurrent() } public IntPtr GetProcAddress(string proc) => GlxInterface.glXGetProcAddress(proc); - public bool TryGetProcAddress(string proc, out IntPtr addr) + public bool TryGetProcAddress(string proc, out IntPtr addr) { addr = GlxInterface.glXGetProcAddress(proc); return addr != IntPtr.Zero; From 8a4e5da4a76ff6f0f458c8812e6663fed8deede1 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 7 Oct 2024 19:31:08 +0300 Subject: [PATCH 17/27] chore: minor touches --- .../Uno.WinUI.Graphics3DGL/GLCanvasElement.cs | 119 +++++++++++------- .../MacOSNativeOpenGLWrapper.cs | 1 - .../Extensions/WpfNativeOpenGLWrapper.cs | 11 +- .../X11NativeOpenGLWrapper.cs | 1 - src/Uno.UI/Graphics/INativeOpenGLWrapper.cs | 3 - 5 files changed, 79 insertions(+), 56 deletions(-) diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs index 4b7287269220..8bd22513e694 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; +using Microsoft.UI.Dispatching; using Silk.NET.OpenGL; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -14,10 +15,6 @@ #if WINAPPSDK using System.Runtime.InteropServices; using System.Runtime.InteropServices.WindowsRuntime; -using Microsoft.Extensions.Logging; -using Microsoft.UI.Dispatching; -using Uno.Extensions; -using Uno.Logging; #else using Uno.Foundation.Extensibility; using Uno.Graphics; @@ -120,13 +117,12 @@ protected GLCanvasElement(uint width, uint height, Func? getWindowFunc) try { #if WINAPPSDK - var nativeOpenGlWrapper = new WinUINativeOpenGLWrapper(getWindowFunc); + var nativeOpenGlWrapper = new WinUINativeOpenGLWrapper(xamlRoot, getWindowFunc!); #else if (!ApiExtensibility.CreateInstance(xamlRoot, out var nativeOpenGlWrapper)) { throw new InvalidOperationException($"Couldn't create a {nameof(INativeOpenGLWrapper)} object. Make sure you are running on a platform with OpenGL support."); } - xamlRoot.HostWindow!.Closed += (_, _) => { nativeOpenGlWrapper.Dispose(); }; #endif return nativeOpenGlWrapper; } @@ -140,6 +136,20 @@ protected GLCanvasElement(uint width, uint height, Func? getWindowFunc) } } + private void OnClosed(object o, WindowEventArgs windowEventArgs) + { + if (GlAvailable!.Value) + { + // OnUnloaded is called after OnClosed, which leads to disposing the context first and then trying to + // delete the framebuffer, etc. and this causes exceptions. + DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () => + { + using var _ = _nativeOpenGlWrapper!.MakeCurrent(); + _nativeOpenGlWrapper?.Dispose(); + }); + } + } + ///

/// Invalidates the rendering, and queues a call to . /// will only be called once after and the output will @@ -152,6 +162,7 @@ protected GLCanvasElement(uint width, uint height, Func? getWindowFunc) public void Invalidate() => NativeDispatcher.Main.Enqueue(Render, NativeDispatcherPriority.Idle); #endif + /// not null after the first instance is loaded. private bool? GlAvailable { get => _glAvailable; @@ -180,51 +191,64 @@ private unsafe void OnLoaded(object sender, RoutedEventArgs routedEventArgs) GlAvailable = true; _nativeOpenGlWrapper = glWrapper; } + } - if (GlAvailable.Value) - { - _gl = GL.GetApi(this); - GlAvailable = true; + if (!GlAvailable!.Value) + { + return; + } + + _gl = GL.GetApi(this); + GlAvailable = true; #if WINAPPSDK - _pixels = Marshal.AllocHGlobal((int)(_width * _height * BytesPerPixel)); + _pixels = Marshal.AllocHGlobal((int)(_width * _height * BytesPerPixel)); #endif - using (_nativeOpenGlWrapper!.MakeCurrent()) + using (_nativeOpenGlWrapper!.MakeCurrent()) + { + _framebuffer = _gl.GenBuffer(); + _gl.BindFramebuffer(GLEnum.Framebuffer, _framebuffer); + { + _textureColorBuffer = _gl.GenTexture(); + _gl.BindTexture(GLEnum.Texture2D, _textureColorBuffer); + { + _gl.TexImage2D(GLEnum.Texture2D, 0, InternalFormat.Rgb, _width, _height, 0, GLEnum.Rgb, + GLEnum.UnsignedByte, (void*)0); + _gl.TexParameterI(GLEnum.Texture2D, GLEnum.TextureMinFilter, (uint)GLEnum.Linear); + _gl.TexParameterI(GLEnum.Texture2D, GLEnum.TextureMagFilter, (uint)GLEnum.Linear); + _gl.FramebufferTexture2D(GLEnum.Framebuffer, FramebufferAttachment.ColorAttachment0, + GLEnum.Texture2D, _textureColorBuffer, 0); + } + _gl.BindTexture(GLEnum.Texture2D, 0); + + _renderBuffer = _gl.GenRenderbuffer(); + _gl.BindRenderbuffer(GLEnum.Renderbuffer, _renderBuffer); + { + _gl.RenderbufferStorage(GLEnum.Renderbuffer, InternalFormat.Depth24Stencil8, _width, _height); + _gl.FramebufferRenderbuffer(GLEnum.Framebuffer, GLEnum.DepthStencilAttachment, + GLEnum.Renderbuffer, _renderBuffer); + } + _gl.BindRenderbuffer(GLEnum.Renderbuffer, 0); + + if (_gl.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete) { - _framebuffer = _gl.GenBuffer(); - _gl.BindFramebuffer(GLEnum.Framebuffer, _framebuffer); - { - _textureColorBuffer = _gl.GenTexture(); - _gl.BindTexture(GLEnum.Texture2D, _textureColorBuffer); - { - _gl.TexImage2D(GLEnum.Texture2D, 0, InternalFormat.Rgb, _width, _height, 0, GLEnum.Rgb, GLEnum.UnsignedByte, (void*)0); - _gl.TexParameterI(GLEnum.Texture2D, GLEnum.TextureMinFilter, (uint)GLEnum.Linear); - _gl.TexParameterI(GLEnum.Texture2D, GLEnum.TextureMagFilter, (uint)GLEnum.Linear); - _gl.FramebufferTexture2D(GLEnum.Framebuffer, FramebufferAttachment.ColorAttachment0, GLEnum.Texture2D, _textureColorBuffer, 0); - } - _gl.BindTexture(GLEnum.Texture2D, 0); - - _renderBuffer = _gl.GenRenderbuffer(); - _gl.BindRenderbuffer(GLEnum.Renderbuffer, _renderBuffer); - { - _gl.RenderbufferStorage(GLEnum.Renderbuffer, InternalFormat.Depth24Stencil8, _width, _height); - _gl.FramebufferRenderbuffer(GLEnum.Framebuffer, GLEnum.DepthStencilAttachment, GLEnum.Renderbuffer, _renderBuffer); - } - _gl.BindRenderbuffer(GLEnum.Renderbuffer, 0); - - if (_gl.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete) - { - throw new InvalidOperationException("Offscreen framebuffer is not complete"); - } - - Init(_gl); - } - _gl.BindFramebuffer(GLEnum.Framebuffer, 0); + throw new InvalidOperationException("Offscreen framebuffer is not complete"); } + + Init(_gl); } + _gl.BindFramebuffer(GLEnum.Framebuffer, 0); } + var window = +#if WINAPPSDK + _getWindowFunc!(); +#else + XamlRoot?.HostWindow; +#endif + window!.Closed += OnClosed; + Invalidate(); } @@ -267,6 +291,14 @@ private void OnUnloaded(object sender, RoutedEventArgs routedEventArgs) #if WINAPPSDK _pixels = default; #endif + + var window = +#if WINAPPSDK + _getWindowFunc!(); +#else + XamlRoot?.HostWindow; +#endif + window!.Closed -= OnClosed; } private unsafe void Render() @@ -305,6 +337,7 @@ private unsafe void Render() } } - public IntPtr GetProcAddress(string proc, int? slot = null) => _nativeOpenGlWrapper!.GetProcAddress(proc); - public bool TryGetProcAddress(string proc, [UnscopedRef] out IntPtr addr, int? slot = null) => _nativeOpenGlWrapper!.TryGetProcAddress(proc, out addr); + IntPtr INativeContext.GetProcAddress(string proc, int? slot) => _nativeOpenGlWrapper!.GetProcAddress(proc); + bool INativeContext.TryGetProcAddress(string proc, [UnscopedRef] out IntPtr addr, int? slot) => _nativeOpenGlWrapper!.TryGetProcAddress(proc, out addr); + void IDisposable.Dispose() { /* Keep this empty. This is only for INativeContext and will be called by Silk.NET, not us. */ } } diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs index 1ba48c61139b..5639f56730a3 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs @@ -107,7 +107,6 @@ public bool TryGetProcAddress(string proc, out IntPtr addr) public void Dispose() { - using var _ = MakeCurrent(); if (_eglDisplay != IntPtr.Zero && _pBufferSurface != IntPtr.Zero) { EglDestroySurface(_eglDisplay, _pBufferSurface); diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs index 846fdd5b1b38..513c15c561c2 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs @@ -51,13 +51,13 @@ internal class WpfNativeOpenGLWrapper }); #if WINDOWS_UWP || WINAPPSDK - private readonly Func _getWindowFunc + private readonly Func _getWindowFunc; #endif private nint _hdc; private nint _glContext; #if WINDOWS_UWP || WINAPPSDK - public WinUINativeOpenGLWrapper(XamlRoot xamlRoot, Func getWindowFunc) : base(xamlRoot) + public WinUINativeOpenGLWrapper(XamlRoot xamlRoot, Func getWindowFunc) { _getWindowFunc = getWindowFunc; #else @@ -118,12 +118,7 @@ public WpfNativeOpenGLWrapper(XamlRoot xamlRoot) // https://sharovarskyi.com/blog/posts/csharp-win32-opengl-silknet/ public bool TryGetProcAddress(string proc, out nint addr) { - if (_opengl32.Value == IntPtr.Zero) - { - addr = IntPtr.Zero; - return false; - } - if (NativeLibrary.TryGetExport(_opengl32.Value, proc, out addr)) + if (_opengl32.Value != IntPtr.Zero && NativeLibrary.TryGetExport(_opengl32.Value, proc, out addr)) { return true; } diff --git a/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs index 478731979cb0..f094f2540b17 100644 --- a/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs +++ b/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs @@ -73,7 +73,6 @@ public unsafe X11NativeOpenGLWrapper(XamlRoot xamlRoot) public void Dispose() { using var lockDisposable = X11Helper.XLock(_display); - using var _ = MakeCurrent(); if (_display != IntPtr.Zero && _pBuffer != IntPtr.Zero) { diff --git a/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs b/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs index 595fedf061db..015d953dcd5d 100644 --- a/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs +++ b/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs @@ -1,9 +1,6 @@ #nullable enable using System; -using Microsoft.UI.Xaml; -using Uno.Foundation.Extensibility; -using Uno.Foundation.Logging; #if WINAPPSDK || WINDOWS_UWP namespace Uno.WinUI.Graphics3DGL; From 2698365fc070058532151b4c5150bfef5e1d0b9f Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 7 Oct 2024 20:28:54 +0300 Subject: [PATCH 18/27] chore: PR comments --- .../Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj | 2 +- src/AddIns/Uno.WinUI.Graphics3DGL/build_angle.sh | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj index e176af8f4173..39f3a9fc6d00 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj @@ -1,6 +1,6 @@  - $(NetPrevious) + $(NetSkiaPreviousAndCurrent) $(NetUWPOrWinUI) Uno.WinUI.Graphics3DGL diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/build_angle.sh b/src/AddIns/Uno.WinUI.Graphics3DGL/build_angle.sh index f0002b1fb185..877faed975c6 100755 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/build_angle.sh +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/build_angle.sh @@ -3,6 +3,6 @@ set -euo pipefail mkdir -p angle_binaries/osx pushd angle_binaries/osx -curl -O -L https://github.com/dotnet/Silk.NET/raw/refs/heads/main/src/Native/Silk.NET.OpenGLES.ANGLE.Native/runtimes/osx/native/libEGL.dylib -curl -O -L https://github.com/dotnet/Silk.NET/raw/refs/heads/main/src/Native/Silk.NET.OpenGLES.ANGLE.Native/runtimes/osx/native/libGLESv2.dylib -popd \ No newline at end of file +curl -O -L https://github.com/dotnet/Silk.NET/raw/a9c75a61528ee7b4e55442f5141cb132f10d72ff/src/Native/Silk.NET.OpenGLES.ANGLE.Native/runtimes/osx/native/libEGL.dylib +curl -O -L https://github.com/dotnet/Silk.NET/raw/a9c75a61528ee7b4e55442f5141cb132f10d72ff/src/Native/Silk.NET.OpenGLES.ANGLE.Native/runtimes/osx/native/libGLESv2.dylib +popd From 23554907895d89b4ea8c8f69922bcbaad49a6416 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 7 Oct 2024 20:29:08 +0300 Subject: [PATCH 19/27] chore: docs refinement --- doc/articles/controls/GLCanvasElement.md | 32 +++++++++++++----------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/doc/articles/controls/GLCanvasElement.md b/doc/articles/controls/GLCanvasElement.md index daf031096401..3f1c56d2e3c9 100644 --- a/doc/articles/controls/GLCanvasElement.md +++ b/doc/articles/controls/GLCanvasElement.md @@ -5,7 +5,7 @@ uid: Uno.Controls.GLCanvasElement ## Uno.WinUI.Graphics3D.GLCanvasElement > [!IMPORTANT] -> This functionality is only available on WinUI, Android and Skia Desktop (`netX.0-desktop`) and targets that are running on platforms with support for hardware acceleration. On Windows and Linux, this means having (desktop) OpenGL support. On Mac OS X, this means having Vulkan support (to be used by ANGLE). This is also not available on MacOS. +> This functionality is only available on WinUI and Skia Desktop (`netX.0-desktop`) targets that are running on platforms with support for hardware acceleration. On Windows and Linux, this means having (desktop) OpenGL support. On Mac OS X, this means having Vulkan support (to be used by [ANGLE](https://en.wikipedia.org/wiki/ANGLE_(software))). `GLCanvasElement` is a `Grid` for drawing 3D graphics with OpenGL. This class comes as a part of the `Uno.WinUI.Graphics3D` package. @@ -21,16 +21,16 @@ protected abstract void OnDestroy(GL gl); The protected constructor has `width` and `height` parameters, which decide the resolution of the offscreen framebuffer that the `GLCanvasElement` will draw onto. Note that these parameters are unrelated to the final size of the drawing in the window. After drawing (using `RenderOverride`) is done, the output is resized to fit the arranged size of the `GLCanvasElement`. You can control the final size just like any other `Grid`, e.g. using `MeasureOverride`, `ArrangeOverride`, the `Width/Height` properties, etc. -On WinUI, the protected constructor additionally requires a `Func` argument that fetches the `Microsoft.UI.Xaml.Window` object that the `GLCanvasElement` belongs to. This function is required because WinUI doesn't provide a way to get the `Window` of a `FrameworkElement`. This paramater is ignored on Uno Platform and can be set to null. This function is only called while the `GLCanvasElement` is loaded. +The protected constructor additionally requires a `Func` argument that fetches the `Microsoft.UI.Xaml.Window` object that the `GLCanvasElement` belongs to. This function is required because WinUI doesn't provide a way to get the `Window` of a `FrameworkElement`. This paramater is ignored on Uno Platform and can be set to null. This function is only called while the `GLCanvasElement` is still in the visual tree. The 3 abstract methods above all take a `Silk.NET.OpenGL.GL` parameter that can be used to make OpenGL calls. -The `Init` method is a regular OpenGL setup method that you can use to set up the needed OpenGL objects, like textures, Vertex Array Buffers (VAOs), Element Array Buffers (EBOs), etc. - -The `OnDestroy` method is the complement of `Init` and is used to clean up any allocated resources. +The `Init` method is a regular OpenGL setup method that you can use to set up the needed OpenGL objects, like textures, Vertex Array Buffers (VAOs), Element Array Buffers (EBOs), etc. The `OnDestroy` method is the complement of `Init` and is used to clean up any allocated resources. `Init` and `OnDestroy` might be called multiple times alternatingly. In other words, 2 `OnDestroy` calls are guaranteed to have an `Init` call in between and vice versa. 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. +On MacOS, since OpenGL support is not natively present, we use [ANGLE](https://en.wikipedia.org/wiki/ANGLE_(software)) to provide OpenGL ES support. This means that we're actually using OpenGL ES 3.00, not OpenGL. Due to the similarity between desktop OpenGL and OpenGL ES, (almost) all the OpenGL ES functions are present in the `Silk.NET.OpenGL.GL` API surface and therefore we can use the same class to represent both the OpenGL and OpenGL ES APIs. To run the same `GLCanvasElement` subclasses on all supported platforms, make sure to use a subset of functions that are present in both APIs (which is almost all of OpenGL ES). + 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`. @@ -82,12 +82,12 @@ public partial class GLCanvasElementExample : UserControl ```csharp // GLTriangleElement.cs # __SKIA__ || WINAPPSDK +// https://learnopengl.com/Getting-started/Hello-Triangle public class SimpleTriangleGlCanvasElement() -#if __SKIA__ - : GLCanvasElement(1200, 800, null) -#elif WINAPPSDK - // getWindowFunc is usually implemented by having a static property that stores the Window object when creating it (usually in App.cs) and then fetching it in getWindowFunc - : GLCanvasElement(1200, 800, /* your getWindowFunc */) +#if WINAPPSDK + : GLCanvasElement(1200, 800, () => SamplesApp.App.MainWindow) +#else + : GLCanvasElement(1200, 800, null) #endif { private uint _vao; @@ -113,10 +113,14 @@ public class SimpleTriangleGlCanvasElement() gl.VertexAttribPointer(0, 3, GLEnum.Float, false, 3 * sizeof(float), (void*)0); gl.EnableVertexAttribArray(0); - // string.Empty is added so that the version line is not interpreted as a preprocessor command + var slVersion = gl.GetStringS(StringName.ShadingLanguageVersion); + var versionDef = slVersion.Contains("OpenGL ES", StringComparison.InvariantCultureIgnoreCase) + ? "#version 300 es" + : "#version 330"; var vertexCode = $$""" - {{string.Empty}}#version 330 + {{versionDef}} + precision highp float; # for OpenGL ES compatibility layout (location = 0) in vec3 aPosition; out vec4 vertexColor; @@ -128,10 +132,10 @@ public class SimpleTriangleGlCanvasElement() } """; - // string.Empty is added so that the version line is not interpreted as a preprocessor command var fragmentCode = $$""" - {{string.Empty}}#version 330 + {{versionDef}} + precision highp float; # for OpenGL ES compatibility out vec4 out_color; in vec4 vertexColor; From 1153aa06015e2424b49c8fa5d3dbf0672146927d Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 7 Oct 2024 20:29:28 +0300 Subject: [PATCH 20/27] chore: precision specifiers for GLES compat --- .../Windows_UI_Composition/RotatingCubeGlCanvasElement.cs | 4 ++-- .../Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs index 2064d2e6278d..1a1bea6feca5 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs @@ -69,7 +69,7 @@ public class RotatingCubeGlCanvasElement() : GLCanvasElement(1200, 800, null) private readonly string _vertexShaderSource = """ - precision highp float; + precision highp float; # for OpenGL ES compatibility layout(location = 0) in vec3 pos; layout(location = 1) in vec3 vertex_color; @@ -86,7 +86,7 @@ void main() { private readonly string FragmentShaderSource = """ - precision highp float; + precision highp float; # for OpenGL ES compatibility in vec3 color; diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs index 33f1e414298a..50ef09912fcf 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs @@ -44,7 +44,7 @@ unsafe protected override void Init(GL gl) var vertexCode = $$""" {{versionDef}} - precision highp float; + precision highp float; # for OpenGL ES compatibility layout (location = 0) in vec3 aPosition; out vec4 vertexColor; @@ -59,7 +59,7 @@ void main() var fragmentCode = $$""" {{versionDef}} - precision highp float; + precision highp float; # for OpenGL ES compatibility out vec4 out_color; in vec4 vertexColor; From fa025c78fd75b17ffcbfa43ed6538c240d69f8fc Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 7 Oct 2024 20:30:08 +0300 Subject: [PATCH 21/27] chore: adjust comment on _nativeOpenGlWrapper lifetime --- src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs index 8bd22513e694..9e0d390b9a61 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs @@ -44,8 +44,8 @@ public abstract partial class GLCanvasElement : Grid, INativeContext private readonly WriteableBitmap _backBuffer; private bool _loadedAtleastOnce; - // These are valid if and only if _loadedAtleastOnce and _glAvailable - private INativeOpenGLWrapper? _nativeOpenGlWrapper; + private INativeOpenGLWrapper? _nativeOpenGlWrapper; // valid if and only if _loadedAtleastOnce and _glAvailable + // These are valid if and only if IsLoaded and _glAvailable private GL? _gl; private uint _framebuffer; private uint _textureColorBuffer; @@ -145,7 +145,7 @@ private void OnClosed(object o, WindowEventArgs windowEventArgs) DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () => { using var _ = _nativeOpenGlWrapper!.MakeCurrent(); - _nativeOpenGlWrapper?.Dispose(); + _nativeOpenGlWrapper.Dispose(); }); } } From cca07f151424c9e5e96d0443d0ce09dd75e7bd66 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 7 Oct 2024 20:32:15 +0300 Subject: [PATCH 22/27] chore: move EGL bindings to a separate file --- .../MacOSNativeOpenGLWrapper.Egl.cs | 77 +++++++++++++++++++ .../MacOSNativeOpenGLWrapper.cs | 66 +--------------- 2 files changed, 78 insertions(+), 65 deletions(-) create mode 100644 src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.Egl.cs diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.Egl.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.Egl.cs new file mode 100644 index 000000000000..b5bb6593db8e --- /dev/null +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.Egl.cs @@ -0,0 +1,77 @@ +// Copyright (c) 2006-2019 Stefanos Apostolopoulos for the Open Toolkit project. +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System.Runtime.InteropServices; +using Microsoft.UI.Xaml; +using Uno.Disposables; +using Uno.Foundation.Extensibility; +using Uno.Foundation.Logging; +using Uno.Graphics; + +namespace Uno.UI.Runtime.Skia.MacOS; + +internal partial class MacOSNativeOpenGLWrapper : INativeOpenGLWrapper +{ + private const string libEGL = "libEGL.dylib"; + + [DllImport(libEGL, EntryPoint = "eglGetDisplay")] + public static extern IntPtr EglGetDisplay(IntPtr display_id); + + [DllImport(libEGL, EntryPoint = "eglInitialize")] + private static extern bool EglInitialize(IntPtr dpy, out int major, out int minor); + + [DllImport(libEGL, EntryPoint = "eglChooseConfig")] + [return: MarshalAs(UnmanagedType.I1)] + private static extern bool EglChooseConfig(IntPtr dpy, int[] attrib_list, [In][Out] IntPtr[] configs, int config_size, out int num_config); + + [DllImport(libEGL, EntryPoint = "eglGetError")] + private static extern ErrorCode EglGetError(); + + [DllImport(libEGL, EntryPoint = "eglCreateContext")] + private static extern IntPtr EglCreateContext(IntPtr dpy, IntPtr config, IntPtr share_context, int[] attrib_list); + + [DllImport(libEGL, EntryPoint = "eglCreatePbufferSurface")] + private static extern IntPtr EglCreatePbufferSurface(IntPtr dpy, IntPtr config, int[] attrib_list); + + [DllImport(libEGL, EntryPoint = "eglDestroySurface")] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool EglDestroySurface(IntPtr dpy, IntPtr surface); + + [DllImport(libEGL, EntryPoint = "eglDestroyContext")] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool EglDestroyContext(IntPtr dpy, IntPtr ctx); + + [DllImport(libEGL, EntryPoint = "eglGetCurrentContext")] + public static extern IntPtr EglGetCurrentContext(); + + [DllImport(libEGL, EntryPoint = "eglGetCurrentDisplay")] + public static extern IntPtr EglGetCurrentDisplay(); + + [DllImport(libEGL, EntryPoint = "eglGetCurrentSurface")] + public static extern IntPtr EglGetCurrentSurface(int readdraw); + + [DllImport(libEGL, EntryPoint = "eglMakeCurrent")] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool EglMakeCurrent(IntPtr dpy, IntPtr draw, IntPtr read, IntPtr ctx); + + private enum ErrorCode + { + SUCCESS = 0x3000, + NOT_INITIALIZED, + BAD_ACCESS, + BAD_ALLOC, + BAD_ATTRIBUTE, + BAD_CONFIG, + BAD_CONTEXT, + BAD_CURRENT_SURFACE, + BAD_DISPLAY, + BAD_MATCH, + BAD_NATIVE_PIXMAP, + BAD_NATIVE_WINDOW, + BAD_PARAMETER, + BAD_SURFACE, + CONTEXT_LOST + } +} diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs index 5639f56730a3..d430600e0495 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeOpenGLWrapper.cs @@ -11,9 +11,8 @@ namespace Uno.UI.Runtime.Skia.MacOS; // https://registry.khronos.org/EGL/api/EGL/egl.h // Permalink: https://github.com/KhronosGroup/EGL-Registry/blob/29c4314e0ef04c730992d295f91b76635019fbba/api/EGL/egl.h -internal class MacOSNativeOpenGLWrapper : INativeOpenGLWrapper +internal partial class MacOSNativeOpenGLWrapper : INativeOpenGLWrapper { - private const string libEGL = "libEGL.dylib"; private static readonly Lazy _libGLES = new Lazy(() => { if (!NativeLibrary.TryLoad("libGLESv2.dylib", typeof(MacOSNativeOpenGLWrapper).Assembly, DllImportSearchPath.UserDirectories, out var _handle)) @@ -147,67 +146,4 @@ public IDisposable MakeCurrent() } public static void Register() => ApiExtensibility.Register(typeof(INativeOpenGLWrapper), xamlRoot => new MacOSNativeOpenGLWrapper(xamlRoot)); - - // Copyright (c) 2006-2019 Stefanos Apostolopoulos for the Open Toolkit project. - // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - [DllImport(libEGL, EntryPoint = "eglGetDisplay")] - public static extern IntPtr EglGetDisplay(IntPtr display_id); - - [DllImport(libEGL, EntryPoint = "eglInitialize")] - private static extern bool EglInitialize(IntPtr dpy, out int major, out int minor); - - [DllImport(libEGL, EntryPoint = "eglChooseConfig")] - [return: MarshalAs(UnmanagedType.I1)] - private static extern bool EglChooseConfig(IntPtr dpy, int[] attrib_list, [In][Out] IntPtr[] configs, int config_size, out int num_config); - - [DllImport(libEGL, EntryPoint = "eglGetError")] - private static extern ErrorCode EglGetError(); - - [DllImport(libEGL, EntryPoint = "eglCreateContext")] - private static extern IntPtr EglCreateContext(IntPtr dpy, IntPtr config, IntPtr share_context, int[] attrib_list); - - [DllImport(libEGL, EntryPoint = "eglCreatePbufferSurface")] - private static extern IntPtr EglCreatePbufferSurface(IntPtr dpy, IntPtr config, int[] attrib_list); - - [DllImport(libEGL, EntryPoint = "eglDestroySurface")] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool EglDestroySurface(IntPtr dpy, IntPtr surface); - - [DllImport(libEGL, EntryPoint = "eglDestroyContext")] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool EglDestroyContext(IntPtr dpy, IntPtr ctx); - - [DllImport(libEGL, EntryPoint = "eglGetCurrentContext")] - public static extern IntPtr EglGetCurrentContext(); - - [DllImport(libEGL, EntryPoint = "eglGetCurrentDisplay")] - public static extern IntPtr EglGetCurrentDisplay(); - - [DllImport(libEGL, EntryPoint = "eglGetCurrentSurface")] - public static extern IntPtr EglGetCurrentSurface(int readdraw); - - [DllImport(libEGL, EntryPoint = "eglMakeCurrent")] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool EglMakeCurrent(IntPtr dpy, IntPtr draw, IntPtr read, IntPtr ctx); - - private enum ErrorCode - { - SUCCESS = 0x3000, - NOT_INITIALIZED, - BAD_ACCESS, - BAD_ALLOC, - BAD_ATTRIBUTE, - BAD_CONFIG, - BAD_CONTEXT, - BAD_CURRENT_SURFACE, - BAD_DISPLAY, - BAD_MATCH, - BAD_NATIVE_PIXMAP, - BAD_NATIVE_WINDOW, - BAD_PARAMETER, - BAD_SURFACE, - CONTEXT_LOST - } } From 71350c9412af3ea6ceaaf128d93202a84f381968 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 7 Oct 2024 21:10:09 +0300 Subject: [PATCH 23/27] chore: formatting --- doc/articles/controls/GLCanvasElement.md | 2 +- src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs | 3 ++- src/Uno.UI/XamlMerge.targets | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/articles/controls/GLCanvasElement.md b/doc/articles/controls/GLCanvasElement.md index 3f1c56d2e3c9..403107cfea2a 100644 --- a/doc/articles/controls/GLCanvasElement.md +++ b/doc/articles/controls/GLCanvasElement.md @@ -25,7 +25,7 @@ The protected constructor additionally requires a `Func` argument that f The 3 abstract methods above all take a `Silk.NET.OpenGL.GL` parameter that can be used to make OpenGL calls. -The `Init` method is a regular OpenGL setup method that you can use to set up the needed OpenGL objects, like textures, Vertex Array Buffers (VAOs), Element Array Buffers (EBOs), etc. The `OnDestroy` method is the complement of `Init` and is used to clean up any allocated resources. `Init` and `OnDestroy` might be called multiple times alternatingly. In other words, 2 `OnDestroy` calls are guaranteed to have an `Init` call in between and vice versa. +The `Init` method is a regular OpenGL setup method that you can use to set up the needed OpenGL objects, like textures, Vertex Array Buffers (VAOs), Element Array Buffers (EBOs), etc. The `OnDestroy` method is the complement of `Init` and is used to clean up any allocated resources. `Init` and `OnDestroy` might be called multiple times alternatingly. In other words, 2 `OnDestroy` calls are guaranteed to have an `Init` call in between and vice versa. 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. diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs index 9e0d390b9a61..ba81c3581263 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs @@ -44,7 +44,8 @@ public abstract partial class GLCanvasElement : Grid, INativeContext private readonly WriteableBitmap _backBuffer; private bool _loadedAtleastOnce; - private INativeOpenGLWrapper? _nativeOpenGlWrapper; // valid if and only if _loadedAtleastOnce and _glAvailable + // valid if and only if _loadedAtleastOnce and _glAvailable + private INativeOpenGLWrapper? _nativeOpenGlWrapper; // These are valid if and only if IsLoaded and _glAvailable private GL? _gl; private uint _framebuffer; diff --git a/src/Uno.UI/XamlMerge.targets b/src/Uno.UI/XamlMerge.targets index c48e88e40453..d5a88a997369 100644 --- a/src/Uno.UI/XamlMerge.targets +++ b/src/Uno.UI/XamlMerge.targets @@ -9,7 +9,7 @@ - $(MSBuildProjectDirectory)\UI\Xaml\Style\mergedstyles.xaml + $(MSBuildProjectDirectory)\UI\Xaml\Style\mergedstyles.xaml From b8b13abc1b3922ccd0aeb489fb8bccb09b56abcd Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 7 Oct 2024 22:07:12 +0300 Subject: [PATCH 24/27] chore: fix error image --- src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs | 2 +- src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs index ba81c3581263..dca9ac589760 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs @@ -34,7 +34,7 @@ namespace Uno.WinUI.Graphics3DGL; public abstract partial class GLCanvasElement : Grid, INativeContext { private const int BytesPerPixel = 4; - private static readonly BitmapImage _fallbackImage = new BitmapImage(new Uri("https://uno-assets.platform.uno/logos/uno.png")); + private static readonly BitmapImage _fallbackImage = new BitmapImage(new Uri("ms-appx:///Assets/error.png")); private static bool? _glAvailable; private readonly uint _width; diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj index 39f3a9fc6d00..2e9dba04f2da 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj @@ -68,7 +68,7 @@ - + From 9f23ac3ee2dfe86b9efdd0793e5d5658664f1141 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 7 Oct 2024 22:14:04 +0300 Subject: [PATCH 25/27] chore: fix uwp build --- src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs | 9 +++++++-- .../Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs index dca9ac589760..ef4bb7be5826 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs @@ -1,7 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; -using Microsoft.UI.Dispatching; using Silk.NET.OpenGL; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -12,6 +11,12 @@ using Uno.Logging; using Window = Microsoft.UI.Xaml.Window; +#if !UNO_UWP_BUILD +using Microsoft.UI.Dispatching; +#else +using Windows.System; +#endif + #if WINAPPSDK using System.Runtime.InteropServices; using System.Runtime.InteropServices.WindowsRuntime; @@ -137,7 +142,7 @@ protected GLCanvasElement(uint width, uint height, Func? getWindowFunc) } } - private void OnClosed(object o, WindowEventArgs windowEventArgs) + private void OnClosed(object _, object __) { if (GlAvailable!.Value) { diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj index 2e9dba04f2da..6826e6639a9b 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj @@ -58,6 +58,10 @@ + + $(DefineConstants);UNO_UWP_BUILD + + $(DefaultItemExcludes);angle/** From 64d89d673550c3f6ff98912715b1bd0a6747a86d Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 8 Oct 2024 12:23:32 +0300 Subject: [PATCH 26/27] chore: download angle binaries even when not on macos --- .../Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj | 4 ++-- .../{build_angle.sh => get_angle_binaries.sh} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename src/AddIns/Uno.WinUI.Graphics3DGL/{build_angle.sh => get_angle_binaries.sh} (100%) mode change 100755 => 100644 diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj index 6826e6639a9b..22941f593b76 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj @@ -66,8 +66,8 @@ $(DefaultItemExcludes);angle/** - - + + diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/build_angle.sh b/src/AddIns/Uno.WinUI.Graphics3DGL/get_angle_binaries.sh old mode 100755 new mode 100644 similarity index 100% rename from src/AddIns/Uno.WinUI.Graphics3DGL/build_angle.sh rename to src/AddIns/Uno.WinUI.Graphics3DGL/get_angle_binaries.sh From 1dfa8242c5d94f392ad6248721e9108b17a75854 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 8 Oct 2024 12:51:00 +0300 Subject: [PATCH 27/27] chore: use DownloadFile instead of a script --- .../Uno.WinUI.Graphics3DGL.csproj | 11 +++++++++-- .../Uno.WinUI.Graphics3DGL/get_angle_binaries.sh | 8 -------- 2 files changed, 9 insertions(+), 10 deletions(-) delete mode 100644 src/AddIns/Uno.WinUI.Graphics3DGL/get_angle_binaries.sh diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj index 22941f593b76..43c1da7ef35f 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj @@ -66,8 +66,15 @@ $(DefaultItemExcludes);angle/** - - + + + + + diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/get_angle_binaries.sh b/src/AddIns/Uno.WinUI.Graphics3DGL/get_angle_binaries.sh deleted file mode 100644 index 877faed975c6..000000000000 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/get_angle_binaries.sh +++ /dev/null @@ -1,8 +0,0 @@ -#/usr/bin/env bash -set -euo pipefail - -mkdir -p angle_binaries/osx -pushd angle_binaries/osx -curl -O -L https://github.com/dotnet/Silk.NET/raw/a9c75a61528ee7b4e55442f5141cb132f10d72ff/src/Native/Silk.NET.OpenGLES.ANGLE.Native/runtimes/osx/native/libEGL.dylib -curl -O -L https://github.com/dotnet/Silk.NET/raw/a9c75a61528ee7b4e55442f5141cb132f10d72ff/src/Native/Silk.NET.OpenGLES.ANGLE.Native/runtimes/osx/native/libGLESv2.dylib -popd