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 cceacbc6ee7b..000000000000 Binary files a/src/AddIns/Uno.WinUI.Graphics3DGL/Assets/error.png and /dev/null differ 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;