diff --git a/build/ci/.azure-devops-package-netcoremobile.yml b/build/ci/.azure-devops-package-netcoremobile.yml index dc4975e5c3b1..927700a53498 100644 --- a/build/ci/.azure-devops-package-netcoremobile.yml +++ b/build/ci/.azure-devops-package-netcoremobile.yml @@ -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 diff --git a/build/ci/.azure-devops-wasdk.yml b/build/ci/.azure-devops-wasdk.yml index 39d1154b3484..4ed80095eccd 100644 --- a/build/ci/.azure-devops-wasdk.yml +++ b/build/ci/.azure-devops-wasdk.yml @@ -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 diff --git a/build/filters/Uno.UI-packages-skia.slnf b/build/filters/Uno.UI-packages-skia.slnf index 276ae5d418b0..acc27dca11a9 100644 --- a/build/filters/Uno.UI-packages-skia.slnf +++ b/build/filters/Uno.UI-packages-skia.slnf @@ -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", diff --git a/build/test-scripts/run-net7-template-linux.ps1 b/build/test-scripts/run-net7-template-linux.ps1 index a04240d974f8..19f014c87b50 100644 --- a/build/test-scripts/run-net7-template-linux.ps1 +++ b/build/test-scripts/run-net7-template-linux.ps1 @@ -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", ""), diff --git a/doc/articles/controls/GLCanvasElement.md b/doc/articles/controls/GLCanvasElement.md new file mode 100644 index 000000000000..668504fe6059 --- /dev/null +++ b/doc/articles/controls/GLCanvasElement.md @@ -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. + +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 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` 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 + + + + + + + + + + + + + +``` + +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(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 +``` diff --git a/doc/articles/controls/SKCanvasElement.md b/doc/articles/controls/SKCanvasElement.md new file mode 100644 index 000000000000..0c732c21e404 --- /dev/null +++ b/doc/articles/controls/SKCanvasElement.md @@ -0,0 +1,209 @@ +--- +uid: Uno.Controls.SKCanvasElement +--- + +## Introduction + +When creating an Uno Platform application, developers might want to create elaborate 2D graphics using a library such as [Skia](https://skia.org) or [Cairo](https://www.cairographics.org), rather than using, for example, a simple [Canvas](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.canvas). To support this use case, SkiaSharp comes with an [SKXamlCanvas](https://learn.microsoft.com/dotnet/api/skiasharp.views.windows.skxamlcanvas) element that allows for drawing in an area using SkiaSharp. + +On Uno Platform Skia Desktop targets, we can utilize the pre-existing internal Skia canvas used to render the application window instead of creating additional Skia surfaces. This way, a lot of Skia functionally can be acquired "for free". For example, unlike `SKXamlCanvas` which doesn't support hardware acceleration on Skia targets, hardware acceleration comes out of the box if the Uno application is already using OpenGL to render. Moreover, `SKXamlCanvas` has to make additional buffer copying, which can be skipped with this implementation. + +> [!IMPORTANT] +> This functionality is only available on Skia Desktop (`netX.0-desktop`) targets. + +## SKCanvasElement + +`SKCanvasElement` is an abstract `FrameworkElement` for 2D drawing with Skia. To use `SKCanvasElement`, create a subclass of `SKCanvasElement` and override the `RenderOverride` method, which takes the canvas that will be drawn on and the clipping area inside the canvas. + +```csharp +protected abstract void RenderOverride(SKCanvas canvas, Size area); +``` + +When adding your drawing logic in `RenderOverride` on the provided canvas, you can assume that the origin is already translated so that `0,0` is the origin of the visual, not the entire window. Drawing outside this area will be clipped. + +Additionally, `SKCanvasElement` has an `Invalidate` method that can be used at any time to tell the Uno Platform runtime to redraw the visual, calling `RenderOverride` in the process. + +By default, an `SKCanvasElement` takes all the available space given to it in the `Measure` cycle. If you want to customize how much space the element takes, you can override its `MeasureOverride` and `ArrangeOverride` methods. + +Note that since an `SKCanvasElement` takes as much space as it can, it's not allowed to place an `SKCanvasElement` inside a `StackPanel`, a `Grid` with `Auto` sizing, or any other element that provides its child(ren) with infinite space. To work around this, you can explicitly set the `Width` and/or `Height` of the `SKCanvasElement`. + +## Full example + +To see this in action, here's a complete sample that uses `SKCanvasElement` to draw 1 of 3 different drawings based on the value of a [Slider](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.slider). Note how you have to be careful with surrounding all the Skia-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 + + + + + + + + + + + + + + + +``` + +Code-behind: + +```csharp +// SKCanvasElementExample.xaml.cs +public partial class SKCanvasElementExample : UserControl +{ +#if __SKIA__ + public int MaxSampleIndex => SKCanvasElementImpl.SampleCount - 1; +#endif + + public SKCanvasElementExample() + { + this.InitializeComponent(); + } +} +``` + +```csharp +// SKCanvasElementImpl.skia.cs <-- NOTICE the `.skia` +public class SKCanvasElementImpl : SKCanvasElement +{ + public static int SampleCount => 3; + + public static DependencyProperty SampleProperty { get; } = DependencyProperty.Register( + nameof(Sample), + typeof(int), + typeof(SKCanvasElementImpl), + new PropertyMetadata(0, (o, args) => ((SKCanvasElementImpl)o).SampleChanged((int)args.NewValue))); + + public int Sample + { + get => (int)GetValue(SampleProperty); + set => SetValue(SampleProperty, value); + } + + private void SampleChanged(int newIndex) + { + Sample = Math.Min(Math.Max(0, newIndex), SampleCount - 1); + } + + protected override void RenderOverride(SKCanvas canvas, Size area) + { + var minDim = Math.Min(area.Width, area.Height); + // rescale to fit the given area, assuming each drawing takes is 260x260 + canvas.Scale((float)(minDim / 260), (float)(minDim / 260)); + + switch (Sample) + { + case 0: + SkiaDrawing0(canvas); + break; + case 1: + SkiaDrawing1(canvas); + break; + case 2: + SkiaDrawing2(canvas); + break; + } + } + + // https://fiddle.skia.org/c/@shapes + private void SkiaDrawing0(SKCanvas canvas) + { + canvas.DrawColor(SKColors.White); + + var paint = new SKPaint(); + paint.Style = SKPaintStyle.Fill; + paint.IsAntialias = true; + paint.StrokeWidth = 4; + paint.Color = new SKColor(0xff4285F4); + + var rect = SKRect.Create(10, 10, 100, 160); + canvas.DrawRect(rect, paint); + + var oval = new SKPath(); + oval.AddRoundRect(rect, 20, 20); + oval.Offset(new SKPoint(40, 80)); + paint.Color = new SKColor(0xffDB4437); + canvas.DrawPath(oval, paint); + + paint.Color = new SKColor(0xff0F9D58); + canvas.DrawCircle(180, 50, 25, paint); + + rect.Offset(80, 50); + paint.Color = new SKColor(0xffF4B400); + paint.Style = SKPaintStyle.Stroke; + canvas.DrawRoundRect(rect, 10, 10, paint); + } + + // https://fiddle.skia.org/c/@bezier_curves + private void SkiaDrawing1(SKCanvas canvas) + { + canvas.DrawColor(SKColors.White); + + var paint = new SKPaint(); + paint.Style = SKPaintStyle.Stroke; + paint.StrokeWidth = 8; + paint.Color = new SKColor(0xff4285F4); + paint.IsAntialias = true; + paint.StrokeCap = SKStrokeCap.Round; + + var path = new SKPath(); + path.MoveTo(10, 10); + path.QuadTo(256, 64, 128, 128); + path.QuadTo(10, 192, 250, 250); + canvas.DrawPath(path, paint); + } + + // https://fiddle.skia.org/c/@shader + private void SkiaDrawing2(SKCanvas canvas) + { + var paint = new SKPaint(); + using var pathEffect = SKPathEffect.CreateDiscrete(10.0f, 4.0f); + paint.PathEffect = pathEffect; + SKPoint[] points = + { + new SKPoint(0.0f, 0.0f), + new SKPoint(256.0f, 256.0f) + }; + SKColor[] colors = + { + new SKColor(66, 133, 244), + new SKColor(15, 157, 88) + }; + paint.Shader = SKShader.CreateLinearGradient(points[0], points[1], colors, SKShaderTileMode.Clamp); + paint.IsAntialias = true; + canvas.Clear(SKColors.White); + var path = Star(); + canvas.DrawPath(path, paint); + + SKPath Star() + { + const float R = 60.0f, C = 128.0f; + var path = new SKPath(); + path.MoveTo(C + R, C); + for (var i = 1; i < 15; ++i) + { + var a = 0.44879895f * i; + var r = R + R * (i % 2); + path.LineTo((float)(C + r * Math.Cos(a)), (float)(C + r * Math.Sin(a))); + } + return path; + } + } +} +``` diff --git a/doc/articles/toc.yml b/doc/articles/toc.yml index 56a5d8ff7564..c383338b4c4c 100644 --- a/doc/articles/toc.yml +++ b/doc/articles/toc.yml @@ -432,8 +432,10 @@ href: controls/DatePicker.md - name: Flyout href: controls/Flyout.md - - name: Flyout + - name: Frame href: controls/Frame.md + - name: GLCanvasElement + href: controls/GLCanvasElement.md - name: Image href: xref:Uno.Features.Image - name: ListView and GridView diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs new file mode 100644 index 000000000000..bdcf860cab63 --- /dev/null +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs @@ -0,0 +1,269 @@ +using System; +using System.Diagnostics; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Media.Imaging; +using Silk.NET.OpenGL; + +#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; +using Uno.UI.Dispatching; +using Buffer = Windows.Storage.Streams.Buffer; +#endif + +namespace Uno.WinUI.Graphics3DGL; + +/// +/// A that exposes the ability to draw 3D graphics using OpenGL and Silk.NET. +/// +/// +/// 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 +{ + private const int BytesPerPixel = 4; + + private readonly INativeOpenGLWrapper _nativeOpenGlWrapper; + + private readonly uint _width; + private readonly uint _height; + + private readonly WriteableBitmap _backBuffer; + + // These are valid if and only if IsLoaded + private GL? _gl; + private uint _framebuffer; + private uint _textureColorBuffer; + private uint _renderBuffer; +#if WINAPPSDK + private IntPtr _pixels; +#endif + + /// + /// Use this function for the initial setup, e.g. setting up VAOs, VBOs, EBOs, etc. + /// + /// + /// might be called multiple times. Every call to except the first one + /// will be preceded by a call to . + /// + protected abstract void Init(GL gl); + /// + /// Use this function for cleaning up previously allocated resources. + /// + /// /// + /// might be called multiple times. Every call to will be preceded by + /// a call to . + /// + protected abstract void OnDestroy(GL gl); + /// + /// The rendering logic goes this. + /// + /// + /// Before is called, the OpenGL viewport is set to the resolution that was provided to + /// the constructor. + /// + /// + /// Due to the fact that both and the skia rendering engine used by Uno both use OpenGL, + /// you must make sure to restore all the OpenGL state values to their original values at the end of . + /// For example, make sure to save the values for the initially-bound OpenGL VAO if you intend to bind your own VAO + /// and bind the original VAO at the end of the method. Similarly, make sure to disable depth testing at + /// the end if you choose to enable it. + /// Some of this may be done for you automatically. + /// + 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(uint width, uint height, Func getWindowFunc) +#else + protected GLCanvasElement(uint width, uint height, Func? getWindowFunc) +#endif + { + _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 + + _backBuffer = new WriteableBitmap((int)width, (int)height); + + Background = new ImageBrush + { + ImageSource = _backBuffer, + RelativeTransform = new ScaleTransform { ScaleX = 1, ScaleY = -1, CenterX = 0.5, CenterY = 0.5 } // because OpenGL coordinates go bottom-to-top + }; + + Loaded += OnLoaded; + Unloaded += OnUnloaded; + } + + /// + /// Invalidates the rendering, and queues a call to . + /// will only be called once after and the output will + /// be saved. You need to call everytime an update is needed. If drawing an + /// animation, call inside to continuously invalidate and update. + /// +#if WINAPPSDK + public void Invalidate() => DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, Render); +#else // WPF hangs if we attempt to enqueue on Low inside RenderOverride + public void Invalidate() => NativeDispatcher.Main.Enqueue(Render, NativeDispatcherPriority.Idle); +#endif + + private unsafe void OnLoaded(object sender, RoutedEventArgs routedEventArgs) + { + _nativeOpenGlWrapper.CreateContext(this); + _gl = (GL)_nativeOpenGlWrapper.CreateGLSilkNETHandle(); + +#if WINAPPSDK + _pixels = Marshal.AllocHGlobal((int)(_width * _height * BytesPerPixel)); +#endif + + using (new GLStateDisposable(this)) + { + _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); + } + + Invalidate(); + } + + private void OnUnloaded(object sender, RoutedEventArgs routedEventArgs) + { + Debug.Assert(_gl is not null); // because OnLoaded creates _gl + +#if WINAPPSDK + Marshal.FreeHGlobal(_pixels); +#endif + + using (new GLStateDisposable(this)) + { +#if WINAPPSDK + if (WindowsRenderingNativeMethods.wglGetCurrentContext() == 0) + { + if (this.Log().IsEnabled(LogLevel.Debug)) + { + this.Log().Debug("Skipping the disposing step because the window is closing. If it's not closing, then this is unexpected."); + } + return; + } +#endif + OnDestroy(_gl); + _gl.DeleteFramebuffer(_framebuffer); + _gl.DeleteTexture(_textureColorBuffer); + _gl.DeleteRenderbuffer(_renderBuffer); + _gl.Dispose(); + } + + _gl = default; + _framebuffer = default; + _textureColorBuffer = default; + _renderBuffer = default; +#if WINAPPSDK + _pixels = default; +#endif + } + + private unsafe void Render() + { + if (!IsLoaded) + { + return; + } + + Debug.Assert(_gl is not null); // because _gl exists if loaded + + using var _ = new GLStateDisposable(this); + + _gl!.BindFramebuffer(GLEnum.Framebuffer, _framebuffer); + { + _gl.Viewport(new System.Drawing.Size((int)_width, (int)_height)); + + RenderOverride(_gl); + + _gl.ReadBuffer(GLEnum.ColorAttachment0); + +#if WINAPPSDK + _gl.ReadPixels(0, 0, _width, _height, GLEnum.Bgra, GLEnum.UnsignedByte, (void*)_pixels); + using (var stream = _backBuffer.PixelBuffer.AsStream()) + { + stream.Write(new ReadOnlySpan((void*)_pixels, (int)(_width * _height * BytesPerPixel))); + } +#else + Buffer.Cast(_backBuffer.PixelBuffer).ApplyActionOnRawBufferPtr(ptr => + { + _gl.ReadPixels(0, 0, _width, _height, GLEnum.Bgra, GLEnum.UnsignedByte, (void*)ptr); + }); + _backBuffer.PixelBuffer.Length = _width * _height * BytesPerPixel; +#endif + _backBuffer.Invalidate(); + } + } + + private readonly struct GLStateDisposable : IDisposable + { + private readonly GLCanvasElement _glCanvasElement; + private readonly IDisposable _contextDisposable; + + public GLStateDisposable(GLCanvasElement glCanvasElement) + { + _glCanvasElement = glCanvasElement; + var gl = _glCanvasElement._gl; + Debug.Assert(gl is not null); + + _contextDisposable = _glCanvasElement._nativeOpenGlWrapper.MakeCurrent(); + } + + public void Dispose() + { + var gl = _glCanvasElement._gl; + 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 new file mode 100644 index 000000000000..6e7ede31d5c4 --- /dev/null +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj @@ -0,0 +1,64 @@ + + + $(NetSkiaPreviousAndCurrent) + $(NetUWPOrWinUI) + + Uno.WinUI.GLCanvasElement + true + + enable + true + + + + + + + + + + + + + + win-x86;win-x64;win-arm64 + + $(DefineConstants);WINDOWS_UWP + $(DefineConstants);WINAPPSDK + $(DefineConstants);WINAPPSDK + true + None + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj b/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj index c85c54f88932..bbc751c691f6 100644 --- a/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj +++ b/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj @@ -1,6 +1,7 @@ $(NetPrevious) + true @@ -30,6 +31,10 @@ + + + + @@ -75,4 +80,14 @@ + + + + + + + diff --git a/src/SamplesApp/SamplesApp.Windows/SamplesApp.Windows.csproj b/src/SamplesApp/SamplesApp.Windows/SamplesApp.Windows.csproj index 3f42c3fbdb45..fface3160006 100644 --- a/src/SamplesApp/SamplesApp.Windows/SamplesApp.Windows.csproj +++ b/src/SamplesApp/SamplesApp.Windows/SamplesApp.Windows.csproj @@ -11,6 +11,7 @@ true true $(DefineConstants);WINAPPSDK + true @@ -75,6 +76,7 @@ + {2569663d-293a-4a1d-bb84-aa8c7b4b7f92} Uno.UI.MSAL diff --git a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems index 690160643260..13e2c051e4cd 100644 --- a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems +++ b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems @@ -4694,6 +4694,14 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -6027,6 +6035,8 @@ AutomationProperties_Name.xaml + + CloseRequestedTests.xaml @@ -8278,6 +8288,12 @@ VisualSurfaceTests.xaml + + GLCanvasElement_Simple.xaml + + + GLCanvasElement_Cube.xaml + VisualTranslationSample.xaml diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Cube.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Cube.xaml new file mode 100644 index 000000000000..a15b34f026e0 --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Cube.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Cube.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Cube.xaml.cs new file mode 100644 index 000000000000..d84d5d75bafa --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Cube.xaml.cs @@ -0,0 +1,14 @@ +using Uno.UI.Samples.Controls; +using Microsoft.UI.Xaml.Controls; + +namespace UITests.Shared.Windows_UI_Composition +{ + [Sample("Microsoft.UI.Composition", IgnoreInSnapshotTests = true, IsManualTest = true, Description = "This sample should show a 3d rotating cube. This only works with hardware acceleration (i.e. might not work in a VM).")] + public sealed partial class GLCanvasElement_Cube : UserControl + { + public GLCanvasElement_Cube() + { + this.InitializeComponent(); + } + } +} diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml new file mode 100644 index 000000000000..c8747740ffc2 --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml.cs new file mode 100644 index 000000000000..4c00bf5c6ccc --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml.cs @@ -0,0 +1,14 @@ +using Uno.UI.Samples.Controls; +using Microsoft.UI.Xaml.Controls; + +namespace UITests.Shared.Windows_UI_Composition +{ + [Sample("Microsoft.UI.Composition", IgnoreInSnapshotTests = true, IsManualTest = true, Description = "This sample should show a simple triangle. This only works with hardware acceleration (i.e. might not work in a VM).")] + public sealed partial class GLCanvasElement_Simple : UserControl + { + public GLCanvasElement_Simple() + { + this.InitializeComponent(); + } + } +} diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs new file mode 100644 index 000000000000..0ab7ad230cf1 --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs @@ -0,0 +1,293 @@ +// MIT License +// +// Copyright (c) 2019-2020 Ultz Limited +// Copyright (c) 2021- .NET Foundation and Contributors +// +// 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. + +// https://github.com/dotnet/Silk.NET/tree/c27224cce6b8136224c01d40de2d608879d709b5/examples/CSharp/OpenGL%20Tutorials + +#if __SKIA__ || WINAPPSDK +using System; +using System.Diagnostics; +using System.Numerics; +using Silk.NET.OpenGL; +using Uno.WinUI.Graphics3DGL; + +namespace UITests.Shared.Windows_UI_Composition +{ +#if WINAPPSDK + public class RotatingCubeGlCanvasElement() : GLCanvasElement(1200, 800, () => SamplesApp.App.MainWindow) +#elif __SKIA__ + public class RotatingCubeGlCanvasElement() : GLCanvasElement(1200, 800, null) +#endif + { + private static BufferObject _vbo; + private static BufferObject _ebo; + private static VertexArrayObject _vao; + private static Shader _shader; + + private static readonly float[] _vertices = + { + // Front face // colors + 0.5f, 0.5f, 0.5f, 1.0f, 0.4f, 0.6f, + -0.5f, 0.5f, 0.5f, 1.0f, 0.9f, 0.2f, + -0.5f, -0.5f, 0.5f, 0.7f, 0.3f, 0.8f, + 0.5f, -0.5f, 0.5f, 0.5f, 0.3f, 1.0f, + // Back face // colors + 0.5f, 0.5f, -0.5f, 0.2f, 0.6f, 1.0f, + -0.5f, 0.5f, -0.5f, 0.6f, 1.0f, 0.4f, + -0.5f, -0.5f, -0.5f, 0.6f, 0.8f, 0.8f, + 0.5f, -0.5f, -0.5f, 0.4f, 0.8f, 0.8f, + }; + + private static readonly uint[] _triangleIndices = + { + // Front + 0, 1, 2, + 2, 3, 0, + // Right + 0, 3, 7, + 7, 4, 0, + // Bottom + 2, 6, 7, + 7, 3, 2, + // Left + 1, 5, 6, + 6, 2, 1, + // Back + 4, 7, 6, + 6, 5, 4, + // Top + 5, 1, 0, + 0, 4, 5, + }; + + private readonly string _vertexShaderSource = "#version 330" + Environment.NewLine + + """ + + layout(location = 0) in vec3 pos; + layout(location = 1) in vec3 vertex_color; + + uniform mat4 transform; + + out vec3 color; + + void main() { + gl_Position = transform * vec4(pos, 1.0); + color = vertex_color; + } + """; + + private readonly string FragmentShaderSource = "#version 330" + Environment.NewLine + + """ + + in vec3 color; + + out vec4 frag_color; + + void main() { + frag_color = vec4(color, 1.0); + } + """; + + protected override void Init(GL Gl) + { + _ebo = new BufferObject(Gl, _triangleIndices, BufferTargetARB.ElementArrayBuffer); + _vbo = new BufferObject(Gl, _vertices, BufferTargetARB.ArrayBuffer); + _vao = new VertexArrayObject(Gl, _vbo, _ebo); + + _vao.VertexAttributePointer(0, 3, VertexAttribPointerType.Float, 6, 0); + _vao.VertexAttributePointer(1, 3, VertexAttribPointerType.Float, 6, 3); + + _shader = new Shader(Gl, _vertexShaderSource, FragmentShaderSource); + } + + protected override void OnDestroy(GL Gl) + { + _vbo.Dispose(); + _ebo.Dispose(); + _vao.Dispose(); + _shader.Dispose(); + } + + // somewhat follows https://github.com/c2d7fa/opengl-cube + protected override unsafe void RenderOverride(GL Gl) + { + Gl.Enable(EnableCap.DepthTest); + Gl.DepthMask(true); + + Gl.ClearColor(0.1f, 0.12f, 0.2f, 1); + Gl.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); + + _vao.Bind(); + _shader.Use(); + + const double duration = 4; + var transform = + Matrix4x4.CreateRotationY((float)((Stopwatch.GetElapsedTime(0).TotalSeconds / duration) * (2 * Math.PI))) * + Matrix4x4.CreateRotationX((float)(0.15 * Math.PI)) * + Matrix4x4.CreateTranslation(0, 0, -3) * + Perspective(); + + _shader.SetUniform("transform", transform); + Gl.DrawElements(PrimitiveType.Triangles, (uint)_triangleIndices.Length, DrawElementsType.UnsignedInt, null); + + // https://www.songho.ca/opengl/gl_projectionmatrix.html + static Matrix4x4 Perspective() + { + const float + r = 0.5f, // Half of the viewport width (at the near plane) + t = 0.5f, // Half of the viewport height (at the near plane) + n = 1, // Distance to near clipping plane + f = 5; // Distance to far clipping plane + + return new Matrix4x4( + n / r, 0, 0, 0, + 0, n / t, 0, 0, + 0, 0, (-f - n) / (f - n), -1, + 0, 0, (2 * f * n) / (n - f), 0 + ); + } + + Invalidate(); // continuous redrawing + } + + public class Shader : IDisposable + { + private readonly uint _handle; + private readonly GL _gl; + + public Shader(GL gl, string vertexShaderSource, string fragmentShaderSource) + { + _gl = gl; + + uint vertex = LoadShader(ShaderType.VertexShader, vertexShaderSource); + uint fragment = LoadShader(ShaderType.FragmentShader, fragmentShaderSource); + _handle = _gl.CreateProgram(); + _gl.AttachShader(_handle, vertex); + _gl.AttachShader(_handle, fragment); + _gl.LinkProgram(_handle); + _gl.GetProgram(_handle, GLEnum.LinkStatus, out var status); + if (status == 0) + { + throw new Exception($"Program failed to link with error: {_gl.GetProgramInfoLog(_handle)}"); + } + _gl.DetachShader(_handle, vertex); + _gl.DetachShader(_handle, fragment); + _gl.DeleteShader(vertex); + _gl.DeleteShader(fragment); + } + + public void Use() + { + _gl.UseProgram(_handle); + } + + public void SetUniform(string name, int value) + { + int location = _gl.GetUniformLocation(_handle, name); + if (location == -1) + { + throw new Exception($"{name} uniform not found on shader."); + } + _gl.Uniform1(location, value); + } + + public unsafe void SetUniform(string name, Matrix4x4 value) + { + //A new overload has been created for setting a uniform so we can use the transform in our shader. + int location = _gl.GetUniformLocation(_handle, name); + if (location == -1) + { + throw new Exception($"{name} uniform not found on shader."); + } + _gl.UniformMatrix4(location, 1, false, (float*)&value); + } + + public void SetUniform(string name, float value) + { + int location = _gl.GetUniformLocation(_handle, name); + if (location == -1) + { + throw new Exception($"{name} uniform not found on shader."); + } + _gl.Uniform1(location, value); + } + + public void Dispose() + { + _gl.DeleteProgram(_handle); + } + + private uint LoadShader(ShaderType type, string src) + { + uint handle = _gl.CreateShader(type); + _gl.ShaderSource(handle, src); + _gl.CompileShader(handle); + string infoLog = _gl.GetShaderInfoLog(handle); + if (!string.IsNullOrWhiteSpace(infoLog)) + { + throw new Exception($"Error compiling shader of type {type}, failed with error {infoLog}"); + } + + return handle; + } + } + + public class BufferObject : IDisposable where TDataType : unmanaged + { + private readonly uint _handle; + private readonly BufferTargetARB _bufferType; + private readonly GL _gl; + + public unsafe BufferObject(GL gl, Span data, BufferTargetARB bufferType) + { + _gl = gl; + _bufferType = bufferType; + + _handle = _gl.GenBuffer(); + Bind(); + fixed (void* d = data) + { + _gl.BufferData(bufferType, (nuint)(data.Length * sizeof(TDataType)), d, BufferUsageARB.StaticDraw); + } + } + + public void Bind() => _gl.BindBuffer(_bufferType, _handle); + public void Dispose() => _gl.DeleteBuffer(_handle); + } + + public class VertexArrayObject : IDisposable + where TVertexType : unmanaged + where TIndexType : unmanaged + { + private readonly uint _handle; + private readonly GL _gl; + + public VertexArrayObject(GL gl, BufferObject vbo, BufferObject ebo) + { + _gl = gl; + + _handle = _gl.GenVertexArray(); + Bind(); + vbo.Bind(); + ebo.Bind(); + } + + public unsafe void VertexAttributePointer(uint index, int count, VertexAttribPointerType type, uint vertexSize, int offSet) + { + _gl.VertexAttribPointer(index, count, type, false, vertexSize * (uint)sizeof(TVertexType), (void*)(offSet * sizeof(TVertexType))); + _gl.EnableVertexAttribArray(index); + } + + public void Bind() => _gl.BindVertexArray(_handle); + public void Dispose() => _gl.DeleteVertexArray(_handle); + } + } +} +#endif diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs new file mode 100644 index 000000000000..27261b1eba4b --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs @@ -0,0 +1,125 @@ +#if __SKIA__ || WINAPPSDK +using System; +using System.Drawing; +using Silk.NET.OpenGL; +using Uno.WinUI.Graphics3DGL; + +namespace UITests.Shared.Windows_UI_Composition +{ + // https://learnopengl.com/Getting-started/Hello-Triangle + public class SimpleTriangleGlCanvasElement() +#if __SKIA__ + : GLCanvasElement(1200, 800, null) +#elif WINAPPSDK + : GLCanvasElement(1200, 800, () => SamplesApp.App.MainWindow) +#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(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 diff --git a/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj b/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj index 31941928eb44..726435ce45e0 100644 --- a/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj +++ b/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj @@ -1,6 +1,7 @@ $(NetSkiaPreviousAndCurrent) + true @@ -17,12 +18,14 @@ + + @@ -51,6 +54,8 @@ + + diff --git a/src/Uno.Sdk/Sdk/Sdk.props.buildschema.json b/src/Uno.Sdk/Sdk/Sdk.props.buildschema.json index f141a11eafb9..88e31a3360bf 100644 --- a/src/Uno.Sdk/Sdk/Sdk.props.buildschema.json +++ b/src/Uno.Sdk/Sdk/Sdk.props.buildschema.json @@ -514,6 +514,9 @@ "Svg": { "description": "Adds support for SVG images.", "helpUrl": "https://aka.platform.uno/feature-svg" + }, + "Canvas3DGL": { + "description": "Adds support for 3D graphics APIs based on OpenGL." } } } diff --git a/src/Uno.Sdk/UnoFeature.cs b/src/Uno.Sdk/UnoFeature.cs index d85eee4d4f95..e83c7e334735 100644 --- a/src/Uno.Sdk/UnoFeature.cs +++ b/src/Uno.Sdk/UnoFeature.cs @@ -90,5 +90,8 @@ public enum UnoFeature Lottie, [UnoArea(UnoArea.Core)] - Svg + Svg, + + [UnoArea(UnoArea.Core)] + Canvas3DGL } diff --git a/src/Uno.Sdk/packages.json b/src/Uno.Sdk/packages.json index c71ac7c18c12..1e9d1d097af6 100644 --- a/src/Uno.Sdk/packages.json +++ b/src/Uno.Sdk/packages.json @@ -19,7 +19,8 @@ "Uno.WinUI.Svg", "Uno.WinUI.WebAssembly", "Uno.WinUI.Runtime.WebAssembly", - "Uno.WinUI.MediaPlayer.WebAssembly" + "Uno.WinUI.MediaPlayer.WebAssembly", + "Uno.WinUI.Graphics3DGL" ] }, { diff --git a/src/Uno.Sdk/targets/Uno.Implicit.Packages.ProjectSystem.Desktop.targets b/src/Uno.Sdk/targets/Uno.Implicit.Packages.ProjectSystem.Desktop.targets index 71e1c06899dd..81fe145ac34d 100644 --- a/src/Uno.Sdk/targets/Uno.Implicit.Packages.ProjectSystem.Desktop.targets +++ b/src/Uno.Sdk/targets/Uno.Implicit.Packages.ProjectSystem.Desktop.targets @@ -6,6 +6,10 @@ --> + + <_UnoProjectSystemPackageReference Include="Uno.WinUI.Graphics3DGL" ProjectSystem="true" /> + + <_UnoProjectSystemPackageReference Include="Uno.WinUI.Skia.Linux.FrameBuffer" ProjectSystem="true" /> <_UnoProjectSystemPackageReference Include="Uno.WinUI.Skia.MacOS" ProjectSystem="true" /> diff --git a/src/Uno.Sdk/targets/Uno.Implicit.Packages.ProjectSystem.WinAppSdk.targets b/src/Uno.Sdk/targets/Uno.Implicit.Packages.ProjectSystem.WinAppSdk.targets index c0e8ade61337..a8a806218f3e 100644 --- a/src/Uno.Sdk/targets/Uno.Implicit.Packages.ProjectSystem.WinAppSdk.targets +++ b/src/Uno.Sdk/targets/Uno.Implicit.Packages.ProjectSystem.WinAppSdk.targets @@ -13,4 +13,8 @@ <_UnoProjectSystemPackageReference Include="SkiaSharp.Views.WinUI" ProjectSystem="true" /> + + + <_UnoProjectSystemPackageReference Include="Uno.WinUI.Graphics3DGL" ProjectSystem="true" /> + diff --git a/src/Uno.UI-Skia-only.slnf b/src/Uno.UI-Skia-only.slnf index b2164fbda27b..1180c2e0875a 100644 --- a/src/Uno.UI-Skia-only.slnf +++ b/src/Uno.UI-Skia-only.slnf @@ -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", "SamplesApp\\Benchmarks.Shared\\SamplesApp.Benchmarks.shproj", "SamplesApp\\SamplesApp.Shared\\SamplesApp.Shared.shproj", "SamplesApp\\SamplesApp.Skia.Generic\\SamplesApp.Skia.Generic.csproj", @@ -58,4 +59,4 @@ "Uno.UWP\\Uno.Skia.csproj" ] } -} \ No newline at end of file +} diff --git a/src/Uno.UI-Windows-only.slnf b/src/Uno.UI-Windows-only.slnf index 2cd31dd1e853..fda7479bf4d5 100644 --- a/src/Uno.UI-Windows-only.slnf +++ b/src/Uno.UI-Windows-only.slnf @@ -3,6 +3,7 @@ "path": "Uno.UI.sln", "projects": [ "AddIns\\Uno.UI.MSAL\\Uno.UI.MSAL.Windows.csproj", + "AddIns\\Uno.WinUI.Graphics3DGL\\Uno.WinUI.Graphics3DGL.csproj", "SamplesApp\\Benchmarks.Shared\\SamplesApp.Benchmarks.shproj", "SamplesApp\\SamplesApp.Windows\\SamplesApp.Windows.csproj", "SamplesApp\\SamplesApp.Shared\\SamplesApp.Shared.shproj", @@ -15,4 +16,4 @@ "Uno.UI.Toolkit\\Uno.UI.Toolkit.Windows.csproj" ] } -} \ No newline at end of file +} diff --git a/src/Uno.UI.Dispatching/AssemblyInfo.cs b/src/Uno.UI.Dispatching/AssemblyInfo.cs index 087f3221acdc..0421ba0ee57d 100644 --- a/src/Uno.UI.Dispatching/AssemblyInfo.cs +++ b/src/Uno.UI.Dispatching/AssemblyInfo.cs @@ -16,6 +16,8 @@ [assembly: InternalsVisibleTo("SamplesApp.Wasm")] [assembly: InternalsVisibleTo("SamplesApp.Skia")] +[assembly: InternalsVisibleTo("Uno.WinUI.Graphics3DGL")] + [assembly: InternalsVisibleTo("Uno")] [assembly: System.Reflection.AssemblyMetadata("IsTrimmable", "True")] diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfExtensionsRegistrar.cs b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfExtensionsRegistrar.cs index e462ac067f4c..f79162569d63 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfExtensionsRegistrar.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfExtensionsRegistrar.cs @@ -24,6 +24,7 @@ using Uno.UI.Runtime.Skia.Extensions.System; using Uno.UI.Runtime.Skia.Wpf.Input; using Microsoft.Web.WebView2.Core; +using Uno.Graphics; namespace Uno.UI.Runtime.Skia.Wpf.Extensions; @@ -57,6 +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()); _registered = true; } diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs new file mode 100644 index 000000000000..b0ebe1220714 --- /dev/null +++ b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs @@ -0,0 +1,181 @@ +#nullable enable + + +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; +using Microsoft.Extensions.Logging; +using Uno.Disposables; +using Uno.Extensions; +using Uno.Logging; +#else +using System.Windows.Interop; +using Microsoft.UI.Xaml; +using Uno.Disposables; +using Uno.Foundation.Logging; +using Uno.Graphics; +using Uno.UI.Runtime.Skia.Wpf.Rendering; +using WpfWindow = System.Windows.Window; +#endif + +#if WINDOWS_UWP || WINAPPSDK +namespace Uno.WinUI.Graphics3DGL; +#else +namespace Uno.UI.Runtime.Skia.Wpf.Extensions; +#endif + +#if WINDOWS_UWP || WINAPPSDK +internal class WinUINativeOpenGLWrapper(Func getWindowFunc) +#else +internal class WpfNativeOpenGLWrapper +#endif + : INativeOpenGLWrapper +{ + private nint _hdc; + private nint _glContext; + + public void CreateContext(UIElement element) + { +#if WINDOWS_UWP || WINAPPSDK + var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(getWindowFunc()); +#else + if (element.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)}."); + } + var hwnd = new WindowInteropHelper(wpfWindow).Handle; +#endif + + _hdc = WindowsRenderingNativeMethods.GetDC(hwnd); + + WindowsRenderingNativeMethods.PIXELFORMATDESCRIPTOR pfd = new(); + pfd.nSize = (ushort)Marshal.SizeOf(pfd); + pfd.nVersion = 1; + pfd.dwFlags = WindowsRenderingNativeMethods.PFD_DRAW_TO_WINDOW | WindowsRenderingNativeMethods.PFD_SUPPORT_OPENGL | WindowsRenderingNativeMethods.PFD_DOUBLEBUFFER; + pfd.iPixelType = WindowsRenderingNativeMethods.PFD_TYPE_RGBA; + pfd.cColorBits = 32; + pfd.cRedBits = 8; + pfd.cGreenBits = 8; + pfd.cBlueBits = 8; + pfd.cAlphaBits = 8; + pfd.cDepthBits = 16; + pfd.cStencilBits = 1; // anything > 0 is fine, we will most likely get 8 + pfd.iLayerType = WindowsRenderingNativeMethods.PFD_MAIN_PLANE; + + var pixelFormat = WindowsRenderingNativeMethods.ChoosePixelFormat(_hdc, ref pfd); + + // To inspect the chosen pixel format: + // WindowsRenderingNativeMethods.PIXELFORMATDESCRIPTOR temp_pfd = default; + // WindowsRenderingNativeMethods.DescribePixelFormat(_hdc, _pixelFormat, (uint)Marshal.SizeOf(), ref temp_pfd); + + 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"); + } + + _glContext = WindowsRenderingNativeMethods.wglCreateContext(_hdc); + + 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()); + + public void DestroyContext() + { + if (WindowsRenderingNativeMethods.wglDeleteContext(_glContext) == 0) + { + if (this.Log().IsEnabled(LogLevel.Error)) + { + this.Log().Error($"{nameof(WindowsRenderingNativeMethods.wglDeleteContext)} failed."); + } + } + _glContext = default; + _hdc = default; + } + + public IDisposable MakeCurrent() + { + var glContext = WindowsRenderingNativeMethods.wglGetCurrentContext(); + var dc = WindowsRenderingNativeMethods.wglGetCurrentDC(); + if (WindowsRenderingNativeMethods.wglMakeCurrent(_hdc, _glContext) == 0) + { + if (this.Log().IsEnabled(LogLevel.Error)) + { + this.Log().Error($"{nameof(WindowsRenderingNativeMethods.wglMakeCurrent)} failed."); + } + } + return Disposable.Create(() => + { + if (WindowsRenderingNativeMethods.wglMakeCurrent(dc, glContext) == 0) + { + if (this.Log().IsEnabled(LogLevel.Error)) + { + this.Log().Error($"{nameof(WindowsRenderingNativeMethods.wglMakeCurrent)} failed."); + } + } + }); + } + + // 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/Rendering/OpenGLWpfRenderer.NativeMethods.cs b/src/Uno.UI.Runtime.Skia.Wpf/Rendering/OpenGLWpfRenderer.NativeMethods.cs deleted file mode 100644 index 64a6bb39df14..000000000000 --- a/src/Uno.UI.Runtime.Skia.Wpf/Rendering/OpenGLWpfRenderer.NativeMethods.cs +++ /dev/null @@ -1,148 +0,0 @@ -#nullable enable - -using System; -using System.Runtime.InteropServices; - -namespace Uno.UI.Runtime.Skia.Wpf.Rendering; - -internal partial class OpenGLWpfRenderer -{ - internal static class NativeMethods - { - [DllImport("user32.dll")] - internal static extern IntPtr GetDC(IntPtr hWnd); - - [DllImport("user32.dll")] - internal static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC); - - [DllImport("gdi32.dll")] - internal static extern int ChoosePixelFormat(IntPtr hdc, ref PIXELFORMATDESCRIPTOR ppfd); - - [DllImport("gdi32.dll")] - internal static extern int SetPixelFormat(IntPtr hdc, int iPixelFormat, ref PIXELFORMATDESCRIPTOR ppfd); - - [DllImport("gdi32.dll")] - internal static extern int DescribePixelFormat(IntPtr hdc, int iPixelFormat, uint nBytes, ref PIXELFORMATDESCRIPTOR ppfd); - - [DllImport("opengl32.dll")] - internal static extern IntPtr wglCreateContext(IntPtr hdc); - - [DllImport("opengl32.dll")] - public static extern IntPtr wglGetCurrentDC(); - - [DllImport("opengl32.dll")] - internal static extern IntPtr wglCreateCompatibleDC(IntPtr hdc); - - [DllImport("opengl32.dll")] - internal static extern int wglDeleteContext(IntPtr hglrc); - - [DllImport("opengl32.dll")] - internal static extern int wglMakeCurrent(IntPtr hdc, IntPtr hglrc); - - [DllImport("opengl32.dll")] - internal static extern void glClearColor(float red, float green, float blue, float alpha); - - [DllImport("opengl32.dll")] - internal static extern void glClear(int mask); - - [DllImport("opengl32.dll")] - internal static extern void glViewport(int x, int y, int width, int height); - - [DllImport("opengl32.dll")] - internal static extern void glBegin(int mode); - - [DllImport("opengl32.dll")] - internal static extern void glEnd(); - - [DllImport("opengl32.dll")] - internal static extern void glFlush(); - - [DllImport("opengl32.dll")] - internal static extern void glFinish(); - - [DllImport("opengl32.dll")] - internal static extern void glColor3f(float red, float green, float blue); - - [DllImport("opengl32.dll")] - internal static extern void glVertex3f(float x, float y, float z); - - [DllImport("opengl32.dll")] - internal static extern void SwapBuffers(IntPtr hdc); - - [DllImport("opengl32.dll")] - internal static extern bool wglSwapLayerBuffers(IntPtr hdc, uint fuPlanes); - - [DllImport("opengl32.dll", SetLastError = true)] - private static extern IntPtr glGetString(int name); - - [DllImport("opengl32.dll")] - internal static extern void glReadPixels(int x, int y, int width, int height, int format, int type, IntPtr pixels); - - [DllImport("opengl32.dll")] - public static extern void glGetIntegerv(int pname, out int data); - - public static string? GetOpenGLVersion() - => Marshal.PtrToStringAnsi(glGetString(GL_VERSION)); - - [StructLayout(LayoutKind.Sequential)] - internal struct PIXELFORMATDESCRIPTOR - { - public ushort nSize; - public ushort nVersion; - public uint dwFlags; - public byte iPixelType; - public byte cColorBits; - public byte cRedBits; - public byte cRedShift; - public byte cGreenBits; - public byte cGreenShift; - public byte cBlueBits; - public byte cBlueShift; - public byte cAlphaBits; - public byte cAlphaShift; - public byte cAccumBits; - public byte cAccumRedBits; - public byte cAccumGreenBits; - public byte cAccumBlueBits; - public byte cAccumAlphaBits; - public byte cDepthBits; - public byte cStencilBits; - public byte cAuxBuffers; - public byte iLayerType; - public byte bReserved; - public uint dwLayerMask; - public uint dwVisibleMask; - public uint dwDamageMask; - } - - internal const int PFD_DRAW_TO_WINDOW = 0x00000004; - internal const int PFD_SUPPORT_OPENGL = 0x00000020; - internal const int PFD_DOUBLEBUFFER = 0x00000001; - internal const int PFD_TYPE_RGBA = 0; - - internal const int PFD_MAIN_PLANE = 0; - internal const int WGL_SWAP_MAIN_PLANE = 1; - - internal const int GL_COLOR_BUFFER_BIT = 0x00004000; - - internal const int GL_TRIANGLES = 0x0004; - - internal const int GL_VERSION = 0x1F02; - - internal const int ERROR_SUCCESS = 0; - - internal const int GL_DEPTH_BUFFER_BIT = 0x00000100; - internal const int GL_STENCIL_BUFFER_BIT = 0x400; - - internal const int GL_PROJECTION = 0x1701; - internal const int GL_MODELVIEW = 0x1700; - - internal const int GL_BGRA_EXT = 0x80E1; - internal const int GL_UNSIGNED_BYTE = 0x1401; - - internal const int GL_FRAMEBUFFER_BINDING = 0x8CA6; - internal const int GL_STENCIL_BITS = 0x0D57; - internal const int GL_STENCIL = 0x1802; - internal const int GL_SAMPLES = 0x80A9; - } -} diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Rendering/OpenGLWpfRenderer.cs b/src/Uno.UI.Runtime.Skia.Wpf/Rendering/OpenGLWpfRenderer.cs index 6121099a9829..bb26fb78c525 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Rendering/OpenGLWpfRenderer.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Rendering/OpenGLWpfRenderer.cs @@ -72,14 +72,14 @@ public bool TryInitialize() _hwnd = hwnd; // Get the device context for the window - _hdc = NativeMethods.GetDC(_hwnd); + _hdc = WindowsRenderingNativeMethods.GetDC(_hwnd); // Set the pixel format - NativeMethods.PIXELFORMATDESCRIPTOR pfd = new(); + WindowsRenderingNativeMethods.PIXELFORMATDESCRIPTOR pfd = new(); pfd.nSize = (ushort)Marshal.SizeOf(pfd); pfd.nVersion = 1; - pfd.dwFlags = NativeMethods.PFD_DRAW_TO_WINDOW | NativeMethods.PFD_SUPPORT_OPENGL | NativeMethods.PFD_DOUBLEBUFFER; - pfd.iPixelType = NativeMethods.PFD_TYPE_RGBA; + pfd.dwFlags = WindowsRenderingNativeMethods.PFD_DRAW_TO_WINDOW | WindowsRenderingNativeMethods.PFD_SUPPORT_OPENGL | WindowsRenderingNativeMethods.PFD_DOUBLEBUFFER; + pfd.iPixelType = WindowsRenderingNativeMethods.PFD_TYPE_RGBA; pfd.cColorBits = 32; pfd.cRedBits = 8; pfd.cGreenBits = 8; @@ -87,10 +87,10 @@ public bool TryInitialize() pfd.cAlphaBits = 8; pfd.cDepthBits = 16; pfd.cStencilBits = 1; // anything > 0 is fine, we will most likely get 8 - pfd.iLayerType = NativeMethods.PFD_MAIN_PLANE; + pfd.iLayerType = WindowsRenderingNativeMethods.PFD_MAIN_PLANE; // Choose the best matching pixel format - _pixelFormat = NativeMethods.ChoosePixelFormat(_hdc, ref pfd); + _pixelFormat = WindowsRenderingNativeMethods.ChoosePixelFormat(_hdc, ref pfd); // To inspect the chosen pixel format: // NativeMethods.PIXELFORMATDESCRIPTOR temp_pfd = default; @@ -107,7 +107,7 @@ public bool TryInitialize() } // Set the pixel format for the device context - if (NativeMethods.SetPixelFormat(_hdc, _pixelFormat, ref pfd) == 0) + if (WindowsRenderingNativeMethods.SetPixelFormat(_hdc, _pixelFormat, ref pfd) == 0) { if (this.Log().IsEnabled(LogLevel.Debug)) { @@ -118,7 +118,7 @@ public bool TryInitialize() } // Create the OpenGL context - _glContext = NativeMethods.wglCreateContext(_hdc); + _glContext = WindowsRenderingNativeMethods.wglCreateContext(_hdc); if (_glContext == IntPtr.Zero) { @@ -131,10 +131,10 @@ public bool TryInitialize() } #pragma warning disable CA1806 // Do not ignore method results - NativeMethods.wglMakeCurrent(_hdc, _glContext); + WindowsRenderingNativeMethods.wglMakeCurrent(_hdc, _glContext); #pragma warning restore CA1806 // Do not ignore method results - var version = NativeMethods.GetOpenGLVersion(); + var version = WindowsRenderingNativeMethods.GetOpenGLVersion(); if (this.Log().IsEnabled(LogLevel.Trace)) { @@ -143,7 +143,7 @@ public bool TryInitialize() #pragma warning disable CA1806 // Do not ignore method results - NativeMethods.wglMakeCurrent(_hdc, _glContext); + WindowsRenderingNativeMethods.wglMakeCurrent(_hdc, _glContext); #pragma warning restore CA1806 // Do not ignore method results return TryCreateGRGLContext(out _grContext); @@ -186,7 +186,7 @@ public void Render(DrawingContext drawingContext) } #pragma warning disable CA1806 // Do not ignore method results - NativeMethods.wglMakeCurrent(_hdc, _glContext); + WindowsRenderingNativeMethods.wglMakeCurrent(_hdc, _glContext); #pragma warning restore CA1806 // Do not ignore method results if (_renderTarget == null || _surface == null || _renderTarget.Width != width || _renderTarget.Height != height) @@ -218,7 +218,7 @@ public void Render(DrawingContext drawingContext) } } - NativeMethods.glClear(NativeMethods.GL_COLOR_BUFFER_BIT | NativeMethods.GL_STENCIL_BUFFER_BIT | NativeMethods.GL_DEPTH_BUFFER_BIT); + WindowsRenderingNativeMethods.glClear(WindowsRenderingNativeMethods.GL_COLOR_BUFFER_BIT | WindowsRenderingNativeMethods.GL_STENCIL_BUFFER_BIT | WindowsRenderingNativeMethods.GL_DEPTH_BUFFER_BIT); var canvas = _surface.Canvas; @@ -240,7 +240,7 @@ public void Render(DrawingContext drawingContext) if (_backBuffer != null) { _backBuffer.Lock(); - NativeMethods.glReadPixels(0, 0, width, height, NativeMethods.GL_BGRA_EXT, NativeMethods.GL_UNSIGNED_BYTE, _backBuffer.BackBuffer); + WindowsRenderingNativeMethods.glReadPixels(0, 0, width, height, WindowsRenderingNativeMethods.GL_BGRA_EXT, WindowsRenderingNativeMethods.GL_UNSIGNED_BYTE, _backBuffer.BackBuffer); _backBuffer.AddDirtyRect(new Int32Rect(0, 0, width, height)); _backBuffer.Unlock(); @@ -252,9 +252,9 @@ public void Render(DrawingContext drawingContext) private (int framebuffer, int stencil, int samples) GetGLBuffers() { - NativeMethods.glGetIntegerv(NativeMethods.GL_FRAMEBUFFER_BINDING, out var framebuffer); - NativeMethods.glGetIntegerv(NativeMethods.GL_STENCIL_BITS, out var stencil); - NativeMethods.glGetIntegerv(NativeMethods.GL_SAMPLES, out var samples); + WindowsRenderingNativeMethods.glGetIntegerv(WindowsRenderingNativeMethods.GL_FRAMEBUFFER_BINDING, out var framebuffer); + WindowsRenderingNativeMethods.glGetIntegerv(WindowsRenderingNativeMethods.GL_STENCIL_BITS, out var stencil); + WindowsRenderingNativeMethods.glGetIntegerv(WindowsRenderingNativeMethods.GL_SAMPLES, out var samples); return (framebuffer, stencil, samples); } @@ -294,8 +294,8 @@ private void Release() { // Cleanup resources #pragma warning disable CA1806 // Do not ignore method results - NativeMethods.wglDeleteContext(_glContext); - NativeMethods.ReleaseDC(_hwnd, _hdc); + WindowsRenderingNativeMethods.wglDeleteContext(_glContext); + WindowsRenderingNativeMethods.ReleaseDC(_hwnd, _hdc); #pragma warning restore CA1806 // Do not ignore method results _glContext = 0; diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Rendering/WindowsRenderingNativeMethods.cs b/src/Uno.UI.Runtime.Skia.Wpf/Rendering/WindowsRenderingNativeMethods.cs new file mode 100644 index 000000000000..d18687b6cee2 --- /dev/null +++ b/src/Uno.UI.Runtime.Skia.Wpf/Rendering/WindowsRenderingNativeMethods.cs @@ -0,0 +1,155 @@ +#nullable enable + +using System; +using System.Runtime.InteropServices; + +#if WINDOWS_UWP || WINAPPSDK +namespace Uno.WinUI.Graphics3DGL; +#else +namespace Uno.UI.Runtime.Skia.Wpf.Rendering; +#endif + +internal static class WindowsRenderingNativeMethods +{ + [DllImport("user32.dll")] + internal static extern IntPtr GetDC(IntPtr hWnd); + + [DllImport("user32.dll")] + internal static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC); + + [DllImport("gdi32.dll")] + internal static extern int ChoosePixelFormat(IntPtr hdc, ref PIXELFORMATDESCRIPTOR ppfd); + + [DllImport("gdi32.dll")] + internal static extern int SetPixelFormat(IntPtr hdc, int iPixelFormat, ref PIXELFORMATDESCRIPTOR ppfd); + + [DllImport("gdi32.dll")] + internal static extern int DescribePixelFormat(IntPtr hdc, int iPixelFormat, uint nBytes, ref PIXELFORMATDESCRIPTOR ppfd); + + [DllImport("opengl32.dll", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)] + public static extern IntPtr wglGetProcAddress(string functionName); + + [DllImport("opengl32.dll")] + internal static extern IntPtr wglCreateContext(IntPtr hdc); + + [DllImport("opengl32.dll")] + public static extern IntPtr wglGetCurrentDC(); + + [DllImport("opengl32.dll")] + public static extern IntPtr wglGetCurrentContext(); + + [DllImport("opengl32.dll")] + internal static extern IntPtr wglCreateCompatibleDC(IntPtr hdc); + + [DllImport("opengl32.dll")] + internal static extern int wglDeleteContext(IntPtr hglrc); + + [DllImport("opengl32.dll")] + internal static extern int wglMakeCurrent(IntPtr hdc, IntPtr hglrc); + + [DllImport("opengl32.dll")] + internal static extern void glClearColor(float red, float green, float blue, float alpha); + + [DllImport("opengl32.dll")] + internal static extern void glClear(int mask); + + [DllImport("opengl32.dll")] + internal static extern void glViewport(int x, int y, int width, int height); + + [DllImport("opengl32.dll")] + internal static extern void glBegin(int mode); + + [DllImport("opengl32.dll")] + internal static extern void glEnd(); + + [DllImport("opengl32.dll")] + internal static extern void glFlush(); + + [DllImport("opengl32.dll")] + internal static extern void glFinish(); + + [DllImport("opengl32.dll")] + internal static extern void glColor3f(float red, float green, float blue); + + [DllImport("opengl32.dll")] + internal static extern void glVertex3f(float x, float y, float z); + + [DllImport("opengl32.dll")] + internal static extern void SwapBuffers(IntPtr hdc); + + [DllImport("opengl32.dll")] + internal static extern bool wglSwapLayerBuffers(IntPtr hdc, uint fuPlanes); + + [DllImport("opengl32.dll", SetLastError = true)] + private static extern IntPtr glGetString(int name); + + [DllImport("opengl32.dll")] + internal static extern void glReadPixels(int x, int y, int width, int height, int format, int type, IntPtr pixels); + + [DllImport("opengl32.dll")] + public static extern void glGetIntegerv(int pname, out int data); + + public static string? GetOpenGLVersion() + => Marshal.PtrToStringAnsi(glGetString(GL_VERSION)); + + [StructLayout(LayoutKind.Sequential)] + internal struct PIXELFORMATDESCRIPTOR + { + public ushort nSize; + public ushort nVersion; + public uint dwFlags; + public byte iPixelType; + public byte cColorBits; + public byte cRedBits; + public byte cRedShift; + public byte cGreenBits; + public byte cGreenShift; + public byte cBlueBits; + public byte cBlueShift; + public byte cAlphaBits; + public byte cAlphaShift; + public byte cAccumBits; + public byte cAccumRedBits; + public byte cAccumGreenBits; + public byte cAccumBlueBits; + public byte cAccumAlphaBits; + public byte cDepthBits; + public byte cStencilBits; + public byte cAuxBuffers; + public byte iLayerType; + public byte bReserved; + public uint dwLayerMask; + public uint dwVisibleMask; + public uint dwDamageMask; + } + + internal const int PFD_DRAW_TO_WINDOW = 0x00000004; + internal const int PFD_SUPPORT_OPENGL = 0x00000020; + internal const int PFD_DOUBLEBUFFER = 0x00000001; + internal const int PFD_TYPE_RGBA = 0; + + internal const int PFD_MAIN_PLANE = 0; + internal const int WGL_SWAP_MAIN_PLANE = 1; + + internal const int GL_COLOR_BUFFER_BIT = 0x00004000; + + internal const int GL_TRIANGLES = 0x0004; + + internal const int GL_VERSION = 0x1F02; + + internal const int ERROR_SUCCESS = 0; + + internal const int GL_DEPTH_BUFFER_BIT = 0x00000100; + internal const int GL_STENCIL_BUFFER_BIT = 0x400; + + internal const int GL_PROJECTION = 0x1701; + internal const int GL_MODELVIEW = 0x1700; + + internal const int GL_BGRA_EXT = 0x80E1; + internal const int GL_UNSIGNED_BYTE = 0x1401; + + internal const int GL_FRAMEBUFFER_BINDING = 0x8CA6; + internal const int GL_STENCIL_BITS = 0x0D57; + internal const int GL_STENCIL = 0x1802; + internal const int GL_SAMPLES = 0x80A9; +} 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 704fa9d750ee..2ac97c1882cf 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,6 +33,7 @@ + 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 9fca805f2504..a4c40812cb32 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,6 +33,8 @@ + + diff --git a/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs b/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs index 1e2f94510ca4..a6d6393c3436 100644 --- a/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs +++ b/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs @@ -66,6 +66,8 @@ static X11ApplicationHost() ApiExtensibility.Register(typeof(ContentPresenter.INativeElementHostingExtension), o => new X11NativeElementHostingExtension(o)); ApiExtensibility.Register(typeof(Windows.ApplicationModel.DataTransfer.DragDrop.Core.IDragDropExtension), o => new X11DragDropExtension(o)); + + ApiExtensibility.Register(typeof(Uno.Graphics.INativeOpenGLWrapper), _ => new X11NativeOpenGLWrapper()); } public X11ApplicationHost(Func appBuilder) diff --git a/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs new file mode 100644 index 000000000000..05b90001ad7f --- /dev/null +++ b/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs @@ -0,0 +1,85 @@ +using System; +using Microsoft.UI.Xaml; +using Silk.NET.OpenGL; +using Uno.Disposables; +using Uno.Graphics; + +namespace Uno.WinUI.Runtime.Skia.X11; + +internal class X11NativeOpenGLWrapper : INativeOpenGLWrapper +{ + private IntPtr _display; + 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) + { + throw new InvalidOperationException($"The XamlRoot and its XamlRootHost must be initialized on the element before calling {nameof(CreateContext)}."); + } + + _display = xamlRootHost.RootX11Window.Display; + + using var lockDisposable = X11Helper.XLock(_display); + + var glxAttribs = new int[]{ + GlxConsts.GLX_DRAWABLE_TYPE , GlxConsts.GLX_PBUFFER_BIT, + GlxConsts.GLX_RED_SIZE , 8, + GlxConsts.GLX_GREEN_SIZE , 8, + GlxConsts.GLX_BLUE_SIZE , 8, + GlxConsts.GLX_ALPHA_SIZE , 8, + GlxConsts.GLX_DEPTH_SIZE , 8, + GlxConsts.GLX_STENCIL_SIZE , 8, + (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) + { + throw new InvalidOperationException($"{nameof(GlxInterface.glXChooseFBConfig)} failed to retrieve GLX framebuffer configurations."); + } + 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 + { + bestFbc = ptr[c]; + visual = visual_; + break; + } + } + + if (visual == null) + { + throw new InvalidOperationException("Could not create correct visual window.\n"); + } + + _glContext = GlxInterface.glXCreateNewContext(_display, bestFbc, GlxConsts.GLX_RGBA_TYPE, IntPtr.Zero, /* True */ 1); + _pBuffer = GlxInterface.glXCreatePbuffer(_display, bestFbc, new[] { (int)X11Helper.None }); + } + + public object CreateGLSilkNETHandle() => GL.GetApi(GlxInterface.glXGetProcAddress); + + public void DestroyContext() + { + using var lockDisposable = X11Helper.XLock(_display); + + GlxInterface.glXDestroyPbuffer(_display, _pBuffer); + GlxInterface.glXDestroyContext(_display, _glContext); + + _display = default; + _glContext = default; + _pBuffer = default; + } + + public IDisposable MakeCurrent() + { + var glContext = GlxInterface.glXGetCurrentContext(); + var drawable = GlxInterface.glXGetCurrentDrawable(); + GlxInterface.glXMakeCurrent(_display, _pBuffer, _glContext); + return Disposable.Create(() => GlxInterface.glXMakeCurrent(_display, drawable, glContext)); + } +} diff --git a/src/Uno.UI.Runtime.Skia.X11/X11XamlRootHost.cs b/src/Uno.UI.Runtime.Skia.X11/X11XamlRootHost.cs index 820cd3975901..3a85977f166f 100644 --- a/src/Uno.UI.Runtime.Skia.X11/X11XamlRootHost.cs +++ b/src/Uno.UI.Runtime.Skia.X11/X11XamlRootHost.cs @@ -42,6 +42,21 @@ internal partial class X11XamlRootHost : IXamlRootHost (IntPtr)EventMask.FocusChangeMask | (IntPtr)EventMask.NoEventMask; + private static readonly int[] _glxAttribs = { + GlxConsts.GLX_X_RENDERABLE , /* True */ 1, + GlxConsts.GLX_DRAWABLE_TYPE , GlxConsts.GLX_WINDOW_BIT, + GlxConsts.GLX_RENDER_TYPE , GlxConsts.GLX_RGBA_BIT, + GlxConsts.GLX_X_VISUAL_TYPE , GlxConsts.GLX_TRUE_COLOR, + GlxConsts.GLX_RED_SIZE , 8, + GlxConsts.GLX_GREEN_SIZE , 8, + GlxConsts.GLX_BLUE_SIZE , 8, + GlxConsts.GLX_ALPHA_SIZE , 8, + GlxConsts.GLX_DEPTH_SIZE , 8, + GlxConsts.GLX_STENCIL_SIZE , 8, + GlxConsts.GLX_DOUBLEBUFFER , /* True */ 1, + (int)X11Helper.None + }; + private static bool _firstWindowCreated; private static readonly object _x11WindowToXamlRootHostMutex = new(); private static readonly Dictionary _x11WindowToXamlRootHost = new(); @@ -394,24 +409,9 @@ private void Initialize() // https://learnopengl.com/Advanced-OpenGL/Framebuffers private unsafe static X11Window CreateGLXWindow(IntPtr display, int screen, Size size, IntPtr parent) { - int[] glxAttribs = { - GlxConsts.GLX_X_RENDERABLE , /* True */ 1, - GlxConsts.GLX_DRAWABLE_TYPE , GlxConsts.GLX_WINDOW_BIT, - GlxConsts.GLX_RENDER_TYPE , GlxConsts.GLX_RGBA_BIT, - GlxConsts.GLX_X_VISUAL_TYPE , GlxConsts.GLX_TRUE_COLOR, - GlxConsts.GLX_RED_SIZE , 8, - GlxConsts.GLX_GREEN_SIZE , 8, - GlxConsts.GLX_BLUE_SIZE , 8, - GlxConsts.GLX_ALPHA_SIZE , 8, - GlxConsts.GLX_DEPTH_SIZE , 24, - GlxConsts.GLX_STENCIL_SIZE , 8, - GlxConsts.GLX_DOUBLEBUFFER , /* True */ 1, - (int)X11Helper.None - }; - IntPtr bestFbc = IntPtr.Zero; XVisualInfo* visual = null; - var ptr = GlxInterface.glXChooseFBConfig(display, screen, glxAttribs, out var count); + var ptr = GlxInterface.glXChooseFBConfig(display, screen, _glxAttribs, out var count); if (ptr == null || *ptr == IntPtr.Zero) { throw new InvalidOperationException($"{nameof(GlxInterface.glXChooseFBConfig)} failed to retrieve GLX frambuffer configurations."); diff --git a/src/Uno.UI.sln b/src/Uno.UI.sln index e3de420f83d5..599d35388e04 100644 --- a/src/Uno.UI.sln +++ b/src/Uno.UI.sln @@ -315,6 +315,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SamplesApp.Skia.Generic", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uno.UI.Runtime.Skia.X11", "Uno.UI.Runtime.Skia.X11\Uno.UI.Runtime.Skia.X11.csproj", "{37441DE3-088B-4B63-A1E2-E70E39BF4222}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Uno.WinUI.Graphics3DGL", "AddIns\Uno.WinUI.Graphics3DGL\Uno.WinUI.Graphics3DGL.csproj", "{0F62DA75-6AD9-4F58-B69C-D63CA9053E34}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -5728,6 +5730,48 @@ Global {37441DE3-088B-4B63-A1E2-E70E39BF4222}.Release|x64.Build.0 = Release|Any CPU {37441DE3-088B-4B63-A1E2-E70E39BF4222}.Release|x86.ActiveCfg = Release|Any CPU {37441DE3-088B-4B63-A1E2-E70E39BF4222}.Release|x86.Build.0 = Release|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Debug|ARM.ActiveCfg = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Debug|ARM.Build.0 = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Debug|ARM64.Build.0 = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Debug|iPhone.Build.0 = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Debug|x64.ActiveCfg = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Debug|x64.Build.0 = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Debug|x86.ActiveCfg = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Debug|x86.Build.0 = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release_NoSamples|Any CPU.ActiveCfg = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release_NoSamples|Any CPU.Build.0 = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release_NoSamples|ARM.ActiveCfg = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release_NoSamples|ARM.Build.0 = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release_NoSamples|ARM64.ActiveCfg = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release_NoSamples|ARM64.Build.0 = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release_NoSamples|iPhone.ActiveCfg = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release_NoSamples|iPhone.Build.0 = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release_NoSamples|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release_NoSamples|iPhoneSimulator.Build.0 = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release_NoSamples|x64.ActiveCfg = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release_NoSamples|x64.Build.0 = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release_NoSamples|x86.ActiveCfg = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release_NoSamples|x86.Build.0 = Debug|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release|Any CPU.Build.0 = Release|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release|ARM.ActiveCfg = Release|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release|ARM.Build.0 = Release|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release|ARM64.ActiveCfg = Release|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release|ARM64.Build.0 = Release|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release|iPhone.ActiveCfg = Release|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release|iPhone.Build.0 = Release|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release|x64.ActiveCfg = Release|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release|x64.Build.0 = Release|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release|x86.ActiveCfg = Release|Any CPU + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -5871,6 +5915,7 @@ Global {A8C22BED-2589-48F1-B1D4-0B436A90FC3B} = {416684CF-A4E3-4079-B380-3FF0B00E433C} {93E6D033-52E2-4C2B-A715-A9FC1F609E3D} = {995C1054-AB61-42EE-9A17-C32155BD6D13} {37441DE3-088B-4B63-A1E2-E70E39BF4222} = {416684CF-A4E3-4079-B380-3FF0B00E433C} + {0F62DA75-6AD9-4F58-B69C-D63CA9053E34} = {E872CD33-A455-4DC4-8A87-5FEEC1C39A44} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9B2608F4-D82B-4B72-B399-33E822DF01D0} diff --git a/src/Uno.UI/AssemblyInfo.cs b/src/Uno.UI/AssemblyInfo.cs index ed3ce8151525..8b4faf31a0d1 100644 --- a/src/Uno.UI/AssemblyInfo.cs +++ b/src/Uno.UI/AssemblyInfo.cs @@ -29,6 +29,8 @@ [assembly: InternalsVisibleTo("Uno.UI.MediaPlayer.Skia.Gtk")] [assembly: InternalsVisibleTo("Uno.UI.MediaPlayer.WebAssembly")] +[assembly: InternalsVisibleTo("Uno.WinUI.Graphics3DGL")] + [assembly: AssemblyMetadata("IsTrimmable", "True")] [assembly: System.Reflection.Metadata.MetadataUpdateHandler(typeof(Uno.UI.RuntimeTypeMetadataUpdateHandler))] diff --git a/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs b/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs new file mode 100644 index 000000000000..e9575a2b2ffb --- /dev/null +++ b/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs @@ -0,0 +1,35 @@ +using System; +using Microsoft.UI.Xaml; + +#if WINAPPSDK || WINDOWS_UWP +namespace Uno.WinUI.Graphics3DGL; +#else +namespace Uno.Graphics; +#endif + +internal interface INativeOpenGLWrapper +{ + 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.GL + public object CreateGLSilkNETHandle(); + + /// + /// Destroys the context created in . This is only called if a preceding + /// call to is made (after the last call to ). + /// + public void DestroyContext(); + + /// + /// 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(); +} diff --git a/src/Uno.UWP/AssemblyInfo.cs b/src/Uno.UWP/AssemblyInfo.cs index cc804c467095..2f199f5d6d22 100644 --- a/src/Uno.UWP/AssemblyInfo.cs +++ b/src/Uno.UWP/AssemblyInfo.cs @@ -16,6 +16,8 @@ [assembly: InternalsVisibleTo("Uno.UI.MediaPlayer.WebAssembly")] [assembly: InternalsVisibleTo("Uno.UI.XamlHost")] +[assembly: InternalsVisibleTo("Uno.WinUI.Graphics3DGL")] + [assembly: InternalsVisibleTo("SamplesApp")] [assembly: InternalsVisibleTo("SamplesApp.Droid")] [assembly: InternalsVisibleTo("SamplesApp.macOS")] diff --git a/src/Uno.UWP/Storage/Streams/Buffer.cs b/src/Uno.UWP/Storage/Streams/Buffer.cs index 1aa13e3181d9..c61ff15196e5 100644 --- a/src/Uno.UWP/Storage/Streams/Buffer.cs +++ b/src/Uno.UWP/Storage/Streams/Buffer.cs @@ -12,6 +12,7 @@ public partial class Buffer : IBuffer /// internal const int DefaultCapacity = 1024 * 1024; // 1M + private readonly byte[] _buffer; private readonly Memory _data; private uint _length; @@ -33,7 +34,8 @@ internal static Buffer Cast(IBuffer impl) public Buffer(uint capacity) { - _data = new Memory(new byte[capacity]); + _buffer = new byte[capacity]; + _data = new Memory(_buffer); } internal Buffer(byte[] data) @@ -73,6 +75,17 @@ public uint Length /// internal Span Span => _data.Span; + /// + /// This is as dangerous as . Read the remarks there. + /// + unsafe internal void ApplyActionOnRawBufferPtr(Action action) + { + fixed (void* asd = _buffer) + { + action((IntPtr)asd); + } + } + /// /// Retrieve the underlying data array. /// WARNING: DANGEROUS METHOD cf. remarks