Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: introduce GLCanvasElement to allow externally adding sophisticated graphics #16621

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
5ff75bd
feat: add a SkiaVisual that allows external use of SkiaSharp
ramezgerges Jan 25, 2024
afa9651
chore: add SKCanvasElement and a lot of details
ramezgerges Jan 26, 2024
f4fe4ef
chore: remove unneeded dpi logic in SKCanvasElement
ramezgerges Jan 29, 2024
c064404
test: add tests for SKCanvasElement
ramezgerges Jan 29, 2024
225de18
chore: rename RespectFlowDirection to MirroredWhenRightToLeft
ramezgerges Jan 29, 2024
a510667
docs: add documentation for SkiaCanvas and SKCanvasElement
ramezgerges Jan 30, 2024
742b4fb
chore: minor adjustments
ramezgerges May 7, 2024
1eca500
chore: relocate implementation
ramezgerges May 7, 2024
ff6e501
feat: introduce the ability to draw using raw OpenGL on skia
ramezgerges Feb 21, 2024
bc50c00
chore: refactoring and major cleanup
ramezgerges May 7, 2024
2fab9b1
docs: typos and tiny changes
ramezgerges May 7, 2024
6a8ae03
chore: session.Surface.Canvas => session.Canvas
ramezgerges May 7, 2024
988605d
chore: RespectFlowDirectionChanged => OnMirroredWhenRightToLeftChanged
ramezgerges May 7, 2024
56139cd
chore: use Visual.Size instead of LayoutSlot in SKCanvasElement
ramezgerges May 7, 2024
d9b12b8
test: add ui sample for GLCanvasElement
ramezgerges May 7, 2024
ba4f644
docs: formatting
ramezgerges May 7, 2024
b7b5bcf
chore: formatting
ramezgerges May 8, 2024
3f3d6e0
chore: move GLCanvasElementImpl to a separate skia-only file
ramezgerges May 8, 2024
99d5cfb
chore: reduce unsafe usage and restore Viewport after render
ramezgerges May 8, 2024
3dac0a3
chore: add GLCanvasElement support for wpf
ramezgerges May 8, 2024
d081f82
chore: clear canvas when rendering GLCanvasElement
ramezgerges May 8, 2024
0c3bb6d
docs: more changes
ramezgerges May 8, 2024
e860c21
chore: explicitly dispose GLVisual's pixmap
ramezgerges May 8, 2024
5a4a5dd
chore: move GKCanvasElement.GLVisual to a separate file
ramezgerges May 8, 2024
b71fc6e
chore: remove MirroredWhenRightToLeftProperty and rename sample
ramezgerges May 8, 2024
6b3a078
chore: adjust sample
ramezgerges May 8, 2024
cfb1899
docs: add a sample to the SKCanvasElement docs
ramezgerges May 8, 2024
48a59a5
chore: make the sample animated
ramezgerges May 9, 2024
a3aac82
chore: licensing and formatting
ramezgerges May 9, 2024
9b6dafb
docs: tabs to spaces
ramezgerges May 9, 2024
98268fc
chore: refactor timestamp calculations
ramezgerges May 10, 2024
cb514d5
chore: DispatchQueueRender on Idle to prevent choking the dispatcher …
ramezgerges May 10, 2024
5f8704a
chore: post rebase
ramezgerges Jul 31, 2024
49e93c8
chore: change SKImage format to Unpremul
ramezgerges Aug 5, 2024
4f26870
chore: Adjust version
MartinZikmund Aug 7, 2024
b849ca5
chore: fix depth testing
ramezgerges Aug 7, 2024
b18f331
chore: only repaint GlVisual when dirty
ramezgerges Aug 7, 2024
b18194f
chore: remove unnecessary lines from shader code
ramezgerges Aug 7, 2024
31e45e3
chore: fix rotation calculation in sample
ramezgerges Aug 7, 2024
5eeba60
chore: more fixes and cleanup
ramezgerges Aug 7, 2024
d6755ce
chore: GlCanvasElementImpl => RotatingCubeGlCanvasElement
ramezgerges Aug 8, 2024
8131b7e
chore: move Canvas.Save() up
ramezgerges Aug 8, 2024
bd5de11
chore: remove SkiaVisual and add docstrings
ramezgerges Aug 8, 2024
9e70bb3
chore: packaging
ramezgerges Aug 8, 2024
7703a7f
chore: enable depth testing per sample
ramezgerges Aug 8, 2024
bb27e70
chore: minor touches
ramezgerges Aug 8, 2024
077185d
chore: move Init down
ramezgerges Aug 8, 2024
684a981
chore: more packaging
ramezgerges Aug 8, 2024
e6e0ad2
chore: typo
ramezgerges Aug 8, 2024
c9dba27
docs: complete SKCanvasElement docs
ramezgerges Aug 8, 2024
a1f75e9
chore: move SKCanvasVisual to a separate partial
ramezgerges Aug 8, 2024
d54f37a
chore: check WGL is present on WPF
ramezgerges Aug 8, 2024
8a08a0e
chore: GLCanvasElement docstrings
ramezgerges Aug 8, 2024
1c41b92
docs: GLCanvasElement docs
ramezgerges Aug 8, 2024
f380086
chore: add another sample
ramezgerges Aug 8, 2024
a1a0e14
chore: add UnoMissingAssemblyAnalyzer entry
ramezgerges Aug 8, 2024
d18339d
chore: add Silk.NET to uno islands samples app
ramezgerges Aug 8, 2024
f83d2d9
chore: formatting
ramezgerges Aug 8, 2024
c6e355b
chore: remove /* UWP don't rename */
ramezgerges Aug 8, 2024
e74fbb5
chore: minor doc touches
ramezgerges Aug 8, 2024
7532073
chore: typo
ramezgerges Aug 8, 2024
c681a8b
chore: build errors
ramezgerges Aug 8, 2024
d881501
chore: formatting and build errors
ramezgerges Aug 9, 2024
69e4a0d
chore: packaging fixes
ramezgerges Aug 12, 2024
f0842f3
chore: remove not_skia to mc:Ignorable
ramezgerges Aug 26, 2024
50ed2f4
chore: fix docs line about SKCanvasElement hardware acceleration
ramezgerges Aug 27, 2024
d5b4f4e
chore: move GLCanvasElement to a separate package
ramezgerges Aug 27, 2024
4960531
chore: implement GLCanvasElement for WinUI
ramezgerges Aug 27, 2024
5092a11
chore: avoid crash on closing window
ramezgerges Aug 27, 2024
4f06a30
chore: move most logic to shared partial
ramezgerges Aug 28, 2024
402787b
chore: major redesign
ramezgerges Aug 28, 2024
baf26ed
chore: build error
ramezgerges Aug 28, 2024
b24d069
chore: implement INativeOpenGLWrapper for X11
ramezgerges Aug 28, 2024
8b05a86
chore: formatting
ramezgerges Aug 28, 2024
672261e
chore: undo UnoMissingAssemblyAnalyzer changes
ramezgerges Aug 28, 2024
436f47d
chore: optimize copying gl output in uno only
ramezgerges Aug 28, 2024
7631bab
chore: Uno.WinUI.Graphics -> Uno.WinUI.Graphics3D
ramezgerges Aug 28, 2024
1980f69
chore: consolodate more logic between wpf and winui
ramezgerges Aug 28, 2024
81592f4
chore: remove unnecessary modifications
ramezgerges Aug 28, 2024
0cb7946
chore: add to ci slnf
ramezgerges Aug 28, 2024
8a86f8e
chore: formatting
ramezgerges Aug 28, 2024
4676bd4
chore: build error
ramezgerges Aug 28, 2024
f503403
chore: add reference to UnoIslandsSamplesApp.Skia.csproj
ramezgerges Aug 30, 2024
47dcd72
chore: Graphics3D -> Graphics3DGL
ramezgerges Sep 2, 2024
c4e7a29
chore: add an UnoFeature for GLCanvasElement
ramezgerges Sep 2, 2024
cf7ab56
chore: don't build Graphics3DGL with the netX.0-windows10 SDK for skia
ramezgerges Sep 2, 2024
94b01ba
chore: remove SKCanvasElement
ramezgerges Sep 2, 2024
3f54083
chore: adjust conditions for UWP
ramezgerges Sep 2, 2024
57891bd
chore: add a BuildGraphics3DGLForWindows condition
ramezgerges Sep 2, 2024
1f029fa
chore: GeneratePackageOnBuild for Graphics3DGL
ramezgerges Sep 2, 2024
2a8cd8b
chore: don't reference WinAppSDK on UWP
ramezgerges Sep 2, 2024
3c02cbb
chore: add uno.png to None
ramezgerges Sep 3, 2024
08eaaa1
chore: don't build Graphics3DGL binaries on UWP
ramezgerges Sep 4, 2024
22e728c
chore: add new docs to toc
ramezgerges Sep 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions build/ci/.azure-devops-package-netcoremobile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@ jobs:
createLogFile: false
displayName: Building WinAppSDK/UWP package binaries

