Skip to content

Commit

Permalink
feat: GLCanvasElement.LoadedSuccessfully
Browse files Browse the repository at this point in the history
  • Loading branch information
ramezgerges committed Nov 20, 2024
1 parent e630951 commit 44a80f3
Show file tree
Hide file tree
Showing 3 changed files with 20 additions and 17 deletions.
6 changes: 5 additions & 1 deletion doc/articles/controls/GLCanvasElement.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Window>` 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<Window>` 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

Expand All @@ -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.
Expand Down
Binary file removed src/AddIns/Uno.WinUI.Graphics3DGL/Assets/error.png
Binary file not shown.
31 changes: 15 additions & 16 deletions src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
#endif

#if WINAPPSDK
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.WindowsRuntime;
#else
using Uno.Foundation.Extensibility;
Expand All @@ -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<XamlRoot, INativeOpenGLWrapper?> _xamlRootToWrapper = new();

private static (int major, int minor) _minVersion = (3, 0);

/// <summary>
/// The minimum required OpenGL version. Set this property depending on the OpenGL features you use.
/// </summary>
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<Window>? _getWindowFunc;

Expand Down Expand Up @@ -111,8 +100,7 @@ protected GLCanvasElement(Func<Window>? 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;
Expand Down Expand Up @@ -159,7 +147,7 @@ protected GLCanvasElement(Func<Window>? 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
Expand All @@ -173,7 +161,7 @@ protected GLCanvasElement(Func<Window>? 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;
Expand Down Expand Up @@ -232,8 +220,16 @@ private void OnClosed(object _, object __)
public void Invalidate() => NativeDispatcher.Main.Enqueue(Render, NativeDispatcherPriority.Idle);
#endif

/// <summary>
/// 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.
/// </summary>
public bool? LoadedSuccessfully { get; private set; }

private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
LoadedSuccessfully = false;

_nativeOpenGlWrapper = GetOrCreateNativeOpenGlWrapper(XamlRoot!, _getWindowFunc);

if (_nativeOpenGlWrapper is null)
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 44a80f3

Please sign in to comment.