From f37972f5c00d5f7156a68e010a6a6fded8dda790 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 19 Nov 2024 19:29:43 +0200 Subject: [PATCH 1/9] chore: add GLCanvasElement checks for minimum available OpenGL version --- .../Uno.WinUI.Graphics3DGL/GLCanvasElement.cs | 88 ++++++++++++++++--- 1 file changed, 75 insertions(+), 13 deletions(-) diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs index fe6a994616b5..b8dbd288e029 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.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; using Silk.NET.OpenGL; using Microsoft.UI.Xaml; @@ -41,13 +42,24 @@ public abstract partial class GLCanvasElement : Grid, INativeContext { private const int BytesPerPixel = 4; private static readonly BitmapImage _fallbackImage = new BitmapImage(new Uri("ms-appx:///Assets/error.png")); - private static readonly Dictionary _xamlRootToWrapper = new(); + private static readonly Dictionary _xamlRootToWrapper = new(); + + private static (int major, int minor) _minVersion = (3, 0); + + /// + /// The minimum required OpenGL version. Set this property depending on the OpenGL features you use. + /// + public static (int major, int minor) MinVersion + { + get => _minVersion; + set => _minVersion = value.major < 3 ? (3, 0) : value; + } private readonly Func? _getWindowFunc; - // valid if and only if _loadedAtleastOnce and OpenGL is available on the running platform + // valid if and only if GLCanvasElement was loaded at least once and OpenGL is available on the running platform private INativeOpenGLWrapper? _nativeOpenGlWrapper; - // These are valid if and only if IsLoaded + // These are valid if and only if IsLoaded and _nativeOpenGlWrapper is not null private GL? _gl; private WriteableBitmap? _backBuffer; private FrameBufferDetails? _details; @@ -88,8 +100,6 @@ public abstract partial class GLCanvasElement : Grid, INativeContext /// protected abstract void RenderOverride(GL gl); - /// The width of the backing framebuffer. - /// The height of the backing framebuffer. /// A function that returns the Window object that this element belongs to. This parameter is only used on WinUI. On Uno Platform, it can be set to null. #if WINAPPSDK protected GLCanvasElement(Func getWindowFunc) @@ -109,7 +119,7 @@ protected GLCanvasElement(Func? getWindowFunc) SizeChanged += (_, _) => UpdateFramebuffer(); } - private static INativeOpenGLWrapper? GetOrCreateNativeOpenGlWrapper(XamlRoot xamlRoot, Func? getWindowFunc) + private static unsafe INativeOpenGLWrapper? GetOrCreateNativeOpenGlWrapper(XamlRoot xamlRoot, Func? getWindowFunc) { try { @@ -119,14 +129,66 @@ protected GLCanvasElement(Func? getWindowFunc) #if WINAPPSDK nativeOpenGlWrapper = new WinUINativeOpenGLWrapper(xamlRoot, getWindowFunc!); #else - if (!ApiExtensibility.CreateInstance(xamlRoot, out nativeOpenGlWrapper)) + if (!ApiExtensibility.CreateInstance(xamlRoot, out nativeOpenGlWrapper)) { - throw new InvalidOperationException($"Couldn't create a {nameof(INativeOpenGLWrapper)} object. Make sure you are running on a platform with OpenGL support."); + if (typeof(GLCanvasElement).Log().IsEnabled(LogLevel.Error)) + { + typeof(GLCanvasElement).Log().Error($"Couldn't create a {nameof(INativeOpenGLWrapper)} object. Make sure you are running on a platform with OpenGL support."); + } + + _xamlRootToWrapper[xamlRoot] = null; + return null; } #endif + var abort = false; + using (nativeOpenGlWrapper.MakeCurrent()) + { + var glGetString = (delegate* unmanaged[Cdecl])nativeOpenGlWrapper.GetProcAddress("glGetString"); + + var glVersionBytePtr = glGetString(GLEnum.Version); + var glVersionString = Marshal.PtrToStringUTF8((IntPtr)glVersionBytePtr); + + if (typeof(GLCanvasElement).Log().IsEnabled(LogLevel.Information)) + { + typeof(GLCanvasElement).Log().Info($"{nameof(GLCanvasElement)} created an OpenGL context with a version string = '{glVersionString}'."); + } + + if (glVersionString?.Contains("ANGLE", StringComparison.Ordinal) ?? false) + { + if (typeof(GLCanvasElement).Log().IsEnabled(LogLevel.Warning)) + { + typeof(GLCanvasElement).Log().Warn($"{nameof(GLCanvasElement)} is using an ANGLE implementation, ignoring {nameof(MinVersion)} checks."); + } + } + else + { + var glGetIntegerv = (delegate* unmanaged[Cdecl])nativeOpenGlWrapper.GetProcAddress("glGetIntegerv"); + int major, minor; + glGetIntegerv(GLEnum.MajorVersion, &major); + glGetIntegerv(GLEnum.MinorVersion, &minor); + + if (major < _minVersion.major || (major == _minVersion.major && minor < _minVersion.minor)) + { + if (typeof(GLCanvasElement).Log().IsEnabled(LogLevel.Error)) + { + typeof(GLCanvasElement).Log().Error($"{nameof(GLCanvasElement)} requires at least {MinVersion.major}.{MinVersion.minor}, but found {major}.{minor}."); + } + + abort = true; + } + } + } + + if (abort) + { + nativeOpenGlWrapper.Dispose(); + nativeOpenGlWrapper = null; + } + _xamlRootToWrapper.Add(xamlRoot, nativeOpenGlWrapper); } + return nativeOpenGlWrapper; } catch (Exception e) @@ -151,8 +213,8 @@ private void OnClosed(object _, object __) } if (_xamlRootToWrapper.Remove(XamlRoot!, out var wrapper)) { - using var _ = wrapper.MakeCurrent(); - wrapper.Dispose(); + using var makeCurrentDisposable = wrapper?.MakeCurrent(); + wrapper?.Dispose(); } }); } @@ -180,7 +242,7 @@ private void OnLoaded(object sender, RoutedEventArgs routedEventArgs) _gl = GL.GetApi(this); - using (_nativeOpenGlWrapper!.MakeCurrent()) + using (_nativeOpenGlWrapper.MakeCurrent()) { UpdateFramebuffer(); Init(_gl); @@ -256,7 +318,7 @@ private void OnUnloaded(object sender, RoutedEventArgs routedEventArgs) private void UpdateFramebuffer() { - if (!IsLoaded) + if (!IsLoaded || _nativeOpenGlWrapper is null) { return; } @@ -290,7 +352,7 @@ private void UpdateFramebuffer() private unsafe void Render() { - if (!IsLoaded) + if (!IsLoaded || _nativeOpenGlWrapper is null) { return; } From e630951e5ac946bf6f7abadf7a7fed5db75f3e0d Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 19 Nov 2024 19:33:16 +0200 Subject: [PATCH 2/9] chore: use forgotten fallback image --- src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs index b8dbd288e029..82893e9fdcc0 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs @@ -111,7 +111,8 @@ protected GLCanvasElement(Func? getWindowFunc) Background = new ImageBrush { - RelativeTransform = new ScaleTransform { ScaleX = 1, ScaleY = -1, CenterX = 0.5, CenterY = 0.5 } // because OpenGL coordinates go bottom-to-top + RelativeTransform = new ScaleTransform { ScaleX = 1, ScaleY = -1, CenterX = 0.5, CenterY = 0.5 }, // because OpenGL coordinates go bottom-to-top + ImageSource = _fallbackImage }; Loaded += OnLoaded; From 44a80f3531cba28fc8ffae64d6c550e8572d90f0 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 20 Nov 2024 15:05:36 +0200 Subject: [PATCH 3/9] feat: GLCanvasElement.LoadedSuccessfully --- doc/articles/controls/GLCanvasElement.md | 6 +++- .../Uno.WinUI.Graphics3DGL/Assets/error.png | Bin 71036 -> 0 bytes .../Uno.WinUI.Graphics3DGL/GLCanvasElement.cs | 31 +++++++++--------- 3 files changed, 20 insertions(+), 17 deletions(-) delete mode 100644 src/AddIns/Uno.WinUI.Graphics3DGL/Assets/error.png diff --git a/doc/articles/controls/GLCanvasElement.md b/doc/articles/controls/GLCanvasElement.md index ed1c4ec02c57..ef399210fdbd 100644 --- a/doc/articles/controls/GLCanvasElement.md +++ b/doc/articles/controls/GLCanvasElement.md @@ -23,7 +23,7 @@ These three abstract methods take a `Silk.NET.OpenGL.GL` parameter that can be u ### The GLCanvasElement constructor -The protected constructor 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 yet provide a way to get the `Window` of a `FrameworkElement`. This parameter is ignored on Uno Platform and must be set to null. This function is only called while the `GLCanvasElement` is still in the visual tree. +The protected constructor 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 yet provide a way to get the `Window` of a `FrameworkElement`. This parameter 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 `Init` method @@ -41,6 +41,10 @@ On MacOS, since OpenGL support is not natively present, we use [ANGLE](https://e 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`. +## Detecting errors + +To detect errors in initializing the OpenGL environment, `GLCanvasElement` exposes a `LoadedSuccessfully` property that shows whether or nor the loading of the element and its OpenGL setup were successful. This property is only valid when the element is loading, i.e. its `IsLoaded` property is true. When the element is not loaded, the value of `LoadedSuccessfully` will be null. + ## How to use Silk.NET 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. diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/Assets/error.png b/src/AddIns/Uno.WinUI.Graphics3DGL/Assets/error.png deleted file mode 100644 index cceacbc6ee7b7e3260c98493db806087c5f06cfe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 71036 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

-6808I1$iW%IA8H$-)O`F2(l_G!;0hr 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 Hr-UW|g%{`= diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs index 82893e9fdcc0..f47f2ee64883 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs @@ -20,7 +20,6 @@ #endif #if WINAPPSDK -using System.Runtime.InteropServices; using System.Runtime.InteropServices.WindowsRuntime; #else using Uno.Foundation.Extensibility; @@ -41,19 +40,9 @@ 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("ms-appx:///Assets/error.png")); private static readonly Dictionary _xamlRootToWrapper = new(); - private static (int major, int minor) _minVersion = (3, 0); - - ///