- task: MSBuild@1
condition: and(succeeded(), eq(variables.UNO_UWP_BUILD, false))
inputs:
solution: src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj
msbuildLocationMethod: version
msbuildVersion: latest
msbuildArchitecture: x86
msbuildArguments: /r /m /v:m /p:Configuration=Release /p:BuildGraphics3DGLForWindows=true /detailedsummary /bl:$(build.artifactstagingdirectory)/build-$(GitVersion.FullSemVer)-graphics3dgl-windows-$(XAML_FLAVOR_BUILD)-binaries.binlog
clean: false
restoreNugetPackages: false
logProjectEvents: false
createLogFile: false
displayName: Building WinAppSDK Graphics3DGL package binaries

- template: templates/copy-package-assets.yml

- task: ArchiveFiles@2
Expand Down
2 changes: 1 addition & 1 deletion build/ci/.azure-devops-wasdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
# ---
# NOTE: The error says to specify a RuntimeIdentifier *OR* platform other than AnyCPU.
# We already specify RuntimeIdentifier=win-x64 in the build below. Still, the error pops up.
msbuildArguments: /r /t:Publish /m /v:m /p:Configuration=Release /p:Platform=x64 /p:RuntimeIdentifier=win-x64 /p:GenerateAppxPackageOnBuild=true /detailedsummary /bl:$(build.artifactstagingdirectory)/build-wasdk.binlog
msbuildArguments: /r /t:Publish /m /v:m /p:Configuration=Release /p:Platform=x64 /p:RuntimeIdentifier=win-x64 /p:BuildGraphics3DGLForWindows=true /p:GenerateAppxPackageOnBuild=true /detailedsummary /bl:$(build.artifactstagingdirectory)/build-wasdk.binlog
clean: false
restoreNugetPackages: false
logProjectEvents: false
Expand Down
1 change: 1 addition & 0 deletions build/filters/Uno.UI-packages-skia.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"AddIns\\Uno.UI.MSAL\\Uno.UI.MSAL.Skia.csproj",
"AddIns\\Uno.UI.MediaPlayer.Skia.Gtk\\Uno.UI.MediaPlayer.Skia.Gtk.csproj",
"AddIns\\Uno.UI.Svg\\Uno.UI.Svg.Skia.csproj",
"AddIns\\Uno.WinUI.Graphics3DGL\\Uno.WinUI.Graphics3DGL.csproj",
"SourceGenerators\\System.Xaml\\Uno.Xaml.csproj",
"SourceGenerators\\Uno.UI.SourceGenerators.Internal\\Uno.UI.SourceGenerators.Internal.csproj",
"SourceGenerators\\Uno.UI.SourceGenerators\\Uno.UI.SourceGenerators.csproj",
Expand Down
3 changes: 3 additions & 0 deletions build/test-scripts/run-net7-template-linux.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ $projects =
# 5.2 Blank SkiaSharp 3
@("5.2/uno52blank/uno52blank/uno52blank.csproj", "-p:SkiaSharpVersion=3.0.0-preview.3.1"),