- /// The minimum required OpenGL version. Set this property depending on the OpenGL features you use. - /// - public static (int major, int minor) MinVersion - { - get => _minVersion; - set => _minVersion = value.major < 3 ? (3, 0) : value; - } + private static readonly (int major, int minor) _minVersion = (3, 0); private readonly Func? _getWindowFunc; @@ -111,8 +100,7 @@ protected GLCanvasElement(Func? getWindowFunc) Background = new ImageBrush { - RelativeTransform = new ScaleTransform { ScaleX = 1, ScaleY = -1, CenterX = 0.5, CenterY = 0.5 }, // because OpenGL coordinates go bottom-to-top - ImageSource = _fallbackImage + RelativeTransform = new ScaleTransform { ScaleX = 1, ScaleY = -1, CenterX = 0.5, CenterY = 0.5 } // because OpenGL coordinates go bottom-to-top }; Loaded += OnLoaded; @@ -159,7 +147,7 @@ protected GLCanvasElement(Func? getWindowFunc) { if (typeof(GLCanvasElement).Log().IsEnabled(LogLevel.Warning)) { - typeof(GLCanvasElement).Log().Warn($"{nameof(GLCanvasElement)} is using an ANGLE implementation, ignoring {nameof(MinVersion)} checks."); + typeof(GLCanvasElement).Log().Warn($"{nameof(GLCanvasElement)} is using an ANGLE implementation, ignoring minimum version checks."); } } else @@ -173,7 +161,7 @@ protected GLCanvasElement(Func? getWindowFunc) { if (typeof(GLCanvasElement).Log().IsEnabled(LogLevel.Error)) { - typeof(GLCanvasElement).Log().Error($"{nameof(GLCanvasElement)} requires at least {MinVersion.major}.{MinVersion.minor}, but found {major}.{minor}."); + typeof(GLCanvasElement).Log().Error($"{nameof(GLCanvasElement)} requires at least {_minVersion.major}.{_minVersion.minor}, but found {major}.{minor}."); } abort = true; @@ -232,8 +220,16 @@ private void OnClosed(object _, object __) public void Invalidate() => NativeDispatcher.Main.Enqueue(Render, NativeDispatcherPriority.Idle); #endif + /// + /// Indicates whether this element was loaded successfully or not, including the OpenGL context creation and setup. + /// This property is only valid when the element is loaded. When the element is not loaded, the value will be null. + /// + public bool? LoadedSuccessfully { get; private set; } + private void OnLoaded(object sender, RoutedEventArgs routedEventArgs) { + LoadedSuccessfully = false; + _nativeOpenGlWrapper = GetOrCreateNativeOpenGlWrapper(XamlRoot!, _getWindowFunc); if (_nativeOpenGlWrapper is null) @@ -263,10 +259,13 @@ private void OnLoaded(object sender, RoutedEventArgs routedEventArgs) { fe.Unloaded += OnClosed; } + + LoadedSuccessfully = true; } private void OnUnloaded(object sender, RoutedEventArgs routedEventArgs) { + LoadedSuccessfully = null; if (_nativeOpenGlWrapper is null) { return; From 98d2b11875f6530e2a7fb5232cf47bcd01881285 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa <66218781+ramezgerges@users.noreply.github.com> Date: Wed, 20 Nov 2024 18:04:45 +0200 Subject: [PATCH 4/9] chore: comment adjustment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jérôme Laban --- src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs index f47f2ee64883..59634cc976a6 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs @@ -222,7 +222,7 @@ private void OnClosed(object _, object __) /// /// Indicates whether this element was loaded successfully or not, including the OpenGL context creation and setup. - /// This property is only valid when the element is loaded. When the element is not loaded, the value will be null. + /// This property is only valid when the element is loaded. When the element is not loaded in the visual tree, the value will be null. /// public bool? LoadedSuccessfully { get; private set; } From d982d1058c9dbcf0cc7ecd8763745cb54caf996d Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 20 Nov 2024 18:26:35 +0200 Subject: [PATCH 5/9] chore: implement INotifyPropertyChanged --- .../Uno.WinUI.Graphics3DGL/GLCanvasElement.cs | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs index 59634cc976a6..0b5312765b40 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; @@ -37,7 +38,7 @@ 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, INativeContext +public abstract partial class GLCanvasElement : Grid, INativeContext, INotifyPropertyChanged { private const int BytesPerPixel = 4; private static readonly Dictionary _xamlRootToWrapper = new(); @@ -46,6 +47,8 @@ public abstract partial class GLCanvasElement : Grid, INativeContext private readonly Func? _getWindowFunc; + private bool? _isGlInitialized; + // valid if and only if GLCanvasElement was loaded at least once and OpenGL is available on the running platform private INativeOpenGLWrapper? _nativeOpenGlWrapper; // These are valid if and only if IsLoaded and _nativeOpenGlWrapper is not null @@ -224,16 +227,25 @@ private void OnClosed(object _, object __) /// Indicates whether this element was loaded successfully or not, including the OpenGL context creation and setup. /// This property is only valid when the element is loaded. When the element is not loaded in the visual tree, the value will be null. /// - public bool? LoadedSuccessfully { get; private set; } + public bool? IsGLInitialized + { + get => _isGlInitialized; + private set + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsGLInitialized))); + _isGlInitialized = value; + } + } + + public event PropertyChangedEventHandler? PropertyChanged; private void OnLoaded(object sender, RoutedEventArgs routedEventArgs) { - LoadedSuccessfully = false; - _nativeOpenGlWrapper = GetOrCreateNativeOpenGlWrapper(XamlRoot!, _getWindowFunc); if (_nativeOpenGlWrapper is null) { + IsGLInitialized = false; return; } @@ -260,12 +272,12 @@ private void OnLoaded(object sender, RoutedEventArgs routedEventArgs) fe.Unloaded += OnClosed; } - LoadedSuccessfully = true; + IsGLInitialized = true; } private void OnUnloaded(object sender, RoutedEventArgs routedEventArgs) { - LoadedSuccessfully = null; + IsGLInitialized = null; if (_nativeOpenGlWrapper is null) { return; From 0848ec90b3c19773af1d3165f97990d00d19f7ec Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 20 Nov 2024 18:31:34 +0200 Subject: [PATCH 6/9] chore: adjust docs --- doc/articles/controls/GLCanvasElement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/articles/controls/GLCanvasElement.md b/doc/articles/controls/GLCanvasElement.md index ef399210fdbd..82e53797632e 100644 --- a/doc/articles/controls/GLCanvasElement.md +++ b/doc/articles/controls/GLCanvasElement.md @@ -43,7 +43,7 @@ Additionally, `GLCanvasElement` has an `Invalidate` method that requests a redra ## Detecting errors -To detect errors in initializing the OpenGL environment, `GLCanvasElement` exposes a `LoadedSuccessfully` property that shows whether or nor the loading of the element and its OpenGL setup were successful. This property is only valid when the element is loading, i.e. its `IsLoaded` property is true. When the element is not loaded, the value of `LoadedSuccessfully` will be null. +To detect errors in initializing the OpenGL environment, `GLCanvasElement` exposes an `IsGLInitialized` property that shows whether or nor the loading of the element and its OpenGL setup were successful. This property is only valid when the element is loaded, i.e. its `IsLoaded` property is true. When the element is not loaded, the value of `IsGLInitialized` will be null. `GLCanvasElement` implements `INotifyPropertyChanged`, so you can use this property in a data bindings, for example to set the visibility of a control as a fallback. ## How to use Silk.NET From b054a3d242224a03d16043457017d91f746f6447 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 20 Nov 2024 18:46:41 +0200 Subject: [PATCH 7/9] chore: expose RaisePropertyChanged --- src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs index 0b5312765b40..d260dc766f3c 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; using Silk.NET.OpenGL; @@ -232,13 +233,16 @@ public bool? IsGLInitialized get => _isGlInitialized; private set { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsGLInitialized))); _isGlInitialized = value; + RaisePropertyChanged(); } } public event PropertyChangedEventHandler? PropertyChanged; + protected void RaisePropertyChanged([CallerMemberName] string? propertyName = null) + => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + private void OnLoaded(object sender, RoutedEventArgs routedEventArgs) { _nativeOpenGlWrapper = GetOrCreateNativeOpenGlWrapper(XamlRoot!, _getWindowFunc); From 357d30b9c5b3d8fc511a3e96e6f835158610506d Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Fri, 22 Nov 2024 16:25:04 +0200 Subject: [PATCH 8/9] chore: move back to a DP --- doc/articles/controls/GLCanvasElement.md | 2 +- .../Uno.WinUI.Graphics3DGL/GLCanvasElement.cs | 36 +++++++++++++------ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/doc/articles/controls/GLCanvasElement.md b/doc/articles/controls/GLCanvasElement.md index 82e53797632e..04018b28b529 100644 --- a/doc/articles/controls/GLCanvasElement.md +++ b/doc/articles/controls/GLCanvasElement.md @@ -43,7 +43,7 @@ Additionally, `GLCanvasElement` has an `Invalidate` method that requests a redra ## Detecting errors -To detect errors in initializing the OpenGL environment, `GLCanvasElement` exposes an `IsGLInitialized` property that shows whether or nor the loading of the element and its OpenGL setup were successful. This property is only valid when the element is loaded, i.e. its `IsLoaded` property is true. When the element is not loaded, the value of `IsGLInitialized` will be null. `GLCanvasElement` implements `INotifyPropertyChanged`, so you can use this property in a data bindings, for example to set the visibility of a control as a fallback. +To detect errors in initializing the OpenGL environment, `GLCanvasElement` exposes an `IsGLInitializedProperty` dependency property that shows whether or nor the loading of the element and its OpenGL setup were successful. This property is only valid when the element is loaded, i.e. its `IsLoaded` property is true. When the element is not loaded, the value of `IsGLInitialized` will be null. `GLCanvasElement` implements `INotifyPropertyChanged`, so you can use this property in a data bindings, for example to set the visibility of a control as a fallback. Attempting to change this property is illegal. ## How to use Silk.NET diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs index d260dc766f3c..6cef9512f466 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs @@ -39,7 +39,7 @@ 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, INativeContext, INotifyPropertyChanged +public abstract partial class GLCanvasElement : Grid, INativeContext { private const int BytesPerPixel = 4; private static readonly Dictionary _xamlRootToWrapper = new(); @@ -48,7 +48,7 @@ public abstract partial class GLCanvasElement : Grid, INativeContext, INotifyPro private readonly Func? _getWindowFunc; - private bool? _isGlInitialized; + private bool _changingGlInitialized; // valid if and only if GLCanvasElement was loaded at least once and OpenGL is available on the running platform private INativeOpenGLWrapper? _nativeOpenGlWrapper; @@ -224,25 +224,41 @@ private void OnClosed(object _, object __) public void Invalidate() => NativeDispatcher.Main.Enqueue(Render, NativeDispatcherPriority.Idle); #endif + public static DependencyProperty IsGLInitializedProperty { get; } = + DependencyProperty.Register( + nameof(IsGLInitialized), + typeof(bool?), + typeof(GLCanvasElement), + new PropertyMetadata(null, (PropertyChangedCallback)((dO, _) => + { + var @this = (GLCanvasElement)dO; + if (!@this._changingGlInitialized) + { + throw new InvalidOperationException($"{nameof(GLCanvasElement)}.{nameof(IsGLInitializedProperty)} is read-only."); + } + + // We should have arrived here from set_IsGLInitialized, so we could put this line at the end of the + // setter. Instead, we set it to false here to prevent users from calling SetValue.IsGLInitializedProperty + // _inside_ a call to GLCanvasElement.set_IsGLInitialized. This way, if a user intercepts this + // change (e.g. with SubscribeToPropertyChanged) and attempts to make a nested SetValue call, we still + // explode in their face. + @this._changingGlInitialized = false; + }))); + /// /// Indicates whether this element was loaded successfully or not, including the OpenGL context creation and setup. /// This property is only valid when the element is loaded. When the element is not loaded in the visual tree, the value will be null. /// public bool? IsGLInitialized { - get => _isGlInitialized; + get => (bool?)GetValue(IsGLInitializedProperty); private set { - _isGlInitialized = value; - RaisePropertyChanged(); + _changingGlInitialized = true; + SetValue(IsGLInitializedProperty, value); } } - public event PropertyChangedEventHandler? PropertyChanged; - - protected void RaisePropertyChanged([CallerMemberName] string? propertyName = null) - => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - private void OnLoaded(object sender, RoutedEventArgs routedEventArgs) { _nativeOpenGlWrapper = GetOrCreateNativeOpenGlWrapper(XamlRoot!, _getWindowFunc); From 809d33b3c91c51509732a06b009d7e4172b75456 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Fri, 22 Nov 2024 16:26:31 +0200 Subject: [PATCH 9/9] chore: add minimum OGL version to docs --- doc/articles/controls/GLCanvasElement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/articles/controls/GLCanvasElement.md b/doc/articles/controls/GLCanvasElement.md index 04018b28b529..42c8d2f79b83 100644 --- a/doc/articles/controls/GLCanvasElement.md +++ b/doc/articles/controls/GLCanvasElement.md @@ -3,7 +3,7 @@ uid: Uno.Controls.GLCanvasElement --- > [!IMPORTANT] -> This functionality is only available on WinAppSDK and Skia Desktop (`netX.0-desktop`) targets that are running on platforms with support for hardware acceleration. On Windows and Linux, OpenGL is used directly and on macOS, Metal is used through the [ANGLE](https://en.wikipedia.org/wiki/ANGLE_(software)) library. +> This functionality is only available on WinAppSDK and Skia Desktop (`netX.0-desktop`) targets that are running on platforms with support for hardware acceleration. On Windows and Linux, OpenGL 3.0+ is used directly and on macOS, Metal is used through the [ANGLE](https://en.wikipedia.org/wiki/ANGLE_(software)) library. `GLCanvasElement` is a control for drawing 3D graphics with OpenGL. It can be enabled by adding the [`GLCanvas` UnoFeature](xref:Uno.Features.Uno.Sdk). The OpenGL APIs provided are provided by [Silk.NET](https://dotnet.github.io/Silk.NET/).