# 5.2 Blank Canvas3DGL
@("5.2/uno52blank/uno52blank/uno52blank.csproj", "-p:UnoFeatures=Canvas3DGL"),

# 5.2 Uno Lib
@("5.2/uno52Lib/uno52Lib.csproj", ""),

Expand Down
201 changes: 201 additions & 0 deletions doc/articles/controls/GLCanvasElement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
---
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.

`GLCanvasElement` is a `Grid` for drawing 3D graphics with OpenGL. This class comes as a part of the `Uno.WinUI.Graphics3D` package.
jeromelaban marked this conversation as resolved.
Show resolved Hide resolved

To use `GLCanvasElement`, create a subclass of `GLCanvasElement` and override the abstract methods `Init`, `RenderOverride` and `OnDestroy`.

```csharp
protected GLCanvasElement(uint width, uint height, Func<Window> getWindowFunc);

protected abstract void Init(GL gl);
protected abstract void RenderOverride(GL gl);
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<Window>` 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 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 `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.

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`.

## Full example

To see this in action, here's a complete sample that uses `GLCanvasElement` to draw a triangle. Note how you have to be careful with surrounding all the OpenGL-related logic in platform-specific guards. This is the case for both the [XAML](platform-specific-xaml) and the [code-behind](platform-specific-csharp).

XAML:

```xaml
<!-- GLCanvasElementExample.xaml -->
<UserControl x:Class="BlankApp.GLCanvasElementExample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BlankApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:skia="http://uno.ui/skia#using:UITests.Shared.Windows_UI_Composition"
xmlns:not_skia="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:win="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:not_win="http://uno.ui/not_win"
mc:Ignorable="skia not_win">

<Grid>
<skia:SimpleTriangleGlCanvasElement />
<win:Grid>
<local:SimpleTriangleGlCanvasElement />
</win:Grid>
<not_win:Grid>
<not_skia:TextBlock Text="This sample is only supported on skia targets and WinUI." />
</not_win:Grid>
</Grid>
</UserControl>
```

Code-behind:

```csharp
// GLCanvasElementExample.xaml.cs
public partial class GLCanvasElementExample : UserControl
{
public GLCanvasElementExample()
{
this.InitializeComponent();
}
}
```

```csharp
// GLTriangleElement.cs
# __SKIA__ || WINAPPSDK
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 */)
#endif
{
private uint _vao;
private uint _vbo;
private uint _program;

unsafe protected override void Init(GL gl)
{
_vao = gl.GenVertexArray();
gl.BindVertexArray(_vao);

float[] vertices =
{
0.5f, -0.5f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, // bottom left
0.0f, 0.5f, 0.0f // top
};

_vbo = gl.GenBuffer();
gl.BindBuffer(BufferTargetARB.ArrayBuffer, _vbo);
gl.BufferData(BufferTargetARB.ArrayBuffer, new ReadOnlySpan<float>(vertices), BufferUsageARB.StaticDraw);

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 vertexCode =
$$"""
{{string.Empty}}#version 330

layout (location = 0) in vec3 aPosition;
out vec4 vertexColor;

void main()
{
gl_Position = vec4(aPosition, 1.0);
vertexColor = vec4(aPosition.x + 0.5, aPosition.y + 0.5, aPosition.z + 0.5, 1.0);
}
""";

// string.Empty is added so that the version line is not interpreted as a preprocessor command
var fragmentCode =
$$"""
{{string.Empty}}#version 330

out vec4 out_color;
in vec4 vertexColor;

void main()
{
out_color = vertexColor;
}
""";

uint vertexShader = gl.CreateShader(ShaderType.VertexShader);
gl.ShaderSource(vertexShader, vertexCode);
gl.CompileShader(vertexShader);

gl.GetShader(vertexShader, ShaderParameterName.CompileStatus, out int vStatus);
if (vStatus != (int)GLEnum.True)
{
throw new Exception("Vertex shader failed to compile: " + gl.GetShaderInfoLog(vertexShader));
}

uint fragmentShader = gl.CreateShader(ShaderType.FragmentShader);
gl.ShaderSource(fragmentShader, fragmentCode);
gl.CompileShader(fragmentShader);

gl.GetShader(fragmentShader, ShaderParameterName.CompileStatus, out int fStatus);
if (fStatus != (int)GLEnum.True)
{
throw new Exception("Fragment shader failed to compile: " + gl.GetShaderInfoLog(fragmentShader));
}

_program = gl.CreateProgram();
gl.AttachShader(_program, vertexShader);
gl.AttachShader(_program, fragmentShader);
gl.LinkProgram(_program);

gl.GetProgram(_program, ProgramPropertyARB.LinkStatus, out int lStatus);
if (lStatus != (int)GLEnum.True)
{
throw new Exception("Program failed to link: " + gl.GetProgramInfoLog(_program));
}

gl.DetachShader(_program, vertexShader);
gl.DetachShader(_program, fragmentShader);
gl.DeleteShader(vertexShader);
gl.DeleteShader(fragmentShader);
}

protected override void OnDestroy(GL gl)
{
gl.DeleteVertexArray(_vao);
gl.DeleteBuffer(_vbo);
gl.DeleteProgram(_program);
}

protected override void RenderOverride(GL gl)
{
gl.ClearColor(Color.Black);
gl.Clear(ClearBufferMask.ColorBufferBit);

gl.UseProgram(_program);

gl.BindVertexArray(_vao);
gl.DrawArrays(PrimitiveType.Triangles, 0, 3);
}
}
#endif
```
Loading
Loading