From 5ff75bd426bf0cb683b8a3b2fa2527d8ef2864d7 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 25 Jan 2024 17:17:40 +0200 Subject: [PATCH 01/94] feat: add a SkiaVisual that allows external use of SkiaSharp --- .../UITests.Shared/UITests.Shared.projitems | 7 + .../SkiaVisualShowcase.xaml | 22 ++ .../SkiaVisualShowcase.xaml.cs | 204 ++++++++++++++++++ .../Composition/SkiaVisual.skia.cs | 14 ++ 4 files changed, 247 insertions(+) create mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml create mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml.cs create mode 100644 src/Uno.UI.Composition/Composition/SkiaVisual.skia.cs diff --git a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems index 690160643260..6f30c9b9884b 100644 --- a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems +++ b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems @@ -4694,6 +4694,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -8278,6 +8282,9 @@ VisualSurfaceTests.xaml + + SkiaVisualShowcase.xaml + VisualTranslationSample.xaml diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml new file mode 100644 index 000000000000..37e2a264204e --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml.cs new file mode 100644 index 000000000000..7eadcee2ef12 --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml.cs @@ -0,0 +1,204 @@ +using Uno.UI.Samples.Controls; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +#if __SKIA__ +using Microsoft.UI.Xaml.Hosting; +using System; +using System.Collections.Generic; +using System.Numerics; +using Windows.Foundation; +using Microsoft.UI.Composition; +using Microsoft.UI.Xaml.Controls.Primitives; +using SkiaSharp; +#endif + +namespace UITests.Shared.Windows_UI_Composition +{ + [Sample("Microsoft.UI.Composition", Name = "SkiaVisualShowcase")] + public sealed partial class SkiaVisualShowcase : UserControl + { + public int MaxSampleIndex => SkiaWrapper.VisualCount - 1; + + public SkiaVisualShowcase() + { + this.InitializeComponent(); + } + } + + public class SkiaWrapper : FrameworkElement + { +#if __SKIA__ + private SkiaVisual _skiaVisual; + private static readonly List _visuals = new List + { + typeof(SkiaVisual1), typeof(SkiaVisual2), typeof(SkiaVisual3), + }; + public static int VisualCount { get; } = _visuals.Count; +#else + public static int VisualCount { get; } = 0; +#endif + + public SkiaWrapper() + { + SampleChanged(0); + } + + public static DependencyProperty SampleProperty { get; } = DependencyProperty.Register( + nameof(Sample), + typeof(int), + typeof(SkiaWrapper), + new PropertyMetadata(-1, (o, args) => ((SkiaWrapper)o).SampleChanged((int)args.NewValue))); + + public int Sample + { + get => (int)GetValue(SampleProperty); + set => SetValue(SampleProperty, value); + } + + private void SampleChanged(int newIndex) + { +#if __SKIA__ + var coercedIndex = Math.Min(Math.Max(0, newIndex), _visuals.Count - 1); + if (coercedIndex != Sample) + { + Sample = coercedIndex; + } + else + { + _skiaVisual = (SkiaVisual)Activator.CreateInstance(_visuals[coercedIndex], Visual.Compositor); + ElementCompositionPreview.SetElementChildVisual(this, _skiaVisual!); + + InvalidateArrange(); + // don't wait for a rendering cycle to update the visual's Size, or else there will be + // a split second where the visual will have invalid clipping + Arrange(LayoutInformation.GetLayoutSlot(this)); + } +#endif + } + +#if __SKIA__ + protected override Size MeasureOverride(Size availableSize) => availableSize; + + protected override Size ArrangeOverride(Size finalSize) + { + if (_skiaVisual is { }) + { + _skiaVisual.Size = new Vector2((float)finalSize.Width, (float)finalSize.Height); + _skiaVisual.Clip = _skiaVisual.Compositor.CreateRectangleClip(0, 0, (float)finalSize.Width, (float)finalSize.Height); + ApplyFlowDirection((float)finalSize.Width); + } + + return base.ArrangeOverride(finalSize); + } + + private void ApplyFlowDirection(float width) + { + if (FlowDirection == FlowDirection.RightToLeft) + { + _skiaVisual.TransformMatrix = new Matrix4x4(new Matrix3x2(-1.0f, 0.0f, 0.0f, 1.0f, width, 0.0f)); + } + else + { + _skiaVisual.TransformMatrix = Matrix4x4.Identity; + } + } +#endif + } + +#if __SKIA__ + public class SkiaVisual1(Compositor compositor) : SkiaVisual(compositor) + { + // https://fiddle.skia.org/c/@shapes + protected override void Invalidate(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); + } + } + + public class SkiaVisual2(Compositor compositor) : SkiaVisual(compositor) + { + // https://fiddle.skia.org/c/@bezier_curves + protected override void Invalidate(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); + } + } + + public class SkiaVisual3(Compositor compositor) : SkiaVisual(compositor) + { + // https://fiddle.skia.org/c/@shader + protected override void Invalidate(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); + } + + private 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; + } + } +#endif +} diff --git a/src/Uno.UI.Composition/Composition/SkiaVisual.skia.cs b/src/Uno.UI.Composition/Composition/SkiaVisual.skia.cs new file mode 100644 index 000000000000..7eeb6ca5aa04 --- /dev/null +++ b/src/Uno.UI.Composition/Composition/SkiaVisual.skia.cs @@ -0,0 +1,14 @@ +using SkiaSharp; +using Uno.UI.Composition; + +namespace Microsoft.UI.Composition; + +public abstract class SkiaVisual(Compositor compositor) : Visual(compositor) +{ + internal override void Draw(in DrawingSession session) + { + Invalidate(session.Surface.Canvas); + } + + protected abstract void Invalidate(SKCanvas canvas); +} From afa9651c44af7222e960cff7761c9d242fe86a62 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Sat, 27 Jan 2024 01:11:45 +0200 Subject: [PATCH 02/94] chore: add SKCanvasElement and a lot of details --- .../SkiaVisualShowcase.xaml | 6 +- .../SkiaVisualShowcase.xaml.cs | 120 ++++++------------ .../Composition/SkiaVisual.skia.cs | 18 ++- .../UI/Xaml/Controls/SKCanvasElement.skia.cs | 111 ++++++++++++++++ 4 files changed, 170 insertions(+), 85 deletions(-) create mode 100644 src/Uno.UI/UI/Xaml/Controls/SKCanvasElement.skia.cs diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml index 37e2a264204e..733dc78f2d6b 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml @@ -12,11 +12,13 @@ + - - + + + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml.cs index 7eadcee2ef12..59f0a3cb6d18 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml.cs @@ -1,16 +1,11 @@ +using System; using Uno.UI.Samples.Controls; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; #if __SKIA__ -using Microsoft.UI.Xaml.Hosting; -using System; -using System.Collections.Generic; -using System.Numerics; -using Windows.Foundation; -using Microsoft.UI.Composition; -using Microsoft.UI.Xaml.Controls.Primitives; using SkiaSharp; +using Windows.Foundation; #endif namespace UITests.Shared.Windows_UI_Composition @@ -18,7 +13,7 @@ namespace UITests.Shared.Windows_UI_Composition [Sample("Microsoft.UI.Composition", Name = "SkiaVisualShowcase")] public sealed partial class SkiaVisualShowcase : UserControl { - public int MaxSampleIndex => SkiaWrapper.VisualCount - 1; + public int MaxSampleIndex => SkiaCanvasShowcaser.SampleCount - 1; public SkiaVisualShowcase() { @@ -26,29 +21,24 @@ public SkiaVisualShowcase() } } - public class SkiaWrapper : FrameworkElement - { #if __SKIA__ - private SkiaVisual _skiaVisual; - private static readonly List _visuals = new List - { - typeof(SkiaVisual1), typeof(SkiaVisual2), typeof(SkiaVisual3), - }; - public static int VisualCount { get; } = _visuals.Count; + public class SkiaCanvasShowcaser : SKCanvasElement #else - public static int VisualCount { get; } = 0; + public class SkiaCanvasShowcaser : FrameworkElement #endif + { + public static int SampleCount => 3; - public SkiaWrapper() + public SkiaCanvasShowcaser() { - SampleChanged(0); + Sample = 0; } public static DependencyProperty SampleProperty { get; } = DependencyProperty.Register( nameof(Sample), typeof(int), - typeof(SkiaWrapper), - new PropertyMetadata(-1, (o, args) => ((SkiaWrapper)o).SampleChanged((int)args.NewValue))); + typeof(SkiaCanvasShowcaser), + new PropertyMetadata(-1, (o, args) => ((SkiaCanvasShowcaser)o).SampleChanged((int)args.NewValue))); public int Sample { @@ -58,59 +48,39 @@ public int Sample private void SampleChanged(int newIndex) { -#if __SKIA__ - var coercedIndex = Math.Min(Math.Max(0, newIndex), _visuals.Count - 1); + var coercedIndex = Math.Min(Math.Max(0, newIndex), SampleCount - 1); if (coercedIndex != Sample) { Sample = coercedIndex; } - else - { - _skiaVisual = (SkiaVisual)Activator.CreateInstance(_visuals[coercedIndex], Visual.Compositor); - ElementCompositionPreview.SetElementChildVisual(this, _skiaVisual!); - - InvalidateArrange(); - // don't wait for a rendering cycle to update the visual's Size, or else there will be - // a split second where the visual will have invalid clipping - Arrange(LayoutInformation.GetLayoutSlot(this)); - } -#endif } #if __SKIA__ - protected override Size MeasureOverride(Size availableSize) => availableSize; - - protected override Size ArrangeOverride(Size finalSize) + protected override void RenderOverride(SKCanvas canvas, Size area) { - if (_skiaVisual is { }) + var minDim = Math.Min(area.Width, area.Height); + if (minDim > 250) { - _skiaVisual.Size = new Vector2((float)finalSize.Width, (float)finalSize.Height); - _skiaVisual.Clip = _skiaVisual.Compositor.CreateRectangleClip(0, 0, (float)finalSize.Width, (float)finalSize.Height); - ApplyFlowDirection((float)finalSize.Width); + // scale up if area is bigger than needed, assuming each drawing takes at most 260x260 + canvas.Scale((float)(minDim / 260), (float)(minDim / 260)); } - return base.ArrangeOverride(finalSize); - } - - private void ApplyFlowDirection(float width) - { - if (FlowDirection == FlowDirection.RightToLeft) + switch (Sample) { - _skiaVisual.TransformMatrix = new Matrix4x4(new Matrix3x2(-1.0f, 0.0f, 0.0f, 1.0f, width, 0.0f)); - } - else - { - _skiaVisual.TransformMatrix = Matrix4x4.Identity; + case 0: + SkiaDrawing0(canvas); + break; + case 1: + SkiaDrawing1(canvas); + break; + case 2: + SkiaDrawing2(canvas); + break; } } -#endif - } -#if __SKIA__ - public class SkiaVisual1(Compositor compositor) : SkiaVisual(compositor) - { // https://fiddle.skia.org/c/@shapes - protected override void Invalidate(SKCanvas canvas) + private void SkiaDrawing0(SKCanvas canvas) { canvas.DrawColor(SKColors.White); @@ -137,12 +107,9 @@ protected override void Invalidate(SKCanvas canvas) paint.Style = SKPaintStyle.Stroke; canvas.DrawRoundRect(rect, 10, 10, paint); } - } - public class SkiaVisual2(Compositor compositor) : SkiaVisual(compositor) - { // https://fiddle.skia.org/c/@bezier_curves - protected override void Invalidate(SKCanvas canvas) + private void SkiaDrawing1(SKCanvas canvas) { canvas.DrawColor(SKColors.White); @@ -159,12 +126,9 @@ protected override void Invalidate(SKCanvas canvas) path.QuadTo(10, 192, 250, 250); canvas.DrawPath(path, paint); } - } - public class SkiaVisual3(Compositor compositor) : SkiaVisual(compositor) - { // https://fiddle.skia.org/c/@shader - protected override void Invalidate(SKCanvas canvas) + private void SkiaDrawing2(SKCanvas canvas) { var paint = new SKPaint(); using var pathEffect = SKPathEffect.CreateDiscrete(10.0f, 4.0f); @@ -184,21 +148,21 @@ protected override void Invalidate(SKCanvas canvas) canvas.Clear(SKColors.White); var path = Star(); canvas.DrawPath(path, paint); - } - private 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) + SKPath Star() { - 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))); + 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; } - return path; } - } #endif + } } diff --git a/src/Uno.UI.Composition/Composition/SkiaVisual.skia.cs b/src/Uno.UI.Composition/Composition/SkiaVisual.skia.cs index 7eeb6ca5aa04..7ba59ad3b4b2 100644 --- a/src/Uno.UI.Composition/Composition/SkiaVisual.skia.cs +++ b/src/Uno.UI.Composition/Composition/SkiaVisual.skia.cs @@ -3,12 +3,20 @@ namespace Microsoft.UI.Composition; +/// +/// A Visual that allows users to directly draw on the Skia Canvas used by Uno to render a window. +/// public abstract class SkiaVisual(Compositor compositor) : Visual(compositor) { - internal override void Draw(in DrawingSession session) - { - Invalidate(session.Surface.Canvas); - } + internal override void Draw(in DrawingSession session) => RenderOverride(session.Surface.Canvas); - protected abstract void Invalidate(SKCanvas canvas); + /// + /// Queue a rendering cycle that will call . + /// + public void Invalidate() => Compositor.InvalidateRender(); + + /// + /// The SkiaSharp drawing logic goes here. + /// + protected abstract void RenderOverride(SKCanvas canvas); } diff --git a/src/Uno.UI/UI/Xaml/Controls/SKCanvasElement.skia.cs b/src/Uno.UI/UI/Xaml/Controls/SKCanvasElement.skia.cs new file mode 100644 index 000000000000..756c01181047 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/SKCanvasElement.skia.cs @@ -0,0 +1,111 @@ +using System.Numerics; +using Windows.Foundation; +using Windows.Graphics.Display; +using Microsoft.UI.Composition; +using Microsoft.UI.Xaml.Hosting; +using SkiaSharp; +using Uno.Disposables; + +namespace Microsoft.UI.Xaml.Controls; + +/// +/// A wrapper around that takes care of sizing, layouting and DPI. +/// +public abstract class SKCanvasElement : FrameworkElement +{ + private class SKCanvasVisual(SKCanvasElement owner, Compositor compositor) : SkiaVisual(compositor) + { + protected override void RenderOverride(SKCanvas canvas) => owner.RenderOverride(canvas, Size.ToSize()); + } + + private const float DpiBase = 96.0f; + private double _dpi = 1; + + private readonly SkiaVisual _skiaVisual; + private readonly SerialDisposable _dpiChangedDisposable = new SerialDisposable(); + + protected SKCanvasElement() + { + _skiaVisual = new SKCanvasVisual(this, Visual.Compositor); + ElementCompositionPreview.SetElementChildVisual(this, _skiaVisual!); + } + + public static DependencyProperty RespectFlowDirectionProperty { get; } = DependencyProperty.Register( + nameof(RespectFlowDirection), + typeof(bool), + typeof(SKCanvasElement), + new FrameworkPropertyMetadata((dO, _) => ((SKCanvasElement)dO).RespectFlowDirectionChanged())); + + /// + /// By default, SKCanvasElement will have the origin at the top-left of the drawing area with the normal directions increasing down and right. + /// If RespectFlowDirection is true, the drawing will be horizontally reflected when is . + /// + public bool RespectFlowDirection + { + get => (bool)GetValue(RespectFlowDirectionProperty); + set => SetValue(RespectFlowDirectionProperty, value); + } + + private void RespectFlowDirectionChanged() + { + if (ApplyFlowDirection()) + { + _skiaVisual.Invalidate(); + } + } + + /// + /// The SkiaSharp drawing logic goes here. + /// + /// The SKCanvas that should be drawn on. The drawing will directly appear in the clipping area. + /// The dimensions of the clipping area. + protected abstract void RenderOverride(SKCanvas canvas, Size area); + + /// + /// By default, SKCanvasElement uses all the given. Subclasses of SKCanvasElement + /// should override this method if they need something different. + /// + protected override Size MeasureOverride(Size availableSize) => availableSize; + + protected override Size ArrangeOverride(Size finalSize) + { + var dpiAwareSize = new Size(finalSize.Width * _dpi, finalSize.Height * _dpi); + _skiaVisual.Size = new Vector2((float)dpiAwareSize.Width, (float)dpiAwareSize.Height); + _skiaVisual.Clip = _skiaVisual.Compositor.CreateRectangleClip(0, 0, (float)dpiAwareSize.Width, (float)dpiAwareSize.Height); + + ApplyFlowDirection(); // if FlowDirection Changes, it will cause an InvalidateArrange, so we recalculate the TransformMatrix here + + return base.ArrangeOverride(finalSize); + } + + private bool ApplyFlowDirection() + { + var oldMatrix = _skiaVisual.TransformMatrix; + if (FlowDirection == FlowDirection.RightToLeft && !RespectFlowDirection) + { + _skiaVisual.TransformMatrix = new Matrix4x4(new Matrix3x2(-1.0f, 0.0f, 0.0f, 1.0f, (float)LayoutSlot.Width, 0.0f)); + } + else + { + _skiaVisual.TransformMatrix = Matrix4x4.Identity; + } + + return oldMatrix != _skiaVisual.TransformMatrix; + } + + private protected override void OnLoaded() + { + var display = DisplayInformation.GetForCurrentView(); + _dpiChangedDisposable.Disposable = Disposable.Create(() => display.DpiChanged -= OnDpiChanged); + display.DpiChanged += OnDpiChanged; + OnDpiChanged(display); + } + + private protected override void OnUnloaded() => _dpiChangedDisposable.Dispose(); + + private void OnDpiChanged(DisplayInformation sender, object args = null) + { + _dpi = sender.LogicalDpi / DpiBase; + _skiaVisual.Invalidate(); + } +} From f4fe4efcf1ff10a92b5ebc7393c18d5ad410c17c Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 29 Jan 2024 15:39:50 +0200 Subject: [PATCH 03/94] chore: remove unneeded dpi logic in SKCanvasElement --- .../UI/Xaml/Controls/SKCanvasElement.skia.cs | 25 ++----------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/Controls/SKCanvasElement.skia.cs b/src/Uno.UI/UI/Xaml/Controls/SKCanvasElement.skia.cs index 756c01181047..088e40cbfb76 100644 --- a/src/Uno.UI/UI/Xaml/Controls/SKCanvasElement.skia.cs +++ b/src/Uno.UI/UI/Xaml/Controls/SKCanvasElement.skia.cs @@ -18,11 +18,7 @@ private class SKCanvasVisual(SKCanvasElement owner, Compositor compositor) : Ski protected override void RenderOverride(SKCanvas canvas) => owner.RenderOverride(canvas, Size.ToSize()); } - private const float DpiBase = 96.0f; - private double _dpi = 1; - private readonly SkiaVisual _skiaVisual; - private readonly SerialDisposable _dpiChangedDisposable = new SerialDisposable(); protected SKCanvasElement() { @@ -69,9 +65,8 @@ private void RespectFlowDirectionChanged() protected override Size ArrangeOverride(Size finalSize) { - var dpiAwareSize = new Size(finalSize.Width * _dpi, finalSize.Height * _dpi); - _skiaVisual.Size = new Vector2((float)dpiAwareSize.Width, (float)dpiAwareSize.Height); - _skiaVisual.Clip = _skiaVisual.Compositor.CreateRectangleClip(0, 0, (float)dpiAwareSize.Width, (float)dpiAwareSize.Height); + _skiaVisual.Size = new Vector2((float)finalSize.Width, (float)finalSize.Height); + _skiaVisual.Clip = _skiaVisual.Compositor.CreateRectangleClip(0, 0, (float)finalSize.Width, (float)finalSize.Height); ApplyFlowDirection(); // if FlowDirection Changes, it will cause an InvalidateArrange, so we recalculate the TransformMatrix here @@ -92,20 +87,4 @@ private bool ApplyFlowDirection() return oldMatrix != _skiaVisual.TransformMatrix; } - - private protected override void OnLoaded() - { - var display = DisplayInformation.GetForCurrentView(); - _dpiChangedDisposable.Disposable = Disposable.Create(() => display.DpiChanged -= OnDpiChanged); - display.DpiChanged += OnDpiChanged; - OnDpiChanged(display); - } - - private protected override void OnUnloaded() => _dpiChangedDisposable.Dispose(); - - private void OnDpiChanged(DisplayInformation sender, object args = null) - { - _dpi = sender.LogicalDpi / DpiBase; - _skiaVisual.Invalidate(); - } } From c064404d3f3e35e2af22ee16bdbe95544d300093 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 29 Jan 2024 17:43:49 +0200 Subject: [PATCH 04/94] test: add tests for SKCanvasElement --- .../Given_SKCanvasElement.skia.cs | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs new file mode 100644 index 000000000000..51f29f8edbf6 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs @@ -0,0 +1,91 @@ +using System.Drawing; +using System.Threading.Tasks; +using Microsoft.UI; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using MUXControlsTestApp.Utilities; +using Private.Infrastructure; +using SkiaSharp; +using Uno.Disposables; +using Uno.UI.RuntimeTests.Helpers; +using Uno.UI.Xaml.Core; +using Size = Windows.Foundation.Size; +namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls; + +[TestClass] +[RunsOnUIThread] +public class Given_SKCanvasElement +{ + [TestMethod] + public async Task When_Clipped_Inside_ScrollViewer() + { + var SUT = new BlueFillSKCanvasElement + { + Height = 400, + Width = 400 + }; + + var border = new Border + { + BorderBrush = Colors.Green, + Height = 400, + Child = new ScrollViewer + { + VerticalAlignment = VerticalAlignment.Top, + Height = 100, + Background = Colors.Red, + Content = SUT + } + }; + + await UITestHelper.Load(border); + + var bitmap = await UITestHelper.ScreenShot(border); + + ImageAssert.HasColorInRectangle(bitmap, new Rectangle(0, 0, 400, 300), Colors.Blue); + ImageAssert.DoesNotHaveColorInRectangle(bitmap, new Rectangle(0, 101, 400, 299), Colors.Blue); + } + + [TestMethod] + [Ignore("RenderTargetBitmap doesn't account for FlowDirection")] + public async Task When_RespectFlowDirection() + { + var SUT = new BlueAndRedFillSKCanvasElement + { + Width = 200, + RespectFlowDirection = true + }; + + await UITestHelper.Load(SUT); + + var bitmap = await UITestHelper.ScreenShot(SUT); + + ImageAssert.DoesNotHaveColorInRectangle(bitmap, new Rectangle(0, 0, bitmap.Width / 2, bitmap.Height), Colors.Red); + ImageAssert.DoesNotHaveColorInRectangle(bitmap, new Rectangle(bitmap.Width / 2, 0, bitmap.Width / 2, bitmap.Height), Colors.Blue); + + SUT.FlowDirection = FlowDirection.RightToLeft; + await TestServices.WindowHelper.WaitForIdle(); + + bitmap = await UITestHelper.ScreenShot(SUT); + + ImageAssert.DoesNotHaveColorInRectangle(bitmap, new Rectangle(0, 0, bitmap.Width / 2, bitmap.Height), Colors.Blue); + ImageAssert.DoesNotHaveColorInRectangle(bitmap, new Rectangle(bitmap.Width / 2, 0, bitmap.Width / 2, bitmap.Height), Colors.Red); + } + + private class BlueFillSKCanvasElement : SKCanvasElement + { + protected override void RenderOverride(SKCanvas canvas, Size area) + { + canvas.DrawRect(new SKRect(0, 0, (float)area.Width, (float)area.Height), new SKPaint { Color = SKColors.Blue }); + } + } + + private class BlueAndRedFillSKCanvasElement : SKCanvasElement + { + protected override void RenderOverride(SKCanvas canvas, Size area) + { + canvas.DrawRect(new SKRect(0, 0, (float)area.Width / 2, (float)area.Height), new SKPaint { Color = SKColors.Blue }); + canvas.DrawRect(new SKRect((float)area.Width / 2, 0, (float)area.Width, (float)area.Height), new SKPaint { Color = SKColors.Red }); + } + } +} From 225de18ea5e7b2a77c25ef48142334ba49d0389e Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 29 Jan 2024 18:12:54 +0200 Subject: [PATCH 05/94] chore: rename RespectFlowDirection to MirroredWhenRightToLeft --- .../Windows_UI_Composition/SkiaVisualShowcase.xaml | 4 ++-- .../Given_SKCanvasElement.skia.cs | 2 +- .../UI/Xaml/Controls/SKCanvasElement.skia.cs | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml index 733dc78f2d6b..22fb50400269 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml @@ -16,9 +16,9 @@ - + - + diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs index 51f29f8edbf6..c6264407d461 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs @@ -53,7 +53,7 @@ public async Task When_RespectFlowDirection() var SUT = new BlueAndRedFillSKCanvasElement { Width = 200, - RespectFlowDirection = true + MirroredWhenRightToLeft = true }; await UITestHelper.Load(SUT); diff --git a/src/Uno.UI/UI/Xaml/Controls/SKCanvasElement.skia.cs b/src/Uno.UI/UI/Xaml/Controls/SKCanvasElement.skia.cs index 088e40cbfb76..ea7b4623317c 100644 --- a/src/Uno.UI/UI/Xaml/Controls/SKCanvasElement.skia.cs +++ b/src/Uno.UI/UI/Xaml/Controls/SKCanvasElement.skia.cs @@ -26,20 +26,20 @@ protected SKCanvasElement() ElementCompositionPreview.SetElementChildVisual(this, _skiaVisual!); } - public static DependencyProperty RespectFlowDirectionProperty { get; } = DependencyProperty.Register( - nameof(RespectFlowDirection), + public static DependencyProperty MirroredWhenRightToLeftProperty { get; } = DependencyProperty.Register( + nameof(MirroredWhenRightToLeft), typeof(bool), typeof(SKCanvasElement), new FrameworkPropertyMetadata((dO, _) => ((SKCanvasElement)dO).RespectFlowDirectionChanged())); /// /// By default, SKCanvasElement will have the origin at the top-left of the drawing area with the normal directions increasing down and right. - /// If RespectFlowDirection is true, the drawing will be horizontally reflected when is . + /// If MirroredWhenRightToLeft is true, the drawing will be horizontally reflected when is . /// - public bool RespectFlowDirection + public bool MirroredWhenRightToLeft { - get => (bool)GetValue(RespectFlowDirectionProperty); - set => SetValue(RespectFlowDirectionProperty, value); + get => (bool)GetValue(MirroredWhenRightToLeftProperty); + set => SetValue(MirroredWhenRightToLeftProperty, value); } private void RespectFlowDirectionChanged() @@ -76,7 +76,7 @@ protected override Size ArrangeOverride(Size finalSize) private bool ApplyFlowDirection() { var oldMatrix = _skiaVisual.TransformMatrix; - if (FlowDirection == FlowDirection.RightToLeft && !RespectFlowDirection) + if (FlowDirection == FlowDirection.RightToLeft && !MirroredWhenRightToLeft) { _skiaVisual.TransformMatrix = new Matrix4x4(new Matrix3x2(-1.0f, 0.0f, 0.0f, 1.0f, (float)LayoutSlot.Width, 0.0f)); } From a510667a5ebc61b51515516f5c007302f52b7fd9 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 30 Jan 2024 12:50:15 +0200 Subject: [PATCH 06/94] docs: add documentation for SkiaCanvas and SKCanvasElement --- doc/articles/controls/SkiaCanvas.md | 56 +++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 doc/articles/controls/SkiaCanvas.md diff --git a/doc/articles/controls/SkiaCanvas.md b/doc/articles/controls/SkiaCanvas.md new file mode 100644 index 000000000000..f03384dc199b --- /dev/null +++ b/doc/articles/controls/SkiaCanvas.md @@ -0,0 +1,56 @@ +--- +uid: Uno.Controls.SKCanvasElement +--- + +# Introduction + +In creating an Uno application, users might want to create elaborate 2D graphics that are more suitable to a 2D graphics 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/en-us/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/en-us/dotnet/api/skiasharp.views.windows.skxamlcanvas?view=skiasharp-views-2.88) element that allows for drawing in an area using SkiaSharp. + +On Uno Skia targets, we can utilize the pre-existing Skia canvas that is used internally by Uno to render the application instead of creating additional Skia surfaces and then copying the resulting renderings to the application (e.g. using a [BitmapImage](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.media.imaging.bitmapimage)). This way, a lot of Skia functionally can be acquired "for free". For example, no additional setup for OpenGL is needed if the Uno application is already using OpenGL to render. + +This functionality is exposed in two parts. `SkiaVisual` is a [Visual](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.composition.visual) that can be given an [SKCanvas](https://learn.microsoft.com/en-us/dotnet/api/skiasharp.skcanvas) to draw on. For more streamlined usage, an `SKCanvasElement` is provided that internally wraps a `SkiaVisual` and can be used like any FrameworkElement, with support for sizing, clipping, RTL, etc. You should use `SKCanvasElement` for most scenarios. Only use a raw `SkiaVisual` if your use case is not covered by `SKCanvasElement`. + +We stress that this functionality is only available on Uno targets that are based on Skia (Gtk and Wpf). + +# SkiaVisual + +A `SkiaVisual` is a abstract Visual that provides Uno applications the ability to utilize SkiaSharp to draw directly on the Skia canvas that is used internally by Uno. To use `SkiaVisual`, create a subclass of `SkiaVisual` and override the `RenderOverride` method. + +```csharp +protected abstract void RenderOverride(SKCanvas canvas); +``` + +You can then add the `SkiaVisual` as a child visual of an element using [ElementCompositionPreview.SetElementChildVisual](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.hosting.elementcompositionpreview.setelementchildvisual?view=windows-app-sdk-1.4#microsoft-ui-xaml-hosting-elementcompositionpreview-setelementchildvisual(microsoft-ui-xaml-uielement-microsoft-ui-composition-visual)). + +Note that you will need to add your own logic to handle sizing and clipping. + +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. + +Additionally, `SkiaVisual` has an `Invalidate` method that can be used at any time to tell the Uno runtime to redraw the visual, calling `RenderOverride` in the process. + +# SKCanvasElement + +`SKCanvasElement` is a ready-made [FrameworkElement](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.frameworkelement) that creates an internal `SkiaVisual` and maintains its state as one would expect. To use `SKCanvasElement`, create a subclass of `SKCanvasElement` and override the `RenderOverride` method, which takes the canvas that will drawn on and the clipping area inside the canvas. Drawing outside this area will be clipped. + +```csharp +protected abstract void RenderOverride(SKCanvas canvas, Size area); +``` + +By default, `SKCanvasElement` takes all the available space given to it in the `Measure` cycke. If you want to customize how much space the element takes, you can override its `MeasureOverride` method. + +Note that since `SKCanvasElement` takes as much space as it can, unexpected behavior will occur if you attempt to place an `SKCanvasElement` inside a [StackPanel](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.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`. + +`SKCanvasElement` also comes with a `MirroredWhenRightToLeftProperty`. If `true`, the drawing will be reflected horizontally when the `FlowDirection` of the `SKCanvasElement` is right-to-left. By default, this property is set to `false`, meaning that the drawing will be the same regardless of the `FlowDirection`. + +# 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/en-us/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 [xaml](https://platform.uno/docs/articles/platform-specific-xaml.html) and the [code-behind](https://platform.uno/docs/articles/platform-specific-csharp.html). + +Xaml: +```xaml +``` + +Code-behind: +```csharp + +``` From 742b4fb984bb2a5c07f38b3b17c7471b9f4e0e77 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 7 May 2024 17:54:16 +0300 Subject: [PATCH 07/94] chore: minor adjustments --- doc/articles/controls/SkiaCanvas.md | 12 +++++----- .../Composition/SkiaVisual.skia.cs | 2 +- .../Given_SKCanvasElement.skia.cs | 3 --- .../UI/Xaml/Controls/SKCanvasElement.skia.cs | 22 +++++++++++++++++-- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/doc/articles/controls/SkiaCanvas.md b/doc/articles/controls/SkiaCanvas.md index f03384dc199b..4588532a2a4e 100644 --- a/doc/articles/controls/SkiaCanvas.md +++ b/doc/articles/controls/SkiaCanvas.md @@ -4,17 +4,17 @@ uid: Uno.Controls.SKCanvasElement # Introduction -In creating an Uno application, users might want to create elaborate 2D graphics that are more suitable to a 2D graphics 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/en-us/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/en-us/dotnet/api/skiasharp.views.windows.skxamlcanvas?view=skiasharp-views-2.88) element that allows for drawing in an area using SkiaSharp. +During creating an Uno application, users might want to create elaborate 2D graphics that are more suitable to a 2D graphics 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/en-us/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/en-us/dotnet/api/skiasharp.views.windows.skxamlcanvas?view=skiasharp-views-2.88) element that allows for drawing in an area using SkiaSharp. On Uno Skia targets, we can utilize the pre-existing Skia canvas that is used internally by Uno to render the application instead of creating additional Skia surfaces and then copying the resulting renderings to the application (e.g. using a [BitmapImage](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.media.imaging.bitmapimage)). This way, a lot of Skia functionally can be acquired "for free". For example, no additional setup for OpenGL is needed if the Uno application is already using OpenGL to render. -This functionality is exposed in two parts. `SkiaVisual` is a [Visual](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.composition.visual) that can be given an [SKCanvas](https://learn.microsoft.com/en-us/dotnet/api/skiasharp.skcanvas) to draw on. For more streamlined usage, an `SKCanvasElement` is provided that internally wraps a `SkiaVisual` and can be used like any FrameworkElement, with support for sizing, clipping, RTL, etc. You should use `SKCanvasElement` for most scenarios. Only use a raw `SkiaVisual` if your use case is not covered by `SKCanvasElement`. +This functionality is exposed in two parts. `SkiaVisual` is a [Visual](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.composition.visual) that gets a [SKCanvas](https://learn.microsoft.com/en-us/dotnet/api/skiasharp.skcanvas) to draw on and is almost completely unmanaged. For more streamlined usage, an `SKCanvasElement` is provided that internally wraps a `SkiaVisual` and can be used like any FrameworkElement, with support for sizing, clipping, RTL, etc. You should use `SKCanvasElement` for most scenarios. Only use a raw `SkiaVisual` if your use case is not covered by `SKCanvasElement`. -We stress that this functionality is only available on Uno targets that are based on Skia (Gtk and Wpf). +We stress that this functionality is only available on Uno targets that are based on Skia. # SkiaVisual -A `SkiaVisual` is a abstract Visual that provides Uno applications the ability to utilize SkiaSharp to draw directly on the Skia canvas that is used internally by Uno. To use `SkiaVisual`, create a subclass of `SkiaVisual` and override the `RenderOverride` method. +A `SkiaVisual` is a abstract Visual that provides Uno applications the ability to utilize SkiaSharp to draw directly on the Skia canvas that is used internally by Uno to draw the window. To use `SkiaVisual`, create a subclass of `SkiaVisual` and override the `RenderOverride` method. ```csharp protected abstract void RenderOverride(SKCanvas canvas); @@ -36,9 +36,9 @@ Additionally, `SkiaVisual` has an `Invalidate` method that can be used at any ti protected abstract void RenderOverride(SKCanvas canvas, Size area); ``` -By default, `SKCanvasElement` takes all the available space given to it in the `Measure` cycke. If you want to customize how much space the element takes, you can override its `MeasureOverride` method. +By default, `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` method. -Note that since `SKCanvasElement` takes as much space as it can, unexpected behavior will occur if you attempt to place an `SKCanvasElement` inside a [StackPanel](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.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`. +Note that since `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`. `SKCanvasElement` also comes with a `MirroredWhenRightToLeftProperty`. If `true`, the drawing will be reflected horizontally when the `FlowDirection` of the `SKCanvasElement` is right-to-left. By default, this property is set to `false`, meaning that the drawing will be the same regardless of the `FlowDirection`. diff --git a/src/Uno.UI.Composition/Composition/SkiaVisual.skia.cs b/src/Uno.UI.Composition/Composition/SkiaVisual.skia.cs index 7ba59ad3b4b2..1331f3ea8cc9 100644 --- a/src/Uno.UI.Composition/Composition/SkiaVisual.skia.cs +++ b/src/Uno.UI.Composition/Composition/SkiaVisual.skia.cs @@ -13,7 +13,7 @@ public abstract class SkiaVisual(Compositor compositor) : Visual(compositor) /// /// Queue a rendering cycle that will call . /// - public void Invalidate() => Compositor.InvalidateRender(); + public void Invalidate() => Compositor.InvalidateRender(this); /// /// The SkiaSharp drawing logic goes here. diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs index c6264407d461..ed7d7d35dac2 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs @@ -3,12 +3,9 @@ using Microsoft.UI; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -using MUXControlsTestApp.Utilities; using Private.Infrastructure; using SkiaSharp; -using Uno.Disposables; using Uno.UI.RuntimeTests.Helpers; -using Uno.UI.Xaml.Core; using Size = Windows.Foundation.Size; namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls; diff --git a/src/Uno.UI/UI/Xaml/Controls/SKCanvasElement.skia.cs b/src/Uno.UI/UI/Xaml/Controls/SKCanvasElement.skia.cs index ea7b4623317c..a2f00cca6b60 100644 --- a/src/Uno.UI/UI/Xaml/Controls/SKCanvasElement.skia.cs +++ b/src/Uno.UI/UI/Xaml/Controls/SKCanvasElement.skia.cs @@ -1,3 +1,4 @@ +using System; using System.Numerics; using Windows.Foundation; using Windows.Graphics.Display; @@ -61,7 +62,17 @@ private void RespectFlowDirectionChanged() /// By default, SKCanvasElement uses all the given. Subclasses of SKCanvasElement /// should override this method if they need something different. /// - protected override Size MeasureOverride(Size availableSize) => availableSize; + protected override Size MeasureOverride(Size availableSize) + { + if (availableSize.Width == Double.PositiveInfinity || + availableSize.Height == Double.PositiveInfinity || + availableSize.Width.IsNaN() || + availableSize.Height.IsNaN()) + { + throw new ArgumentException($"{nameof(SKCanvasElement)} cannot be measured with infinite or NaN values, but received availableSize={availableSize}."); + } + return availableSize; + } protected override Size ArrangeOverride(Size finalSize) { @@ -70,7 +81,14 @@ protected override Size ArrangeOverride(Size finalSize) ApplyFlowDirection(); // if FlowDirection Changes, it will cause an InvalidateArrange, so we recalculate the TransformMatrix here - return base.ArrangeOverride(finalSize); + if (finalSize.Width == Double.PositiveInfinity || + finalSize.Height == Double.PositiveInfinity || + finalSize.Width.IsNaN() || + finalSize.Height.IsNaN()) + { + throw new ArgumentException($"{nameof(SKCanvasElement)} cannot be arranged with infinite or NaN values, but received finalSize={finalSize}."); + } + return finalSize; } private bool ApplyFlowDirection() From 1eca5005f308a46baea107f63a77aabce13b3f40 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 7 May 2024 20:16:09 +0300 Subject: [PATCH 08/94] chore: relocate implementation --- src/Uno.UI.Composition/AssemblyInfo.skia.cs | 1 + .../SKCanvasElement.cs} | 15 ++++++++------- .../SkiaVisual.cs} | 0 .../Uno.UI.RuntimeTests.Skia.csproj | 1 + 4 files changed, 10 insertions(+), 7 deletions(-) rename src/{Uno.UI/UI/Xaml/Controls/SKCanvasElement.skia.cs => Uno.UI.Runtime.Skia/SKCanvasElement.cs} (88%) rename src/{Uno.UI.Composition/Composition/SkiaVisual.skia.cs => Uno.UI.Runtime.Skia/SkiaVisual.cs} (100%) diff --git a/src/Uno.UI.Composition/AssemblyInfo.skia.cs b/src/Uno.UI.Composition/AssemblyInfo.skia.cs index 3d58e1d1027e..8c2734441d2b 100644 --- a/src/Uno.UI.Composition/AssemblyInfo.skia.cs +++ b/src/Uno.UI.Composition/AssemblyInfo.skia.cs @@ -1,5 +1,6 @@ using global::System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("Uno.UI.Runtime.Skia")] [assembly: InternalsVisibleTo("Uno.UI.Runtime.Skia.Gtk")] [assembly: InternalsVisibleTo("Uno.UI.Runtime.Skia.MacOS")] [assembly: InternalsVisibleTo("Uno.UI.Runtime.Skia.Wpf")] diff --git a/src/Uno.UI/UI/Xaml/Controls/SKCanvasElement.skia.cs b/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs similarity index 88% rename from src/Uno.UI/UI/Xaml/Controls/SKCanvasElement.skia.cs rename to src/Uno.UI.Runtime.Skia/SKCanvasElement.cs index a2f00cca6b60..9210f16f82f4 100644 --- a/src/Uno.UI/UI/Xaml/Controls/SKCanvasElement.skia.cs +++ b/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs @@ -3,6 +3,7 @@ using Windows.Foundation; using Windows.Graphics.Display; using Microsoft.UI.Composition; +using Microsoft.UI.Xaml.Controls.Primitives; using Microsoft.UI.Xaml.Hosting; using SkiaSharp; using Uno.Disposables; @@ -23,7 +24,7 @@ private class SKCanvasVisual(SKCanvasElement owner, Compositor compositor) : Ski protected SKCanvasElement() { - _skiaVisual = new SKCanvasVisual(this, Visual.Compositor); + _skiaVisual = new SKCanvasVisual(this, ElementCompositionPreview.GetElementVisual(this).Compositor); ElementCompositionPreview.SetElementChildVisual(this, _skiaVisual!); } @@ -31,7 +32,7 @@ protected SKCanvasElement() nameof(MirroredWhenRightToLeft), typeof(bool), typeof(SKCanvasElement), - new FrameworkPropertyMetadata((dO, _) => ((SKCanvasElement)dO).RespectFlowDirectionChanged())); + new PropertyMetadata((dO, _) => ((SKCanvasElement)dO).RespectFlowDirectionChanged())); /// /// By default, SKCanvasElement will have the origin at the top-left of the drawing area with the normal directions increasing down and right. @@ -66,8 +67,8 @@ protected override Size MeasureOverride(Size availableSize) { if (availableSize.Width == Double.PositiveInfinity || availableSize.Height == Double.PositiveInfinity || - availableSize.Width.IsNaN() || - availableSize.Height.IsNaN()) + double.IsNaN(availableSize.Width) || + double.IsNaN(availableSize.Height)) { throw new ArgumentException($"{nameof(SKCanvasElement)} cannot be measured with infinite or NaN values, but received availableSize={availableSize}."); } @@ -83,8 +84,8 @@ protected override Size ArrangeOverride(Size finalSize) if (finalSize.Width == Double.PositiveInfinity || finalSize.Height == Double.PositiveInfinity || - finalSize.Width.IsNaN() || - finalSize.Height.IsNaN()) + double.IsNaN(finalSize.Width) || + double.IsNaN(finalSize.Height)) { throw new ArgumentException($"{nameof(SKCanvasElement)} cannot be arranged with infinite or NaN values, but received finalSize={finalSize}."); } @@ -96,7 +97,7 @@ private bool ApplyFlowDirection() var oldMatrix = _skiaVisual.TransformMatrix; if (FlowDirection == FlowDirection.RightToLeft && !MirroredWhenRightToLeft) { - _skiaVisual.TransformMatrix = new Matrix4x4(new Matrix3x2(-1.0f, 0.0f, 0.0f, 1.0f, (float)LayoutSlot.Width, 0.0f)); + _skiaVisual.TransformMatrix = new Matrix4x4(new Matrix3x2(-1.0f, 0.0f, 0.0f, 1.0f, (float)LayoutInformation.GetLayoutSlot(this).Width, 0.0f)); } else { diff --git a/src/Uno.UI.Composition/Composition/SkiaVisual.skia.cs b/src/Uno.UI.Runtime.Skia/SkiaVisual.cs similarity index 100% rename from src/Uno.UI.Composition/Composition/SkiaVisual.skia.cs rename to src/Uno.UI.Runtime.Skia/SkiaVisual.cs diff --git a/src/Uno.UI.RuntimeTests/Uno.UI.RuntimeTests.Skia.csproj b/src/Uno.UI.RuntimeTests/Uno.UI.RuntimeTests.Skia.csproj index 258eb3ecf2e6..71de9ab1b8af 100644 --- a/src/Uno.UI.RuntimeTests/Uno.UI.RuntimeTests.Skia.csproj +++ b/src/Uno.UI.RuntimeTests/Uno.UI.RuntimeTests.Skia.csproj @@ -63,6 +63,7 @@ + From ff6e501764a2fef5e2d9e29613eb408b32851ec7 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 21 Feb 2024 20:25:52 +0200 Subject: [PATCH 09/94] feat: introduce the ability to draw using raw OpenGL on skia --- .../SamplesApp.Skia/SamplesApp.Skia.csproj | 6 + .../UnoIslandsSamplesApp.Skia.csproj | 10 ++ .../SkiaCompositionSurface.skia.cs | 7 + .../Uno.UI.Runtime.Skia.X11.csproj | 5 + .../X11ApplicationHost.cs | 1 + .../X11OpenGLRenderer.cs | 1 + .../X11XamlRootHost.cs | 21 +++ src/Uno.UI/Hosting/IXamlRootHost.cs | 10 ++ .../Xaml/Controls/Image/OpenGLImage.skia.cs | 130 ++++++++++++++++++ .../Xaml/Media/Imaging/GLImageSource.skia.cs | 25 ++++ src/Uno.UI/UI/Xaml/XamlRoot.cs | 24 ++++ 11 files changed, 240 insertions(+) create mode 100644 src/Uno.UI/UI/Xaml/Controls/Image/OpenGLImage.skia.cs create mode 100644 src/Uno.UI/UI/Xaml/Media/Imaging/GLImageSource.skia.cs diff --git a/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj b/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj index c85c54f88932..21751e42c99d 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,11 @@ + + + + + diff --git a/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj b/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj index 31941928eb44..ffb952aa2872 100644 --- a/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj +++ b/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj @@ -1,6 +1,7 @@ $(NetSkiaPreviousAndCurrent) + true @@ -69,4 +70,13 @@ + + + ..\..\..\..\..\..\.nuget\packages\silk.net.core\2.16.0\lib\net6.0\Silk.NET.Core.dll + + + ..\..\..\..\..\..\.nuget\packages\silk.net.opengl\2.16.0\lib\net5.0\Silk.NET.OpenGL.dll + + + diff --git a/src/Uno.UI.Composition/Composition/SkiaCompositionSurface.skia.cs b/src/Uno.UI.Composition/Composition/SkiaCompositionSurface.skia.cs index 74a10d7c6eda..04607efe9bfa 100644 --- a/src/Uno.UI.Composition/Composition/SkiaCompositionSurface.skia.cs +++ b/src/Uno.UI.Composition/Composition/SkiaCompositionSurface.skia.cs @@ -113,6 +113,13 @@ internal unsafe void CopyPixels(int pixelWidth, int pixelHeight, ReadOnlyMemory< } } + internal void CopyPixels(int pixelWidth, int pixelHeight, IntPtr data) + { + var info = new SKImageInfo(pixelWidth, pixelHeight, SKColorType.Bgra8888, SKAlphaType.Premul); + + SetFrameProviderAndOnFrameChanged(FrameProviderFactory.Create(SKImage.FromPixelCopy(info, data, pixelWidth * 4)), null); + } + ~SkiaCompositionSurface() { SetFrameProviderAndOnFrameChanged(null, null); 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..f92a3f4572d1 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 @@ -35,6 +35,11 @@ + + + + + diff --git a/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs b/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs index 1e2f94510ca4..270e61c0936a 100644 --- a/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs +++ b/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs @@ -52,6 +52,7 @@ static X11ApplicationHost() ApiExtensibility.Register(typeof(IUnoCorePointerInputSource), o => new X11PointerInputSource(o)); ApiExtensibility.Register(typeof(IUnoKeyboardInputSource), o => new X11KeyboardInputSource(o)); + ApiExtensibility.Register(typeof(XamlRootMap), _ => X11Manager.XamlRootMap); ApiExtensibility.Register(typeof(INativeWindowFactoryExtension), _ => new X11NativeWindowFactoryExtension()); diff --git a/src/Uno.UI.Runtime.Skia.X11/X11OpenGLRenderer.cs b/src/Uno.UI.Runtime.Skia.X11/X11OpenGLRenderer.cs index c0ef1d82c2a6..a6cec70d182f 100644 --- a/src/Uno.UI.Runtime.Skia.X11/X11OpenGLRenderer.cs +++ b/src/Uno.UI.Runtime.Skia.X11/X11OpenGLRenderer.cs @@ -39,6 +39,7 @@ public X11OpenGLRenderer(IXamlRootHost host, X11Window x11window) void IX11Renderer.Render() { using var lockDiposable = X11Helper.XLock(_x11Window.Display); + using var _ = _host.LockGL(); if (_host is X11XamlRootHost { Closed.IsCompleted: true }) { diff --git a/src/Uno.UI.Runtime.Skia.X11/X11XamlRootHost.cs b/src/Uno.UI.Runtime.Skia.X11/X11XamlRootHost.cs index 820cd3975901..e15b6e2d9c01 100644 --- a/src/Uno.UI.Runtime.Skia.X11/X11XamlRootHost.cs +++ b/src/Uno.UI.Runtime.Skia.X11/X11XamlRootHost.cs @@ -13,6 +13,7 @@ using Uno.Foundation.Logging; using Uno.UI.Hosting; using Microsoft.UI.Xaml; +using Silk.NET.OpenGL; using SkiaSharp; using Uno.Disposables; using Uno.UI; @@ -56,6 +57,8 @@ internal partial class X11XamlRootHost : IXamlRootHost private int _synchronizedShutDownTopWindowIdleCounter; + private readonly object _glLock = new object(); + private X11Window? _x11Window; private X11Window? _x11TopWindow; private IX11Renderer? _renderer; @@ -607,4 +610,22 @@ private void UpdateRendererBackground() } } } + + object? IXamlRootHost.GetGL() => GL.GetApi(GlxInterface.glXGetProcAddress); + + // To prevent concurrent GL operations breaking the state, you should obtain the lock while + // using GL commands. Make sure to restore all the state to default before unlocking (i.e. unbind + // all used buffers, textures, etc.) + IDisposable IXamlRootHost.LockGL() + { + // we don't use a SemaphoreSlim as it's not reentrant. + Monitor.Enter(_glLock); + return new GLLockDisposable(_glLock); + } + + private readonly struct GLLockDisposable(object @lock) : IDisposable + { + + public void Dispose() => Monitor.Exit(@lock); + } } diff --git a/src/Uno.UI/Hosting/IXamlRootHost.cs b/src/Uno.UI/Hosting/IXamlRootHost.cs index d70b6b4f5865..a64d0222764c 100644 --- a/src/Uno.UI/Hosting/IXamlRootHost.cs +++ b/src/Uno.UI/Hosting/IXamlRootHost.cs @@ -1,6 +1,8 @@ #nullable enable +using System; using Microsoft.UI.Xaml; +using Uno.Disposables; namespace Uno.UI.Hosting; @@ -9,4 +11,12 @@ internal interface IXamlRootHost UIElement? RootElement { get; } void InvalidateRender(); + + // should be cast to a Silk.NET GL object. + object? GetGL() => null; + + // To prevent concurrent GL operations breaking the state, you should obtain the lock while + // using GL commands. Make sure to restore all the state to default before unlocking (i.e. unbind + // all used buffers, textures, etc.) + IDisposable LockGL() => Disposable.Empty; } diff --git a/src/Uno.UI/UI/Xaml/Controls/Image/OpenGLImage.skia.cs b/src/Uno.UI/UI/Xaml/Controls/Image/OpenGLImage.skia.cs new file mode 100644 index 000000000000..a1464269d7a2 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/Image/OpenGLImage.skia.cs @@ -0,0 +1,130 @@ +using System; +using System.Runtime.InteropServices; +using Windows.Foundation; +using Microsoft.UI.Xaml.Media.Imaging; +using Silk.NET.OpenGL; +using Uno.Disposables; +namespace Microsoft.UI.Xaml.Controls; + +public abstract class OpenGLImage : Image +{ + private const int BytesPerPixel = 4; + + private readonly uint _width; + private readonly uint _height; + private bool _firstLoad = true; + + private GL _gl; + private uint _framebuffer; + private uint _textureColorBuffer; + private GLImageSource _writableBitmap; + private unsafe readonly void* _pixels; + + unsafe protected OpenGLImage(Size resolution) + { + _width = (uint)resolution.Width; + _height = (uint)resolution.Height; + _pixels = (void*)Marshal.AllocHGlobal((int)(_width * _height * BytesPerPixel)); + } + + unsafe ~OpenGLImage() + { + Marshal.FreeHGlobal((IntPtr)_pixels); + } + + protected abstract void OnLoad(GL gl); + protected abstract void OnDestroy(GL gl); + protected abstract void RenderOverride(GL gl); + + private unsafe protected override void OnLoaded() + { + base.OnLoaded(); + + _gl = XamlRoot!.GetGL() as GL ?? throw new InvalidOperationException("Couldn't get the Silk.NET GL handle."); + + if (_firstLoad) + { + _firstLoad = false; + + using var _1 = XamlRoot?.LockGL(); + using var _2 = RestoreGLState(); + + _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); + + var rbo = _gl.GenRenderbuffer(); + _gl.BindRenderbuffer(GLEnum.Renderbuffer, rbo); + { + _gl.RenderbufferStorage(GLEnum.Renderbuffer, InternalFormat.Depth24Stencil8, _width, _height); + _gl.FramebufferRenderbuffer(GLEnum.Framebuffer, GLEnum.DepthStencilAttachment, GLEnum.Renderbuffer, rbo); + + OnLoad(_gl); + } + _gl.BindRenderbuffer(GLEnum.Renderbuffer, 0); + + if (_gl.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete) + { + throw new InvalidOperationException("Offscreen framebuffer is not complete"); + } + } + _gl.BindFramebuffer(GLEnum.Framebuffer, 0); + + _writableBitmap = new GLImageSource(_width, _height, _pixels); + Source = _writableBitmap; + } + + Render(); + } + + private unsafe void Render() + { + if (!IsLoaded) + { + return; + } + + using var _1 = XamlRoot!.LockGL(); + using var _2 = RestoreGLState(); + + _gl.BindFramebuffer(GLEnum.Framebuffer, _framebuffer); + { + _gl.Viewport(new System.Drawing.Size((int)_width, (int)_height)); + RenderOverride(_gl); + + _gl.ReadBuffer(GLEnum.ColorAttachment0); + _gl.ReadPixels(0, 0, _width, _height, GLEnum.Bgra, GLEnum.UnsignedByte, _pixels); + _writableBitmap.Render(); + } + + Invalidate(); + } + + private IDisposable RestoreGLState() + { + _gl.GetInteger(GLEnum.ArrayBufferBinding, out var oldArrayBuffer); + _gl.GetInteger(GLEnum.VertexArrayBinding, out var oldVertexArray); + _gl.GetInteger(GLEnum.FramebufferBinding, out var oldFramebuffer); + _gl.GetInteger(GLEnum.TextureBinding2D, out var oldTextureColorBuffer); + _gl.GetInteger(GLEnum.RenderbufferBinding, out var oldRbo); + return Disposable.Create(() => + { + _gl.BindVertexArray((uint)oldVertexArray); + _gl.BindBuffer(BufferTargetARB.ArrayBuffer, (uint)oldArrayBuffer); + _gl.BindFramebuffer(GLEnum.Framebuffer, (uint)oldFramebuffer); + _gl.BindTexture(GLEnum.Texture2D, (uint)oldTextureColorBuffer); + _gl.BindRenderbuffer(GLEnum.Renderbuffer, (uint)oldRbo); + }); + } + + public void Invalidate() => DispatcherQueue.TryEnqueue(Render); +} diff --git a/src/Uno.UI/UI/Xaml/Media/Imaging/GLImageSource.skia.cs b/src/Uno.UI/UI/Xaml/Media/Imaging/GLImageSource.skia.cs new file mode 100644 index 000000000000..8333f8a710f2 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Media/Imaging/GLImageSource.skia.cs @@ -0,0 +1,25 @@ +#nullable enable + +using System; +using Microsoft.UI.Composition; +using Uno.UI.Xaml.Media; + +using WinUICoreServices = Uno.UI.Xaml.Core.CoreServices; + +namespace Microsoft.UI.Xaml.Media.Imaging +{ + internal unsafe class GLImageSource(uint width, uint height, void* pixels) : ImageSource + { + private SkiaCompositionSurface _surface = new SkiaCompositionSurface(); + + private protected override bool TryOpenSourceSync(int? targetWidth, int? targetHeight, out ImageData image) + { + _surface.CopyPixels((int)width, (int)height, (IntPtr)pixels); + image = ImageData.FromCompositionSurface(_surface); + InvalidateImageSource(); + return image.HasData; + } + + public void Render() { InvalidateSource(); } + } +} diff --git a/src/Uno.UI/UI/Xaml/XamlRoot.cs b/src/Uno.UI/UI/Xaml/XamlRoot.cs index 1f49c375e765..75b69ec1639c 100644 --- a/src/Uno.UI/UI/Xaml/XamlRoot.cs +++ b/src/Uno.UI/UI/Xaml/XamlRoot.cs @@ -7,6 +7,10 @@ using Windows.Foundation; using Windows.Graphics.Display; using Uno.UI.Extensions; +using Windows.UI.Composition; +using Uno.Disposables; +using Uno.Foundation.Extensibility; +using Uno.UI.Hosting; using Uno.UI.Xaml.Controls; namespace Microsoft.UI.Xaml; @@ -115,4 +119,24 @@ internal IDisposable OpenPopup(Microsoft.UI.Xaml.Controls.Primitives.Popup popup return VisualTree.PopupRoot.OpenPopup(popup); } + + public object? GetGL() + { + if (ApiExtensibility.CreateInstance>(this, out var map) && map.GetHostForRoot(this) is { } host) + { + return host.GetGL(); + } + + return null; + } + + public IDisposable LockGL() + { + if (ApiExtensibility.CreateInstance>(this, out var map) && map.GetHostForRoot(this) is { } host) + { + return host.LockGL(); + } + + return Disposable.Empty; + } } From bc50c00508c280155f9b8a0b962a7c83bcceca73 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 7 May 2024 20:36:29 +0300 Subject: [PATCH 10/94] chore: refactoring and major cleanup --- .../UnoIslandsSamplesApp.Skia.csproj | 9 - .../Uno.UI.Runtime.Skia.X11.csproj | 5 - .../X11ApplicationHost.cs | 3 +- .../X11OpenGLRenderer.cs | 1 - .../X11XamlRootHost.cs | 21 -- src/Uno.UI.Runtime.Skia/GLCanvasElement.cs | 190 ++++++++++++++++++ src/Uno.UI.Runtime.Skia/GLGetProcAddress.cs | 3 + src/Uno.UI.Runtime.Skia/SKCanvasElement.cs | 8 +- .../Uno.UI.Runtime.Skia.csproj | 4 + src/Uno.UI/AssemblyInfo.skia.cs | 1 + src/Uno.UI/Hosting/IXamlRootHost.cs | 10 - .../Xaml/Controls/Image/OpenGLImage.skia.cs | 130 ------------ .../Xaml/Media/Imaging/GLImageSource.skia.cs | 25 --- src/Uno.UI/UI/Xaml/XamlRoot.cs | 23 --- 14 files changed, 204 insertions(+), 229 deletions(-) create mode 100644 src/Uno.UI.Runtime.Skia/GLCanvasElement.cs create mode 100644 src/Uno.UI.Runtime.Skia/GLGetProcAddress.cs delete mode 100644 src/Uno.UI/UI/Xaml/Controls/Image/OpenGLImage.skia.cs delete mode 100644 src/Uno.UI/UI/Xaml/Media/Imaging/GLImageSource.skia.cs diff --git a/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj b/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj index ffb952aa2872..e889d88d4385 100644 --- a/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj +++ b/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj @@ -70,13 +70,4 @@ - - - ..\..\..\..\..\..\.nuget\packages\silk.net.core\2.16.0\lib\net6.0\Silk.NET.Core.dll - - - ..\..\..\..\..\..\.nuget\packages\silk.net.opengl\2.16.0\lib\net5.0\Silk.NET.OpenGL.dll - - - 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 f92a3f4572d1..9fca805f2504 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 @@ -35,11 +35,6 @@ - - - - - diff --git a/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs b/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs index 270e61c0936a..bb1aac28b81c 100644 --- a/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs +++ b/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs @@ -52,7 +52,6 @@ static X11ApplicationHost() ApiExtensibility.Register(typeof(IUnoCorePointerInputSource), o => new X11PointerInputSource(o)); ApiExtensibility.Register(typeof(IUnoKeyboardInputSource), o => new X11KeyboardInputSource(o)); - ApiExtensibility.Register(typeof(XamlRootMap), _ => X11Manager.XamlRootMap); ApiExtensibility.Register(typeof(INativeWindowFactoryExtension), _ => new X11NativeWindowFactoryExtension()); @@ -67,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(GLGetProcAddress), _ => new GLGetProcAddress(GlxInterface.glXGetProcAddress)); } public X11ApplicationHost(Func appBuilder) diff --git a/src/Uno.UI.Runtime.Skia.X11/X11OpenGLRenderer.cs b/src/Uno.UI.Runtime.Skia.X11/X11OpenGLRenderer.cs index a6cec70d182f..c0ef1d82c2a6 100644 --- a/src/Uno.UI.Runtime.Skia.X11/X11OpenGLRenderer.cs +++ b/src/Uno.UI.Runtime.Skia.X11/X11OpenGLRenderer.cs @@ -39,7 +39,6 @@ public X11OpenGLRenderer(IXamlRootHost host, X11Window x11window) void IX11Renderer.Render() { using var lockDiposable = X11Helper.XLock(_x11Window.Display); - using var _ = _host.LockGL(); if (_host is X11XamlRootHost { Closed.IsCompleted: true }) { diff --git a/src/Uno.UI.Runtime.Skia.X11/X11XamlRootHost.cs b/src/Uno.UI.Runtime.Skia.X11/X11XamlRootHost.cs index e15b6e2d9c01..820cd3975901 100644 --- a/src/Uno.UI.Runtime.Skia.X11/X11XamlRootHost.cs +++ b/src/Uno.UI.Runtime.Skia.X11/X11XamlRootHost.cs @@ -13,7 +13,6 @@ using Uno.Foundation.Logging; using Uno.UI.Hosting; using Microsoft.UI.Xaml; -using Silk.NET.OpenGL; using SkiaSharp; using Uno.Disposables; using Uno.UI; @@ -57,8 +56,6 @@ internal partial class X11XamlRootHost : IXamlRootHost private int _synchronizedShutDownTopWindowIdleCounter; - private readonly object _glLock = new object(); - private X11Window? _x11Window; private X11Window? _x11TopWindow; private IX11Renderer? _renderer; @@ -610,22 +607,4 @@ private void UpdateRendererBackground() } } } - - object? IXamlRootHost.GetGL() => GL.GetApi(GlxInterface.glXGetProcAddress); - - // To prevent concurrent GL operations breaking the state, you should obtain the lock while - // using GL commands. Make sure to restore all the state to default before unlocking (i.e. unbind - // all used buffers, textures, etc.) - IDisposable IXamlRootHost.LockGL() - { - // we don't use a SemaphoreSlim as it's not reentrant. - Monitor.Enter(_glLock); - return new GLLockDisposable(_glLock); - } - - private readonly struct GLLockDisposable(object @lock) : IDisposable - { - - public void Dispose() => Monitor.Exit(@lock); - } } diff --git a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs new file mode 100644 index 000000000000..f7b318a2900e --- /dev/null +++ b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs @@ -0,0 +1,190 @@ +using System.Runtime.InteropServices; +using Windows.Foundation; +using Microsoft.UI.Composition; +using Silk.NET.OpenGL; +using SkiaSharp; +using Uno.Foundation.Extensibility; +using Uno.UI.Composition; +using Uno.UI.Runtime.Skia; + +namespace Microsoft.UI.Xaml.Controls; + +public abstract class GLCanvasElement : FrameworkElement +{ + private class GLVisual(GLCanvasElement owner, Compositor compositor) : Visual(compositor) + { + private unsafe SKPixmap _pixmap = new SKPixmap(new SKImageInfo((int)owner._width, (int)owner._height, SKColorType.Bgra8888), (IntPtr)owner._pixels); + internal override void Draw(in DrawingSession session) + { + owner.Render(); + session.Canvas.DrawImage(SKImage.FromPixels(_pixmap), new SKRect(0, 0, owner.Visual.Size.X, owner.Visual.Size.Y)); + } + } + + private const int BytesPerPixel = 4; + + private readonly uint _width; + private readonly uint _height; + private bool _firstLoad = true; + + private readonly GLVisual _visual; + + private GL? _gl; + private uint _framebuffer; + private uint _textureColorBuffer; + private unsafe readonly void* _pixels; + private uint _renderBuffer; + + unsafe protected GLCanvasElement(Size resolution) + { + _width = (uint)resolution.Width; + _height = (uint)resolution.Height; + _pixels = (void*)Marshal.AllocHGlobal((int)(_width * _height * BytesPerPixel)); + + _visual = new GLVisual(this, Visual.Compositor); + Visual.Children.InsertAtTop(_visual); + } + + unsafe ~GLCanvasElement() + { + Marshal.FreeHGlobal((IntPtr)_pixels); + + if (_gl is { }) + { + _gl.DeleteFramebuffer(_framebuffer); + _gl.DeleteTexture(_textureColorBuffer); + _gl.DeleteRenderbuffer(_renderBuffer); + } + } + + protected abstract void Init(GL gl); + protected abstract void OnDestroy(GL gl); + protected abstract void RenderOverride(GL gl); + + public void Invalidate() => _visual.Compositor.InvalidateRender(_visual); + + private unsafe protected override void OnLoaded() + { + base.OnLoaded(); + + ApiExtensibility.CreateInstance(this, out var getProcAddress); + getProcAddress = getProcAddress ?? throw new InvalidOperationException($"Couldn't get GetProcAddress for {nameof(GLCanvasElement)}. Make sure you are running on a platform with {nameof(GLCanvasElement)} support."); + _gl = GL.GetApi(getProcAddress.Invoke); + + if (_firstLoad) + { + _firstLoad = false; + + using var _ = new GLStateDisposable(_gl); + + _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); + + Init(_gl); + } + _gl.BindRenderbuffer(GLEnum.Renderbuffer, 0); + + if (_gl.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete) + { + throw new InvalidOperationException("Offscreen framebuffer is not complete"); + } + } + _gl.BindFramebuffer(GLEnum.Framebuffer, 0); + } + + Render(); + } + + private unsafe void Render() + { + if (!IsLoaded) + { + return; + } + + using var _ = new GLStateDisposable(_gl!); + + _gl!.BindFramebuffer(GLEnum.Framebuffer, _framebuffer); + { + _gl.Viewport(new System.Drawing.Size((int)_width, (int)_height)); + RenderOverride(_gl); + + // Can we do without this copy? + _gl.ReadBuffer(GLEnum.ColorAttachment0); + _gl.ReadPixels(0, 0, _width, _height, GLEnum.Bgra, GLEnum.UnsignedByte, _pixels); + } + } + + /// + /// By default, SKCanvasElement uses all the given. Subclasses of SKCanvasElement + /// should override this method if they need something different. + /// + protected override Size MeasureOverride(Size availableSize) + { + if (availableSize.Width == Double.PositiveInfinity || + availableSize.Height == Double.PositiveInfinity || + double.IsNaN(availableSize.Width) || + double.IsNaN(availableSize.Height)) + { + throw new ArgumentException($"{nameof(GLCanvasElement)} cannot be measured with infinite or NaN values, but received availableSize={availableSize}."); + } + return availableSize; + } + + protected override Size ArrangeOverride(Size finalSize) + { + if (finalSize.Width == Double.PositiveInfinity || + finalSize.Height == Double.PositiveInfinity || + double.IsNaN(finalSize.Width) || + double.IsNaN(finalSize.Height)) + { + throw new ArgumentException($"{nameof(SKCanvasElement)} cannot be arranged with infinite or NaN values, but received finalSize={finalSize}."); + } + return finalSize; + } + + private readonly struct GLStateDisposable : IDisposable + { + private readonly GL _gl; + private readonly int _oldArrayBuffer; + private readonly int _oldVertexArray; + private readonly int _oldFramebuffer; + private readonly int _oldTextureColorBuffer; + private readonly int _oldRbo; + + public GLStateDisposable(GL gl) + { + _gl = gl; + gl.GetInteger(GLEnum.ArrayBufferBinding, out _oldArrayBuffer); + gl.GetInteger(GLEnum.VertexArrayBinding, out _oldVertexArray); + gl.GetInteger(GLEnum.FramebufferBinding, out _oldFramebuffer); + gl.GetInteger(GLEnum.TextureBinding2D, out _oldTextureColorBuffer); + gl.GetInteger(GLEnum.RenderbufferBinding, out _oldRbo); + } + + public void Dispose() + { + _gl.BindVertexArray((uint)_oldVertexArray); + _gl.BindBuffer(BufferTargetARB.ArrayBuffer, (uint)_oldArrayBuffer); + _gl.BindFramebuffer(GLEnum.Framebuffer, (uint)_oldFramebuffer); + _gl.BindTexture(GLEnum.Texture2D, (uint)_oldTextureColorBuffer); + _gl.BindRenderbuffer(GLEnum.Renderbuffer, (uint)_oldRbo); + } + } +} diff --git a/src/Uno.UI.Runtime.Skia/GLGetProcAddress.cs b/src/Uno.UI.Runtime.Skia/GLGetProcAddress.cs new file mode 100644 index 000000000000..96e332433c9f --- /dev/null +++ b/src/Uno.UI.Runtime.Skia/GLGetProcAddress.cs @@ -0,0 +1,3 @@ +namespace Uno.UI.Runtime.Skia; + +internal delegate IntPtr GLGetProcAddress(string proc); diff --git a/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs b/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs index 9210f16f82f4..47117649b6e7 100644 --- a/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs +++ b/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs @@ -1,12 +1,9 @@ -using System; using System.Numerics; using Windows.Foundation; -using Windows.Graphics.Display; using Microsoft.UI.Composition; using Microsoft.UI.Xaml.Controls.Primitives; using Microsoft.UI.Xaml.Hosting; using SkiaSharp; -using Uno.Disposables; namespace Microsoft.UI.Xaml.Controls; @@ -25,7 +22,7 @@ private class SKCanvasVisual(SKCanvasElement owner, Compositor compositor) : Ski protected SKCanvasElement() { _skiaVisual = new SKCanvasVisual(this, ElementCompositionPreview.GetElementVisual(this).Compositor); - ElementCompositionPreview.SetElementChildVisual(this, _skiaVisual!); + Visual.Children.InsertAtTop(_skiaVisual); } public static DependencyProperty MirroredWhenRightToLeftProperty { get; } = DependencyProperty.Register( @@ -72,12 +69,15 @@ protected override Size MeasureOverride(Size availableSize) { throw new ArgumentException($"{nameof(SKCanvasElement)} cannot be measured with infinite or NaN values, but received availableSize={availableSize}."); } + return availableSize; } protected override Size ArrangeOverride(Size finalSize) { _skiaVisual.Size = new Vector2((float)finalSize.Width, (float)finalSize.Height); + // clipping is necessary in case a user does a canvas clear without any clipping defined. + // In that case. the entire window will be cleared. _skiaVisual.Clip = _skiaVisual.Compositor.CreateRectangleClip(0, 0, (float)finalSize.Width, (float)finalSize.Height); ApplyFlowDirection(); // if FlowDirection Changes, it will cause an InvalidateArrange, so we recalculate the TransformMatrix here diff --git a/src/Uno.UI.Runtime.Skia/Uno.UI.Runtime.Skia.csproj b/src/Uno.UI.Runtime.Skia/Uno.UI.Runtime.Skia.csproj index 9944a9e0d315..2228ddf9e7d6 100644 --- a/src/Uno.UI.Runtime.Skia/Uno.UI.Runtime.Skia.csproj +++ b/src/Uno.UI.Runtime.Skia/Uno.UI.Runtime.Skia.csproj @@ -31,4 +31,8 @@ + + + + diff --git a/src/Uno.UI/AssemblyInfo.skia.cs b/src/Uno.UI/AssemblyInfo.skia.cs index 2e34cb986d28..acf088ad4ab5 100644 --- a/src/Uno.UI/AssemblyInfo.skia.cs +++ b/src/Uno.UI/AssemblyInfo.skia.cs @@ -1,5 +1,6 @@ using global::System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("Uno.UI.Runtime.Skia")] [assembly: InternalsVisibleTo("Uno.UI.Runtime.Skia.Gtk")] [assembly: InternalsVisibleTo("Uno.UI.Runtime.Skia.MacOS")] [assembly: InternalsVisibleTo("Uno.UI.Runtime.Skia.Wpf")] diff --git a/src/Uno.UI/Hosting/IXamlRootHost.cs b/src/Uno.UI/Hosting/IXamlRootHost.cs index a64d0222764c..d70b6b4f5865 100644 --- a/src/Uno.UI/Hosting/IXamlRootHost.cs +++ b/src/Uno.UI/Hosting/IXamlRootHost.cs @@ -1,8 +1,6 @@ #nullable enable -using System; using Microsoft.UI.Xaml; -using Uno.Disposables; namespace Uno.UI.Hosting; @@ -11,12 +9,4 @@ internal interface IXamlRootHost UIElement? RootElement { get; } void InvalidateRender(); - - // should be cast to a Silk.NET GL object. - object? GetGL() => null; - - // To prevent concurrent GL operations breaking the state, you should obtain the lock while - // using GL commands. Make sure to restore all the state to default before unlocking (i.e. unbind - // all used buffers, textures, etc.) - IDisposable LockGL() => Disposable.Empty; } diff --git a/src/Uno.UI/UI/Xaml/Controls/Image/OpenGLImage.skia.cs b/src/Uno.UI/UI/Xaml/Controls/Image/OpenGLImage.skia.cs deleted file mode 100644 index a1464269d7a2..000000000000 --- a/src/Uno.UI/UI/Xaml/Controls/Image/OpenGLImage.skia.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using Windows.Foundation; -using Microsoft.UI.Xaml.Media.Imaging; -using Silk.NET.OpenGL; -using Uno.Disposables; -namespace Microsoft.UI.Xaml.Controls; - -public abstract class OpenGLImage : Image -{ - private const int BytesPerPixel = 4; - - private readonly uint _width; - private readonly uint _height; - private bool _firstLoad = true; - - private GL _gl; - private uint _framebuffer; - private uint _textureColorBuffer; - private GLImageSource _writableBitmap; - private unsafe readonly void* _pixels; - - unsafe protected OpenGLImage(Size resolution) - { - _width = (uint)resolution.Width; - _height = (uint)resolution.Height; - _pixels = (void*)Marshal.AllocHGlobal((int)(_width * _height * BytesPerPixel)); - } - - unsafe ~OpenGLImage() - { - Marshal.FreeHGlobal((IntPtr)_pixels); - } - - protected abstract void OnLoad(GL gl); - protected abstract void OnDestroy(GL gl); - protected abstract void RenderOverride(GL gl); - - private unsafe protected override void OnLoaded() - { - base.OnLoaded(); - - _gl = XamlRoot!.GetGL() as GL ?? throw new InvalidOperationException("Couldn't get the Silk.NET GL handle."); - - if (_firstLoad) - { - _firstLoad = false; - - using var _1 = XamlRoot?.LockGL(); - using var _2 = RestoreGLState(); - - _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); - - var rbo = _gl.GenRenderbuffer(); - _gl.BindRenderbuffer(GLEnum.Renderbuffer, rbo); - { - _gl.RenderbufferStorage(GLEnum.Renderbuffer, InternalFormat.Depth24Stencil8, _width, _height); - _gl.FramebufferRenderbuffer(GLEnum.Framebuffer, GLEnum.DepthStencilAttachment, GLEnum.Renderbuffer, rbo); - - OnLoad(_gl); - } - _gl.BindRenderbuffer(GLEnum.Renderbuffer, 0); - - if (_gl.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete) - { - throw new InvalidOperationException("Offscreen framebuffer is not complete"); - } - } - _gl.BindFramebuffer(GLEnum.Framebuffer, 0); - - _writableBitmap = new GLImageSource(_width, _height, _pixels); - Source = _writableBitmap; - } - - Render(); - } - - private unsafe void Render() - { - if (!IsLoaded) - { - return; - } - - using var _1 = XamlRoot!.LockGL(); - using var _2 = RestoreGLState(); - - _gl.BindFramebuffer(GLEnum.Framebuffer, _framebuffer); - { - _gl.Viewport(new System.Drawing.Size((int)_width, (int)_height)); - RenderOverride(_gl); - - _gl.ReadBuffer(GLEnum.ColorAttachment0); - _gl.ReadPixels(0, 0, _width, _height, GLEnum.Bgra, GLEnum.UnsignedByte, _pixels); - _writableBitmap.Render(); - } - - Invalidate(); - } - - private IDisposable RestoreGLState() - { - _gl.GetInteger(GLEnum.ArrayBufferBinding, out var oldArrayBuffer); - _gl.GetInteger(GLEnum.VertexArrayBinding, out var oldVertexArray); - _gl.GetInteger(GLEnum.FramebufferBinding, out var oldFramebuffer); - _gl.GetInteger(GLEnum.TextureBinding2D, out var oldTextureColorBuffer); - _gl.GetInteger(GLEnum.RenderbufferBinding, out var oldRbo); - return Disposable.Create(() => - { - _gl.BindVertexArray((uint)oldVertexArray); - _gl.BindBuffer(BufferTargetARB.ArrayBuffer, (uint)oldArrayBuffer); - _gl.BindFramebuffer(GLEnum.Framebuffer, (uint)oldFramebuffer); - _gl.BindTexture(GLEnum.Texture2D, (uint)oldTextureColorBuffer); - _gl.BindRenderbuffer(GLEnum.Renderbuffer, (uint)oldRbo); - }); - } - - public void Invalidate() => DispatcherQueue.TryEnqueue(Render); -} diff --git a/src/Uno.UI/UI/Xaml/Media/Imaging/GLImageSource.skia.cs b/src/Uno.UI/UI/Xaml/Media/Imaging/GLImageSource.skia.cs deleted file mode 100644 index 8333f8a710f2..000000000000 --- a/src/Uno.UI/UI/Xaml/Media/Imaging/GLImageSource.skia.cs +++ /dev/null @@ -1,25 +0,0 @@ -#nullable enable - -using System; -using Microsoft.UI.Composition; -using Uno.UI.Xaml.Media; - -using WinUICoreServices = Uno.UI.Xaml.Core.CoreServices; - -namespace Microsoft.UI.Xaml.Media.Imaging -{ - internal unsafe class GLImageSource(uint width, uint height, void* pixels) : ImageSource - { - private SkiaCompositionSurface _surface = new SkiaCompositionSurface(); - - private protected override bool TryOpenSourceSync(int? targetWidth, int? targetHeight, out ImageData image) - { - _surface.CopyPixels((int)width, (int)height, (IntPtr)pixels); - image = ImageData.FromCompositionSurface(_surface); - InvalidateImageSource(); - return image.HasData; - } - - public void Render() { InvalidateSource(); } - } -} diff --git a/src/Uno.UI/UI/Xaml/XamlRoot.cs b/src/Uno.UI/UI/Xaml/XamlRoot.cs index 75b69ec1639c..d591649e0174 100644 --- a/src/Uno.UI/UI/Xaml/XamlRoot.cs +++ b/src/Uno.UI/UI/Xaml/XamlRoot.cs @@ -8,9 +8,6 @@ using Windows.Graphics.Display; using Uno.UI.Extensions; using Windows.UI.Composition; -using Uno.Disposables; -using Uno.Foundation.Extensibility; -using Uno.UI.Hosting; using Uno.UI.Xaml.Controls; namespace Microsoft.UI.Xaml; @@ -119,24 +116,4 @@ internal IDisposable OpenPopup(Microsoft.UI.Xaml.Controls.Primitives.Popup popup return VisualTree.PopupRoot.OpenPopup(popup); } - - public object? GetGL() - { - if (ApiExtensibility.CreateInstance>(this, out var map) && map.GetHostForRoot(this) is { } host) - { - return host.GetGL(); - } - - return null; - } - - public IDisposable LockGL() - { - if (ApiExtensibility.CreateInstance>(this, out var map) && map.GetHostForRoot(this) is { } host) - { - return host.LockGL(); - } - - return Disposable.Empty; - } } From 2fab9b1aaa5690b2e8c30561beed7d049d61f044 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 8 May 2024 01:31:46 +0300 Subject: [PATCH 11/94] docs: typos and tiny changes --- doc/articles/controls/SkiaCanvas.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/articles/controls/SkiaCanvas.md b/doc/articles/controls/SkiaCanvas.md index 4588532a2a4e..c0d03ed5fb77 100644 --- a/doc/articles/controls/SkiaCanvas.md +++ b/doc/articles/controls/SkiaCanvas.md @@ -4,23 +4,23 @@ uid: Uno.Controls.SKCanvasElement # Introduction -During creating an Uno application, users might want to create elaborate 2D graphics that are more suitable to a 2D graphics 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/en-us/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/en-us/dotnet/api/skiasharp.views.windows.skxamlcanvas?view=skiasharp-views-2.88) element that allows for drawing in an area using SkiaSharp. +During creating an Uno application, users might want to create elaborate 2D graphics that are more suitable to a 2D graphics 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 Skia targets, we can utilize the pre-existing Skia canvas that is used internally by Uno to render the application instead of creating additional Skia surfaces and then copying the resulting renderings to the application (e.g. using a [BitmapImage](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.media.imaging.bitmapimage)). This way, a lot of Skia functionally can be acquired "for free". For example, no additional setup for OpenGL is needed if the Uno application is already using OpenGL to render. +On Uno Skia targets, we can utilize the pre-existing Skia canvas that is used internally by Uno to render the application window instead of creating additional Skia surfaces and then copying the resulting renderings to the application (e.g. using a [BitmapImage](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.media.imaging.bitmapimage)). This way, a lot of Skia functionally can be acquired "for free". For example, no additional additional packages are needed, and setup for hardware acceleration is not needed if the Uno application is already using OpenGL to render. -This functionality is exposed in two parts. `SkiaVisual` is a [Visual](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.composition.visual) that gets a [SKCanvas](https://learn.microsoft.com/en-us/dotnet/api/skiasharp.skcanvas) to draw on and is almost completely unmanaged. For more streamlined usage, an `SKCanvasElement` is provided that internally wraps a `SkiaVisual` and can be used like any FrameworkElement, with support for sizing, clipping, RTL, etc. You should use `SKCanvasElement` for most scenarios. Only use a raw `SkiaVisual` if your use case is not covered by `SKCanvasElement`. +This functionality is exposed in two parts. `SkiaVisual` is a [Visual](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.composition.visual) that gets an [SKCanvas](https://learn.microsoft.com/dotnet/api/skiasharp.skcanvas) to draw on and is almost completely unmanaged. For more streamlined usage, an `SKCanvasElement` is provided that internally wraps a `SkiaVisual` and can be used like any FrameworkElement, with support for sizing, clipping, RTL, etc. You should use `SKCanvasElement` for most scenarios. Only use a raw `SkiaVisual` if your use case is not covered by `SKCanvasElement`. We stress that this functionality is only available on Uno targets that are based on Skia. # SkiaVisual -A `SkiaVisual` is a abstract Visual that provides Uno applications the ability to utilize SkiaSharp to draw directly on the Skia canvas that is used internally by Uno to draw the window. To use `SkiaVisual`, create a subclass of `SkiaVisual` and override the `RenderOverride` method. +A `SkiaVisual` is an abstract `Visual` that provides Uno applications the ability to utilize SkiaSharp to draw directly on the Skia canvas that is used internally by Uno to draw the window. To use `SkiaVisual`, create a subclass of `SkiaVisual` and override the `RenderOverride` method. ```csharp protected abstract void RenderOverride(SKCanvas canvas); ``` -You can then add the `SkiaVisual` as a child visual of an element using [ElementCompositionPreview.SetElementChildVisual](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.hosting.elementcompositionpreview.setelementchildvisual?view=windows-app-sdk-1.4#microsoft-ui-xaml-hosting-elementcompositionpreview-setelementchildvisual(microsoft-ui-xaml-uielement-microsoft-ui-composition-visual)). +You can then add the `SkiaVisual` as a child visual of an element using [ElementCompositionPreview.SetElementChildVisual](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.hosting.elementcompositionpreview.setelementchildvisual#microsoft-ui-xaml-hosting-elementcompositionpreview-setelementchildvisual(microsoft-ui-xaml-uielement-microsoft-ui-composition-visual)). Note that you will need to add your own logic to handle sizing and clipping. @@ -30,7 +30,7 @@ Additionally, `SkiaVisual` has an `Invalidate` method that can be used at any ti # SKCanvasElement -`SKCanvasElement` is a ready-made [FrameworkElement](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.frameworkelement) that creates an internal `SkiaVisual` and maintains its state as one would expect. To use `SKCanvasElement`, create a subclass of `SKCanvasElement` and override the `RenderOverride` method, which takes the canvas that will drawn on and the clipping area inside the canvas. Drawing outside this area will be clipped. +`SKCanvasElement` is a ready-made `FrameworkElement` that creates an internal `SkiaVisual` and maintains its state as one would expect. 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. Drawing outside this area will be clipped. ```csharp protected abstract void RenderOverride(SKCanvas canvas, Size area); @@ -44,7 +44,7 @@ Note that since `SKCanvasElement` takes as much space as it can, it's not allowe # 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/en-us/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 [xaml](https://platform.uno/docs/articles/platform-specific-xaml.html) and the [code-behind](https://platform.uno/docs/articles/platform-specific-csharp.html). +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 [xaml](platform-specific-xaml) and the [code-behind](platform-specific-csharp). Xaml: ```xaml From 6a8ae0356558a1f554c69fcb167c6cff2549400c Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 8 May 2024 01:32:08 +0300 Subject: [PATCH 12/94] chore: session.Surface.Canvas => session.Canvas --- src/Uno.UI.Runtime.Skia/SkiaVisual.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Uno.UI.Runtime.Skia/SkiaVisual.cs b/src/Uno.UI.Runtime.Skia/SkiaVisual.cs index 1331f3ea8cc9..707675585658 100644 --- a/src/Uno.UI.Runtime.Skia/SkiaVisual.cs +++ b/src/Uno.UI.Runtime.Skia/SkiaVisual.cs @@ -8,7 +8,7 @@ namespace Microsoft.UI.Composition; /// public abstract class SkiaVisual(Compositor compositor) : Visual(compositor) { - internal override void Draw(in DrawingSession session) => RenderOverride(session.Surface.Canvas); + internal override void Draw(in DrawingSession session) => RenderOverride(session.Canvas); /// /// Queue a rendering cycle that will call . From 988605dc7f69e25400141be3acaf421ea4ce2a56 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 8 May 2024 01:32:59 +0300 Subject: [PATCH 13/94] chore: RespectFlowDirectionChanged => OnMirroredWhenRightToLeftChanged --- src/Uno.UI.Runtime.Skia/SKCanvasElement.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs b/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs index 47117649b6e7..2edb86b86a34 100644 --- a/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs +++ b/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs @@ -29,7 +29,7 @@ protected SKCanvasElement() nameof(MirroredWhenRightToLeft), typeof(bool), typeof(SKCanvasElement), - new PropertyMetadata((dO, _) => ((SKCanvasElement)dO).RespectFlowDirectionChanged())); + new PropertyMetadata((dO, _) => ((SKCanvasElement)dO).OnMirroredWhenRightToLeftChanged())); /// /// By default, SKCanvasElement will have the origin at the top-left of the drawing area with the normal directions increasing down and right. @@ -41,7 +41,7 @@ public bool MirroredWhenRightToLeft set => SetValue(MirroredWhenRightToLeftProperty, value); } - private void RespectFlowDirectionChanged() + private void OnMirroredWhenRightToLeftChanged() { if (ApplyFlowDirection()) { From 56139cdcf405f348522a4b72c5579b2152fb70ad Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 8 May 2024 01:34:09 +0300 Subject: [PATCH 14/94] chore: use Visual.Size instead of LayoutSlot in SKCanvasElement --- src/Uno.UI.Runtime.Skia/SKCanvasElement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs b/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs index 2edb86b86a34..67aa0d4ab298 100644 --- a/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs +++ b/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs @@ -97,7 +97,7 @@ private bool ApplyFlowDirection() var oldMatrix = _skiaVisual.TransformMatrix; if (FlowDirection == FlowDirection.RightToLeft && !MirroredWhenRightToLeft) { - _skiaVisual.TransformMatrix = new Matrix4x4(new Matrix3x2(-1.0f, 0.0f, 0.0f, 1.0f, (float)LayoutInformation.GetLayoutSlot(this).Width, 0.0f)); + _skiaVisual.TransformMatrix = new Matrix4x4(new Matrix3x2(-1.0f, 0.0f, 0.0f, 1.0f, Visual.Size.X, 0.0f)); } else { From d9b12b84c4477ec70cbdac104bdb25c7f47194b9 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 8 May 2024 02:00:51 +0300 Subject: [PATCH 15/94] test: add ui sample for GLCanvasElement --- .../UITests.Shared/UITests.Shared.projitems | 7 + .../GLCanvasElement_Simple.xaml | 17 +++ .../GLCanvasElement_Simple.xaml.cs | 128 ++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml create mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml.cs diff --git a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems index 6f30c9b9884b..67c503c5739c 100644 --- a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems +++ b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems @@ -4698,6 +4698,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -8285,6 +8289,9 @@ SkiaVisualShowcase.xaml + + GLCanvasElement_Simple.xaml + VisualTranslationSample.xaml 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..be05325cb8a6 --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml @@ -0,0 +1,17 @@ + + + + + + + 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..fab1a38cef9b --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml.cs @@ -0,0 +1,128 @@ +using System; +using System.Drawing; +using Uno.UI.Samples.Controls; +using Microsoft.UI.Xaml.Controls; +using Size = Windows.Foundation.Size; + +#if __SKIA__ +using Silk.NET.OpenGL; +#endif + +namespace UITests.Shared.Windows_UI_Composition +{ + [Sample("Microsoft.UI.Composition")] + public sealed partial class GLCanvasElement_Simple : UserControl + { + public GLCanvasElement_Simple() + { + this.InitializeComponent(); + } + } + +#if __SKIA__ + public class GlCanvasElementImpl() : GLCanvasElement(new Size(800, 600)) +#else + public class GlCanvasElementImpl : FrameworkElement +#endif + { +#if __SKIA__ + private int _counter; + 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); + + const string vertexCode = @" +#version 330 core + +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); +}"; + + const string fragmentCode = @" +#version 330 core + +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.FromArgb(_counter++ % 255, 0, 0)); + gl.Clear(ClearBufferMask.ColorBufferBit); + + gl.UseProgram(_program); + + gl.BindVertexArray(_vao); + gl.DrawArrays(PrimitiveType.Triangles, 0, 3); + + Invalidate(); // continuous redrawing + } +#endif + } +} From ba4f644b4e59be00474fa72c9283afad987ad41c Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 8 May 2024 02:11:30 +0300 Subject: [PATCH 16/94] docs: formatting --- doc/articles/controls/SkiaCanvas.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/doc/articles/controls/SkiaCanvas.md b/doc/articles/controls/SkiaCanvas.md index c0d03ed5fb77..a7085564bdb3 100644 --- a/doc/articles/controls/SkiaCanvas.md +++ b/doc/articles/controls/SkiaCanvas.md @@ -2,7 +2,7 @@ uid: Uno.Controls.SKCanvasElement --- -# Introduction +## Introduction During creating an Uno application, users might want to create elaborate 2D graphics that are more suitable to a 2D graphics 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. @@ -12,7 +12,7 @@ This functionality is exposed in two parts. `SkiaVisual` is a [Visual](https://l We stress that this functionality is only available on Uno targets that are based on Skia. -# SkiaVisual +## SkiaVisual A `SkiaVisual` is an abstract `Visual` that provides Uno applications the ability to utilize SkiaSharp to draw directly on the Skia canvas that is used internally by Uno to draw the window. To use `SkiaVisual`, create a subclass of `SkiaVisual` and override the `RenderOverride` method. @@ -28,7 +28,7 @@ When adding your drawing logic in `RenderOverride` on the provided canvas, you c Additionally, `SkiaVisual` has an `Invalidate` method that can be used at any time to tell the Uno runtime to redraw the visual, calling `RenderOverride` in the process. -# SKCanvasElement +## SKCanvasElement `SKCanvasElement` is a ready-made `FrameworkElement` that creates an internal `SkiaVisual` and maintains its state as one would expect. 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. Drawing outside this area will be clipped. @@ -42,15 +42,18 @@ Note that since `SKCanvasElement` takes as much space as it can, it's not allowe `SKCanvasElement` also comes with a `MirroredWhenRightToLeftProperty`. If `true`, the drawing will be reflected horizontally when the `FlowDirection` of the `SKCanvasElement` is right-to-left. By default, this property is set to `false`, meaning that the drawing will be the same regardless of the `FlowDirection`. -# Full example +## 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 [xaml](platform-specific-xaml) and the [code-behind](platform-specific-csharp). Xaml: + ```xaml + ``` Code-behind: + ```csharp ``` From b7b5bcf39fa528aeb9d96dcc8b49d7aff8d1c655 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 8 May 2024 11:17:55 +0300 Subject: [PATCH 17/94] chore: formatting --- src/Uno.UI.Runtime.Skia/GLCanvasElement.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs index f7b318a2900e..e78391e18ac1 100644 --- a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs +++ b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs @@ -127,7 +127,7 @@ private unsafe void Render() // Can we do without this copy? _gl.ReadBuffer(GLEnum.ColorAttachment0); - _gl.ReadPixels(0, 0, _width, _height, GLEnum.Bgra, GLEnum.UnsignedByte, _pixels); + _gl.ReadPixels(0, 0, _width, _height, GLEnum.Bgra, GLEnum.UnsignedByte, _pixels); } } @@ -158,7 +158,7 @@ protected override Size ArrangeOverride(Size finalSize) } return finalSize; } - + private readonly struct GLStateDisposable : IDisposable { private readonly GL _gl; From 3f3d6e043fe0c96497303f557d932e1f27f43686 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 8 May 2024 15:02:54 +0300 Subject: [PATCH 18/94] chore: move GLCanvasElementImpl to a separate skia-only file --- .../UITests.Shared/UITests.Shared.projitems | 1 + .../GLCanvasElement_Simple.xaml | 8 +- .../GLCanvasElement_Simple.xaml.cs | 114 ------------------ .../GlCanvasElementImpl.skia.cs | 110 +++++++++++++++++ 4 files changed, 114 insertions(+), 119 deletions(-) create mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs diff --git a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems index 67c503c5739c..0a2087efa626 100644 --- a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems +++ b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems @@ -6035,6 +6035,7 @@ AutomationProperties_Name.xaml + CloseRequestedTests.xaml diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml index be05325cb8a6..ca83a23fe9dc 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml @@ -4,14 +4,12 @@ xmlns:local="using:UITests.Shared.Windows_UI_Composition" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:skia="http://uno.ui/skia" + xmlns:skia="http://uno.ui/skia#using:UITests.Shared.Windows_UI_Composition" xmlns:not_skia="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - mc:Ignorable="d" + mc:Ignorable="d skia" d:DesignHeight="300" d:DesignWidth="400"> - - - + 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 index fab1a38cef9b..d37239e0490c 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml.cs @@ -1,12 +1,5 @@ -using System; -using System.Drawing; using Uno.UI.Samples.Controls; using Microsoft.UI.Xaml.Controls; -using Size = Windows.Foundation.Size; - -#if __SKIA__ -using Silk.NET.OpenGL; -#endif namespace UITests.Shared.Windows_UI_Composition { @@ -18,111 +11,4 @@ public GLCanvasElement_Simple() this.InitializeComponent(); } } - -#if __SKIA__ - public class GlCanvasElementImpl() : GLCanvasElement(new Size(800, 600)) -#else - public class GlCanvasElementImpl : FrameworkElement -#endif - { -#if __SKIA__ - private int _counter; - 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); - - const string vertexCode = @" -#version 330 core - -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); -}"; - - const string fragmentCode = @" -#version 330 core - -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.FromArgb(_counter++ % 255, 0, 0)); - gl.Clear(ClearBufferMask.ColorBufferBit); - - gl.UseProgram(_program); - - gl.BindVertexArray(_vao); - gl.DrawArrays(PrimitiveType.Triangles, 0, 3); - - Invalidate(); // continuous redrawing - } -#endif - } } diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs new file mode 100644 index 000000000000..dadcb3444dc8 --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs @@ -0,0 +1,110 @@ +using System; +using System.Drawing; +using Microsoft.UI.Xaml.Controls; +using Size = Windows.Foundation.Size; + +using Silk.NET.OpenGL; + +namespace UITests.Shared.Windows_UI_Composition +{ + public class GlCanvasElementImpl() : GLCanvasElement(new Size(800, 600)) + { + private int _counter; + 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); + + const string vertexCode = @" +#version 330 core + +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); +}"; + + const string fragmentCode = @" +#version 330 core + +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.FromArgb(_counter++ % 255, 0, 0)); + gl.Clear(ClearBufferMask.ColorBufferBit); + + gl.UseProgram(_program); + + gl.BindVertexArray(_vao); + gl.DrawArrays(PrimitiveType.Triangles, 0, 3); + + Invalidate(); // continuous redrawing + } + } +} From 99d5cfbb38ba500a0367be5d38b75c610b04ffcf Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 8 May 2024 17:20:08 +0300 Subject: [PATCH 19/94] chore: reduce unsafe usage and restore Viewport after render --- src/Uno.UI.Runtime.Skia/GLCanvasElement.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs index e78391e18ac1..c72f24f633c3 100644 --- a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs +++ b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs @@ -13,7 +13,7 @@ public abstract class GLCanvasElement : FrameworkElement { private class GLVisual(GLCanvasElement owner, Compositor compositor) : Visual(compositor) { - private unsafe SKPixmap _pixmap = new SKPixmap(new SKImageInfo((int)owner._width, (int)owner._height, SKColorType.Bgra8888), (IntPtr)owner._pixels); + private readonly SKPixmap _pixmap = new SKPixmap(new SKImageInfo((int)owner._width, (int)owner._height, SKColorType.Bgra8888), owner._pixels); internal override void Draw(in DrawingSession session) { owner.Render(); @@ -25,21 +25,21 @@ internal override void Draw(in DrawingSession session) private readonly uint _width; private readonly uint _height; - private bool _firstLoad = true; - + private readonly IntPtr _pixels; private readonly GLVisual _visual; + private bool _firstLoad = true; + private GL? _gl; private uint _framebuffer; private uint _textureColorBuffer; - private unsafe readonly void* _pixels; private uint _renderBuffer; - unsafe protected GLCanvasElement(Size resolution) + protected GLCanvasElement(Size resolution) { _width = (uint)resolution.Width; _height = (uint)resolution.Height; - _pixels = (void*)Marshal.AllocHGlobal((int)(_width * _height * BytesPerPixel)); + _pixels = Marshal.AllocHGlobal((int)(_width * _height * BytesPerPixel)); _visual = new GLVisual(this, Visual.Compositor); Visual.Children.InsertAtTop(_visual); @@ -127,7 +127,7 @@ private unsafe void Render() // Can we do without this copy? _gl.ReadBuffer(GLEnum.ColorAttachment0); - _gl.ReadPixels(0, 0, _width, _height, GLEnum.Bgra, GLEnum.UnsignedByte, _pixels); + _gl.ReadPixels(0, 0, _width, _height, GLEnum.Bgra, GLEnum.UnsignedByte, (void*)_pixels); } } @@ -167,10 +167,12 @@ protected override Size ArrangeOverride(Size finalSize) private readonly int _oldFramebuffer; private readonly int _oldTextureColorBuffer; private readonly int _oldRbo; + private readonly int[] _oldViewport = new int[4]; public GLStateDisposable(GL gl) { _gl = gl; + gl.GetInteger(GLEnum.Viewport, new Span(_oldViewport)); gl.GetInteger(GLEnum.ArrayBufferBinding, out _oldArrayBuffer); gl.GetInteger(GLEnum.VertexArrayBinding, out _oldVertexArray); gl.GetInteger(GLEnum.FramebufferBinding, out _oldFramebuffer); @@ -185,6 +187,7 @@ public void Dispose() _gl.BindFramebuffer(GLEnum.Framebuffer, (uint)_oldFramebuffer); _gl.BindTexture(GLEnum.Texture2D, (uint)_oldTextureColorBuffer); _gl.BindRenderbuffer(GLEnum.Renderbuffer, (uint)_oldRbo); + _gl.Viewport(_oldViewport[0], _oldViewport[1], (uint)_oldViewport[2], (uint)_oldViewport[3]); } } } From 3dac0a398b7dcd06905e7df2541e95e8ada11452 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 8 May 2024 17:20:33 +0300 Subject: [PATCH 20/94] chore: add GLCanvasElement support for wpf --- .../Extensions/WpfExtensionsRegistrar.cs | 2 ++ .../Extensions/WpfGlNativeContext.cs | 34 +++++++++++++++++++ .../OpenGLWpfRenderer.NativeMethods.cs | 3 ++ src/Uno.UI.Runtime.Skia/GLCanvasElement.cs | 16 +++++++-- 4 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfGlNativeContext.cs diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfExtensionsRegistrar.cs b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfExtensionsRegistrar.cs index e462ac067f4c..717a40b98bcf 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfExtensionsRegistrar.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfExtensionsRegistrar.cs @@ -21,6 +21,7 @@ using Windows.System.Profile.Internal; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Silk.NET.Core.Contexts; using Uno.UI.Runtime.Skia.Extensions.System; using Uno.UI.Runtime.Skia.Wpf.Input; using Microsoft.Web.WebView2.Core; @@ -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(INativeContext), _ => new WpfGlNativeContext()); _registered = true; } diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfGlNativeContext.cs b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfGlNativeContext.cs new file mode 100644 index 000000000000..49f438b401f7 --- /dev/null +++ b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfGlNativeContext.cs @@ -0,0 +1,34 @@ +using System; +using Silk.NET.Core.Contexts; +using Silk.NET.Core.Loader; +using Uno.UI.Runtime.Skia.Wpf.Rendering; +namespace Uno.UI.Runtime.Skia.Wpf.Extensions; + +// https://sharovarskyi.com/blog/posts/csharp-win32-opengl-silknet/ +internal class WpfGlNativeContext : INativeContext +{ + private readonly UnmanagedLibrary _l = new UnmanagedLibrary("opengl32.dll"); + + public bool TryGetProcAddress(string proc, out nint addr, int? slot = null) + { + if (_l.TryLoadFunction(proc, out addr)) + { + return true; + } + + addr = OpenGLWpfRenderer.NativeMethods.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 index 64a6bb39df14..2c0305c9bd0c 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Rendering/OpenGLWpfRenderer.NativeMethods.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Rendering/OpenGLWpfRenderer.NativeMethods.cs @@ -24,6 +24,9 @@ internal static class NativeMethods [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); diff --git a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs index c72f24f633c3..9f12005d455c 100644 --- a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs +++ b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs @@ -1,6 +1,7 @@ using System.Runtime.InteropServices; using Windows.Foundation; using Microsoft.UI.Composition; +using Silk.NET.Core.Contexts; using Silk.NET.OpenGL; using SkiaSharp; using Uno.Foundation.Extensibility; @@ -67,9 +68,18 @@ private unsafe protected override void OnLoaded() { base.OnLoaded(); - ApiExtensibility.CreateInstance(this, out var getProcAddress); - getProcAddress = getProcAddress ?? throw new InvalidOperationException($"Couldn't get GetProcAddress for {nameof(GLCanvasElement)}. Make sure you are running on a platform with {nameof(GLCanvasElement)} support."); - _gl = GL.GetApi(getProcAddress.Invoke); + if (ApiExtensibility.CreateInstance(this, out var nativeContext)) + { + _gl = GL.GetApi(nativeContext); + } + else if (ApiExtensibility.CreateInstance(this, out var getProcAddress)) + { + _gl = GL.GetApi(getProcAddress.Invoke); + } + else + { + throw new InvalidOperationException($"Couldn't create a {nameof(GL)} object for {nameof(GLCanvasElement)}. Make sure you are running on a platform with {nameof(GLCanvasElement)} support."); + } if (_firstLoad) { From d081f82c3ac0fbbe74284bfc12d1fbf53d66d326 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 8 May 2024 17:51:45 +0300 Subject: [PATCH 21/94] chore: clear canvas when rendering GLCanvasElement --- src/Uno.UI.Runtime.Skia/GLCanvasElement.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs index 9f12005d455c..e1802a712edd 100644 --- a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs +++ b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs @@ -17,6 +17,10 @@ private class GLVisual(GLCanvasElement owner, Compositor compositor) : Visual(co private readonly SKPixmap _pixmap = new SKPixmap(new SKImageInfo((int)owner._width, (int)owner._height, SKColorType.Bgra8888), owner._pixels); internal override void Draw(in DrawingSession session) { + // we clear the drawing area here because in some cases when unloading the GLCanvasElement, the + // drawing isn't cleared for some reason (a possible hypothesis is timing problems between raw GL and skia). + session.Canvas.ClipRect(new SKRect(0, 0, owner.Visual.Size.X, owner.Visual.Size.Y)); + session.Canvas.Clear(SKColors.Transparent); owner.Render(); session.Canvas.DrawImage(SKImage.FromPixels(_pixmap), new SKRect(0, 0, owner.Visual.Size.X, owner.Visual.Size.Y)); } @@ -46,9 +50,9 @@ protected GLCanvasElement(Size resolution) Visual.Children.InsertAtTop(_visual); } - unsafe ~GLCanvasElement() + ~GLCanvasElement() { - Marshal.FreeHGlobal((IntPtr)_pixels); + Marshal.FreeHGlobal(_pixels); if (_gl is { }) { From 0c3bb6dc2ede9f66c125755a097e24d74ec86c6a Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 8 May 2024 17:58:04 +0300 Subject: [PATCH 22/94] docs: more changes --- doc/articles/controls/SkiaCanvas.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/doc/articles/controls/SkiaCanvas.md b/doc/articles/controls/SkiaCanvas.md index a7085564bdb3..567e2c7b0d53 100644 --- a/doc/articles/controls/SkiaCanvas.md +++ b/doc/articles/controls/SkiaCanvas.md @@ -4,17 +4,18 @@ uid: Uno.Controls.SKCanvasElement ## Introduction -During creating an Uno application, users might want to create elaborate 2D graphics that are more suitable to a 2D graphics 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. +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 Skia targets, we can utilize the pre-existing Skia canvas that is used internally by Uno to render the application window instead of creating additional Skia surfaces and then copying the resulting renderings to the application (e.g. using a [BitmapImage](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.media.imaging.bitmapimage)). This way, a lot of Skia functionally can be acquired "for free". For example, no additional additional packages are needed, and setup for hardware acceleration is not needed if the Uno application is already using OpenGL to render. +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. It is then possible to copy the resulting renderings to the application (e.g. using a [BitmapImage](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.media.imaging.bitmapimage)). This way, a lot of Skia functionally can be acquired "for free". For example, no additional additional packages are needed, and setup for hardware acceleration is not needed if the Uno application is already using OpenGL to render. -This functionality is exposed in two parts. `SkiaVisual` is a [Visual](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.composition.visual) that gets an [SKCanvas](https://learn.microsoft.com/dotnet/api/skiasharp.skcanvas) to draw on and is almost completely unmanaged. For more streamlined usage, an `SKCanvasElement` is provided that internally wraps a `SkiaVisual` and can be used like any FrameworkElement, with support for sizing, clipping, RTL, etc. You should use `SKCanvasElement` for most scenarios. Only use a raw `SkiaVisual` if your use case is not covered by `SKCanvasElement`. +This functionality is exposed in two parts. `SkiaVisual` is a [Composition API Visual](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.composition.visual) that gets an [SKCanvas](https://learn.microsoft.com/dotnet/api/skiasharp.skcanvas) to draw on and is almost completely unmanaged. For more streamlined usage, an `SKCanvasElement` is provided that internally wraps a `SkiaVisual` and can be used like any `FrameworkElement`, with support for sizing, clipping, RTL, etc. You should use `SKCanvasElement` for most scenarios. Only use a raw `SkiaVisual` if your use case is not covered by `SKCanvasElement`. -We stress that this functionality is only available on Uno targets that are based on Skia. +> [!IMPORTANT] +> This functionality is only available on Skia Desktop (`net8.0-desktop`) targets. ## SkiaVisual -A `SkiaVisual` is an abstract `Visual` that provides Uno applications the ability to utilize SkiaSharp to draw directly on the Skia canvas that is used internally by Uno to draw the window. To use `SkiaVisual`, create a subclass of `SkiaVisual` and override the `RenderOverride` method. +A `SkiaVisual` is an abstract `Visual` that provides Uno Platform applications the ability to utilize SkiaSharp to draw directly on the Skia canvas that is used internally by Uno to draw the window. To use `SkiaVisual`, create a subclass of `SkiaVisual` and override the `RenderOverride` method. ```csharp protected abstract void RenderOverride(SKCanvas canvas); @@ -26,7 +27,7 @@ Note that you will need to add your own logic to handle sizing and clipping. 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. -Additionally, `SkiaVisual` has an `Invalidate` method that can be used at any time to tell the Uno runtime to redraw the visual, calling `RenderOverride` in the process. +Additionally, `SkiaVisual` 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. ## SKCanvasElement From e860c211354fa44b64ac5d11169c5c2da022c9f4 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 8 May 2024 18:02:04 +0300 Subject: [PATCH 23/94] chore: explicitly dispose GLVisual's pixmap --- src/Uno.UI.Runtime.Skia/GLCanvasElement.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs index e1802a712edd..709cea34c90a 100644 --- a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs +++ b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs @@ -15,6 +15,7 @@ public abstract class GLCanvasElement : FrameworkElement private class GLVisual(GLCanvasElement owner, Compositor compositor) : Visual(compositor) { private readonly SKPixmap _pixmap = new SKPixmap(new SKImageInfo((int)owner._width, (int)owner._height, SKColorType.Bgra8888), owner._pixels); + internal override void Draw(in DrawingSession session) { // we clear the drawing area here because in some cases when unloading the GLCanvasElement, the @@ -22,8 +23,15 @@ internal override void Draw(in DrawingSession session) session.Canvas.ClipRect(new SKRect(0, 0, owner.Visual.Size.X, owner.Visual.Size.Y)); session.Canvas.Clear(SKColors.Transparent); owner.Render(); + this.Dispose(); session.Canvas.DrawImage(SKImage.FromPixels(_pixmap), new SKRect(0, 0, owner.Visual.Size.X, owner.Visual.Size.Y)); } + + private protected override void DisposeInternal() + { + base.Dispose(); + _pixmap.Dispose(); + } } private const int BytesPerPixel = 4; From 5a4a5dd1fb09a4776c1f1735884750aa03305050 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 8 May 2024 18:10:38 +0300 Subject: [PATCH 24/94] chore: move GKCanvasElement.GLVisual to a separate file --- .../GLCanvasElement.GLVisual.cs | 29 +++++++++++++++++++ src/Uno.UI.Runtime.Skia/GLCanvasElement.cs | 27 +---------------- 2 files changed, 30 insertions(+), 26 deletions(-) create mode 100644 src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs diff --git a/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs b/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs new file mode 100644 index 000000000000..f7545961d391 --- /dev/null +++ b/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs @@ -0,0 +1,29 @@ +using Microsoft.UI.Composition; +using SkiaSharp; +using Uno.UI.Composition; + +namespace Microsoft.UI.Xaml.Controls; + +public abstract partial class GLCanvasElement +{ + private class GLVisual(GLCanvasElement owner, Compositor compositor) : Visual(compositor) + { + private readonly SKPixmap _pixmap = new SKPixmap(new SKImageInfo((int)owner._width, (int)owner._height, SKColorType.Bgra8888), owner._pixels); + + internal override void Draw(in DrawingSession session) + { + // we clear the drawing area here because in some cases when unloading the GLCanvasElement, the + // drawing isn't cleared for some reason (a possible hypothesis is timing problems between raw GL and skia). + session.Canvas.ClipRect(new SKRect(0, 0, owner.Visual.Size.X, owner.Visual.Size.Y)); + session.Canvas.Clear(SKColors.Transparent); + owner.Render(); + session.Canvas.DrawImage(SKImage.FromPixels(_pixmap), new SKRect(0, 0, owner.Visual.Size.X, owner.Visual.Size.Y)); + } + + private protected override void DisposeInternal() + { + base.DisposeInternal(); + _pixmap.Dispose(); + } + } +} diff --git a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs index 709cea34c90a..1b3dbb1925ab 100644 --- a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs +++ b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs @@ -1,39 +1,14 @@ using System.Runtime.InteropServices; using Windows.Foundation; -using Microsoft.UI.Composition; using Silk.NET.Core.Contexts; using Silk.NET.OpenGL; -using SkiaSharp; using Uno.Foundation.Extensibility; -using Uno.UI.Composition; using Uno.UI.Runtime.Skia; namespace Microsoft.UI.Xaml.Controls; -public abstract class GLCanvasElement : FrameworkElement +public abstract partial class GLCanvasElement : FrameworkElement { - private class GLVisual(GLCanvasElement owner, Compositor compositor) : Visual(compositor) - { - private readonly SKPixmap _pixmap = new SKPixmap(new SKImageInfo((int)owner._width, (int)owner._height, SKColorType.Bgra8888), owner._pixels); - - internal override void Draw(in DrawingSession session) - { - // we clear the drawing area here because in some cases when unloading the GLCanvasElement, the - // drawing isn't cleared for some reason (a possible hypothesis is timing problems between raw GL and skia). - session.Canvas.ClipRect(new SKRect(0, 0, owner.Visual.Size.X, owner.Visual.Size.Y)); - session.Canvas.Clear(SKColors.Transparent); - owner.Render(); - this.Dispose(); - session.Canvas.DrawImage(SKImage.FromPixels(_pixmap), new SKRect(0, 0, owner.Visual.Size.X, owner.Visual.Size.Y)); - } - - private protected override void DisposeInternal() - { - base.Dispose(); - _pixmap.Dispose(); - } - } - private const int BytesPerPixel = 4; private readonly uint _width; From b71fc6ef91bd553ea08173519089cbf24e2f527a Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 8 May 2024 19:34:12 +0300 Subject: [PATCH 25/94] chore: remove MirroredWhenRightToLeftProperty and rename sample --- doc/articles/controls/SkiaCanvas.md | 6 +- .../UITests.Shared/UITests.Shared.projitems | 7 +- .../SKCanvasElementImpl.skia.cs | 146 +++++++++++++++ ...wcase.xaml => SKCanvasElement_Simple.xaml} | 5 +- .../SKCanvasElement_Simple.xaml.cs | 23 +++ .../SkiaVisualShowcase.xaml.cs | 168 ------------------ src/Uno.UI.Runtime.Skia/SKCanvasElement.cs | 42 ----- .../Given_SKCanvasElement.skia.cs | 35 ---- 8 files changed, 177 insertions(+), 255 deletions(-) create mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElementImpl.skia.cs rename src/SamplesApp/UITests.Shared/Windows_UI_Composition/{SkiaVisualShowcase.xaml => SKCanvasElement_Simple.xaml} (79%) create mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml.cs delete mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml.cs diff --git a/doc/articles/controls/SkiaCanvas.md b/doc/articles/controls/SkiaCanvas.md index 567e2c7b0d53..55a432b02ef0 100644 --- a/doc/articles/controls/SkiaCanvas.md +++ b/doc/articles/controls/SkiaCanvas.md @@ -41,13 +41,11 @@ By default, `SKCanvasElement` takes all the available space given to it in the ` Note that since `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`. -`SKCanvasElement` also comes with a `MirroredWhenRightToLeftProperty`. If `true`, the drawing will be reflected horizontally when the `FlowDirection` of the `SKCanvasElement` is right-to-left. By default, this property is set to `false`, meaning that the drawing will be the same regardless of the `FlowDirection`. - ## 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 [xaml](platform-specific-xaml) and the [code-behind](platform-specific-csharp). +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: ```xaml diff --git a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems index 0a2087efa626..9263fa1473bb 100644 --- a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems +++ b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems @@ -4694,7 +4694,7 @@ Designer MSBuild:Compile - + Designer MSBuild:Compile @@ -6036,6 +6036,7 @@ AutomationProperties_Name.xaml + CloseRequestedTests.xaml @@ -8287,8 +8288,8 @@ VisualSurfaceTests.xaml - - SkiaVisualShowcase.xaml + + SKCanvasElement_Simple.xaml GLCanvasElement_Simple.xaml diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElementImpl.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElementImpl.skia.cs new file mode 100644 index 000000000000..401a818de953 --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElementImpl.skia.cs @@ -0,0 +1,146 @@ +using System; +using Windows.Foundation; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using SkiaSharp; + +namespace UITests.Shared.Windows_UI_Composition; + +public class SKCanvasElementImpl : SKCanvasElement +{ + public static int SampleCount => 3; + + public SKCanvasElementImpl() + { + Sample = 0; + } + + public static DependencyProperty SampleProperty { get; } = DependencyProperty.Register( + nameof(Sample), + typeof(int), + typeof(SKCanvasElementImpl), + new PropertyMetadata(-1, (o, args) => ((SKCanvasElementImpl)o).SampleChanged((int)args.NewValue))); + + public int Sample + { + get => (int)GetValue(SampleProperty); + set => SetValue(SampleProperty, value); + } + + private void SampleChanged(int newIndex) + { + var coercedIndex = Math.Min(Math.Max(0, newIndex), SampleCount - 1); + if (coercedIndex != Sample) + { + Sample = coercedIndex; + } + } + + protected override void RenderOverride(SKCanvas canvas, Size area) + { + var minDim = Math.Min(area.Width, area.Height); + if (minDim > 250) + { + // scale up if area is bigger than needed, 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/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml similarity index 79% rename from src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml rename to src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml index 22fb50400269..1ebfc2bff510 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml @@ -1,4 +1,4 @@ - - - + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml.cs new file mode 100644 index 000000000000..ebbe345b14d8 --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml.cs @@ -0,0 +1,23 @@ +using System; +using Uno.UI.Samples.Controls; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +#if __SKIA__ +using SkiaSharp; +using Windows.Foundation; +#endif + +namespace UITests.Shared.Windows_UI_Composition +{ + [Sample("Microsoft.UI.Composition")] + public sealed partial class SKCanvasElement_Simple : UserControl + { + public int MaxSampleIndex => SKCanvasElementImpl.SampleCount - 1; + + public SKCanvasElement_Simple() + { + this.InitializeComponent(); + } + } +} diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml.cs deleted file mode 100644 index 59f0a3cb6d18..000000000000 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SkiaVisualShowcase.xaml.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System; -using Uno.UI.Samples.Controls; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; - -#if __SKIA__ -using SkiaSharp; -using Windows.Foundation; -#endif - -namespace UITests.Shared.Windows_UI_Composition -{ - [Sample("Microsoft.UI.Composition", Name = "SkiaVisualShowcase")] - public sealed partial class SkiaVisualShowcase : UserControl - { - public int MaxSampleIndex => SkiaCanvasShowcaser.SampleCount - 1; - - public SkiaVisualShowcase() - { - this.InitializeComponent(); - } - } - -#if __SKIA__ - public class SkiaCanvasShowcaser : SKCanvasElement -#else - public class SkiaCanvasShowcaser : FrameworkElement -#endif - { - public static int SampleCount => 3; - - public SkiaCanvasShowcaser() - { - Sample = 0; - } - - public static DependencyProperty SampleProperty { get; } = DependencyProperty.Register( - nameof(Sample), - typeof(int), - typeof(SkiaCanvasShowcaser), - new PropertyMetadata(-1, (o, args) => ((SkiaCanvasShowcaser)o).SampleChanged((int)args.NewValue))); - - public int Sample - { - get => (int)GetValue(SampleProperty); - set => SetValue(SampleProperty, value); - } - - private void SampleChanged(int newIndex) - { - var coercedIndex = Math.Min(Math.Max(0, newIndex), SampleCount - 1); - if (coercedIndex != Sample) - { - Sample = coercedIndex; - } - } - -#if __SKIA__ - protected override void RenderOverride(SKCanvas canvas, Size area) - { - var minDim = Math.Min(area.Width, area.Height); - if (minDim > 250) - { - // scale up if area is bigger than needed, assuming each drawing takes at most 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; - } - } -#endif - } -} diff --git a/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs b/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs index 67aa0d4ab298..00b093ac5328 100644 --- a/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs +++ b/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs @@ -1,7 +1,6 @@ using System.Numerics; using Windows.Foundation; using Microsoft.UI.Composition; -using Microsoft.UI.Xaml.Controls.Primitives; using Microsoft.UI.Xaml.Hosting; using SkiaSharp; @@ -25,30 +24,6 @@ protected SKCanvasElement() Visual.Children.InsertAtTop(_skiaVisual); } - public static DependencyProperty MirroredWhenRightToLeftProperty { get; } = DependencyProperty.Register( - nameof(MirroredWhenRightToLeft), - typeof(bool), - typeof(SKCanvasElement), - new PropertyMetadata((dO, _) => ((SKCanvasElement)dO).OnMirroredWhenRightToLeftChanged())); - - /// - /// By default, SKCanvasElement will have the origin at the top-left of the drawing area with the normal directions increasing down and right. - /// If MirroredWhenRightToLeft is true, the drawing will be horizontally reflected when is . - /// - public bool MirroredWhenRightToLeft - { - get => (bool)GetValue(MirroredWhenRightToLeftProperty); - set => SetValue(MirroredWhenRightToLeftProperty, value); - } - - private void OnMirroredWhenRightToLeftChanged() - { - if (ApplyFlowDirection()) - { - _skiaVisual.Invalidate(); - } - } - /// /// The SkiaSharp drawing logic goes here. /// @@ -80,8 +55,6 @@ protected override Size ArrangeOverride(Size finalSize) // In that case. the entire window will be cleared. _skiaVisual.Clip = _skiaVisual.Compositor.CreateRectangleClip(0, 0, (float)finalSize.Width, (float)finalSize.Height); - ApplyFlowDirection(); // if FlowDirection Changes, it will cause an InvalidateArrange, so we recalculate the TransformMatrix here - if (finalSize.Width == Double.PositiveInfinity || finalSize.Height == Double.PositiveInfinity || double.IsNaN(finalSize.Width) || @@ -91,19 +64,4 @@ protected override Size ArrangeOverride(Size finalSize) } return finalSize; } - - private bool ApplyFlowDirection() - { - var oldMatrix = _skiaVisual.TransformMatrix; - if (FlowDirection == FlowDirection.RightToLeft && !MirroredWhenRightToLeft) - { - _skiaVisual.TransformMatrix = new Matrix4x4(new Matrix3x2(-1.0f, 0.0f, 0.0f, 1.0f, Visual.Size.X, 0.0f)); - } - else - { - _skiaVisual.TransformMatrix = Matrix4x4.Identity; - } - - return oldMatrix != _skiaVisual.TransformMatrix; - } } diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs index ed7d7d35dac2..0b76f649f907 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs @@ -43,32 +43,6 @@ public async Task When_Clipped_Inside_ScrollViewer() ImageAssert.DoesNotHaveColorInRectangle(bitmap, new Rectangle(0, 101, 400, 299), Colors.Blue); } - [TestMethod] - [Ignore("RenderTargetBitmap doesn't account for FlowDirection")] - public async Task When_RespectFlowDirection() - { - var SUT = new BlueAndRedFillSKCanvasElement - { - Width = 200, - MirroredWhenRightToLeft = true - }; - - await UITestHelper.Load(SUT); - - var bitmap = await UITestHelper.ScreenShot(SUT); - - ImageAssert.DoesNotHaveColorInRectangle(bitmap, new Rectangle(0, 0, bitmap.Width / 2, bitmap.Height), Colors.Red); - ImageAssert.DoesNotHaveColorInRectangle(bitmap, new Rectangle(bitmap.Width / 2, 0, bitmap.Width / 2, bitmap.Height), Colors.Blue); - - SUT.FlowDirection = FlowDirection.RightToLeft; - await TestServices.WindowHelper.WaitForIdle(); - - bitmap = await UITestHelper.ScreenShot(SUT); - - ImageAssert.DoesNotHaveColorInRectangle(bitmap, new Rectangle(0, 0, bitmap.Width / 2, bitmap.Height), Colors.Blue); - ImageAssert.DoesNotHaveColorInRectangle(bitmap, new Rectangle(bitmap.Width / 2, 0, bitmap.Width / 2, bitmap.Height), Colors.Red); - } - private class BlueFillSKCanvasElement : SKCanvasElement { protected override void RenderOverride(SKCanvas canvas, Size area) @@ -76,13 +50,4 @@ protected override void RenderOverride(SKCanvas canvas, Size area) canvas.DrawRect(new SKRect(0, 0, (float)area.Width, (float)area.Height), new SKPaint { Color = SKColors.Blue }); } } - - private class BlueAndRedFillSKCanvasElement : SKCanvasElement - { - protected override void RenderOverride(SKCanvas canvas, Size area) - { - canvas.DrawRect(new SKRect(0, 0, (float)area.Width / 2, (float)area.Height), new SKPaint { Color = SKColors.Blue }); - canvas.DrawRect(new SKRect((float)area.Width / 2, 0, (float)area.Width, (float)area.Height), new SKPaint { Color = SKColors.Red }); - } - } } From 6b3a07861dd7db64261852ce0413566dcaa08813 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 8 May 2024 19:40:12 +0300 Subject: [PATCH 26/94] chore: adjust sample --- .../SKCanvasElementImpl.skia.cs | 20 ++++--------------- .../SKCanvasElement_Simple.xaml.cs | 7 ------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElementImpl.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElementImpl.skia.cs index 401a818de953..89003386aa1d 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElementImpl.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElementImpl.skia.cs @@ -10,16 +10,11 @@ public class SKCanvasElementImpl : SKCanvasElement { public static int SampleCount => 3; - public SKCanvasElementImpl() - { - Sample = 0; - } - public static DependencyProperty SampleProperty { get; } = DependencyProperty.Register( nameof(Sample), typeof(int), typeof(SKCanvasElementImpl), - new PropertyMetadata(-1, (o, args) => ((SKCanvasElementImpl)o).SampleChanged((int)args.NewValue))); + new PropertyMetadata(0, (o, args) => ((SKCanvasElementImpl)o).SampleChanged((int)args.NewValue))); public int Sample { @@ -29,21 +24,14 @@ public int Sample private void SampleChanged(int newIndex) { - var coercedIndex = Math.Min(Math.Max(0, newIndex), SampleCount - 1); - if (coercedIndex != Sample) - { - Sample = coercedIndex; - } + 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); - if (minDim > 250) - { - // scale up if area is bigger than needed, assuming each drawing takes is 260x260 - canvas.Scale((float)(minDim / 260), (float)(minDim / 260)); - } + // rescale to fit the given area, assuming each drawing takes is 260x260 + canvas.Scale((float)(minDim / 260), (float)(minDim / 260)); switch (Sample) { diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml.cs index ebbe345b14d8..bcab2c3980eb 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml.cs @@ -1,13 +1,6 @@ -using System; using Uno.UI.Samples.Controls; -using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -#if __SKIA__ -using SkiaSharp; -using Windows.Foundation; -#endif - namespace UITests.Shared.Windows_UI_Composition { [Sample("Microsoft.UI.Composition")] From cfb1899a31a6331ae9d75575ef7b1202c02e7798 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 8 May 2024 19:48:03 +0300 Subject: [PATCH 27/94] docs: add a sample to the SKCanvasElement docs --- doc/articles/controls/SkiaCanvas.md | 164 +++++++++++++++++++++++++++- 1 file changed, 163 insertions(+), 1 deletion(-) diff --git a/doc/articles/controls/SkiaCanvas.md b/doc/articles/controls/SkiaCanvas.md index 55a432b02ef0..f48cf96b45b7 100644 --- a/doc/articles/controls/SkiaCanvas.md +++ b/doc/articles/controls/SkiaCanvas.md @@ -48,11 +48,173 @@ To see this in action, here's a complete sample that uses `SKCanvasElement` to d XAML: ```xaml - + + + + + + + + + + + + + + ``` Code-behind: ```csharp +// SKCanvasElementExample.xaml.cs +public partial class SKCanvasElementExample : UserControl +{ + public int MaxSampleIndex => SKCanvasElementImpl.SampleCount - 1; + + public SKCanvasElement_Simple() + { + 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; + } + } +} ``` From 48a59a5b56140c164c83e35db62ac792b8e79e52 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 9 May 2024 15:37:54 +0300 Subject: [PATCH 28/94] chore: make the sample animated --- .../GlCanvasElementImpl.skia.cs | 305 ++++++++++++++---- .../UnoIslandsSamplesApp.Skia.csproj | 1 + .../GLCanvasElement.GLVisual.cs | 8 +- 3 files changed, 244 insertions(+), 70 deletions(-) diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs index dadcb3444dc8..e8ecc3c3ecba 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs @@ -1,5 +1,6 @@ using System; -using System.Drawing; +using System.Diagnostics; +using System.Numerics; using Microsoft.UI.Xaml.Controls; using Size = Windows.Foundation.Size; @@ -7,104 +8,270 @@ namespace UITests.Shared.Windows_UI_Composition { - public class GlCanvasElementImpl() : GLCanvasElement(new Size(800, 600)) + public class GlCanvasElementImpl() : GLCanvasElement(new Size(1200, 800)) { - private int _counter; - private uint _vao; - private uint _vbo; - private uint _program; + private static BufferObject _vbo; + private static BufferObject _ebo; + private static VertexArrayObject _vao; + private static Shader _shader; - unsafe protected override void Init(GL gl) + private static readonly float[] _vertices = { - _vao = gl.GenVertexArray(); - gl.BindVertexArray(_vao); + // 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, - 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 - }; + // 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, + }; - _vbo = gl.GenBuffer(); - gl.BindBuffer(BufferTargetARB.ArrayBuffer, _vbo); - gl.BufferData(BufferTargetARB.ArrayBuffer, new ReadOnlySpan(vertices), BufferUsageARB.StaticDraw); + 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, + }; - gl.VertexAttribPointer(0, 3, GLEnum.Float, false, 3 * sizeof(float), (void*)0); - gl.EnableVertexAttribArray(0); + private const string vertexShaderSource = @" +#version 450 - const string vertexCode = @" -#version 330 core +layout(location = 0) in vec3 pos; +layout(location = 1) in vec3 vertex_color; -layout (location = 0) in vec3 aPosition; -out vec4 vertexColor; +uniform mat4 transform; -void main() -{ - gl_Position = vec4(aPosition, 1.0); - vertexColor = vec4(aPosition.x + 0.5, aPosition.y + 0.5, aPosition.z + 0.5, 1.0); +out vec3 color; + +void main() { + mat4 M = mat4( + vec4(2.000000, 0.000000, 0.000000, 0.000000), + vec4(0.000000, 1.782013, -0.680986, -0.453991), + vec4(0.000000, -0.907981, -1.336510, -0.891007), + vec4(0.000000, 0.000000, 2.000000, 3.000000) + ); + + gl_Position = transform * vec4(pos, 1.0); + color = vertex_color; }"; - const string fragmentCode = @" -#version 330 core + private const string fragmentShaderSource = @" +#version 450 -out vec4 out_color; -in vec4 vertexColor; +in vec3 color; -void main() -{ - out_color = vertexColor; +out vec4 frag_color; + +void main() { + frag_color = vec4(color, 1.0); }"; - uint vertexShader = gl.CreateShader(ShaderType.VertexShader); - gl.ShaderSource(vertexShader, vertexCode); - gl.CompileShader(vertexShader); + 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(); + } + + protected override void RenderOverride(GL Gl) + { + Gl.ClearColor(0.1f, 0.12f, 0.2f, 1); + Gl.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); - gl.GetShader(vertexShader, ShaderParameterName.CompileStatus, out int vStatus); - if (vStatus != (int) GLEnum.True) - throw new Exception("Vertex shader failed to compile: " + gl.GetShaderInfoLog(vertexShader)); + // Gl.Enable(EnableCap.DepthTest); + _vao.Bind(); + _shader.Use(); - uint fragmentShader = gl.CreateShader(ShaderType.FragmentShader); - gl.ShaderSource(fragmentShader, fragmentCode); - gl.CompileShader(fragmentShader); + const double duration = 4; + var transform = + Matrix4x4.CreateRotationY((float)(2 * Math.PI * (float)(Stopwatch.GetTimestamp() * 1.0 / 1000000000 / duration % 1))) * + Matrix4x4.CreateRotationX((float)(0.15 * Math.PI)) * + Matrix4x4.CreateTranslation(0, 0, -3) * + Perspective(); - gl.GetShader(fragmentShader, ShaderParameterName.CompileStatus, out int fStatus); - if (fStatus != (int) GLEnum.True) - throw new Exception("Fragment shader failed to compile: " + gl.GetShaderInfoLog(fragmentShader)); + _shader.SetUniform("transform", transform); + Gl.DrawElements(PrimitiveType.Triangles, (uint) _triangleIndices.Length, DrawElementsType.UnsignedInt, null); - _program = gl.CreateProgram(); - gl.AttachShader(_program, vertexShader); - gl.AttachShader(_program, fragmentShader); - gl.LinkProgram(_program); + 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 - gl.GetProgram(_program, ProgramPropertyARB.LinkStatus, out int lStatus); - if (lStatus != (int) GLEnum.True) - throw new Exception("Program failed to link: " + gl.GetProgramInfoLog(_program)); + 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 + ); + } - gl.DetachShader(_program, vertexShader); - gl.DetachShader(_program, fragmentShader); - gl.DeleteShader(vertexShader); - gl.DeleteShader(fragmentShader); + 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; - protected override void OnDestroy(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 { - gl.DeleteVertexArray(_vao); - gl.DeleteBuffer(_vbo); - gl.DeleteProgram(_program); + 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); } - protected override void RenderOverride(GL gl) + public class VertexArrayObject : IDisposable + where TVertexType : unmanaged + where TIndexType : unmanaged { - gl.ClearColor(Color.FromArgb(_counter++ % 255, 0, 0)); - gl.Clear(ClearBufferMask.ColorBufferBit); + private readonly uint _handle; + private readonly GL _gl; - gl.UseProgram(_program); + public VertexArrayObject(GL gl, BufferObject vbo, BufferObject ebo) + { + _gl = gl; - gl.BindVertexArray(_vao); - gl.DrawArrays(PrimitiveType.Triangles, 0, 3); + _handle = _gl.GenVertexArray(); + Bind(); + vbo.Bind(); + ebo.Bind(); + } - Invalidate(); // continuous redrawing + 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); } } } diff --git a/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj b/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj index e889d88d4385..6a3c181cfbe8 100644 --- a/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj +++ b/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj @@ -24,6 +24,7 @@ + diff --git a/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs b/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs index f7545961d391..9b41002c69e9 100644 --- a/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs +++ b/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs @@ -16,8 +16,14 @@ internal override void Draw(in DrawingSession session) // drawing isn't cleared for some reason (a possible hypothesis is timing problems between raw GL and skia). session.Canvas.ClipRect(new SKRect(0, 0, owner.Visual.Size.X, owner.Visual.Size.Y)); session.Canvas.Clear(SKColors.Transparent); + session.Canvas.Save(); owner.Render(); - session.Canvas.DrawImage(SKImage.FromPixels(_pixmap), new SKRect(0, 0, owner.Visual.Size.X, owner.Visual.Size.Y)); + using var image = SKImage.FromPixels(_pixmap); + // opengl coordinates go bottom-up, so we concat a matrix to flip horizontally and vertically + var flip = new SKMatrix(scaleX: -1, scaleY: -1, skewX: 0, skewY: 0, transX: owner.Visual.Size.X, transY: owner.Visual.Size.Y, persp0: 0, persp1: 0, persp2: 1); + session.Canvas.Concat(ref flip); + session.Canvas.DrawImage(image, new SKRect(0, 0, owner.Visual.Size.X, owner.Visual.Size.Y)); + session.Canvas.Restore(); } private protected override void DisposeInternal() From a3aac829e1d8c70df040ae3e0c8e56cac4a38512 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 9 May 2024 15:47:42 +0300 Subject: [PATCH 29/94] chore: licensing and formatting --- .../GlCanvasElementImpl.skia.cs | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs index e8ecc3c3ecba..fa9e57d82249 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs @@ -1,9 +1,21 @@ +// 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 + using System; using System.Diagnostics; using System.Numerics; using Microsoft.UI.Xaml.Controls; using Size = Windows.Foundation.Size; - using Silk.NET.OpenGL; namespace UITests.Shared.Windows_UI_Composition @@ -22,7 +34,6 @@ public class GlCanvasElementImpl() : GLCanvasElement(new Size(1200, 800)) -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, @@ -30,7 +41,8 @@ public class GlCanvasElementImpl() : GLCanvasElement(new Size(1200, 800)) 0.5f, -0.5f, -0.5f, 0.4f, 0.8f, 0.8f, }; - private static readonly uint[] _triangleIndices = { + private static readonly uint[] _triangleIndices = + { // Front 0, 1, 2, 2, 3, 0, @@ -104,6 +116,7 @@ protected override void OnDestroy(GL Gl) _shader.Dispose(); } + // somewhat follows https://github.com/c2d7fa/opengl-cube protected override void RenderOverride(GL Gl) { Gl.ClearColor(0.1f, 0.12f, 0.2f, 1); @@ -121,7 +134,7 @@ protected override void RenderOverride(GL Gl) Perspective(); _shader.SetUniform("transform", transform); - Gl.DrawElements(PrimitiveType.Triangles, (uint) _triangleIndices.Length, DrawElementsType.UnsignedInt, null); + Gl.DrawElements(PrimitiveType.Triangles, (uint)_triangleIndices.Length, DrawElementsType.UnsignedInt, null); static Matrix4x4 Perspective() { @@ -141,7 +154,7 @@ const float Invalidate(); // continuous redrawing } - + public class Shader : IDisposable { private readonly uint _handle; @@ -191,7 +204,7 @@ public unsafe void SetUniform(string name, Matrix4x4 value) { throw new Exception($"{name} uniform not found on shader."); } - _gl.UniformMatrix4(location, 1, false, (float*) &value); + _gl.UniformMatrix4(location, 1, false, (float*)&value); } public void SetUniform(string name, float value) @@ -239,7 +252,7 @@ public unsafe BufferObject(GL gl, Span data, BufferTargetARB bufferTy Bind(); fixed (void* d = data) { - _gl.BufferData(bufferType, (nuint) (data.Length * sizeof(TDataType)), d, BufferUsageARB.StaticDraw); + _gl.BufferData(bufferType, (nuint)(data.Length * sizeof(TDataType)), d, BufferUsageARB.StaticDraw); } } @@ -266,7 +279,7 @@ public VertexArrayObject(GL gl, BufferObject vbo, BufferObject Date: Thu, 9 May 2024 15:51:41 +0300 Subject: [PATCH 30/94] docs: tabs to spaces --- doc/articles/controls/SkiaCanvas.md | 266 ++++++++++++++-------------- 1 file changed, 133 insertions(+), 133 deletions(-) diff --git a/doc/articles/controls/SkiaCanvas.md b/doc/articles/controls/SkiaCanvas.md index f48cf96b45b7..881d63b6958e 100644 --- a/doc/articles/controls/SkiaCanvas.md +++ b/doc/articles/controls/SkiaCanvas.md @@ -61,16 +61,16 @@ XAML: d:DesignHeight="300" d:DesignWidth="400"> - - - - - - - - - - + + + + + + + + + + ``` @@ -93,128 +93,128 @@ public partial class SKCanvasElementExample : UserControl // 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; - } - } + 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; + } + } } ``` From 98268fc0b1c7009eac2429be41d9d922779f58c9 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Fri, 10 May 2024 15:38:53 +0300 Subject: [PATCH 31/94] chore: refactor timestamp calculations --- .../Windows_UI_Composition/GlCanvasElementImpl.skia.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs index fa9e57d82249..8353d324520c 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs @@ -117,7 +117,7 @@ protected override void OnDestroy(GL Gl) } // somewhat follows https://github.com/c2d7fa/opengl-cube - protected override void RenderOverride(GL Gl) + protected unsafe override void RenderOverride(GL Gl) { Gl.ClearColor(0.1f, 0.12f, 0.2f, 1); Gl.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); @@ -128,7 +128,7 @@ protected override void RenderOverride(GL Gl) const double duration = 4; var transform = - Matrix4x4.CreateRotationY((float)(2 * Math.PI * (float)(Stopwatch.GetTimestamp() * 1.0 / 1000000000 / duration % 1))) * + Matrix4x4.CreateRotationY((float)(2 * Math.PI * (float)(Stopwatch.GetElapsedTime(0).Milliseconds * 1.0 / 1000 / duration % 1))) * Matrix4x4.CreateRotationX((float)(0.15 * Math.PI)) * Matrix4x4.CreateTranslation(0, 0, -3) * Perspective(); From cb514d55fb467ef441552d023691ffa0164575a7 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Fri, 10 May 2024 15:39:28 +0300 Subject: [PATCH 32/94] chore: DispatchQueueRender on Idle to prevent choking the dispatcher when continuously invalidating render --- src/Uno.UI/UI/Xaml/XamlRoot.crossruntime.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Uno.UI/UI/Xaml/XamlRoot.crossruntime.cs b/src/Uno.UI/UI/Xaml/XamlRoot.crossruntime.cs index 4e9c2a4d5fc5..45de4466a344 100644 --- a/src/Uno.UI/UI/Xaml/XamlRoot.crossruntime.cs +++ b/src/Uno.UI/UI/Xaml/XamlRoot.crossruntime.cs @@ -51,7 +51,7 @@ private void DispatchQueueRender() _renderQueued = false; InvalidateRender(); } - }); + }, NativeDispatcherPriority.Idle); } /// From 5f8704a351f251bfd05cb7254680f1d1f7909c1b Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 31 Jul 2024 17:12:53 +0300 Subject: [PATCH 33/94] chore: post rebase --- src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs | 2 +- src/Uno.UI.Runtime.Skia/GLCanvasElement.cs | 8 ++++---- src/Uno.UI.Runtime.Skia/SkiaVisual.cs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs b/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs index 9b41002c69e9..ed53e0ac84c0 100644 --- a/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs +++ b/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs @@ -10,7 +10,7 @@ private class GLVisual(GLCanvasElement owner, Compositor compositor) : Visual(co { private readonly SKPixmap _pixmap = new SKPixmap(new SKImageInfo((int)owner._width, (int)owner._height, SKColorType.Bgra8888), owner._pixels); - internal override void Draw(in DrawingSession session) + internal override void Paint(in PaintingSession session) { // we clear the drawing area here because in some cases when unloading the GLCanvasElement, the // drawing isn't cleared for some reason (a possible hypothesis is timing problems between raw GL and skia). diff --git a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs index 1b3dbb1925ab..6b50e98bcc6d 100644 --- a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs +++ b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs @@ -14,7 +14,7 @@ public abstract partial class GLCanvasElement : FrameworkElement private readonly uint _width; private readonly uint _height; private readonly IntPtr _pixels; - private readonly GLVisual _visual; + private readonly GLVisual _glVisual; private bool _firstLoad = true; @@ -29,8 +29,8 @@ protected GLCanvasElement(Size resolution) _height = (uint)resolution.Height; _pixels = Marshal.AllocHGlobal((int)(_width * _height * BytesPerPixel)); - _visual = new GLVisual(this, Visual.Compositor); - Visual.Children.InsertAtTop(_visual); + _glVisual = new GLVisual(this, Visual.Compositor); + Visual.Children.InsertAtTop(_glVisual); } ~GLCanvasElement() @@ -49,7 +49,7 @@ protected GLCanvasElement(Size resolution) protected abstract void OnDestroy(GL gl); protected abstract void RenderOverride(GL gl); - public void Invalidate() => _visual.Compositor.InvalidateRender(_visual); + public void Invalidate() => _glVisual.Compositor.InvalidateRender(_glVisual); private unsafe protected override void OnLoaded() { diff --git a/src/Uno.UI.Runtime.Skia/SkiaVisual.cs b/src/Uno.UI.Runtime.Skia/SkiaVisual.cs index 707675585658..e7dda0db4647 100644 --- a/src/Uno.UI.Runtime.Skia/SkiaVisual.cs +++ b/src/Uno.UI.Runtime.Skia/SkiaVisual.cs @@ -8,7 +8,7 @@ namespace Microsoft.UI.Composition; /// public abstract class SkiaVisual(Compositor compositor) : Visual(compositor) { - internal override void Draw(in DrawingSession session) => RenderOverride(session.Canvas); + internal override void Paint(in PaintingSession session) => RenderOverride(session.Canvas); /// /// Queue a rendering cycle that will call . From 49e93c8136c5fe9ea6b409ea37c3f5755a7f3d7d Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 5 Aug 2024 10:53:47 +0300 Subject: [PATCH 34/94] chore: change SKImage format to Unpremul --- src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs b/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs index ed53e0ac84c0..c86973f09550 100644 --- a/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs +++ b/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs @@ -8,7 +8,7 @@ public abstract partial class GLCanvasElement { private class GLVisual(GLCanvasElement owner, Compositor compositor) : Visual(compositor) { - private readonly SKPixmap _pixmap = new SKPixmap(new SKImageInfo((int)owner._width, (int)owner._height, SKColorType.Bgra8888), owner._pixels); + private readonly SKPixmap _pixmap = new SKPixmap(new SKImageInfo((int)owner._width, (int)owner._height, SKColorType.Bgra8888, SKAlphaType.Unpremul), owner._pixels); internal override void Paint(in PaintingSession session) { From 4f2687051c746d88a52a7e3a4a3534694bdc39dd Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Wed, 7 Aug 2024 16:22:27 +0200 Subject: [PATCH 35/94] chore: Adjust version --- .../Windows_UI_Composition/GlCanvasElementImpl.skia.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs index 8353d324520c..455216454b7e 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs @@ -64,7 +64,7 @@ public class GlCanvasElementImpl() : GLCanvasElement(new Size(1200, 800)) }; private const string vertexShaderSource = @" -#version 450 +#version 410 layout(location = 0) in vec3 pos; layout(location = 1) in vec3 vertex_color; @@ -86,7 +86,7 @@ void main() { }"; private const string fragmentShaderSource = @" -#version 450 +#version 410 in vec3 color; From b849ca54ee922806f2eb42033fd3b79e29473d54 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 7 Aug 2024 22:50:09 +0300 Subject: [PATCH 36/94] chore: fix depth testing --- src/Uno.UI.Runtime.Skia/GLCanvasElement.cs | 30 ++++++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs index 6b50e98bcc6d..7d72f87d2556 100644 --- a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs +++ b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs @@ -4,6 +4,7 @@ using Silk.NET.OpenGL; using Uno.Foundation.Extensibility; using Uno.UI.Runtime.Skia; +using Boolean = Silk.NET.OpenGL.Boolean; namespace Microsoft.UI.Xaml.Controls; @@ -120,6 +121,10 @@ private unsafe void Render() _gl!.BindFramebuffer(GLEnum.Framebuffer, _framebuffer); { _gl.Viewport(new System.Drawing.Size((int)_width, (int)_height)); + + _gl.Enable(EnableCap.DepthTest); + _gl.DepthMask(true); + RenderOverride(_gl); // Can we do without this copy? @@ -164,17 +169,22 @@ protected override Size ArrangeOverride(Size finalSize) private readonly int _oldFramebuffer; private readonly int _oldTextureColorBuffer; private readonly int _oldRbo; + private readonly bool _depthTestEnabled; + private readonly bool _depthTestMask; private readonly int[] _oldViewport = new int[4]; public GLStateDisposable(GL gl) { _gl = gl; + + _depthTestEnabled = gl.GetBoolean(GLEnum.DepthTest); + _depthTestMask = gl.GetBoolean(GLEnum.DepthWritemask); + _oldArrayBuffer = gl.GetInteger(GLEnum.ArrayBufferBinding); + _oldVertexArray = gl.GetInteger(GLEnum.VertexArrayBinding); + _oldFramebuffer = gl.GetInteger(GLEnum.FramebufferBinding); + _oldTextureColorBuffer = gl.GetInteger(GLEnum.TextureBinding2D); + _oldRbo = gl.GetInteger(GLEnum.RenderbufferBinding); gl.GetInteger(GLEnum.Viewport, new Span(_oldViewport)); - gl.GetInteger(GLEnum.ArrayBufferBinding, out _oldArrayBuffer); - gl.GetInteger(GLEnum.VertexArrayBinding, out _oldVertexArray); - gl.GetInteger(GLEnum.FramebufferBinding, out _oldFramebuffer); - gl.GetInteger(GLEnum.TextureBinding2D, out _oldTextureColorBuffer); - gl.GetInteger(GLEnum.RenderbufferBinding, out _oldRbo); } public void Dispose() @@ -185,6 +195,16 @@ public void Dispose() _gl.BindTexture(GLEnum.Texture2D, (uint)_oldTextureColorBuffer); _gl.BindRenderbuffer(GLEnum.Renderbuffer, (uint)_oldRbo); _gl.Viewport(_oldViewport[0], _oldViewport[1], (uint)_oldViewport[2], (uint)_oldViewport[3]); + if (_depthTestEnabled) + { + _gl.Enable(EnableCap.DepthTest); + } + else + { + _gl.Disable(EnableCap.DepthTest); + } + + _gl.DepthMask(_depthTestMask); } } } From b18f331179b5bc1c66d8b3bfcfd1bd3a644d2bb4 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 7 Aug 2024 23:16:45 +0300 Subject: [PATCH 37/94] chore: only repaint GlVisual when dirty --- .../GlCanvasElementImpl.skia.cs | 1 - .../GLCanvasElement.GLVisual.cs | 6 ++++-- src/Uno.UI.Runtime.Skia/GLCanvasElement.cs | 17 +++++++++-------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs index 455216454b7e..13e233a5c918 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs @@ -122,7 +122,6 @@ protected unsafe override void RenderOverride(GL Gl) Gl.ClearColor(0.1f, 0.12f, 0.2f, 1); Gl.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); - // Gl.Enable(EnableCap.DepthTest); _vao.Bind(); _shader.Use(); diff --git a/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs b/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs index c86973f09550..d57f72465c93 100644 --- a/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs +++ b/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs @@ -1,6 +1,5 @@ using Microsoft.UI.Composition; using SkiaSharp; -using Uno.UI.Composition; namespace Microsoft.UI.Xaml.Controls; @@ -17,7 +16,10 @@ internal override void Paint(in PaintingSession session) session.Canvas.ClipRect(new SKRect(0, 0, owner.Visual.Size.X, owner.Visual.Size.Y)); session.Canvas.Clear(SKColors.Transparent); session.Canvas.Save(); - owner.Render(); + if (owner._renderDirty) + { + owner.Render(); + } using var image = SKImage.FromPixels(_pixmap); // opengl coordinates go bottom-up, so we concat a matrix to flip horizontally and vertically var flip = new SKMatrix(scaleX: -1, scaleY: -1, skewX: 0, skewY: 0, transX: owner.Visual.Size.X, transY: owner.Visual.Size.Y, persp0: 0, persp1: 0, persp2: 1); diff --git a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs index 7d72f87d2556..ec8fcf1f27b7 100644 --- a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs +++ b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs @@ -1,10 +1,10 @@ +using System.Diagnostics; using System.Runtime.InteropServices; using Windows.Foundation; using Silk.NET.Core.Contexts; using Silk.NET.OpenGL; using Uno.Foundation.Extensibility; using Uno.UI.Runtime.Skia; -using Boolean = Silk.NET.OpenGL.Boolean; namespace Microsoft.UI.Xaml.Controls; @@ -18,6 +18,7 @@ public abstract partial class GLCanvasElement : FrameworkElement private readonly GLVisual _glVisual; private bool _firstLoad = true; + private bool _renderDirty = true; private GL? _gl; private uint _framebuffer; @@ -50,7 +51,11 @@ protected GLCanvasElement(Size resolution) protected abstract void OnDestroy(GL gl); protected abstract void RenderOverride(GL gl); - public void Invalidate() => _glVisual.Compositor.InvalidateRender(_glVisual); + public void Invalidate() + { + _renderDirty = true; + _glVisual.Compositor.InvalidateRender(_glVisual); + } private unsafe protected override void OnLoaded() { @@ -105,16 +110,12 @@ private unsafe protected override void OnLoaded() } _gl.BindFramebuffer(GLEnum.Framebuffer, 0); } - - Render(); } private unsafe void Render() { - if (!IsLoaded) - { - return; - } + Debug.Assert(_renderDirty); + _renderDirty = false; using var _ = new GLStateDisposable(_gl!); From b18194f3cf27d57384ec690709e24878003e78c5 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 7 Aug 2024 23:57:30 +0300 Subject: [PATCH 38/94] chore: remove unnecessary lines from shader code --- .../Windows_UI_Composition/GlCanvasElementImpl.skia.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs index 13e233a5c918..737ced1da203 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs @@ -74,13 +74,6 @@ public class GlCanvasElementImpl() : GLCanvasElement(new Size(1200, 800)) out vec3 color; void main() { - mat4 M = mat4( - vec4(2.000000, 0.000000, 0.000000, 0.000000), - vec4(0.000000, 1.782013, -0.680986, -0.453991), - vec4(0.000000, -0.907981, -1.336510, -0.891007), - vec4(0.000000, 0.000000, 2.000000, 3.000000) - ); - gl_Position = transform * vec4(pos, 1.0); color = vertex_color; }"; From 31e45e366c1118bdd705f3a990a862b918a2b8ce Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 7 Aug 2024 23:58:00 +0300 Subject: [PATCH 39/94] chore: fix rotation calculation in sample --- .../Windows_UI_Composition/GlCanvasElementImpl.skia.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs index 737ced1da203..86b233365278 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs @@ -120,7 +120,7 @@ protected unsafe override void RenderOverride(GL Gl) const double duration = 4; var transform = - Matrix4x4.CreateRotationY((float)(2 * Math.PI * (float)(Stopwatch.GetElapsedTime(0).Milliseconds * 1.0 / 1000 / duration % 1))) * + Matrix4x4.CreateRotationY((float)((Stopwatch.GetElapsedTime(0).TotalSeconds / duration) * (2 * Math.PI))) * Matrix4x4.CreateRotationX((float)(0.15 * Math.PI)) * Matrix4x4.CreateTranslation(0, 0, -3) * Perspective(); From 5eeba607c387bfe2874968d96a0bade0eeebe7e0 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 8 Aug 2024 01:35:25 +0300 Subject: [PATCH 40/94] chore: more fixes and cleanup --- .../GLCanvasElement.GLVisual.cs | 30 ++++-- src/Uno.UI.Runtime.Skia/GLCanvasElement.cs | 91 +++++++++---------- 2 files changed, 64 insertions(+), 57 deletions(-) diff --git a/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs b/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs index d57f72465c93..d964c83b4f87 100644 --- a/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs +++ b/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs @@ -5,33 +5,45 @@ namespace Microsoft.UI.Xaml.Controls; public abstract partial class GLCanvasElement { - private class GLVisual(GLCanvasElement owner, Compositor compositor) : Visual(compositor) + private class GLVisual : Visual { - private readonly SKPixmap _pixmap = new SKPixmap(new SKImageInfo((int)owner._width, (int)owner._height, SKColorType.Bgra8888, SKAlphaType.Unpremul), owner._pixels); + private SKPixmap? _pixmap; + private readonly GLCanvasElement _owner; + + public GLVisual(GLCanvasElement owner, Compositor compositor) : base(compositor) + { + _owner = owner; + _owner.Loaded += OnOwnerLoaded; + } + + private void OnOwnerLoaded(object sender, RoutedEventArgs e) + { + _pixmap?.Dispose(); + _pixmap = new SKPixmap(new SKImageInfo((int)_owner._width, (int)_owner._height, SKColorType.Bgra8888, SKAlphaType.Unpremul), _owner._pixels); + } internal override void Paint(in PaintingSession session) { // we clear the drawing area here because in some cases when unloading the GLCanvasElement, the // drawing isn't cleared for some reason (a possible hypothesis is timing problems between raw GL and skia). - session.Canvas.ClipRect(new SKRect(0, 0, owner.Visual.Size.X, owner.Visual.Size.Y)); + session.Canvas.ClipRect(new SKRect(0, 0, _owner.Visual.Size.X, _owner.Visual.Size.Y)); session.Canvas.Clear(SKColors.Transparent); session.Canvas.Save(); - if (owner._renderDirty) + if (_owner._renderDirty) { - owner.Render(); + _owner.Render(); } - using var image = SKImage.FromPixels(_pixmap); // opengl coordinates go bottom-up, so we concat a matrix to flip horizontally and vertically - var flip = new SKMatrix(scaleX: -1, scaleY: -1, skewX: 0, skewY: 0, transX: owner.Visual.Size.X, transY: owner.Visual.Size.Y, persp0: 0, persp1: 0, persp2: 1); + var flip = new SKMatrix(scaleX: -1, scaleY: -1, skewX: 0, skewY: 0, transX: _owner.Visual.Size.X, transY: _owner.Visual.Size.Y, persp0: 0, persp1: 0, persp2: 1); session.Canvas.Concat(ref flip); - session.Canvas.DrawImage(image, new SKRect(0, 0, owner.Visual.Size.X, owner.Visual.Size.Y)); + session.Canvas.DrawImage(SKImage.FromPixels(_pixmap), new SKRect(0, 0, _owner.Visual.Size.X, _owner.Visual.Size.Y)); session.Canvas.Restore(); } private protected override void DisposeInternal() { base.DisposeInternal(); - _pixmap.Dispose(); + _pixmap?.Dispose(); } } } diff --git a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs index ec8fcf1f27b7..db5a63c9e219 100644 --- a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs +++ b/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs @@ -14,13 +14,12 @@ public abstract partial class GLCanvasElement : FrameworkElement private readonly uint _width; private readonly uint _height; - private readonly IntPtr _pixels; private readonly GLVisual _glVisual; - private bool _firstLoad = true; private bool _renderDirty = true; private GL? _gl; + private IntPtr _pixels; private uint _framebuffer; private uint _textureColorBuffer; private uint _renderBuffer; @@ -29,24 +28,11 @@ protected GLCanvasElement(Size resolution) { _width = (uint)resolution.Width; _height = (uint)resolution.Height; - _pixels = Marshal.AllocHGlobal((int)(_width * _height * BytesPerPixel)); _glVisual = new GLVisual(this, Visual.Compositor); Visual.Children.InsertAtTop(_glVisual); } - ~GLCanvasElement() - { - Marshal.FreeHGlobal(_pixels); - - if (_gl is { }) - { - _gl.DeleteFramebuffer(_framebuffer); - _gl.DeleteTexture(_textureColorBuffer); - _gl.DeleteRenderbuffer(_renderBuffer); - } - } - protected abstract void Init(GL gl); protected abstract void OnDestroy(GL gl); protected abstract void RenderOverride(GL gl); @@ -57,7 +43,7 @@ public void Invalidate() _glVisual.Compositor.InvalidateRender(_glVisual); } - private unsafe protected override void OnLoaded() + private protected override unsafe void OnLoaded() { base.OnLoaded(); @@ -74,42 +60,40 @@ private unsafe protected override void OnLoaded() throw new InvalidOperationException($"Couldn't create a {nameof(GL)} object for {nameof(GLCanvasElement)}. Make sure you are running on a platform with {nameof(GLCanvasElement)} support."); } - if (_firstLoad) + using var _ = new GLStateDisposable(_gl); + + _pixels = Marshal.AllocHGlobal((int)(_width * _height * BytesPerPixel)); + _framebuffer = _gl.GenBuffer(); + _gl.BindFramebuffer(GLEnum.Framebuffer, _framebuffer); { - _firstLoad = false; + _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); - using var _ = new GLStateDisposable(_gl); + _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); - _framebuffer = _gl.GenBuffer(); - _gl.BindFramebuffer(GLEnum.Framebuffer, _framebuffer); + Init(_gl); + } + _gl.BindRenderbuffer(GLEnum.Renderbuffer, 0); + + if (_gl.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete) { - _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); - - Init(_gl); - } - _gl.BindRenderbuffer(GLEnum.Renderbuffer, 0); - - if (_gl.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete) - { - throw new InvalidOperationException("Offscreen framebuffer is not complete"); - } + throw new InvalidOperationException("Offscreen framebuffer is not complete"); } - _gl.BindFramebuffer(GLEnum.Framebuffer, 0); } + _gl.BindFramebuffer(GLEnum.Framebuffer, 0); + + Invalidate(); } private unsafe void Render() @@ -134,6 +118,18 @@ private unsafe void Render() } } + private protected override void OnUnloaded() + { + Marshal.FreeHGlobal(_pixels); + + if (_gl is { }) + { + _gl.DeleteFramebuffer(_framebuffer); + _gl.DeleteTexture(_textureColorBuffer); + _gl.DeleteRenderbuffer(_renderBuffer); + } + } + /// /// By default, SKCanvasElement uses all the given. Subclasses of SKCanvasElement /// should override this method if they need something different. @@ -196,6 +192,7 @@ public void Dispose() _gl.BindTexture(GLEnum.Texture2D, (uint)_oldTextureColorBuffer); _gl.BindRenderbuffer(GLEnum.Renderbuffer, (uint)_oldRbo); _gl.Viewport(_oldViewport[0], _oldViewport[1], (uint)_oldViewport[2], (uint)_oldViewport[3]); + _gl.DepthMask(_depthTestMask); if (_depthTestEnabled) { _gl.Enable(EnableCap.DepthTest); @@ -204,8 +201,6 @@ public void Dispose() { _gl.Disable(EnableCap.DepthTest); } - - _gl.DepthMask(_depthTestMask); } } } From d6755ce49435d5a9e3b7eea99f7cec33dc7c381c Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 8 Aug 2024 13:11:38 +0300 Subject: [PATCH 41/94] chore: GlCanvasElementImpl => RotatingCubeGlCanvasElement --- src/SamplesApp/UITests.Shared/UITests.Shared.projitems | 2 +- .../Windows_UI_Composition/GLCanvasElement_Simple.xaml | 2 +- .../Windows_UI_Composition/GLCanvasElement_Simple.xaml.cs | 2 +- ...sElementImpl.skia.cs => RotatingCubeGlCanvasElement.skia.cs} | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename src/SamplesApp/UITests.Shared/Windows_UI_Composition/{GlCanvasElementImpl.skia.cs => RotatingCubeGlCanvasElement.skia.cs} (99%) diff --git a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems index 9263fa1473bb..a3bd7d418318 100644 --- a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems +++ b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems @@ -6035,7 +6035,7 @@ AutomationProperties_Name.xaml - + CloseRequestedTests.xaml diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml index ca83a23fe9dc..8d345993215d 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml @@ -10,6 +10,6 @@ d:DesignHeight="300" d:DesignWidth="400"> - + 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 index d37239e0490c..792229c50d7c 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml.cs @@ -3,7 +3,7 @@ namespace UITests.Shared.Windows_UI_Composition { - [Sample("Microsoft.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_Simple : UserControl { public GLCanvasElement_Simple() diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs similarity index 99% rename from src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs rename to src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs index 86b233365278..0ea250912351 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GlCanvasElementImpl.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs @@ -20,7 +20,7 @@ namespace UITests.Shared.Windows_UI_Composition { - public class GlCanvasElementImpl() : GLCanvasElement(new Size(1200, 800)) + public class RotatingCubeGlCanvasElement() : GLCanvasElement(new Size(1200, 800)) { private static BufferObject _vbo; private static BufferObject _ebo; From 8131b7ef15388a2dd6a6ab36c2744cfc69620786 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 8 Aug 2024 13:13:17 +0300 Subject: [PATCH 42/94] chore: move Canvas.Save() up --- src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs b/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs index d964c83b4f87..5ed7ca39e7d8 100644 --- a/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs +++ b/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs @@ -26,9 +26,9 @@ internal override void Paint(in PaintingSession session) { // we clear the drawing area here because in some cases when unloading the GLCanvasElement, the // drawing isn't cleared for some reason (a possible hypothesis is timing problems between raw GL and skia). + session.Canvas.Save(); session.Canvas.ClipRect(new SKRect(0, 0, _owner.Visual.Size.X, _owner.Visual.Size.Y)); session.Canvas.Clear(SKColors.Transparent); - session.Canvas.Save(); if (_owner._renderDirty) { _owner.Render(); From bd5de11a9a30b0fb7f9b24cdb2e10e4410fdc829 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 8 Aug 2024 13:55:51 +0300 Subject: [PATCH 43/94] chore: remove SkiaVisual and add docstrings --- src/Uno.UI.Runtime.Skia/SKCanvasElement.cs | 43 ++++++++++++++++------ src/Uno.UI.Runtime.Skia/SkiaVisual.cs | 22 ----------- 2 files changed, 32 insertions(+), 33 deletions(-) delete mode 100644 src/Uno.UI.Runtime.Skia/SkiaVisual.cs diff --git a/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs b/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs index 00b093ac5328..c8f4d79af091 100644 --- a/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs +++ b/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs @@ -7,34 +7,53 @@ namespace Microsoft.UI.Xaml.Controls; /// -/// A wrapper around that takes care of sizing, layouting and DPI. +/// A that exposes the ability to draw directly on the window using SkiaSharp. /// +/// This is only available on skia-based targets. public abstract class SKCanvasElement : FrameworkElement { - private class SKCanvasVisual(SKCanvasElement owner, Compositor compositor) : SkiaVisual(compositor) + private class SKCanvasVisual(SKCanvasElement owner, Compositor compositor) : Visual(compositor) { - protected override void RenderOverride(SKCanvas canvas) => owner.RenderOverride(canvas, Size.ToSize()); + internal override void Paint(in PaintingSession session) + { + session.Canvas.Save(); + // clipping here guards against a naked canvas.Clear() call which would wipe out the entire window. + session.Canvas.ClipRect(new SKRect(0, 0, Size.X, Size.Y)); + owner.RenderOverride(session.Canvas, Size.ToSize()); + session.Canvas.Restore(); + } + + public void Invalidate() => Compositor.InvalidateRender(this); } - private readonly SkiaVisual _skiaVisual; + private readonly SKCanvasVisual _skiaVisual; protected SKCanvasElement() { _skiaVisual = new SKCanvasVisual(this, ElementCompositionPreview.GetElementVisual(this).Compositor); Visual.Children.InsertAtTop(_skiaVisual); + + SizeChanged += OnSizeChanged; } + /// + /// Queue a rendering cycle that will call . + /// + public void Invalidate() => _skiaVisual.Invalidate(); + /// /// The SkiaSharp drawing logic goes here. /// - /// The SKCanvas that should be drawn on. The drawing will directly appear in the clipping area. + /// The SKCanvas that should be drawn on. /// The dimensions of the clipping area. + /// When called, the is already set up such that the origin (0,0) is at the top-left of the clipping area. protected abstract void RenderOverride(SKCanvas canvas, Size area); /// - /// By default, SKCanvasElement uses all the given. Subclasses of SKCanvasElement + /// By default, SKCanvasElement uses all the given. Subclasses of /// should override this method if they need something different. /// + /// An exception will be thrown if availableSize is infinite (e.g. if inside a StackPanel). protected override Size MeasureOverride(Size availableSize) { if (availableSize.Width == Double.PositiveInfinity || @@ -48,13 +67,13 @@ protected override Size MeasureOverride(Size availableSize) return availableSize; } + /// + /// By default, SKCanvasElement uses all the given. Subclasses of + /// should override this method if they need something different. + /// + /// An exception will be thrown if is infinite (e.g. if inside a StackPanel). protected override Size ArrangeOverride(Size finalSize) { - _skiaVisual.Size = new Vector2((float)finalSize.Width, (float)finalSize.Height); - // clipping is necessary in case a user does a canvas clear without any clipping defined. - // In that case. the entire window will be cleared. - _skiaVisual.Clip = _skiaVisual.Compositor.CreateRectangleClip(0, 0, (float)finalSize.Width, (float)finalSize.Height); - if (finalSize.Width == Double.PositiveInfinity || finalSize.Height == Double.PositiveInfinity || double.IsNaN(finalSize.Width) || @@ -64,4 +83,6 @@ protected override Size ArrangeOverride(Size finalSize) } return finalSize; } + + private void OnSizeChanged(object sender, SizeChangedEventArgs args) => _skiaVisual.Size = args.NewSize.ToVector2(); } diff --git a/src/Uno.UI.Runtime.Skia/SkiaVisual.cs b/src/Uno.UI.Runtime.Skia/SkiaVisual.cs deleted file mode 100644 index e7dda0db4647..000000000000 --- a/src/Uno.UI.Runtime.Skia/SkiaVisual.cs +++ /dev/null @@ -1,22 +0,0 @@ -using SkiaSharp; -using Uno.UI.Composition; - -namespace Microsoft.UI.Composition; - -/// -/// A Visual that allows users to directly draw on the Skia Canvas used by Uno to render a window. -/// -public abstract class SkiaVisual(Compositor compositor) : Visual(compositor) -{ - internal override void Paint(in PaintingSession session) => RenderOverride(session.Canvas); - - /// - /// Queue a rendering cycle that will call . - /// - public void Invalidate() => Compositor.InvalidateRender(this); - - /// - /// The SkiaSharp drawing logic goes here. - /// - protected abstract void RenderOverride(SKCanvas canvas); -} From 9e70bb3776aaada4ac4f32c1605a3100eedaf81d Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 8 Aug 2024 14:57:52 +0300 Subject: [PATCH 44/94] chore: packaging --- .../SamplesApp.Skia/SamplesApp.Skia.csproj | 1 - src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs | 2 +- src/Uno.UI.Runtime.Skia/GLGetProcAddress.cs | 3 --- src/Uno.UI.Runtime.Skia/Uno.UI.Runtime.Skia.csproj | 4 ---- .../Graphics/GLCanvasElement.GLVisual.skia.cs} | 1 + .../Xaml/Graphics/GLCanvasElement.crossruntime.cs | 14 ++++++++++++++ .../UI/Xaml/Graphics/GLCanvasElement.skia.cs} | 6 +++++- .../Xaml/Graphics/SKCanvasElement.crossruntime.cs | 13 +++++++++++++ .../UI/Xaml/Graphics/SKCanvasElement.skia.cs} | 5 +++-- 9 files changed, 37 insertions(+), 12 deletions(-) delete mode 100644 src/Uno.UI.Runtime.Skia/GLGetProcAddress.cs rename src/{Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs => Uno.UI/UI/Xaml/Graphics/GLCanvasElement.GLVisual.skia.cs} (99%) create mode 100644 src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.crossruntime.cs rename src/{Uno.UI.Runtime.Skia/GLCanvasElement.cs => Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs} (98%) create mode 100644 src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.crossruntime.cs rename src/{Uno.UI.Runtime.Skia/SKCanvasElement.cs => Uno.UI/UI/Xaml/Graphics/SKCanvasElement.skia.cs} (96%) diff --git a/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj b/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj index 21751e42c99d..8718552a5720 100644 --- a/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj +++ b/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj @@ -33,7 +33,6 @@ - diff --git a/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs b/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs index bb1aac28b81c..16be427aeead 100644 --- a/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs +++ b/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs @@ -67,7 +67,7 @@ static X11ApplicationHost() ApiExtensibility.Register(typeof(Windows.ApplicationModel.DataTransfer.DragDrop.Core.IDragDropExtension), o => new X11DragDropExtension(o)); - ApiExtensibility.Register(typeof(GLGetProcAddress), _ => new GLGetProcAddress(GlxInterface.glXGetProcAddress)); + ApiExtensibility.Register(typeof(GLCanvasElement.GLGetProcAddress), _ => new GLCanvasElement.GLGetProcAddress(GlxInterface.glXGetProcAddress)); } public X11ApplicationHost(Func appBuilder) diff --git a/src/Uno.UI.Runtime.Skia/GLGetProcAddress.cs b/src/Uno.UI.Runtime.Skia/GLGetProcAddress.cs deleted file mode 100644 index 96e332433c9f..000000000000 --- a/src/Uno.UI.Runtime.Skia/GLGetProcAddress.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Uno.UI.Runtime.Skia; - -internal delegate IntPtr GLGetProcAddress(string proc); diff --git a/src/Uno.UI.Runtime.Skia/Uno.UI.Runtime.Skia.csproj b/src/Uno.UI.Runtime.Skia/Uno.UI.Runtime.Skia.csproj index 2228ddf9e7d6..9944a9e0d315 100644 --- a/src/Uno.UI.Runtime.Skia/Uno.UI.Runtime.Skia.csproj +++ b/src/Uno.UI.Runtime.Skia/Uno.UI.Runtime.Skia.csproj @@ -31,8 +31,4 @@ - - - - diff --git a/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs b/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.GLVisual.skia.cs similarity index 99% rename from src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs rename to src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.GLVisual.skia.cs index 5ed7ca39e7d8..c32d726c0d94 100644 --- a/src/Uno.UI.Runtime.Skia/GLCanvasElement.GLVisual.cs +++ b/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.GLVisual.skia.cs @@ -1,3 +1,4 @@ +#nullable enable using Microsoft.UI.Composition; using SkiaSharp; diff --git a/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.crossruntime.cs b/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.crossruntime.cs new file mode 100644 index 000000000000..89ab6b0d342d --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.crossruntime.cs @@ -0,0 +1,14 @@ +#if !__SKIA__ +using System; +using Windows.Foundation; + +namespace Microsoft.UI.Xaml.Controls; + +public abstract partial class GLCanvasElement : FrameworkElement +{ + protected GLCanvasElement(Size resolution) + { + throw new PlatformNotSupportedException($"${nameof(GLCanvasElement)} is only available on skia targets."); + } +} +#endif diff --git a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs b/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs similarity index 98% rename from src/Uno.UI.Runtime.Skia/GLCanvasElement.cs rename to src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs index db5a63c9e219..9981b8d889ae 100644 --- a/src/Uno.UI.Runtime.Skia/GLCanvasElement.cs +++ b/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs @@ -1,15 +1,19 @@ +#nullable enable + +using System; using System.Diagnostics; using System.Runtime.InteropServices; using Windows.Foundation; using Silk.NET.Core.Contexts; using Silk.NET.OpenGL; using Uno.Foundation.Extensibility; -using Uno.UI.Runtime.Skia; namespace Microsoft.UI.Xaml.Controls; public abstract partial class GLCanvasElement : FrameworkElement { + internal delegate IntPtr GLGetProcAddress(string proc); + private const int BytesPerPixel = 4; private readonly uint _width; diff --git a/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.crossruntime.cs b/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.crossruntime.cs new file mode 100644 index 000000000000..1db22d381438 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.crossruntime.cs @@ -0,0 +1,13 @@ +#if !__SKIA__ +using System; + +namespace Microsoft.UI.Xaml.Controls; + +public abstract class SKCanvasElement : FrameworkElement +{ + protected SKCanvasElement() + { + throw new PlatformNotSupportedException($"${nameof(SKCanvasElement)} is only available on skia targets."); + } +} +#endif diff --git a/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs b/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.skia.cs similarity index 96% rename from src/Uno.UI.Runtime.Skia/SKCanvasElement.cs rename to src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.skia.cs index c8f4d79af091..60435bad903d 100644 --- a/src/Uno.UI.Runtime.Skia/SKCanvasElement.cs +++ b/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.skia.cs @@ -1,3 +1,4 @@ +using System; using System.Numerics; using Windows.Foundation; using Microsoft.UI.Composition; @@ -74,8 +75,8 @@ protected override Size MeasureOverride(Size availableSize) /// An exception will be thrown if is infinite (e.g. if inside a StackPanel). protected override Size ArrangeOverride(Size finalSize) { - if (finalSize.Width == Double.PositiveInfinity || - finalSize.Height == Double.PositiveInfinity || + if (finalSize.Width == double.PositiveInfinity || + finalSize.Height == double.PositiveInfinity || double.IsNaN(finalSize.Width) || double.IsNaN(finalSize.Height)) { From 7703a7f3cf46dd4d0b2cd01cb7f1251eb77660bd Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 8 Aug 2024 15:00:14 +0300 Subject: [PATCH 45/94] chore: enable depth testing per sample --- .../RotatingCubeGlCanvasElement.skia.cs | 5 ++++- src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs | 3 --- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs index 0ea250912351..4e41643f5f1f 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs @@ -110,8 +110,11 @@ protected override void OnDestroy(GL Gl) } // somewhat follows https://github.com/c2d7fa/opengl-cube - protected unsafe override void RenderOverride(GL Gl) + 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); diff --git a/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs b/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs index 9981b8d889ae..b953fab8709a 100644 --- a/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs +++ b/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs @@ -111,9 +111,6 @@ private unsafe void Render() { _gl.Viewport(new System.Drawing.Size((int)_width, (int)_height)); - _gl.Enable(EnableCap.DepthTest); - _gl.DepthMask(true); - RenderOverride(_gl); // Can we do without this copy? From bb27e7028c6bf0357748f7f33da4bac2f80027fc Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 8 Aug 2024 15:09:47 +0300 Subject: [PATCH 46/94] chore: minor touches --- .../RotatingCubeGlCanvasElement.skia.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs index 4e41643f5f1f..b2b4f84dc040 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs @@ -63,8 +63,8 @@ public class RotatingCubeGlCanvasElement() : GLCanvasElement(new Size(1200, 800) 0, 4, 5, }; - private const string vertexShaderSource = @" -#version 410 + private const string VertexShaderSource = @" +#version 330 layout(location = 0) in vec3 pos; layout(location = 1) in vec3 vertex_color; @@ -78,8 +78,8 @@ void main() { color = vertex_color; }"; - private const string fragmentShaderSource = @" -#version 410 + private const string FragmentShaderSource = @" +#version 330 in vec3 color; @@ -98,7 +98,7 @@ protected override void Init(GL Gl) _vao.VertexAttributePointer(0, 3, VertexAttribPointerType.Float, 6, 0); _vao.VertexAttributePointer(1, 3, VertexAttribPointerType.Float, 6, 3); - _shader = new Shader(Gl, vertexShaderSource, fragmentShaderSource); + _shader = new Shader(Gl, VertexShaderSource, FragmentShaderSource); } protected override void OnDestroy(GL Gl) @@ -131,6 +131,7 @@ protected override unsafe void RenderOverride(GL Gl) _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 From 077185dd5341abc4096f59ee8581cbc93a22dcf3 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 8 Aug 2024 16:23:58 +0300 Subject: [PATCH 47/94] chore: move Init down --- src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs b/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs index b953fab8709a..54e4e94a3d3e 100644 --- a/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs +++ b/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs @@ -85,8 +85,6 @@ private protected override unsafe void OnLoaded() { _gl.RenderbufferStorage(GLEnum.Renderbuffer, InternalFormat.Depth24Stencil8, _width, _height); _gl.FramebufferRenderbuffer(GLEnum.Framebuffer, GLEnum.DepthStencilAttachment, GLEnum.Renderbuffer, _renderBuffer); - - Init(_gl); } _gl.BindRenderbuffer(GLEnum.Renderbuffer, 0); @@ -94,6 +92,8 @@ private protected override unsafe void OnLoaded() { throw new InvalidOperationException("Offscreen framebuffer is not complete"); } + + Init(_gl); } _gl.BindFramebuffer(GLEnum.Framebuffer, 0); From 684a9810ddb4b4787361fa6673eedc5889ec28d6 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 8 Aug 2024 16:40:13 +0300 Subject: [PATCH 48/94] chore: more packaging --- src/Uno.UI.Composition/AssemblyInfo.skia.cs | 1 - src/Uno.UI.Runtime.Skia.Wpf/Uno.UI.Runtime.Skia.Wpf.csproj | 1 + src/Uno.UI.RuntimeTests/Uno.UI.RuntimeTests.Skia.csproj | 1 - src/Uno.UI/AssemblyInfo.skia.cs | 1 - src/Uno.UI/UI/Xaml/XamlRoot.cs | 1 - 5 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Uno.UI.Composition/AssemblyInfo.skia.cs b/src/Uno.UI.Composition/AssemblyInfo.skia.cs index 8c2734441d2b..3d58e1d1027e 100644 --- a/src/Uno.UI.Composition/AssemblyInfo.skia.cs +++ b/src/Uno.UI.Composition/AssemblyInfo.skia.cs @@ -1,6 +1,5 @@ using global::System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Uno.UI.Runtime.Skia")] [assembly: InternalsVisibleTo("Uno.UI.Runtime.Skia.Gtk")] [assembly: InternalsVisibleTo("Uno.UI.Runtime.Skia.MacOS")] [assembly: InternalsVisibleTo("Uno.UI.Runtime.Skia.Wpf")] 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.RuntimeTests/Uno.UI.RuntimeTests.Skia.csproj b/src/Uno.UI.RuntimeTests/Uno.UI.RuntimeTests.Skia.csproj index 71de9ab1b8af..258eb3ecf2e6 100644 --- a/src/Uno.UI.RuntimeTests/Uno.UI.RuntimeTests.Skia.csproj +++ b/src/Uno.UI.RuntimeTests/Uno.UI.RuntimeTests.Skia.csproj @@ -63,7 +63,6 @@ - diff --git a/src/Uno.UI/AssemblyInfo.skia.cs b/src/Uno.UI/AssemblyInfo.skia.cs index acf088ad4ab5..2e34cb986d28 100644 --- a/src/Uno.UI/AssemblyInfo.skia.cs +++ b/src/Uno.UI/AssemblyInfo.skia.cs @@ -1,6 +1,5 @@ using global::System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Uno.UI.Runtime.Skia")] [assembly: InternalsVisibleTo("Uno.UI.Runtime.Skia.Gtk")] [assembly: InternalsVisibleTo("Uno.UI.Runtime.Skia.MacOS")] [assembly: InternalsVisibleTo("Uno.UI.Runtime.Skia.Wpf")] diff --git a/src/Uno.UI/UI/Xaml/XamlRoot.cs b/src/Uno.UI/UI/Xaml/XamlRoot.cs index d591649e0174..1f49c375e765 100644 --- a/src/Uno.UI/UI/Xaml/XamlRoot.cs +++ b/src/Uno.UI/UI/Xaml/XamlRoot.cs @@ -7,7 +7,6 @@ using Windows.Foundation; using Windows.Graphics.Display; using Uno.UI.Extensions; -using Windows.UI.Composition; using Uno.UI.Xaml.Controls; namespace Microsoft.UI.Xaml; From e6e0ad2bf81c32067752f70722d2545abccff525 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 8 Aug 2024 16:43:13 +0300 Subject: [PATCH 49/94] chore: typo --- .../Windows_UI_Composition/SKCanvasElementImpl.skia.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElementImpl.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElementImpl.skia.cs index 89003386aa1d..c4828f2b7b36 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElementImpl.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElementImpl.skia.cs @@ -30,7 +30,7 @@ private void SampleChanged(int newIndex) 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 + // rescale to fit the given area, assuming each drawing is 260x260 canvas.Scale((float)(minDim / 260), (float)(minDim / 260)); switch (Sample) From c9dba271b44cc3678b63f9bbd580c0008747f603 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 8 Aug 2024 17:18:48 +0300 Subject: [PATCH 50/94] docs: complete SKCanvasElement docs --- .../{SkiaCanvas.md => SKCanvasElement.md} | 35 ++++++------------- .../SKCanvasElement_Simple.xaml | 5 ++- .../UI/Xaml/Graphics/SKCanvasElement.skia.cs | 9 +++-- 3 files changed, 18 insertions(+), 31 deletions(-) rename doc/articles/controls/{SkiaCanvas.md => SKCanvasElement.md} (67%) diff --git a/doc/articles/controls/SkiaCanvas.md b/doc/articles/controls/SKCanvasElement.md similarity index 67% rename from doc/articles/controls/SkiaCanvas.md rename to doc/articles/controls/SKCanvasElement.md index 881d63b6958e..26cf420b106a 100644 --- a/doc/articles/controls/SkiaCanvas.md +++ b/doc/articles/controls/SKCanvasElement.md @@ -6,40 +6,26 @@ uid: Uno.Controls.SKCanvasElement 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. It is then possible to copy the resulting renderings to the application (e.g. using a [BitmapImage](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.media.imaging.bitmapimage)). This way, a lot of Skia functionally can be acquired "for free". For example, no additional additional packages are needed, and setup for hardware acceleration is not needed if the Uno application is already using OpenGL to render. - -This functionality is exposed in two parts. `SkiaVisual` is a [Composition API Visual](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.composition.visual) that gets an [SKCanvas](https://learn.microsoft.com/dotnet/api/skiasharp.skcanvas) to draw on and is almost completely unmanaged. For more streamlined usage, an `SKCanvasElement` is provided that internally wraps a `SkiaVisual` and can be used like any `FrameworkElement`, with support for sizing, clipping, RTL, etc. You should use `SKCanvasElement` for most scenarios. Only use a raw `SkiaVisual` if your use case is not covered by `SKCanvasElement`. +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, no additional packages are needed, and setup for hardware acceleration is not needed if the Uno application is already using OpenGL to render. Moreover, this is faster than `SKXamlCanvas`, which has to make additional buffer copying. > [!IMPORTANT] > This functionality is only available on Skia Desktop (`net8.0-desktop`) targets. -## SkiaVisual - -A `SkiaVisual` is an abstract `Visual` that provides Uno Platform applications the ability to utilize SkiaSharp to draw directly on the Skia canvas that is used internally by Uno to draw the window. To use `SkiaVisual`, create a subclass of `SkiaVisual` and override the `RenderOverride` method. - -```csharp -protected abstract void RenderOverride(SKCanvas canvas); -``` - -You can then add the `SkiaVisual` as a child visual of an element using [ElementCompositionPreview.SetElementChildVisual](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.hosting.elementcompositionpreview.setelementchildvisual#microsoft-ui-xaml-hosting-elementcompositionpreview-setelementchildvisual(microsoft-ui-xaml-uielement-microsoft-ui-composition-visual)). - -Note that you will need to add your own logic to handle sizing and clipping. - -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. - -Additionally, `SkiaVisual` 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. - ## SKCanvasElement -`SKCanvasElement` is a ready-made `FrameworkElement` that creates an internal `SkiaVisual` and maintains its state as one would expect. 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. Drawing outside this area will be clipped. +`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); ``` -By default, `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` method. +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. -Note that since `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`. +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 @@ -63,12 +49,11 @@ XAML: - - - + + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml index 1ebfc2bff510..dcb9d9652030 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml @@ -12,12 +12,11 @@ - - - + + diff --git a/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.skia.cs b/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.skia.cs index 60435bad903d..90063f2089fc 100644 --- a/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.skia.cs +++ b/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.skia.cs @@ -33,7 +33,7 @@ protected SKCanvasElement() { _skiaVisual = new SKCanvasVisual(this, ElementCompositionPreview.GetElementVisual(this).Compositor); Visual.Children.InsertAtTop(_skiaVisual); - + SizeChanged += OnSizeChanged; } @@ -47,7 +47,10 @@ protected SKCanvasElement() /// /// The SKCanvas that should be drawn on. /// The dimensions of the clipping area. - /// When called, the is already set up such that the origin (0,0) is at the top-left of the clipping area. + /// + /// When called, the is already set up such that the origin (0,0) is at the top-left of the clipping area. + /// Drawing outside this area (i.e. outside the (0, 0, area.Width, area.Height rectangle) will be clipped out. + /// protected abstract void RenderOverride(SKCanvas canvas, Size area); /// @@ -84,6 +87,6 @@ protected override Size ArrangeOverride(Size finalSize) } return finalSize; } - + private void OnSizeChanged(object sender, SizeChangedEventArgs args) => _skiaVisual.Size = args.NewSize.ToVector2(); } From a1f75e95132274e00376e074023a16eb4cc19b37 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 8 Aug 2024 17:31:33 +0300 Subject: [PATCH 51/94] chore: move SKCanvasVisual to a separate partial --- .../SKCanvasElement.SKCanvasVisual.skia.cs | 26 +++++++++++++++++++ .../Graphics/SKCanvasElement.crossruntime.cs | 2 +- .../UI/Xaml/Graphics/SKCanvasElement.skia.cs | 17 +----------- 3 files changed, 28 insertions(+), 17 deletions(-) create mode 100644 src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.SKCanvasVisual.skia.cs diff --git a/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.SKCanvasVisual.skia.cs b/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.SKCanvasVisual.skia.cs new file mode 100644 index 000000000000..d4dc8ce40189 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.SKCanvasVisual.skia.cs @@ -0,0 +1,26 @@ +using System.Numerics; +using Microsoft.UI.Composition; +using SkiaSharp; + +namespace Microsoft.UI.Xaml.Controls; + +/// +/// A that exposes the ability to draw directly on the window using SkiaSharp. +/// +/// This is only available on skia-based targets. +public abstract partial class SKCanvasElement +{ + private class SKCanvasVisual(SKCanvasElement owner, Compositor compositor) : Visual(compositor) + { + internal override void Paint(in PaintingSession session) + { + session.Canvas.Save(); + // clipping here guards against a naked canvas.Clear() call which would wipe out the entire window. + session.Canvas.ClipRect(new SKRect(0, 0, Size.X, Size.Y)); + owner.RenderOverride(session.Canvas, Size.ToSize()); + session.Canvas.Restore(); + } + + public void Invalidate() => Compositor.InvalidateRender(this); + } +} diff --git a/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.crossruntime.cs b/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.crossruntime.cs index 1db22d381438..dd10739ffde4 100644 --- a/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.crossruntime.cs +++ b/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.crossruntime.cs @@ -3,7 +3,7 @@ namespace Microsoft.UI.Xaml.Controls; -public abstract class SKCanvasElement : FrameworkElement +public abstract partial class SKCanvasElement : FrameworkElement { protected SKCanvasElement() { diff --git a/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.skia.cs b/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.skia.cs index 90063f2089fc..536b396170ba 100644 --- a/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.skia.cs +++ b/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.skia.cs @@ -1,7 +1,6 @@ using System; using System.Numerics; using Windows.Foundation; -using Microsoft.UI.Composition; using Microsoft.UI.Xaml.Hosting; using SkiaSharp; @@ -11,22 +10,8 @@ namespace Microsoft.UI.Xaml.Controls; /// A that exposes the ability to draw directly on the window using SkiaSharp. /// /// This is only available on skia-based targets. -public abstract class SKCanvasElement : FrameworkElement +public abstract partial class SKCanvasElement : FrameworkElement { - private class SKCanvasVisual(SKCanvasElement owner, Compositor compositor) : Visual(compositor) - { - internal override void Paint(in PaintingSession session) - { - session.Canvas.Save(); - // clipping here guards against a naked canvas.Clear() call which would wipe out the entire window. - session.Canvas.ClipRect(new SKRect(0, 0, Size.X, Size.Y)); - owner.RenderOverride(session.Canvas, Size.ToSize()); - session.Canvas.Restore(); - } - - public void Invalidate() => Compositor.InvalidateRender(this); - } - private readonly SKCanvasVisual _skiaVisual; protected SKCanvasElement() From d54f37a663c17077b40dbe2e795d0e05d5c74f66 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 8 Aug 2024 17:43:54 +0300 Subject: [PATCH 52/94] chore: check WGL is present on WPF --- .../Extensions/WpfGlNativeContext.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfGlNativeContext.cs b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfGlNativeContext.cs index 49f438b401f7..00e84c9d5ad7 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfGlNativeContext.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfGlNativeContext.cs @@ -7,7 +7,16 @@ namespace Uno.UI.Runtime.Skia.Wpf.Extensions; // https://sharovarskyi.com/blog/posts/csharp-win32-opengl-silknet/ internal class WpfGlNativeContext : INativeContext { - private readonly UnmanagedLibrary _l = new UnmanagedLibrary("opengl32.dll"); + private readonly UnmanagedLibrary _l; + + public WpfGlNativeContext() + { + _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) { From 8a08a0ed1cbd9748ebc43735f1b87234bbb54147 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 8 Aug 2024 18:55:34 +0300 Subject: [PATCH 53/94] chore: GLCanvasElement docstrings --- .../UI/Xaml/Graphics/GLCanvasElement.skia.cs | 50 ++++++++++++++++++- .../UI/Xaml/Graphics/SKCanvasElement.skia.cs | 4 +- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs b/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs index 54e4e94a3d3e..678b21d2c2e3 100644 --- a/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs +++ b/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs @@ -10,6 +10,13 @@ namespace Microsoft.UI.Xaml.Controls; +/// +/// A that exposes the ability to draw 3D graphics using OpenGL and Silk.NET. +/// +/// +/// This is only available on skia-based targets and when running with hardware acceleration. +/// This is currently only available on the WPF and X11 targets. +/// public abstract partial class GLCanvasElement : FrameworkElement { internal delegate IntPtr GLGetProcAddress(string proc); @@ -28,6 +35,7 @@ public abstract partial class GLCanvasElement : FrameworkElement private uint _textureColorBuffer; private uint _renderBuffer; + /// The resolution of the backing framebuffer. protected GLCanvasElement(Size resolution) { _width = (uint)resolution.Width; @@ -37,10 +45,44 @@ protected GLCanvasElement(Size resolution) Visual.Children.InsertAtTop(_glVisual); } + /// + /// 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 way interacts with Skia (which also uses OpenGL), you must make sure + /// to restore all the OpenGL state values to their original values. 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); + /// + /// Invalidates the rendering, and calls in the next rendering cycle. + /// 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. + /// public void Invalidate() { _renderDirty = true; @@ -132,9 +174,10 @@ private protected override void OnUnloaded() } /// - /// By default, SKCanvasElement uses all the given. Subclasses of SKCanvasElement + /// By default, uses all the given. Subclasses of /// should override this method if they need something different. /// + /// An exception will be thrown if availableSize is infinite (e.g. if inside a StackPanel). protected override Size MeasureOverride(Size availableSize) { if (availableSize.Width == Double.PositiveInfinity || @@ -147,6 +190,11 @@ protected override Size MeasureOverride(Size availableSize) return availableSize; } + /// + /// By default, uses all the given. Subclasses of + /// should override this method if they need something different. + /// + /// An exception will be thrown if is infinite (e.g. if inside a StackPanel). protected override Size ArrangeOverride(Size finalSize) { if (finalSize.Width == Double.PositiveInfinity || diff --git a/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.skia.cs b/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.skia.cs index 536b396170ba..b24f67adab39 100644 --- a/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.skia.cs +++ b/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.skia.cs @@ -39,7 +39,7 @@ protected SKCanvasElement() protected abstract void RenderOverride(SKCanvas canvas, Size area); /// - /// By default, SKCanvasElement uses all the given. Subclasses of + /// By default, uses all the given. Subclasses of /// should override this method if they need something different. /// /// An exception will be thrown if availableSize is infinite (e.g. if inside a StackPanel). @@ -57,7 +57,7 @@ protected override Size MeasureOverride(Size availableSize) } /// - /// By default, SKCanvasElement uses all the given. Subclasses of + /// By default, uses all the given. Subclasses of /// should override this method if they need something different. /// /// An exception will be thrown if is infinite (e.g. if inside a StackPanel). From 1c41b921330dab59f9005dd1115b238e1a34c8c9 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 8 Aug 2024 20:03:21 +0300 Subject: [PATCH 54/94] docs: GLCanvasElement docs --- doc/articles/controls/GLCanvasElement.md | 196 ++++++++++++++++++ doc/articles/controls/SKCanvasElement.md | 2 +- .../UI/Xaml/Graphics/GLCanvasElement.skia.cs | 11 +- 3 files changed, 203 insertions(+), 6 deletions(-) create mode 100644 doc/articles/controls/GLCanvasElement.md diff --git a/doc/articles/controls/GLCanvasElement.md b/doc/articles/controls/GLCanvasElement.md new file mode 100644 index 000000000000..d568b05fa52b --- /dev/null +++ b/doc/articles/controls/GLCanvasElement.md @@ -0,0 +1,196 @@ +--- +uid: Uno.Controls.GLCanvasElement +--- + +## GLCanvasElement + +> [!IMPORTANT] +> This functionality is only available on Skia Desktop (`net8.0-desktop`) targets that are running with hardware acceleration. This is also not available on MacOS. + +> [!IMPORTANT] +> If your application uses `GLCanvasElement`, you will need to add a PackageReference to `Silk.NET.OpenGL`. + +`GLCanvasElement` is a `FrameworkElement` for drawing 3D graphics with OpenGL. + +To use `GLCanvasElement`, create a subclass of `GLCanvasElement` and override the abstract methods `Init`, `RenderOverride` and `OnDestroy`. + +```csharp +protected GLCanvasElement(Size resolution); + +protected abstract void Init(GL gl); +protected abstract void RenderOverride(GL gl); +protected abstract void OnDestroy(GL gl); +``` + +The protected constructor has a `Size` parameter, which decides the resolution of the offscreen framebuffer that the `GLCanvasElement` will draw onto. Note that the `resolution` parameter is unrelated to the final size of the drawing in the Uno 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 `UIelement`, e.g. using `MeasureOverride`, `ArrangeOverride`, the `Width/Height` properties, etc. + +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. + +> [!IMPORTANT] +> `Init` and `OnDestroy` might be called multiple times in pairs. Every call to `OnDestroy` will be preceded by a call to `Init`. + +The `RenderOverride` method takes a `Silk.NET.OpenGL.GL` instance that can be used to make OpenGL calls. When `RenderOverride` is called, you can assume that + +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 Uno Platform has its own support for input and windowing. + +When adding your drawing logic in `RenderOverride` on the provided canvas, 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. Due to the fact that both `GLCanvasElement` 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 `RenderOverride`. For example, make sure to save the values for the initially-bound VAO if you intend to bind your own VAO and bind the original VAO at the end of `RenderOverride`. + +```csharp +protected override void RenderOverride(GL gl) +{ + var oldVAO = gl.GetInteger(GLEnum.VertexArrayBinding); + gl.BindVertexArray(myVAO); + + // draw with myVAO + + gl.BindVertexArray(oldVAO); +} +``` +Similarly, make sure to disable depth testing at the end if you choose to enable it. To reduce bugs, some of the more common OpenGL state variables are restored automatically for you, but don't depend on this behaviour. + +Additionally, `GLCanvasElement` has an `Invalidate` method that can be used at any time to tell the Uno Platform runtime to redraw 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`. + +By default, a `GLCanvasElement` 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 a `GLCanvasElement` takes as much space as it can, it's not allowed to place a `GLCanvasElement` 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 `GLCanvasElement`. + +## 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.skia.cs <-- NOTICE the `.skia` +public class GLTriangleElement() : GLCanvasElement(new Size(1200, 800)) +{ + 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); + + const string vertexCode = @" +#version 330 core + +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); +}"; + + const string fragmentCode = @" +#version 330 core + +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); + } +} +``` diff --git a/doc/articles/controls/SKCanvasElement.md b/doc/articles/controls/SKCanvasElement.md index 26cf420b106a..70afb357d7d3 100644 --- a/doc/articles/controls/SKCanvasElement.md +++ b/doc/articles/controls/SKCanvasElement.md @@ -67,7 +67,7 @@ public partial class SKCanvasElementExample : UserControl { public int MaxSampleIndex => SKCanvasElementImpl.SampleCount - 1; - public SKCanvasElement_Simple() + public SKCanvasElementExample() { this.InitializeComponent(); } diff --git a/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs b/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs index 678b21d2c2e3..4b2e8c2a0d03 100644 --- a/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs +++ b/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs @@ -69,11 +69,12 @@ protected GLCanvasElement(Size resolution) /// the constructor. /// /// - /// Due to the way interacts with Skia (which also uses OpenGL), you must make sure - /// to restore all the OpenGL state values to their original values. 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. + /// 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); From f380086403a76cd69f384b42bc767b5505a4a69e Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 8 Aug 2024 20:29:51 +0300 Subject: [PATCH 55/94] chore: add another sample --- .../UITests.Shared/UITests.Shared.projitems | 8 ++ .../GLCanvasElement_Cube.xaml | 15 +++ .../GLCanvasElement_Cube.xaml.cs | 14 +++ .../GLCanvasElement_Simple.xaml | 2 +- .../GLCanvasElement_Simple.xaml.cs | 2 +- .../SimpleTriangleGlCanvasElement.skia.cs | 107 ++++++++++++++++++ 6 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Cube.xaml create mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Cube.xaml.cs create mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs diff --git a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems index a3bd7d418318..dbe7a048d4d1 100644 --- a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems +++ b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems @@ -4702,6 +4702,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -6036,6 +6040,7 @@ AutomationProperties_Name.xaml + CloseRequestedTests.xaml @@ -8294,6 +8299,9 @@ 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..ff704f05689f --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Cube.xaml @@ -0,0 +1,15 @@ + + + + + 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 index 8d345993215d..4de8bee29112 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml @@ -10,6 +10,6 @@ d:DesignHeight="300" d:DesignWidth="400"> - + 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 index 792229c50d7c..4c00bf5c6ccc 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml.cs @@ -3,7 +3,7 @@ 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).")] + [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() diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs new file mode 100644 index 000000000000..703fc9ba13e3 --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs @@ -0,0 +1,107 @@ +using System; +using System.Drawing; +using Microsoft.UI.Xaml.Controls; +using Size = Windows.Foundation.Size; +using Silk.NET.OpenGL; + +namespace UITests.Shared.Windows_UI_Composition +{ + // https://learnopengl.com/Getting-started/Hello-Triangle + public class SimpleTriangleGlCanvasElement() : GLCanvasElement(new Size(1200, 800)) + { + 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); + + const string vertexCode = @" +#version 330 core + +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); +}"; + + const string fragmentCode = @" +#version 330 core + +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); + } + } +} From a1a0e148100f49225c7116db87ae766894d86717 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 8 Aug 2024 20:37:13 +0300 Subject: [PATCH 56/94] chore: add UnoMissingAssemblyAnalyzer entry --- src/Uno.Analyzers/UnoMissingAssemblyAnalyzer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Uno.Analyzers/UnoMissingAssemblyAnalyzer.cs b/src/Uno.Analyzers/UnoMissingAssemblyAnalyzer.cs index 7ad29cae90cb..76faef42355e 100644 --- a/src/Uno.Analyzers/UnoMissingAssemblyAnalyzer.cs +++ b/src/Uno.Analyzers/UnoMissingAssemblyAnalyzer.cs @@ -39,6 +39,7 @@ public override void Initialize(AnalysisContext context) { var assemblies = context.Compilation.ReferencedAssemblyNames.Select(a => a.Name).ToImmutableHashSet(); var progressRing = context.Compilation.GetTypeByMetadataName("Microsoft" /* UWP don't rename */ + ".UI.Xaml.Controls.ProgressRing"); + var glCanvasElement = context.Compilation.GetTypeByMetadataName("Microsoft" /* UWP don't rename */ + ".UI.Xaml.Controls.GLCanvasElement"); var mpe = context.Compilation.GetTypeByMetadataName("Microsoft.UI.Xaml.Controls.MediaPlayerElement"); _ = context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.UnoRuntimeIdentifier", out var unoRuntimeIdentifier); _ = context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.IsUnoHead", out var isUnoHead); @@ -66,6 +67,10 @@ public override void Initialize(AnalysisContext context) context.ReportDiagnostic(Diagnostic.Create(Rule, objectCreation.Syntax.GetLocation(), "ProgressRing", lottieNuGetPackageName)); } + else if (type.DerivesFrom(glCanvasElement) && !assemblies.Contains("Silk.NET.OpenGL")) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, objectCreation.Syntax.GetLocation(), "GLCanvasElement", "Silk.NET.OpenGL")); + } else if (type.DerivesFrom(mpe)) { if (unoRuntimeIdentifier?.Equals("WebAssembly", StringComparison.OrdinalIgnoreCase) == true) From d18339d87d7990e7829cd0e5d8e6d7263bfb297c Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 8 Aug 2024 20:37:30 +0300 Subject: [PATCH 57/94] chore: add Silk.NET to uno islands samples app --- .../UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj b/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj index 6a3c181cfbe8..23c15e50a3d6 100644 --- a/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj +++ b/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj @@ -53,6 +53,8 @@ + + From f83d2d9e64b400ff72dd9804e369af862a4f2837 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 8 Aug 2024 21:40:01 +0300 Subject: [PATCH 58/94] chore: formatting --- doc/articles/controls/GLCanvasElement.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/doc/articles/controls/GLCanvasElement.md b/doc/articles/controls/GLCanvasElement.md index d568b05fa52b..90eb683dfb7c 100644 --- a/doc/articles/controls/GLCanvasElement.md +++ b/doc/articles/controls/GLCanvasElement.md @@ -2,11 +2,11 @@ uid: Uno.Controls.GLCanvasElement --- -## GLCanvasElement - > [!IMPORTANT] > This functionality is only available on Skia Desktop (`net8.0-desktop`) targets that are running with hardware acceleration. This is also not available on MacOS. +## GLCanvasElement + > [!IMPORTANT] > If your application uses `GLCanvasElement`, you will need to add a PackageReference to `Silk.NET.OpenGL`. @@ -31,11 +31,7 @@ The `OnDestroy` method is the complement of `Init` and is used to clean up any a > [!IMPORTANT] > `Init` and `OnDestroy` might be called multiple times in pairs. Every call to `OnDestroy` will be preceded by a call to `Init`. -The `RenderOverride` method takes a `Silk.NET.OpenGL.GL` instance that can be used to make OpenGL calls. When `RenderOverride` is called, you can assume that - -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 Uno Platform has its own support for input and windowing. - -When adding your drawing logic in `RenderOverride` on the provided canvas, 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. Due to the fact that both `GLCanvasElement` 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 `RenderOverride`. For example, make sure to save the values for the initially-bound VAO if you intend to bind your own VAO and bind the original VAO at the end of `RenderOverride`. +The `RenderOverride` method takes a `Silk.NET.OpenGL.GL` instance that can be used to make OpenGL calls. When adding your drawing logic in `RenderOverride` on the provided canvas, 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. Due to the fact that both `GLCanvasElement` 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 `RenderOverride`. For example, make sure to save the values for the initially-bound VAO if you intend to bind your own VAO and bind the original VAO at the end of `RenderOverride`. ```csharp protected override void RenderOverride(GL gl) @@ -48,8 +44,11 @@ protected override void RenderOverride(GL gl) gl.BindVertexArray(oldVAO); } ``` + Similarly, make sure to disable depth testing at the end if you choose to enable it. To reduce bugs, some of the more common OpenGL state variables are restored automatically for you, but don't depend on this behaviour. +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 Uno Platform has its own support for input and windowing. + Additionally, `GLCanvasElement` has an `Invalidate` method that can be used at any time to tell the Uno Platform runtime to redraw 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`. By default, a `GLCanvasElement` 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. @@ -77,7 +76,7 @@ XAML: d:DesignWidth="400"> - + ``` From c6e355b756de6ee17d002293043d175bc53e95d6 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 8 Aug 2024 21:42:24 +0300 Subject: [PATCH 59/94] chore: remove /* UWP don't rename */ --- src/Uno.Analyzers/UnoMissingAssemblyAnalyzer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Uno.Analyzers/UnoMissingAssemblyAnalyzer.cs b/src/Uno.Analyzers/UnoMissingAssemblyAnalyzer.cs index 76faef42355e..201b75737ade 100644 --- a/src/Uno.Analyzers/UnoMissingAssemblyAnalyzer.cs +++ b/src/Uno.Analyzers/UnoMissingAssemblyAnalyzer.cs @@ -39,7 +39,7 @@ public override void Initialize(AnalysisContext context) { var assemblies = context.Compilation.ReferencedAssemblyNames.Select(a => a.Name).ToImmutableHashSet(); var progressRing = context.Compilation.GetTypeByMetadataName("Microsoft" /* UWP don't rename */ + ".UI.Xaml.Controls.ProgressRing"); - var glCanvasElement = context.Compilation.GetTypeByMetadataName("Microsoft" /* UWP don't rename */ + ".UI.Xaml.Controls.GLCanvasElement"); + var glCanvasElement = context.Compilation.GetTypeByMetadataName("Microsoft.UI.Xaml.Controls.GLCanvasElement"); var mpe = context.Compilation.GetTypeByMetadataName("Microsoft.UI.Xaml.Controls.MediaPlayerElement"); _ = context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.UnoRuntimeIdentifier", out var unoRuntimeIdentifier); _ = context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.IsUnoHead", out var isUnoHead); From e74fbb5a9b3553ed12271110879614e178a21c06 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 8 Aug 2024 22:28:13 +0300 Subject: [PATCH 60/94] chore: minor doc touches --- doc/articles/controls/GLCanvasElement.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/doc/articles/controls/GLCanvasElement.md b/doc/articles/controls/GLCanvasElement.md index 90eb683dfb7c..bc09a24d995c 100644 --- a/doc/articles/controls/GLCanvasElement.md +++ b/doc/articles/controls/GLCanvasElement.md @@ -2,13 +2,10 @@ uid: Uno.Controls.GLCanvasElement --- -> [!IMPORTANT] -> This functionality is only available on Skia Desktop (`net8.0-desktop`) targets that are running with hardware acceleration. This is also not available on MacOS. - ## GLCanvasElement - + > [!IMPORTANT] -> If your application uses `GLCanvasElement`, you will need to add a PackageReference to `Silk.NET.OpenGL`. +> This functionality is only available on Skia Desktop (`net8.0-desktop`) targets that are running with hardware acceleration. This is also not available on MacOS. `GLCanvasElement` is a `FrameworkElement` for drawing 3D graphics with OpenGL. @@ -24,6 +21,11 @@ protected abstract void OnDestroy(GL gl); The protected constructor has a `Size` parameter, which decides the resolution of the offscreen framebuffer that the `GLCanvasElement` will draw onto. Note that the `resolution` parameter is unrelated to the final size of the drawing in the Uno 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 `UIelement`, e.g. using `MeasureOverride`, `ArrangeOverride`, the `Width/Height` properties, etc. +The 3 abstract methods above all take a `Silk.NET.OpenGL.GL` parameter that can be used to make OpenGL calls. + +> [!IMPORTANT] +> If your application uses `GLCanvasElement`, you will need to add a PackageReference to `Silk.NET.OpenGL`. + 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. @@ -31,7 +33,7 @@ The `OnDestroy` method is the complement of `Init` and is used to clean up any a > [!IMPORTANT] > `Init` and `OnDestroy` might be called multiple times in pairs. Every call to `OnDestroy` will be preceded by a call to `Init`. -The `RenderOverride` method takes a `Silk.NET.OpenGL.GL` instance that can be used to make OpenGL calls. When adding your drawing logic in `RenderOverride` on the provided canvas, 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. Due to the fact that both `GLCanvasElement` 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 `RenderOverride`. For example, make sure to save the values for the initially-bound VAO if you intend to bind your own VAO and bind the original VAO at the end of `RenderOverride`. +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. Due to the fact that both `GLCanvasElement` 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 `RenderOverride`. For example, make sure to save the values for the initially-bound VAO if you intend to bind your own VAO and bind the original VAO at the end of `RenderOverride`. ```csharp protected override void RenderOverride(GL gl) From 7532073a19d12ad4acd924a3013dd976e4763584 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Fri, 9 Aug 2024 00:15:07 +0300 Subject: [PATCH 61/94] chore: typo --- doc/articles/controls/GLCanvasElement.md | 2 +- doc/articles/controls/SKCanvasElement.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/articles/controls/GLCanvasElement.md b/doc/articles/controls/GLCanvasElement.md index bc09a24d995c..9ca9aeff2559 100644 --- a/doc/articles/controls/GLCanvasElement.md +++ b/doc/articles/controls/GLCanvasElement.md @@ -5,7 +5,7 @@ uid: Uno.Controls.GLCanvasElement ## GLCanvasElement > [!IMPORTANT] -> This functionality is only available on Skia Desktop (`net8.0-desktop`) targets that are running with hardware acceleration. This is also not available on MacOS. +> This functionality is only available on Skia Desktop (`netX.0-desktop`) targets that are running with hardware acceleration. This is also not available on MacOS. `GLCanvasElement` is a `FrameworkElement` for drawing 3D graphics with OpenGL. diff --git a/doc/articles/controls/SKCanvasElement.md b/doc/articles/controls/SKCanvasElement.md index 70afb357d7d3..dd4d15fd21f8 100644 --- a/doc/articles/controls/SKCanvasElement.md +++ b/doc/articles/controls/SKCanvasElement.md @@ -9,7 +9,7 @@ When creating an Uno Platform application, developers might want to create elabo 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, no additional packages are needed, and setup for hardware acceleration is not needed if the Uno application is already using OpenGL to render. Moreover, this is faster than `SKXamlCanvas`, which has to make additional buffer copying. > [!IMPORTANT] -> This functionality is only available on Skia Desktop (`net8.0-desktop`) targets. +> This functionality is only available on Skia Desktop (`netX.0-desktop`) targets. ## SKCanvasElement From c681a8b5f5a2b7b3c4afdfdf17a36b9bf29be943 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Fri, 9 Aug 2024 00:15:18 +0300 Subject: [PATCH 62/94] chore: build errors --- doc/articles/controls/GLCanvasElement.md | 6 +++-- doc/articles/controls/SKCanvasElement.md | 20 +++++++++-------- .../GLCanvasElement_Cube.xaml | 6 +++-- .../GLCanvasElement_Simple.xaml | 6 +++-- .../RotatingCubeGlCanvasElement.skia.cs | 2 ++ .../SKCanvasElementImpl.skia.cs | 2 ++ .../SKCanvasElement_Simple.xaml | 20 +++++++++-------- .../SimpleTriangleGlCanvasElement.skia.cs | 2 ++ .../UI/Xaml/Graphics/GLCanvasElement.skia.cs | 22 +++++++++---------- 9 files changed, 51 insertions(+), 35 deletions(-) diff --git a/doc/articles/controls/GLCanvasElement.md b/doc/articles/controls/GLCanvasElement.md index 9ca9aeff2559..95ce47780ecb 100644 --- a/doc/articles/controls/GLCanvasElement.md +++ b/doc/articles/controls/GLCanvasElement.md @@ -77,8 +77,10 @@ XAML: d:DesignHeight="300" d:DesignWidth="400"> - - + + + + ``` diff --git a/doc/articles/controls/SKCanvasElement.md b/doc/articles/controls/SKCanvasElement.md index dd4d15fd21f8..a09f95499f00 100644 --- a/doc/articles/controls/SKCanvasElement.md +++ b/doc/articles/controls/SKCanvasElement.md @@ -47,15 +47,17 @@ XAML: d:DesignHeight="300" d:DesignWidth="400"> - - - - - - - - - + + + + + + + + + + + ``` diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Cube.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Cube.xaml index ff704f05689f..c0b57fdbecd8 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Cube.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Cube.xaml @@ -10,6 +10,8 @@ d:DesignHeight="300" d:DesignWidth="400"> - - + + + + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml index 4de8bee29112..d7746386788b 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml @@ -10,6 +10,8 @@ d:DesignHeight="300" d:DesignWidth="400"> - - + + + + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs index b2b4f84dc040..f9e6f725d371 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs @@ -11,6 +11,7 @@ // https://github.com/dotnet/Silk.NET/tree/c27224cce6b8136224c01d40de2d608879d709b5/examples/CSharp/OpenGL%20Tutorials +#if __SKIA__ using System; using System.Diagnostics; using System.Numerics; @@ -284,3 +285,4 @@ public unsafe void VertexAttributePointer(uint index, int count, VertexAttribPoi } } } +#endif diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElementImpl.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElementImpl.skia.cs index c4828f2b7b36..dd3c99d8c3f6 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElementImpl.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElementImpl.skia.cs @@ -1,3 +1,4 @@ +#if __SKIA__ using System; using Windows.Foundation; using Microsoft.UI.Xaml; @@ -132,3 +133,4 @@ SKPath Star() } } } +#endif diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml index dcb9d9652030..7f3874309498 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml @@ -10,13 +10,15 @@ d:DesignHeight="300" d:DesignWidth="400"> - - - - - - - - - + + + + + + + + + + + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs index 703fc9ba13e3..d0ff70a00393 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs @@ -1,3 +1,4 @@ +#if __SKIA__ using System; using System.Drawing; using Microsoft.UI.Xaml.Controls; @@ -105,3 +106,4 @@ protected override void RenderOverride(GL gl) } } } +#endif diff --git a/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs b/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs index 4b2e8c2a0d03..83b8a0482cb2 100644 --- a/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs +++ b/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs @@ -135,7 +135,7 @@ private protected override unsafe void OnLoaded() { throw new InvalidOperationException("Offscreen framebuffer is not complete"); } - + Init(_gl); } _gl.BindFramebuffer(GLEnum.Framebuffer, 0); @@ -163,16 +163,16 @@ private unsafe void Render() } private protected override void OnUnloaded() - { - Marshal.FreeHGlobal(_pixels); - - if (_gl is { }) - { - _gl.DeleteFramebuffer(_framebuffer); - _gl.DeleteTexture(_textureColorBuffer); - _gl.DeleteRenderbuffer(_renderBuffer); - } - } + { + Marshal.FreeHGlobal(_pixels); + + if (_gl is { }) + { + _gl.DeleteFramebuffer(_framebuffer); + _gl.DeleteTexture(_textureColorBuffer); + _gl.DeleteRenderbuffer(_renderBuffer); + } + } /// /// By default, uses all the given. Subclasses of From d881501747d77e0b1c90c848c4b2c07abab67308 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Fri, 9 Aug 2024 11:59:12 +0300 Subject: [PATCH 63/94] chore: formatting and build errors --- .../RotatingCubeGlCanvasElement.skia.cs | 38 +++--- .../SimpleTriangleGlCanvasElement.skia.cs | 110 ++++++++++-------- .../Given_SKCanvasElement.skia.cs | 8 +- .../UI/Xaml/Graphics/GLCanvasElement.skia.cs | 2 +- 4 files changed, 84 insertions(+), 74 deletions(-) diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs index f9e6f725d371..98312f1eb9fd 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs @@ -64,31 +64,33 @@ public class RotatingCubeGlCanvasElement() : GLCanvasElement(new Size(1200, 800) 0, 4, 5, }; - private const string VertexShaderSource = @" -#version 330 + private readonly string _vertexShaderSource = "#version 330" + Environment.NewLine + + """ -layout(location = 0) in vec3 pos; -layout(location = 1) in vec3 vertex_color; + layout(location = 0) in vec3 pos; + layout(location = 1) in vec3 vertex_color; -uniform mat4 transform; + uniform mat4 transform; -out vec3 color; + out vec3 color; -void main() { - gl_Position = transform * vec4(pos, 1.0); - color = vertex_color; -}"; + void main() { + gl_Position = transform * vec4(pos, 1.0); + color = vertex_color; + } + """; - private const string FragmentShaderSource = @" -#version 330 + private readonly string FragmentShaderSource = "#version 330" + Environment.NewLine + + """ -in vec3 color; + in vec3 color; -out vec4 frag_color; + out vec4 frag_color; -void main() { - frag_color = vec4(color, 1.0); -}"; + void main() { + frag_color = vec4(color, 1.0); + } + """; protected override void Init(GL Gl) { @@ -99,7 +101,7 @@ protected override void Init(GL Gl) _vao.VertexAttributePointer(0, 3, VertexAttribPointerType.Float, 6, 0); _vao.VertexAttributePointer(1, 3, VertexAttribPointerType.Float, 6, 3); - _shader = new Shader(Gl, VertexShaderSource, FragmentShaderSource); + _shader = new Shader(Gl, _vertexShaderSource, FragmentShaderSource); } protected override void OnDestroy(GL Gl) diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs index d0ff70a00393..f1ba88abd9e2 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs @@ -17,7 +17,7 @@ public class SimpleTriangleGlCanvasElement() : GLCanvasElement(new Size(1200, 80 unsafe protected override void Init(GL gl) { _vao = gl.GenVertexArray(); - gl.BindVertexArray(_vao); + gl.BindVertexArray(_vao); float[] vertices = { @@ -26,65 +26,73 @@ unsafe protected override void Init(GL gl) 0.0f, 0.5f, 0.0f // top }; - _vbo = gl.GenBuffer(); - gl.BindBuffer(BufferTargetARB.ArrayBuffer, _vbo); - gl.BufferData(BufferTargetARB.ArrayBuffer, new ReadOnlySpan(vertices), BufferUsageARB.StaticDraw); + _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); - const string vertexCode = @" -#version 330 core + var vertexCode = "#version 330" + Environment.NewLine + + """ -layout (location = 0) in vec3 aPosition; -out vec4 vertexColor; + 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); -}"; + void main() + { + gl_Position = vec4(aPosition, 1.0); + vertexColor = vec4(aPosition.x + 0.5, aPosition.y + 0.5, aPosition.z + 0.5, 1.0); + } + """; - const string fragmentCode = @" -#version 330 core + var fragmentCode = "#version 330" + Environment.NewLine + + """ -out vec4 out_color; -in vec4 vertexColor; + 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); + 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) diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs index 0b76f649f907..37a2930177d7 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs @@ -24,13 +24,13 @@ public async Task When_Clipped_Inside_ScrollViewer() var border = new Border { - BorderBrush = Colors.Green, + BorderBrush = Microsoft.UI.Colors.Green, Height = 400, Child = new ScrollViewer { VerticalAlignment = VerticalAlignment.Top, Height = 100, - Background = Colors.Red, + Background = Microsoft.UI.Colors.Red, Content = SUT } }; @@ -39,8 +39,8 @@ public async Task When_Clipped_Inside_ScrollViewer() var bitmap = await UITestHelper.ScreenShot(border); - ImageAssert.HasColorInRectangle(bitmap, new Rectangle(0, 0, 400, 300), Colors.Blue); - ImageAssert.DoesNotHaveColorInRectangle(bitmap, new Rectangle(0, 101, 400, 299), Colors.Blue); + ImageAssert.HasColorInRectangle(bitmap, new Rectangle(0, 0, 400, 300), Microsoft.UI.Colors.Blue); + ImageAssert.DoesNotHaveColorInRectangle(bitmap, new Rectangle(0, 101, 400, 299), Microsoft.UI.Colors.Blue); } private class BlueFillSKCanvasElement : SKCanvasElement diff --git a/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs b/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs index 83b8a0482cb2..9c61d8a37e03 100644 --- a/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs +++ b/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs @@ -152,7 +152,7 @@ private unsafe void Render() _gl!.BindFramebuffer(GLEnum.Framebuffer, _framebuffer); { - _gl.Viewport(new System.Drawing.Size((int)_width, (int)_height)); + _gl.Viewport(new global::System.Drawing.Size((int)_width, (int)_height)); RenderOverride(_gl); From 69e4a0d3b2ae90d6fab0e98614d956bc81941756 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 12 Aug 2024 12:44:26 +0300 Subject: [PATCH 64/94] chore: packaging fixes --- doc/articles/controls/GLCanvasElement.md | 16 +++++-- doc/articles/controls/SKCanvasElement.md | 4 +- .../GLCanvasElement_Simple.xaml | 2 +- .../SKCanvasElement_Simple.xaml | 2 +- .../SKCanvasElement_Simple.xaml.cs | 2 + .../SimpleTriangleGlCanvasElement.skia.cs | 2 +- .../Graphics/GLCanvasElement.crossruntime.cs | 46 +++++++++++++++++++ .../Graphics/SKCanvasElement.crossruntime.cs | 10 ++++ src/Uno.UI/Uno.UI.Reference.csproj | 5 ++ src/Uno.UI/Uno.UI.Wasm.csproj | 3 ++ 10 files changed, 83 insertions(+), 9 deletions(-) diff --git a/doc/articles/controls/GLCanvasElement.md b/doc/articles/controls/GLCanvasElement.md index 95ce47780ecb..b6ea5cd30093 100644 --- a/doc/articles/controls/GLCanvasElement.md +++ b/doc/articles/controls/GLCanvasElement.md @@ -71,9 +71,9 @@ XAML: xmlns:local="using:BlankApp" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:skia="http://uno.ui/skia" + xmlns:skia="http://uno.ui/skia#using:BlankApp" xmlns:not_skia="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - mc:Ignorable="d" + mc:Ignorable="d skia not_skia" d:DesignHeight="300" d:DesignWidth="400"> @@ -152,16 +152,20 @@ out_color = vertexColor; gl.CompileShader(vertexShader); gl.GetShader(vertexShader, ShaderParameterName.CompileStatus, out int vStatus); - if (vStatus != (int) GLEnum.True) + 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) + if (fStatus != (int)GLEnum.True) + { throw new Exception("Fragment shader failed to compile: " + gl.GetShaderInfoLog(fragmentShader)); + } _program = gl.CreateProgram(); gl.AttachShader(_program, vertexShader); @@ -169,8 +173,10 @@ out_color = vertexColor; gl.LinkProgram(_program); gl.GetProgram(_program, ProgramPropertyARB.LinkStatus, out int lStatus); - if (lStatus != (int) GLEnum.True) + if (lStatus != (int)GLEnum.True) + { throw new Exception("Program failed to link: " + gl.GetProgramInfoLog(_program)); + } gl.DetachShader(_program, vertexShader); gl.DetachShader(_program, fragmentShader); diff --git a/doc/articles/controls/SKCanvasElement.md b/doc/articles/controls/SKCanvasElement.md index a09f95499f00..ca6e70428f6a 100644 --- a/doc/articles/controls/SKCanvasElement.md +++ b/doc/articles/controls/SKCanvasElement.md @@ -43,7 +43,7 @@ XAML: xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:skia="http://uno.ui/skia" xmlns:not_skia="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - mc:Ignorable="d" + mc:Ignorable="d skia not_skia" d:DesignHeight="300" d:DesignWidth="400"> @@ -67,7 +67,9 @@ Code-behind: // SKCanvasElementExample.xaml.cs public partial class SKCanvasElementExample : UserControl { +#if __SKIA__ public int MaxSampleIndex => SKCanvasElementImpl.SampleCount - 1; +#endif public SKCanvasElementExample() { diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml index d7746386788b..5af3012e24b7 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml @@ -6,7 +6,7 @@ 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" - mc:Ignorable="d skia" + mc:Ignorable="d skia not_skia" d:DesignHeight="300" d:DesignWidth="400"> diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml index 7f3874309498..399355450112 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml @@ -6,7 +6,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:skia="http://uno.ui/skia" xmlns:not_skia="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - mc:Ignorable="d" + mc:Ignorable="d skia not_skia" d:DesignHeight="300" d:DesignWidth="400"> diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml.cs index bcab2c3980eb..f6d3d39688d8 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml.cs @@ -6,7 +6,9 @@ namespace UITests.Shared.Windows_UI_Composition [Sample("Microsoft.UI.Composition")] public sealed partial class SKCanvasElement_Simple : UserControl { +#if __SKIA__ public int MaxSampleIndex => SKCanvasElementImpl.SampleCount - 1; +#endif public SKCanvasElement_Simple() { diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs index f1ba88abd9e2..e981d381351b 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs @@ -43,7 +43,7 @@ void main() { gl_Position = vec4(aPosition, 1.0); vertexColor = vec4(aPosition.x + 0.5, aPosition.y + 0.5, aPosition.z + 0.5, 1.0); - } + } """; var fragmentCode = "#version 330" + Environment.NewLine + diff --git a/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.crossruntime.cs b/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.crossruntime.cs index 89ab6b0d342d..782462e5680f 100644 --- a/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.crossruntime.cs +++ b/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.crossruntime.cs @@ -1,6 +1,7 @@ #if !__SKIA__ using System; using Windows.Foundation; +using Silk.NET.OpenGL; namespace Microsoft.UI.Xaml.Controls; @@ -10,5 +11,50 @@ protected GLCanvasElement(Size resolution) { throw new PlatformNotSupportedException($"${nameof(GLCanvasElement)} is only available on skia targets."); } + + /// + /// 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); + + /// + /// Invalidates the rendering, and calls in the next rendering cycle. + /// 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. + /// + public void Invalidate() { } + + protected override Size MeasureOverride(Size availableSize) => availableSize; + + protected override Size ArrangeOverride(Size finalSize) => finalSize; } #endif diff --git a/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.crossruntime.cs b/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.crossruntime.cs index dd10739ffde4..67247f69d27c 100644 --- a/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.crossruntime.cs +++ b/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.crossruntime.cs @@ -1,5 +1,7 @@ #if !__SKIA__ using System; +using Windows.Foundation; +using SkiaSharp; namespace Microsoft.UI.Xaml.Controls; @@ -9,5 +11,13 @@ protected SKCanvasElement() { throw new PlatformNotSupportedException($"${nameof(SKCanvasElement)} is only available on skia targets."); } + + public void Invalidate() { } + + protected abstract void RenderOverride(SKCanvas canvas, Size area); + + protected override Size MeasureOverride(Size availableSize) => availableSize; + + protected override Size ArrangeOverride(Size finalSize) => finalSize; } #endif diff --git a/src/Uno.UI/Uno.UI.Reference.csproj b/src/Uno.UI/Uno.UI.Reference.csproj index 2cdaf4c997dd..517e50eaaf9d 100644 --- a/src/Uno.UI/Uno.UI.Reference.csproj +++ b/src/Uno.UI/Uno.UI.Reference.csproj @@ -79,6 +79,11 @@ + + + + + $(AssemblyName).xml diff --git a/src/Uno.UI/Uno.UI.Wasm.csproj b/src/Uno.UI/Uno.UI.Wasm.csproj index 6c1261ccae5a..eebf67292823 100644 --- a/src/Uno.UI/Uno.UI.Wasm.csproj +++ b/src/Uno.UI/Uno.UI.Wasm.csproj @@ -62,6 +62,9 @@ + + + From f0842f386f47b010b7d6dfe421270dd64c858cc2 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 26 Aug 2024 22:12:16 +0300 Subject: [PATCH 65/94] chore: remove not_skia to mc:Ignorable --- .../Windows_UI_Composition/GLCanvasElement_Simple.xaml | 2 +- .../Windows_UI_Composition/SKCanvasElement_Simple.xaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml index 5af3012e24b7..d7746386788b 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml @@ -6,7 +6,7 @@ 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" - mc:Ignorable="d skia not_skia" + mc:Ignorable="d skia" d:DesignHeight="300" d:DesignWidth="400"> diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml index 399355450112..904ef6496384 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml @@ -6,7 +6,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:skia="http://uno.ui/skia" xmlns:not_skia="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - mc:Ignorable="d skia not_skia" + mc:Ignorable="d skia" d:DesignHeight="300" d:DesignWidth="400"> From 50ed2f42b03486e6f8e7f27c52ccfa30fba776e6 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 27 Aug 2024 18:14:04 +0300 Subject: [PATCH 66/94] chore: fix docs line about SKCanvasElement hardware acceleration --- doc/articles/controls/SKCanvasElement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/articles/controls/SKCanvasElement.md b/doc/articles/controls/SKCanvasElement.md index ca6e70428f6a..0c732c21e404 100644 --- a/doc/articles/controls/SKCanvasElement.md +++ b/doc/articles/controls/SKCanvasElement.md @@ -6,7 +6,7 @@ uid: Uno.Controls.SKCanvasElement 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, no additional packages are needed, and setup for hardware acceleration is not needed if the Uno application is already using OpenGL to render. Moreover, this is faster than `SKXamlCanvas`, which has to make additional buffer copying. +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. From d5b4f4ebf249fada8984a95f759a561351d6d90e Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 27 Aug 2024 19:50:56 +0300 Subject: [PATCH 67/94] chore: move GLCanvasElement to a separate package --- .../GLCanvasElement.GLVisual.cs} | 8 ++- .../Uno.WinUI.Graphics/GLCanvasElement.cs} | 12 ++-- .../Uno.WinUI.Graphics.csproj | 64 +++++++++++++++++++ .../SamplesApp.Skia/SamplesApp.Skia.csproj | 12 +++- .../RotatingCubeGlCanvasElement.skia.cs | 1 + .../SimpleTriangleGlCanvasElement.skia.cs | 1 + src/Uno.UI-Skia-only.slnf | 1 + src/Uno.UI.Composition/AssemblyInfo.cs | 2 + .../X11ApplicationHost.cs | 2 +- src/Uno.UI.sln | 45 +++++++++++++ src/Uno.UI/AssemblyInfo.cs | 2 + src/Uno.UI/Graphics/GLGetProcAddress.skia.cs | 6 ++ .../Graphics/GLCanvasElement.crossruntime.cs | 60 ----------------- 13 files changed, 146 insertions(+), 70 deletions(-) rename src/{Uno.UI/UI/Xaml/Graphics/GLCanvasElement.GLVisual.skia.cs => AddIns/Uno.WinUI.Graphics/GLCanvasElement.GLVisual.cs} (95%) rename src/{Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs => AddIns/Uno.WinUI.Graphics/GLCanvasElement.cs} (97%) create mode 100644 src/AddIns/Uno.WinUI.Graphics/Uno.WinUI.Graphics.csproj create mode 100644 src/Uno.UI/Graphics/GLGetProcAddress.skia.cs delete mode 100644 src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.crossruntime.cs diff --git a/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.GLVisual.skia.cs b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.GLVisual.cs similarity index 95% rename from src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.GLVisual.skia.cs rename to src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.GLVisual.cs index c32d726c0d94..89f6b036fcf9 100644 --- a/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.GLVisual.skia.cs +++ b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.GLVisual.cs @@ -1,8 +1,10 @@ -#nullable enable +#if !WINAPPSDK + using Microsoft.UI.Composition; +using Microsoft.UI.Xaml; using SkiaSharp; -namespace Microsoft.UI.Xaml.Controls; +namespace Uno.WinUI.Graphics; public abstract partial class GLCanvasElement { @@ -48,3 +50,5 @@ private protected override void DisposeInternal() } } } + +#endif diff --git a/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.cs similarity index 97% rename from src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs rename to src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.cs index 9c61d8a37e03..038eafcbbc88 100644 --- a/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.skia.cs +++ b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.cs @@ -1,14 +1,15 @@ -#nullable enable +#if !WINAPPSDK using System; using System.Diagnostics; using System.Runtime.InteropServices; using Windows.Foundation; +using Microsoft.UI.Xaml; using Silk.NET.Core.Contexts; using Silk.NET.OpenGL; using Uno.Foundation.Extensibility; -namespace Microsoft.UI.Xaml.Controls; +namespace Uno.WinUI.Graphics; /// /// A that exposes the ability to draw 3D graphics using OpenGL and Silk.NET. @@ -19,8 +20,6 @@ namespace Microsoft.UI.Xaml.Controls; /// public abstract partial class GLCanvasElement : FrameworkElement { - internal delegate IntPtr GLGetProcAddress(string proc); - private const int BytesPerPixel = 4; private readonly uint _width; @@ -98,7 +97,7 @@ private protected override unsafe void OnLoaded() { _gl = GL.GetApi(nativeContext); } - else if (ApiExtensibility.CreateInstance(this, out var getProcAddress)) + else if (ApiExtensibility.CreateInstance(this, out var getProcAddress)) { _gl = GL.GetApi(getProcAddress.Invoke); } @@ -203,7 +202,7 @@ protected override Size ArrangeOverride(Size finalSize) double.IsNaN(finalSize.Width) || double.IsNaN(finalSize.Height)) { - throw new ArgumentException($"{nameof(SKCanvasElement)} cannot be arranged with infinite or NaN values, but received finalSize={finalSize}."); + throw new ArgumentException($"{nameof(GLCanvasElement)} cannot be arranged with infinite or NaN values, but received finalSize={finalSize}."); } return finalSize; } @@ -254,3 +253,4 @@ public void Dispose() } } } +#endif diff --git a/src/AddIns/Uno.WinUI.Graphics/Uno.WinUI.Graphics.csproj b/src/AddIns/Uno.WinUI.Graphics/Uno.WinUI.Graphics.csproj new file mode 100644 index 000000000000..020afce227ee --- /dev/null +++ b/src/AddIns/Uno.WinUI.Graphics/Uno.WinUI.Graphics.csproj @@ -0,0 +1,64 @@ + + + + $(NetSkiaPreviousAndCurrent); + $(NetPrevious)-windows10.0.19041.0 + + + enable + + Uno.WinUI.GLCanvasElement + + true + + + + + + + + + + + true + + + + + + + + + + + + + $(DefineConstants);WINAPPSDK + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj b/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj index 8718552a5720..48b183000ba0 100644 --- a/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj +++ b/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj @@ -32,7 +32,7 @@ - + @@ -80,4 +80,14 @@ + + + + + + + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs index 98312f1eb9fd..57342f25ee98 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs @@ -18,6 +18,7 @@ using Microsoft.UI.Xaml.Controls; using Size = Windows.Foundation.Size; using Silk.NET.OpenGL; +using Uno.WinUI.Graphics; namespace UITests.Shared.Windows_UI_Composition { diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs index e981d381351b..592601e83e00 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs @@ -4,6 +4,7 @@ using Microsoft.UI.Xaml.Controls; using Size = Windows.Foundation.Size; using Silk.NET.OpenGL; +using Uno.WinUI.Graphics; namespace UITests.Shared.Windows_UI_Composition { diff --git a/src/Uno.UI-Skia-only.slnf b/src/Uno.UI-Skia-only.slnf index b2164fbda27b..edf15b7c2e48 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.Graphics\\Uno.WinUI.Graphics.csproj", "SamplesApp\\Benchmarks.Shared\\SamplesApp.Benchmarks.shproj", "SamplesApp\\SamplesApp.Shared\\SamplesApp.Shared.shproj", "SamplesApp\\SamplesApp.Skia.Generic\\SamplesApp.Skia.Generic.csproj", diff --git a/src/Uno.UI.Composition/AssemblyInfo.cs b/src/Uno.UI.Composition/AssemblyInfo.cs index 8d471f7539da..1958a93eb34a 100644 --- a/src/Uno.UI.Composition/AssemblyInfo.cs +++ b/src/Uno.UI.Composition/AssemblyInfo.cs @@ -16,6 +16,8 @@ [assembly: InternalsVisibleTo("SamplesApp.Wasm")] [assembly: InternalsVisibleTo("SamplesApp.Skia")] +[assembly: InternalsVisibleTo("Uno.WinUI.Graphics")] + [assembly: InternalsVisibleTo("UnoIslandsSamplesApp")] [assembly: InternalsVisibleTo("UnoIslandsSamplesApp.Skia")] [assembly: System.Reflection.AssemblyMetadata("IsTrimmable", "True")] diff --git a/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs b/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs index 16be427aeead..79e80ca17999 100644 --- a/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs +++ b/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs @@ -67,7 +67,7 @@ static X11ApplicationHost() ApiExtensibility.Register(typeof(Windows.ApplicationModel.DataTransfer.DragDrop.Core.IDragDropExtension), o => new X11DragDropExtension(o)); - ApiExtensibility.Register(typeof(GLCanvasElement.GLGetProcAddress), _ => new GLCanvasElement.GLGetProcAddress(GlxInterface.glXGetProcAddress)); + ApiExtensibility.Register(typeof(Uno.Graphics.GLGetProcAddress), _ => new Uno.Graphics.GLGetProcAddress(GlxInterface.glXGetProcAddress)); } public X11ApplicationHost(Func appBuilder) diff --git a/src/Uno.UI.sln b/src/Uno.UI.sln index e3de420f83d5..773dd1078ba2 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.Graphics", "AddIns\Uno.WinUI.Graphics\Uno.WinUI.Graphics.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..6385bf50077c 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.Graphics")] + [assembly: AssemblyMetadata("IsTrimmable", "True")] [assembly: System.Reflection.Metadata.MetadataUpdateHandler(typeof(Uno.UI.RuntimeTypeMetadataUpdateHandler))] diff --git a/src/Uno.UI/Graphics/GLGetProcAddress.skia.cs b/src/Uno.UI/Graphics/GLGetProcAddress.skia.cs new file mode 100644 index 000000000000..67c33e058f9d --- /dev/null +++ b/src/Uno.UI/Graphics/GLGetProcAddress.skia.cs @@ -0,0 +1,6 @@ +using System; + +namespace Uno.Graphics +{ + internal delegate IntPtr GLGetProcAddress(string proc); +} diff --git a/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.crossruntime.cs b/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.crossruntime.cs deleted file mode 100644 index 782462e5680f..000000000000 --- a/src/Uno.UI/UI/Xaml/Graphics/GLCanvasElement.crossruntime.cs +++ /dev/null @@ -1,60 +0,0 @@ -#if !__SKIA__ -using System; -using Windows.Foundation; -using Silk.NET.OpenGL; - -namespace Microsoft.UI.Xaml.Controls; - -public abstract partial class GLCanvasElement : FrameworkElement -{ - protected GLCanvasElement(Size resolution) - { - throw new PlatformNotSupportedException($"${nameof(GLCanvasElement)} is only available on skia targets."); - } - - /// - /// 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); - - /// - /// Invalidates the rendering, and calls in the next rendering cycle. - /// 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. - /// - public void Invalidate() { } - - protected override Size MeasureOverride(Size availableSize) => availableSize; - - protected override Size ArrangeOverride(Size finalSize) => finalSize; -} -#endif From 496053124e37228ecae0613b315b7b093ae86ad8 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 28 Aug 2024 00:03:34 +0300 Subject: [PATCH 68/94] chore: implement GLCanvasElement for WinUI --- .../GLCanvasElement.GLVisual.cs | 2 +- .../GLCanvasElement.WinUI.cs | 376 ++++++++++++++++++ .../Uno.WinUI.Graphics/GLCanvasElement.cs | 110 +---- .../GLCanvasElement.shared.cs | 137 +++++++ .../Uno.WinUI.Graphics.csproj | 26 +- .../SamplesApp.Windows.csproj | 2 + .../UITests.Shared/UITests.Shared.projitems | 4 +- .../GLCanvasElement_Cube.xaml | 11 +- .../GLCanvasElement_Simple.xaml | 11 +- ...skia.cs => RotatingCubeGlCanvasElement.cs} | 8 +- ...ia.cs => SimpleTriangleGlCanvasElement.cs} | 23 +- src/Uno.UI-Windows-only.slnf | 1 + 12 files changed, 575 insertions(+), 136 deletions(-) create mode 100644 src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs create mode 100644 src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.shared.cs rename src/SamplesApp/UITests.Shared/Windows_UI_Composition/{RotatingCubeGlCanvasElement.skia.cs => RotatingCubeGlCanvasElement.cs} (97%) rename src/SamplesApp/UITests.Shared/Windows_UI_Composition/{SimpleTriangleGlCanvasElement.skia.cs => SimpleTriangleGlCanvasElement.cs} (85%) diff --git a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.GLVisual.cs b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.GLVisual.cs index 89f6b036fcf9..c534ae44ed4f 100644 --- a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.GLVisual.cs +++ b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.GLVisual.cs @@ -37,7 +37,7 @@ internal override void Paint(in PaintingSession session) _owner.Render(); } // opengl coordinates go bottom-up, so we concat a matrix to flip horizontally and vertically - var flip = new SKMatrix(scaleX: -1, scaleY: -1, skewX: 0, skewY: 0, transX: _owner.Visual.Size.X, transY: _owner.Visual.Size.Y, persp0: 0, persp1: 0, persp2: 1); + var flip = new SKMatrix(scaleX: 1, scaleY: -1, skewX: 0, skewY: 0, transX: _owner.Visual.Size.X, transY: _owner.Visual.Size.Y, persp0: 0, persp1: 0, persp2: 1); session.Canvas.Concat(ref flip); session.Canvas.DrawImage(SKImage.FromPixels(_pixmap), new SKRect(0, 0, _owner.Visual.Size.X, _owner.Visual.Size.Y)); session.Canvas.Restore(); diff --git a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs new file mode 100644 index 000000000000..91b79f06e645 --- /dev/null +++ b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs @@ -0,0 +1,376 @@ +#if WINAPPSDK + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Foundation; +using Microsoft.Extensions.Logging; +using Microsoft.UI.Dispatching; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Media.Imaging; +using Silk.NET.Core.Contexts; +using Silk.NET.Core.Loader; +using Silk.NET.OpenGL; +using Uno.Extensions; +using Uno.Logging; +using InvalidOperationException = System.InvalidOperationException; + +namespace Uno.WinUI.Graphics; + +/// +/// A that exposes the ability to draw 3D graphics using OpenGL and Silk.NET. +/// +/// +/// This is only available on skia-based targets and when running with hardware acceleration. +/// This is currently only available on the WPF and X11 targets. +/// +public abstract partial class GLCanvasElement +{ + private const int BytesPerPixel = 4; + + private readonly uint _width; + private readonly uint _height; + + private readonly Window _window; + private readonly WriteableBitmap _backBuffer; + private readonly Image _image; + private readonly ScaleTransform _scaleTransform; + + private nint _hwnd; + private nint _hdc; + private int _pixelFormat; + private nint _glContext; + + // These are valid if and only if IsLoaded + private GL? _gl; + private uint _framebuffer; + private uint _textureColorBuffer; + private uint _renderBuffer; + private IntPtr _pixels; + + /// The width of the backing framebuffer. + /// The height of the backing framebuffer. + /// The window that this element will belong to. + protected GLCanvasElement(uint width, uint height, Window window) + { + _width = width; + _height = height; + _window = window; + + _backBuffer = new WriteableBitmap((int)width, (int)height); + + _scaleTransform = new ScaleTransform { ScaleX = 1, ScaleY = -1 }; // because OpenGL coordinates go bottom-to-top + _image = new Image + { + Source = _backBuffer, + RenderTransform = _scaleTransform + }; + Content = _image; + + Loaded += OnLoaded; + Unloaded += OnUnloaded; + SizeChanged += OnSizeChanged; + } + + private unsafe void OnLoaded(object sender, RoutedEventArgs e) + { + SetupOpenGlContext(); + Debug.Assert(_gl is not null); + + _pixels = Marshal.AllocHGlobal((int)(_width * _height * BytesPerPixel)); + + using (new GLStateDisposable(_gl, _hdc, _glContext)) + { + + _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 e) + { + Debug.Assert(_gl is not null); // because OnLoaded creates _gl + + Marshal.FreeHGlobal(_pixels); + + using (var _ = new GLStateDisposable(_gl, _hdc, _glContext)) + { + OnDestroy(_gl); + _gl.DeleteFramebuffer(_framebuffer); + _gl.DeleteTexture(_textureColorBuffer); + _gl.DeleteRenderbuffer(_renderBuffer); + _gl.Dispose(); + } + NativeMethods.wglDeleteContext(_glContext); + + _gl = default; + _framebuffer = default; + _textureColorBuffer = default; + _renderBuffer = default; + _pixels = default; + _glContext = default; + } + + private void SetupOpenGlContext() + { + _hwnd = WinRT.Interop.WindowNative.GetWindowHandle(_window); + + _hdc = NativeMethods.GetDC(_hwnd); + + NativeMethods.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.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 = NativeMethods.PFD_MAIN_PLANE; + + _pixelFormat = NativeMethods.ChoosePixelFormat(_hdc, ref pfd); + + // To inspect the chosen pixel format: + // NativeMethods.PIXELFORMATDESCRIPTOR temp_pfd = default; + // NativeMethods.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 (NativeMethods.SetPixelFormat(_hdc, _pixelFormat, ref pfd) == 0) + { + if (this.Log().IsEnabled(LogLevel.Error)) + { + this.Log().Error($"SetPixelFormat failed"); + } + throw new InvalidOperationException("ChoosePixelFormat failed"); + } + + _glContext = NativeMethods.wglCreateContext(_hdc); + + if (_glContext == IntPtr.Zero) + { + if (this.Log().IsEnabled(LogLevel.Error)) + { + this.Log().Error($"wglCreateContext failed"); + } + throw new InvalidOperationException("ChoosePixelFormat failed"); + } + + _gl = GL.GetApi(new WinUINativeContext()); + } + + public partial void Invalidate() => DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, Render); + + private unsafe void Render() + { + if (!IsLoaded) + { + return; + } + + Debug.Assert(_gl is not null); // because _gl exists if loaded + + using var _ = new GLStateDisposable(_gl, _hdc, _glContext); + + _gl!.BindFramebuffer(GLEnum.Framebuffer, _framebuffer); + { + _gl.Viewport(new global::System.Drawing.Size((int)_width, (int)_height)); + + RenderOverride(_gl); + + // Can we do without this copy? + _gl.ReadBuffer(GLEnum.ColorAttachment0); + _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))); + } + _backBuffer.Invalidate(); + } + } + + protected override partial Size MeasureOverride(Size availableSize) + { + if (availableSize.Width == Double.PositiveInfinity || + availableSize.Height == Double.PositiveInfinity || + double.IsNaN(availableSize.Width) || + double.IsNaN(availableSize.Height)) + { + throw new ArgumentException($"{nameof(GLCanvasElement)} cannot be measured with infinite or NaN values, but received availableSize={availableSize}."); + } + return availableSize; + } + + protected override partial Size ArrangeOverride(Size finalSize) + { + if (finalSize.Width == Double.PositiveInfinity || + finalSize.Height == Double.PositiveInfinity || + double.IsNaN(finalSize.Width) || + double.IsNaN(finalSize.Height)) + { + throw new ArgumentException($"{nameof(GLCanvasElement)} cannot be arranged with infinite or NaN values, but received finalSize={finalSize}."); + } + _image.Arrange(new Rect(new Point(), finalSize)); + return finalSize; + } + + private void OnSizeChanged(object sender, SizeChangedEventArgs args) + { + _scaleTransform.CenterY = args.NewSize.Height / 2; + } + + // https://sharovarskyi.com/blog/posts/csharp-win32-opengl-silknet/ + private class WinUINativeContext : INativeContext + { + private readonly UnmanagedLibrary _l; + + public WinUINativeContext() + { + _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 = NativeMethods.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(); + } + + private static class NativeMethods + { + [DllImport("user32.dll")] + internal static extern IntPtr GetDC(IntPtr hWnd); + + [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 int wglDeleteContext(IntPtr hglrc); + + [DllImport("opengl32.dll")] + internal static extern int wglMakeCurrent(IntPtr hdc, IntPtr hglrc); + + [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; + } +} + +#endif diff --git a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.cs b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.cs index 038eafcbbc88..593bbf3c7cba 100644 --- a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.cs +++ b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.cs @@ -34,56 +34,18 @@ public abstract partial class GLCanvasElement : FrameworkElement private uint _textureColorBuffer; private uint _renderBuffer; - /// The resolution of the backing framebuffer. - protected GLCanvasElement(Size resolution) + /// The width of the backing framebuffer. + /// The height of the backing framebuffer. + protected GLCanvasElement(uint width, uint height, Window window) { - _width = (uint)resolution.Width; - _height = (uint)resolution.Height; + _width = width; + _height = height; _glVisual = new GLVisual(this, Visual.Compositor); Visual.Children.InsertAtTop(_glVisual); } - /// - /// 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); - - /// - /// Invalidates the rendering, and calls in the next rendering cycle. - /// 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. - /// - public void Invalidate() + public partial void Invalidate() { _renderDirty = true; _glVisual.Compositor.InvalidateRender(_glVisual); @@ -173,12 +135,7 @@ private protected override void OnUnloaded() } } - /// - /// By default, uses all the given. Subclasses of - /// should override this method if they need something different. - /// - /// An exception will be thrown if availableSize is infinite (e.g. if inside a StackPanel). - protected override Size MeasureOverride(Size availableSize) + protected override partial Size MeasureOverride(Size availableSize) { if (availableSize.Width == Double.PositiveInfinity || availableSize.Height == Double.PositiveInfinity || @@ -190,12 +147,7 @@ protected override Size MeasureOverride(Size availableSize) return availableSize; } - /// - /// By default, uses all the given. Subclasses of - /// should override this method if they need something different. - /// - /// An exception will be thrown if is infinite (e.g. if inside a StackPanel). - protected override Size ArrangeOverride(Size finalSize) + protected override partial Size ArrangeOverride(Size finalSize) { if (finalSize.Width == Double.PositiveInfinity || finalSize.Height == Double.PositiveInfinity || @@ -206,51 +158,5 @@ protected override Size ArrangeOverride(Size finalSize) } return finalSize; } - - private readonly struct GLStateDisposable : IDisposable - { - private readonly GL _gl; - private readonly int _oldArrayBuffer; - private readonly int _oldVertexArray; - private readonly int _oldFramebuffer; - private readonly int _oldTextureColorBuffer; - private readonly int _oldRbo; - private readonly bool _depthTestEnabled; - private readonly bool _depthTestMask; - private readonly int[] _oldViewport = new int[4]; - - public GLStateDisposable(GL gl) - { - _gl = gl; - - _depthTestEnabled = gl.GetBoolean(GLEnum.DepthTest); - _depthTestMask = gl.GetBoolean(GLEnum.DepthWritemask); - _oldArrayBuffer = gl.GetInteger(GLEnum.ArrayBufferBinding); - _oldVertexArray = gl.GetInteger(GLEnum.VertexArrayBinding); - _oldFramebuffer = gl.GetInteger(GLEnum.FramebufferBinding); - _oldTextureColorBuffer = gl.GetInteger(GLEnum.TextureBinding2D); - _oldRbo = gl.GetInteger(GLEnum.RenderbufferBinding); - gl.GetInteger(GLEnum.Viewport, new Span(_oldViewport)); - } - - public void Dispose() - { - _gl.BindVertexArray((uint)_oldVertexArray); - _gl.BindBuffer(BufferTargetARB.ArrayBuffer, (uint)_oldArrayBuffer); - _gl.BindFramebuffer(GLEnum.Framebuffer, (uint)_oldFramebuffer); - _gl.BindTexture(GLEnum.Texture2D, (uint)_oldTextureColorBuffer); - _gl.BindRenderbuffer(GLEnum.Renderbuffer, (uint)_oldRbo); - _gl.Viewport(_oldViewport[0], _oldViewport[1], (uint)_oldViewport[2], (uint)_oldViewport[3]); - _gl.DepthMask(_depthTestMask); - if (_depthTestEnabled) - { - _gl.Enable(EnableCap.DepthTest); - } - else - { - _gl.Disable(EnableCap.DepthTest); - } - } - } } #endif diff --git a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.shared.cs b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.shared.cs new file mode 100644 index 000000000000..0d13c8112756 --- /dev/null +++ b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.shared.cs @@ -0,0 +1,137 @@ +using System; +using Windows.Foundation; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Silk.NET.OpenGL; + +namespace Uno.WinUI.Graphics; + +/// +/// 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 : UserControl +{ + /// + /// 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); + + /// + /// Invalidates the rendering, and calls in the next rendering cycle. + /// 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. + /// + public partial void Invalidate(); + + /// + /// By default, uses all the given. Subclasses of + /// should override this method if they need something different. + /// + /// An exception will be thrown if availableSize is infinite (e.g. if inside a StackPanel). + protected override partial Size MeasureOverride(Size availableSize); + + /// + /// By default, uses all the given. Subclasses of + /// should override this method if they need something different. + /// + /// An exception will be thrown if is infinite (e.g. if inside a StackPanel). + protected override partial Size ArrangeOverride(Size finalSize); + + private readonly struct GLStateDisposable : IDisposable + { + private readonly GL _gl; + private readonly int _oldArrayBuffer; + private readonly int _oldVertexArray; + private readonly int _oldFramebuffer; + private readonly int _oldTextureColorBuffer; + private readonly int _oldRbo; + private readonly bool _depthTestEnabled; + private readonly bool _depthTestMask; + private readonly int[] _oldViewport = new int[4]; + +#if WINAPPSDK + private readonly IntPtr _dc; + private readonly IntPtr _glContext; +#endif + +#if WINAPPSDK + public GLStateDisposable(GL gl, IntPtr dc, IntPtr glContext) +#else + public GLStateDisposable(GL gl) +#endif + { + _gl = gl; + +#if WINAPPSDK + _glContext = NativeMethods.wglGetCurrentContext(); + _dc = NativeMethods.wglGetCurrentDC(); + NativeMethods.wglMakeCurrent(dc, glContext); +#endif + + _depthTestEnabled = gl.GetBoolean(GLEnum.DepthTest); + _depthTestMask = gl.GetBoolean(GLEnum.DepthWritemask); + _oldArrayBuffer = gl.GetInteger(GLEnum.ArrayBufferBinding); + _oldVertexArray = gl.GetInteger(GLEnum.VertexArrayBinding); + _oldFramebuffer = gl.GetInteger(GLEnum.FramebufferBinding); + _oldTextureColorBuffer = gl.GetInteger(GLEnum.TextureBinding2D); + _oldRbo = gl.GetInteger(GLEnum.RenderbufferBinding); + gl.GetInteger(GLEnum.Viewport, new Span(_oldViewport)); + } + + public void Dispose() + { + _gl.BindVertexArray((uint)_oldVertexArray); + _gl.BindBuffer(BufferTargetARB.ArrayBuffer, (uint)_oldArrayBuffer); + _gl.BindFramebuffer(GLEnum.Framebuffer, (uint)_oldFramebuffer); + _gl.BindTexture(GLEnum.Texture2D, (uint)_oldTextureColorBuffer); + _gl.BindRenderbuffer(GLEnum.Renderbuffer, (uint)_oldRbo); + _gl.Viewport(_oldViewport[0], _oldViewport[1], (uint)_oldViewport[2], (uint)_oldViewport[3]); + _gl.DepthMask(_depthTestMask); + if (_depthTestEnabled) + { + _gl.Enable(EnableCap.DepthTest); + } + else + { + _gl.Disable(EnableCap.DepthTest); + } + +#if WINAPPSDK + NativeMethods.wglMakeCurrent(_dc, _glContext); +#endif + } + } +} diff --git a/src/AddIns/Uno.WinUI.Graphics/Uno.WinUI.Graphics.csproj b/src/AddIns/Uno.WinUI.Graphics/Uno.WinUI.Graphics.csproj index 020afce227ee..6b1f877d4e74 100644 --- a/src/AddIns/Uno.WinUI.Graphics/Uno.WinUI.Graphics.csproj +++ b/src/AddIns/Uno.WinUI.Graphics/Uno.WinUI.Graphics.csproj @@ -4,45 +4,35 @@ $(NetSkiaPreviousAndCurrent); $(NetPrevious)-windows10.0.19041.0 - + enable - Uno.WinUI.GLCanvasElement - true - - - - - - - - - true - - - + + + win-x86;win-x64;win-arm64 $(DefineConstants);WINAPPSDK + true + None - + - @@ -55,7 +45,7 @@ diff --git a/src/SamplesApp/SamplesApp.Windows/SamplesApp.Windows.csproj b/src/SamplesApp/SamplesApp.Windows/SamplesApp.Windows.csproj index 3f42c3fbdb45..b8ff65412697 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 dbe7a048d4d1..368015d7831e 100644 --- a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems +++ b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems @@ -6039,8 +6039,8 @@ AutomationProperties_Name.xaml - - + + CloseRequestedTests.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 index c0b57fdbecd8..a15b34f026e0 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Cube.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Cube.xaml @@ -6,12 +6,19 @@ 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" - mc:Ignorable="d skia" + xmlns:win="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:not_win="http://uno.ui/not_win" + mc:Ignorable="d skia not_win" d:DesignHeight="300" d:DesignWidth="400"> - + + + + + + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml index d7746386788b..c8747740ffc2 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/GLCanvasElement_Simple.xaml @@ -6,12 +6,19 @@ 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" - mc:Ignorable="d skia" + xmlns:win="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:not_win="http://uno.ui/not_win" + mc:Ignorable="d skia not_win" d:DesignHeight="300" d:DesignWidth="400"> - + + + + + + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs similarity index 97% rename from src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs rename to src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs index 57342f25ee98..be36ffcecaaa 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs @@ -11,7 +11,7 @@ // https://github.com/dotnet/Silk.NET/tree/c27224cce6b8136224c01d40de2d608879d709b5/examples/CSharp/OpenGL%20Tutorials -#if __SKIA__ +#if __SKIA__ || WINAPPSDK using System; using System.Diagnostics; using System.Numerics; @@ -22,7 +22,11 @@ namespace UITests.Shared.Windows_UI_Composition { - public class RotatingCubeGlCanvasElement() : GLCanvasElement(new Size(1200, 800)) +#if WINAPPSDK + public class RotatingCubeGlCanvasElement() : GLCanvasElement(1200, 800, SamplesApp.App.MainWindow) +#elif __SKIA__ + public class RotatingCubeGlCanvasElement() : GLCanvasElement(1200, 800) +#endif { private static BufferObject _vbo; private static BufferObject _ebo; diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs similarity index 85% rename from src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs rename to src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs index 592601e83e00..505d1c502e93 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.skia.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs @@ -1,4 +1,4 @@ -#if __SKIA__ +#if __SKIA__ || WINAPPSDK using System; using System.Drawing; using Microsoft.UI.Xaml.Controls; @@ -9,7 +9,12 @@ namespace UITests.Shared.Windows_UI_Composition { // https://learnopengl.com/Getting-started/Hello-Triangle - public class SimpleTriangleGlCanvasElement() : GLCanvasElement(new Size(1200, 800)) + public class SimpleTriangleGlCanvasElement() +#if __SKIA__ + : GLCanvasElement(1200, 800) +#elif WINAPPSDK + : GLCanvasElement(1200, 800, SamplesApp.App.MainWindow) +#endif { private uint _vao; private uint _vbo; @@ -34,8 +39,10 @@ unsafe protected override void Init(GL gl) gl.VertexAttribPointer(0, 3, GLEnum.Float, false, 3 * sizeof(float), (void*)0); gl.EnableVertexAttribArray(0); - var vertexCode = "#version 330" + Environment.NewLine + - """ + // 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; @@ -47,8 +54,10 @@ void main() } """; - var fragmentCode = "#version 330" + Environment.NewLine + - """ + // 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; @@ -56,7 +65,7 @@ void main() void main() { out_color = vertexColor; - } + } """; uint vertexShader = gl.CreateShader(ShaderType.VertexShader); diff --git a/src/Uno.UI-Windows-only.slnf b/src/Uno.UI-Windows-only.slnf index 2cd31dd1e853..7b88489034a9 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.Graphics\\Uno.WinUI.Graphics.csproj", "SamplesApp\\Benchmarks.Shared\\SamplesApp.Benchmarks.shproj", "SamplesApp\\SamplesApp.Windows\\SamplesApp.Windows.csproj", "SamplesApp\\SamplesApp.Shared\\SamplesApp.Shared.shproj", From 5092a11022d18ade559950dbf8fb208084fe5053 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 28 Aug 2024 00:14:02 +0300 Subject: [PATCH 69/94] chore: avoid crash on closing window --- src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs | 8 ++++++++ .../Windows_UI_Composition/RotatingCubeGlCanvasElement.cs | 2 -- .../SimpleTriangleGlCanvasElement.cs | 2 -- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs index 91b79f06e645..f209f936b1cc 100644 --- a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs +++ b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs @@ -127,6 +127,14 @@ private void OnUnloaded(object sender, RoutedEventArgs e) using (var _ = new GLStateDisposable(_gl, _hdc, _glContext)) { + if (NativeMethods.wglMakeCurrent(_hdc, _glContext) != 1) + { + 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; + } OnDestroy(_gl); _gl.DeleteFramebuffer(_framebuffer); _gl.DeleteTexture(_textureColorBuffer); diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs index be36ffcecaaa..b618e5d3b8d5 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs @@ -15,8 +15,6 @@ using System; using System.Diagnostics; using System.Numerics; -using Microsoft.UI.Xaml.Controls; -using Size = Windows.Foundation.Size; using Silk.NET.OpenGL; using Uno.WinUI.Graphics; diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs index 505d1c502e93..8adeeafa76f6 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs @@ -1,8 +1,6 @@ #if __SKIA__ || WINAPPSDK using System; using System.Drawing; -using Microsoft.UI.Xaml.Controls; -using Size = Windows.Foundation.Size; using Silk.NET.OpenGL; using Uno.WinUI.Graphics; From 4f06a3025bfe8c5d2382c9ecab10234a97bdf057 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 28 Aug 2024 13:21:52 +0300 Subject: [PATCH 70/94] chore: move most logic to shared partial --- ...ual.cs => GLCanvasElement.GLVisual.uno.cs} | 2 +- .../GLCanvasElement.WinUI.cs | 142 +-------------- .../Uno.WinUI.Graphics/GLCanvasElement.cs | 162 ----------------- .../GLCanvasElement.shared.cs | 165 +++++++++++++++++- .../Uno.WinUI.Graphics/GLCanvasElement.uno.cs | 66 +++++++ 5 files changed, 233 insertions(+), 304 deletions(-) rename src/AddIns/Uno.WinUI.Graphics/{GLCanvasElement.GLVisual.cs => GLCanvasElement.GLVisual.uno.cs} (94%) delete mode 100644 src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.cs create mode 100644 src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.uno.cs diff --git a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.GLVisual.cs b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.GLVisual.uno.cs similarity index 94% rename from src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.GLVisual.cs rename to src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.GLVisual.uno.cs index c534ae44ed4f..ed70e74853e1 100644 --- a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.GLVisual.cs +++ b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.GLVisual.uno.cs @@ -37,7 +37,7 @@ internal override void Paint(in PaintingSession session) _owner.Render(); } // opengl coordinates go bottom-up, so we concat a matrix to flip horizontally and vertically - var flip = new SKMatrix(scaleX: 1, scaleY: -1, skewX: 0, skewY: 0, transX: _owner.Visual.Size.X, transY: _owner.Visual.Size.Y, persp0: 0, persp1: 0, persp2: 1); + var flip = new SKMatrix(scaleX: 1, scaleY: -1, skewX: 0, skewY: 0, transX: 0, transY: _owner.Visual.Size.Y, persp0: 0, persp1: 0, persp2: 1); session.Canvas.Concat(ref flip); session.Canvas.DrawImage(SKImage.FromPixels(_pixmap), new SKRect(0, 0, _owner.Visual.Size.X, _owner.Visual.Size.Y)); session.Canvas.Restore(); diff --git a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs index f209f936b1cc..f4510fe6cc31 100644 --- a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs +++ b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs @@ -3,8 +3,6 @@ using System; using System.Diagnostics; using System.Runtime.InteropServices; -using System.Runtime.InteropServices.WindowsRuntime; -using Windows.Foundation; using Microsoft.Extensions.Logging; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; @@ -16,7 +14,6 @@ using Silk.NET.OpenGL; using Uno.Extensions; using Uno.Logging; -using InvalidOperationException = System.InvalidOperationException; namespace Uno.WinUI.Graphics; @@ -29,10 +26,6 @@ namespace Uno.WinUI.Graphics; /// public abstract partial class GLCanvasElement { - private const int BytesPerPixel = 4; - - private readonly uint _width; - private readonly uint _height; private readonly Window _window; private readonly WriteableBitmap _backBuffer; @@ -44,13 +37,6 @@ public abstract partial class GLCanvasElement private int _pixelFormat; private nint _glContext; - // These are valid if and only if IsLoaded - private GL? _gl; - private uint _framebuffer; - private uint _textureColorBuffer; - private uint _renderBuffer; - private IntPtr _pixels; - /// The width of the backing framebuffer. /// The height of the backing framebuffer. /// The window that this element will belong to. @@ -75,81 +61,13 @@ protected GLCanvasElement(uint width, uint height, Window window) SizeChanged += OnSizeChanged; } - private unsafe void OnLoaded(object sender, RoutedEventArgs e) + private void OnLoaded(object sender, RoutedEventArgs e) { SetupOpenGlContext(); - Debug.Assert(_gl is not null); - - _pixels = Marshal.AllocHGlobal((int)(_width * _height * BytesPerPixel)); - - using (new GLStateDisposable(_gl, _hdc, _glContext)) - { - - _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(); + OnLoadedShared(); } - private void OnUnloaded(object sender, RoutedEventArgs e) - { - Debug.Assert(_gl is not null); // because OnLoaded creates _gl - - Marshal.FreeHGlobal(_pixels); - - using (var _ = new GLStateDisposable(_gl, _hdc, _glContext)) - { - if (NativeMethods.wglMakeCurrent(_hdc, _glContext) != 1) - { - 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; - } - OnDestroy(_gl); - _gl.DeleteFramebuffer(_framebuffer); - _gl.DeleteTexture(_textureColorBuffer); - _gl.DeleteRenderbuffer(_renderBuffer); - _gl.Dispose(); - } - NativeMethods.wglDeleteContext(_glContext); - - _gl = default; - _framebuffer = default; - _textureColorBuffer = default; - _renderBuffer = default; - _pixels = default; - _glContext = default; - } + private void OnUnloaded(object sender, RoutedEventArgs e) => OnUnloadedShared(); private void SetupOpenGlContext() { @@ -211,60 +129,6 @@ private void SetupOpenGlContext() public partial void Invalidate() => DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, Render); - private unsafe void Render() - { - if (!IsLoaded) - { - return; - } - - Debug.Assert(_gl is not null); // because _gl exists if loaded - - using var _ = new GLStateDisposable(_gl, _hdc, _glContext); - - _gl!.BindFramebuffer(GLEnum.Framebuffer, _framebuffer); - { - _gl.Viewport(new global::System.Drawing.Size((int)_width, (int)_height)); - - RenderOverride(_gl); - - // Can we do without this copy? - _gl.ReadBuffer(GLEnum.ColorAttachment0); - _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))); - } - _backBuffer.Invalidate(); - } - } - - protected override partial Size MeasureOverride(Size availableSize) - { - if (availableSize.Width == Double.PositiveInfinity || - availableSize.Height == Double.PositiveInfinity || - double.IsNaN(availableSize.Width) || - double.IsNaN(availableSize.Height)) - { - throw new ArgumentException($"{nameof(GLCanvasElement)} cannot be measured with infinite or NaN values, but received availableSize={availableSize}."); - } - return availableSize; - } - - protected override partial Size ArrangeOverride(Size finalSize) - { - if (finalSize.Width == Double.PositiveInfinity || - finalSize.Height == Double.PositiveInfinity || - double.IsNaN(finalSize.Width) || - double.IsNaN(finalSize.Height)) - { - throw new ArgumentException($"{nameof(GLCanvasElement)} cannot be arranged with infinite or NaN values, but received finalSize={finalSize}."); - } - _image.Arrange(new Rect(new Point(), finalSize)); - return finalSize; - } - private void OnSizeChanged(object sender, SizeChangedEventArgs args) { _scaleTransform.CenterY = args.NewSize.Height / 2; diff --git a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.cs b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.cs deleted file mode 100644 index 593bbf3c7cba..000000000000 --- a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.cs +++ /dev/null @@ -1,162 +0,0 @@ -#if !WINAPPSDK - -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; -using Windows.Foundation; -using Microsoft.UI.Xaml; -using Silk.NET.Core.Contexts; -using Silk.NET.OpenGL; -using Uno.Foundation.Extensibility; - -namespace Uno.WinUI.Graphics; - -/// -/// A that exposes the ability to draw 3D graphics using OpenGL and Silk.NET. -/// -/// -/// This is only available on skia-based targets and when running with hardware acceleration. -/// This is currently only available on the WPF and X11 targets. -/// -public abstract partial class GLCanvasElement : FrameworkElement -{ - private const int BytesPerPixel = 4; - - private readonly uint _width; - private readonly uint _height; - private readonly GLVisual _glVisual; - - private bool _renderDirty = true; - - private GL? _gl; - private IntPtr _pixels; - private uint _framebuffer; - private uint _textureColorBuffer; - private uint _renderBuffer; - - /// The width of the backing framebuffer. - /// The height of the backing framebuffer. - protected GLCanvasElement(uint width, uint height, Window window) - { - _width = width; - _height = height; - - _glVisual = new GLVisual(this, Visual.Compositor); - Visual.Children.InsertAtTop(_glVisual); - } - - public partial void Invalidate() - { - _renderDirty = true; - _glVisual.Compositor.InvalidateRender(_glVisual); - } - - private protected override unsafe void OnLoaded() - { - base.OnLoaded(); - - if (ApiExtensibility.CreateInstance(this, out var nativeContext)) - { - _gl = GL.GetApi(nativeContext); - } - else if (ApiExtensibility.CreateInstance(this, out var getProcAddress)) - { - _gl = GL.GetApi(getProcAddress.Invoke); - } - else - { - throw new InvalidOperationException($"Couldn't create a {nameof(GL)} object for {nameof(GLCanvasElement)}. Make sure you are running on a platform with {nameof(GLCanvasElement)} support."); - } - - using var _ = new GLStateDisposable(_gl); - - _pixels = Marshal.AllocHGlobal((int)(_width * _height * BytesPerPixel)); - _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 unsafe void Render() - { - Debug.Assert(_renderDirty); - _renderDirty = false; - - using var _ = new GLStateDisposable(_gl!); - - _gl!.BindFramebuffer(GLEnum.Framebuffer, _framebuffer); - { - _gl.Viewport(new global::System.Drawing.Size((int)_width, (int)_height)); - - RenderOverride(_gl); - - // Can we do without this copy? - _gl.ReadBuffer(GLEnum.ColorAttachment0); - _gl.ReadPixels(0, 0, _width, _height, GLEnum.Bgra, GLEnum.UnsignedByte, (void*)_pixels); - } - } - - private protected override void OnUnloaded() - { - Marshal.FreeHGlobal(_pixels); - - if (_gl is { }) - { - _gl.DeleteFramebuffer(_framebuffer); - _gl.DeleteTexture(_textureColorBuffer); - _gl.DeleteRenderbuffer(_renderBuffer); - } - } - - protected override partial Size MeasureOverride(Size availableSize) - { - if (availableSize.Width == Double.PositiveInfinity || - availableSize.Height == Double.PositiveInfinity || - double.IsNaN(availableSize.Width) || - double.IsNaN(availableSize.Height)) - { - throw new ArgumentException($"{nameof(GLCanvasElement)} cannot be measured with infinite or NaN values, but received availableSize={availableSize}."); - } - return availableSize; - } - - protected override partial Size ArrangeOverride(Size finalSize) - { - if (finalSize.Width == Double.PositiveInfinity || - finalSize.Height == Double.PositiveInfinity || - double.IsNaN(finalSize.Width) || - double.IsNaN(finalSize.Height)) - { - throw new ArgumentException($"{nameof(GLCanvasElement)} cannot be arranged with infinite or NaN values, but received finalSize={finalSize}."); - } - return finalSize; - } -} -#endif diff --git a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.shared.cs b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.shared.cs index 0d13c8112756..7866718965c9 100644 --- a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.shared.cs +++ b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.shared.cs @@ -1,9 +1,18 @@ using System; +using System.Diagnostics; +using System.Runtime.InteropServices; using Windows.Foundation; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Silk.NET.OpenGL; +#if WINAPPSDK +using System.Runtime.InteropServices.WindowsRuntime; +using Microsoft.Extensions.Logging; +using Uno.Extensions; +using Uno.Logging; +#endif + namespace Uno.WinUI.Graphics; /// @@ -15,6 +24,18 @@ namespace Uno.WinUI.Graphics; /// public abstract partial class GLCanvasElement : UserControl { + private const int BytesPerPixel = 4; + + private readonly uint _width; + private readonly uint _height; + + // These are valid if and only if IsLoaded + private GL? _gl; + private uint _framebuffer; + private uint _textureColorBuffer; + private uint _renderBuffer; + private IntPtr _pixels; + /// /// Use this function for the initial setup, e.g. setting up VAOs, VBOs, EBOs, etc. /// @@ -56,19 +77,159 @@ public abstract partial class GLCanvasElement : UserControl /// public partial void Invalidate(); + private GLStateDisposable CreateGlStateDisposable() +#if WINAPPSDK + => new GLStateDisposable(_gl!, _hdc, _glContext); +#else + => new GLStateDisposable(_gl!); +#endif + + private unsafe void OnLoadedShared() + { + Debug.Assert(_gl is not null); + + _pixels = Marshal.AllocHGlobal((int)(_width * _height * BytesPerPixel)); + + using (CreateGlStateDisposable()) + { + _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 OnUnloadedShared() + { + Debug.Assert(_gl is not null); // because OnLoaded creates _gl + + Marshal.FreeHGlobal(_pixels); + + using (CreateGlStateDisposable()) + { +#if WINAPPSDK + if (NativeMethods.wglMakeCurrent(_hdc, _glContext) != 1) + { + 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; + _pixels = default; + +#if WINAPPSDK + NativeMethods.wglDeleteContext(_glContext); + _glContext = default; +#endif + } + + private unsafe void Render() + { + if (!IsLoaded) + { + return; + } + + Debug.Assert(_gl is not null); // because _gl exists if loaded + + using var _ = CreateGlStateDisposable(); + + _gl!.BindFramebuffer(GLEnum.Framebuffer, _framebuffer); + { + _gl.Viewport(new global::System.Drawing.Size((int)_width, (int)_height)); + + RenderOverride(_gl); + + // Can we do without this copy? + _gl.ReadBuffer(GLEnum.ColorAttachment0); + _gl.ReadPixels(0, 0, _width, _height, GLEnum.Bgra, GLEnum.UnsignedByte, (void*)_pixels); + +#if WINAPPSDK + using (var stream = _backBuffer.PixelBuffer.AsStream()) + { + stream.Write(new ReadOnlySpan((void*)_pixels, (int)(_width * _height * BytesPerPixel))); + } + _backBuffer.Invalidate(); +#endif + } + } + /// /// By default, uses all the given. Subclasses of /// should override this method if they need something different. /// /// An exception will be thrown if availableSize is infinite (e.g. if inside a StackPanel). - protected override partial Size MeasureOverride(Size availableSize); + protected override Size MeasureOverride(Size availableSize) + { + if (availableSize.Width == Double.PositiveInfinity || + availableSize.Height == Double.PositiveInfinity || + double.IsNaN(availableSize.Width) || + double.IsNaN(availableSize.Height)) + { + throw new ArgumentException($"{nameof(GLCanvasElement)} cannot be measured with infinite or NaN values, but received availableSize={availableSize}."); + } + return availableSize; + } /// /// By default, uses all the given. Subclasses of /// should override this method if they need something different. /// /// An exception will be thrown if is infinite (e.g. if inside a StackPanel). - protected override partial Size ArrangeOverride(Size finalSize); + protected override Size ArrangeOverride(Size finalSize) + { + if (finalSize.Width == Double.PositiveInfinity || + finalSize.Height == Double.PositiveInfinity || + double.IsNaN(finalSize.Width) || + double.IsNaN(finalSize.Height)) + { + throw new ArgumentException($"{nameof(GLCanvasElement)} cannot be arranged with infinite or NaN values, but received finalSize={finalSize}."); + } +#if WINAPPSDK + _image.Arrange(new Rect(new Point(), finalSize)); +#endif + return finalSize; + } private readonly struct GLStateDisposable : IDisposable { diff --git a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.uno.cs b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.uno.cs new file mode 100644 index 000000000000..5c60309769d7 --- /dev/null +++ b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.uno.cs @@ -0,0 +1,66 @@ +#if !WINAPPSDK + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Windows.Foundation; +using Microsoft.UI.Xaml; +using Silk.NET.Core.Contexts; +using Silk.NET.OpenGL; +using Uno.Foundation.Extensibility; + +namespace Uno.WinUI.Graphics; + +/// +/// A that exposes the ability to draw 3D graphics using OpenGL and Silk.NET. +/// +/// +/// This is only available on skia-based targets and when running with hardware acceleration. +/// This is currently only available on the WPF and X11 targets. +/// +public abstract partial class GLCanvasElement +{ + private readonly GLVisual _glVisual; + + private bool _renderDirty = true; + + /// The width of the backing framebuffer. + /// The height of the backing framebuffer. + protected GLCanvasElement(uint width, uint height) + { + _width = width; + _height = height; + + _glVisual = new GLVisual(this, Visual.Compositor); + Visual.Children.InsertAtTop(_glVisual); + } + + public partial void Invalidate() + { + _renderDirty = true; + _glVisual.Compositor.InvalidateRender(_glVisual); + } + + private protected override unsafe void OnLoaded() + { + base.OnLoaded(); + + if (ApiExtensibility.CreateInstance(this, out var nativeContext)) + { + _gl = GL.GetApi(nativeContext); + } + else if (ApiExtensibility.CreateInstance(this, out var getProcAddress)) + { + _gl = GL.GetApi(getProcAddress.Invoke); + } + else + { + throw new InvalidOperationException($"Couldn't create a {nameof(GL)} object for {nameof(GLCanvasElement)}. Make sure you are running on a platform with {nameof(GLCanvasElement)} support."); + } + + OnLoadedShared(); + } + + private protected override void OnUnloaded() => OnUnloadedShared(); +} +#endif From 402787b202a72ebf6ef0f8625c8d53ab7bccc965 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 28 Aug 2024 18:57:46 +0300 Subject: [PATCH 71/94] chore: major redesign --- doc/articles/controls/GLCanvasElement.md | 112 ++++----- .../GLCanvasElement.GLVisual.uno.cs | 54 ---- .../GLCanvasElement.WinUI.cs | 232 ++++++++++-------- .../GLCanvasElement.shared.cs | 182 +++++--------- .../Uno.WinUI.Graphics/GLCanvasElement.uno.cs | 66 ----- .../Uno.WinUI.Graphics.csproj | 1 + .../RotatingCubeGlCanvasElement.cs | 4 +- .../SimpleTriangleGlCanvasElement.cs | 4 +- src/Uno.UI.Dispatching/AssemblyInfo.cs | 2 + .../Extensions/WpfExtensionsRegistrar.cs | 4 +- .../Extensions/WpfGlNativeContext.cs | 43 ---- .../Extensions/WpfNativeOpenGLWrapper.cs | 137 +++++++++++ .../OpenGLWpfRenderer.NativeMethods.cs | 151 ------------ .../Rendering/OpenGLWpfRenderer.cs | 38 +-- .../Rendering/WpfRenderingNativeMethods.cs | 151 ++++++++++++ .../X11ApplicationHost.cs | 2 +- .../X11NativeOpenGLWrapper.cs | 16 ++ src/Uno.UI/Graphics/GLGetProcAddress.skia.cs | 6 - src/Uno.UI/Graphics/NativeOpenGLWrapper.cs | 32 +++ 19 files changed, 619 insertions(+), 618 deletions(-) delete mode 100644 src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.GLVisual.uno.cs delete mode 100644 src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.uno.cs delete mode 100644 src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfGlNativeContext.cs create mode 100644 src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs delete mode 100644 src/Uno.UI.Runtime.Skia.Wpf/Rendering/OpenGLWpfRenderer.NativeMethods.cs create mode 100644 src/Uno.UI.Runtime.Skia.Wpf/Rendering/WpfRenderingNativeMethods.cs create mode 100644 src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs delete mode 100644 src/Uno.UI/Graphics/GLGetProcAddress.skia.cs create mode 100644 src/Uno.UI/Graphics/NativeOpenGLWrapper.cs diff --git a/doc/articles/controls/GLCanvasElement.md b/doc/articles/controls/GLCanvasElement.md index b6ea5cd30093..04068e9890af 100644 --- a/doc/articles/controls/GLCanvasElement.md +++ b/doc/articles/controls/GLCanvasElement.md @@ -2,60 +2,38 @@ uid: Uno.Controls.GLCanvasElement --- -## GLCanvasElement +## Uno.WinUI.Graphics.GLCanvasElement > [!IMPORTANT] -> This functionality is only available on Skia Desktop (`netX.0-desktop`) targets that are running with hardware acceleration. This is also not available on MacOS. +> 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 `FrameworkElement` for drawing 3D graphics with OpenGL. +`GLCanvasElement` is a `Grid` for drawing 3D graphics with OpenGL. This class comes as a part of the `Uno.WinUI.Graphics` package. To use `GLCanvasElement`, create a subclass of `GLCanvasElement` and override the abstract methods `Init`, `RenderOverride` and `OnDestroy`. ```csharp -protected GLCanvasElement(Size resolution); +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 a `Size` parameter, which decides the resolution of the offscreen framebuffer that the `GLCanvasElement` will draw onto. Note that the `resolution` parameter is unrelated to the final size of the drawing in the Uno 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 `UIelement`, e.g. using `MeasureOverride`, `ArrangeOverride`, the `Width/Height` properties, etc. +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. -The 3 abstract methods above all take a `Silk.NET.OpenGL.GL` parameter that can be used to make OpenGL calls. +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. -> [!IMPORTANT] -> If your application uses `GLCanvasElement`, you will need to add a PackageReference to `Silk.NET.OpenGL`. +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. -> [!IMPORTANT] -> `Init` and `OnDestroy` might be called multiple times in pairs. Every call to `OnDestroy` will be preceded by a call to `Init`. - -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. Due to the fact that both `GLCanvasElement` 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 `RenderOverride`. For example, make sure to save the values for the initially-bound VAO if you intend to bind your own VAO and bind the original VAO at the end of `RenderOverride`. +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. -```csharp -protected override void RenderOverride(GL gl) -{ - var oldVAO = gl.GetInteger(GLEnum.VertexArrayBinding); - gl.BindVertexArray(myVAO); - - // draw with myVAO - - gl.BindVertexArray(oldVAO); -} -``` +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. -Similarly, make sure to disable depth testing at the end if you choose to enable it. To reduce bugs, some of the more common OpenGL state variables are restored automatically for you, but don't depend on this behaviour. - -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 Uno Platform has its own support for input and windowing. - -Additionally, `GLCanvasElement` has an `Invalidate` method that can be used at any time to tell the Uno Platform runtime to redraw 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`. - -By default, a `GLCanvasElement` 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 a `GLCanvasElement` takes as much space as it can, it's not allowed to place a `GLCanvasElement` 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 `GLCanvasElement`. +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 @@ -69,17 +47,21 @@ XAML: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:BlankApp" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:skia="http://uno.ui/skia#using:BlankApp" + xmlns:skia="http://uno.ui/skia#using:UITests.Shared.Windows_UI_Composition" xmlns:not_skia="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - mc:Ignorable="d skia not_skia" - d:DesignHeight="300" - d:DesignWidth="400"> + xmlns:win="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:not_win="http://uno.ui/not_win" + mc:Ignorable="skia not_win"> - - + + + + + + + ``` @@ -98,8 +80,15 @@ public partial class GLCanvasElementExample : UserControl ``` ```csharp -// GLTriangleElement.skia.cs <-- NOTICE the `.skia` -public class GLTriangleElement() : GLCanvasElement(new Size(1200, 800)) +// 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; @@ -124,28 +113,34 @@ public class GLTriangleElement() : GLCanvasElement(new Size(1200, 800)) gl.VertexAttribPointer(0, 3, GLEnum.Float, false, 3 * sizeof(float), (void*)0); gl.EnableVertexAttribArray(0); - const string vertexCode = @" -#version 330 core + // 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; + 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); -}"; + void main() + { + gl_Position = vec4(aPosition, 1.0); + vertexColor = vec4(aPosition.x + 0.5, aPosition.y + 0.5, aPosition.z + 0.5, 1.0); + } + """; - const string fragmentCode = @" -#version 330 core + // 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; + out vec4 out_color; + in vec4 vertexColor; -void main() -{ -out_color = vertexColor; -}"; + void main() + { + out_color = vertexColor; + } + """; uint vertexShader = gl.CreateShader(ShaderType.VertexShader); gl.ShaderSource(vertexShader, vertexCode); @@ -202,4 +197,5 @@ out_color = vertexColor; gl.DrawArrays(PrimitiveType.Triangles, 0, 3); } } +#endif ``` diff --git a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.GLVisual.uno.cs b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.GLVisual.uno.cs deleted file mode 100644 index ed70e74853e1..000000000000 --- a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.GLVisual.uno.cs +++ /dev/null @@ -1,54 +0,0 @@ -#if !WINAPPSDK - -using Microsoft.UI.Composition; -using Microsoft.UI.Xaml; -using SkiaSharp; - -namespace Uno.WinUI.Graphics; - -public abstract partial class GLCanvasElement -{ - private class GLVisual : Visual - { - private SKPixmap? _pixmap; - private readonly GLCanvasElement _owner; - - public GLVisual(GLCanvasElement owner, Compositor compositor) : base(compositor) - { - _owner = owner; - _owner.Loaded += OnOwnerLoaded; - } - - private void OnOwnerLoaded(object sender, RoutedEventArgs e) - { - _pixmap?.Dispose(); - _pixmap = new SKPixmap(new SKImageInfo((int)_owner._width, (int)_owner._height, SKColorType.Bgra8888, SKAlphaType.Unpremul), _owner._pixels); - } - - internal override void Paint(in PaintingSession session) - { - // we clear the drawing area here because in some cases when unloading the GLCanvasElement, the - // drawing isn't cleared for some reason (a possible hypothesis is timing problems between raw GL and skia). - session.Canvas.Save(); - session.Canvas.ClipRect(new SKRect(0, 0, _owner.Visual.Size.X, _owner.Visual.Size.Y)); - session.Canvas.Clear(SKColors.Transparent); - if (_owner._renderDirty) - { - _owner.Render(); - } - // opengl coordinates go bottom-up, so we concat a matrix to flip horizontally and vertically - var flip = new SKMatrix(scaleX: 1, scaleY: -1, skewX: 0, skewY: 0, transX: 0, transY: _owner.Visual.Size.Y, persp0: 0, persp1: 0, persp2: 1); - session.Canvas.Concat(ref flip); - session.Canvas.DrawImage(SKImage.FromPixels(_pixmap), new SKRect(0, 0, _owner.Visual.Size.X, _owner.Visual.Size.Y)); - session.Canvas.Restore(); - } - - private protected override void DisposeInternal() - { - base.DisposeInternal(); - _pixmap?.Dispose(); - } - } -} - -#endif diff --git a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs index f4510fe6cc31..f027201039cd 100644 --- a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs +++ b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs @@ -1,17 +1,13 @@ #if WINAPPSDK using System; -using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; -using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Media; -using Microsoft.UI.Xaml.Media.Imaging; using Silk.NET.Core.Contexts; using Silk.NET.Core.Loader; using Silk.NET.OpenGL; +using Uno.Disposables; using Uno.Extensions; using Uno.Logging; @@ -26,112 +22,148 @@ namespace Uno.WinUI.Graphics; /// public abstract partial class GLCanvasElement { + 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(); + } - private readonly Window _window; - private readonly WriteableBitmap _backBuffer; - private readonly Image _image; - private readonly ScaleTransform _scaleTransform; + internal class WinUINativeOpenGLWrapper(Func getWindowFunc) : INativeOpenGLWrapper + { + private nint _hdc; + private nint _glContext; - private nint _hwnd; - private nint _hdc; - private int _pixelFormat; - private nint _glContext; + public void CreateContext(UIElement element) + { + var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(getWindowFunc()); + + _hdc = NativeMethods.GetDC(hwnd); + + NativeMethods.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.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 = NativeMethods.PFD_MAIN_PLANE; + + var pixelFormat = NativeMethods.ChoosePixelFormat(_hdc, ref pfd); + + // To inspect the chosen pixel format: + // WpfRenderingNativeMethods.PIXELFORMATDESCRIPTOR temp_pfd = default; + // WpfRenderingNativeMethods.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"); + } - /// The width of the backing framebuffer. - /// The height of the backing framebuffer. - /// The window that this element will belong to. - protected GLCanvasElement(uint width, uint height, Window window) - { - _width = width; - _height = height; - _window = window; + if (NativeMethods.SetPixelFormat(_hdc, pixelFormat, ref pfd) == 0) + { + if (this.Log().IsEnabled(LogLevel.Error)) + { + this.Log().Error($"SetPixelFormat failed"); + } + throw new InvalidOperationException("ChoosePixelFormat failed"); + } - _backBuffer = new WriteableBitmap((int)width, (int)height); + _glContext = NativeMethods.wglCreateContext(_hdc); - _scaleTransform = new ScaleTransform { ScaleX = 1, ScaleY = -1 }; // because OpenGL coordinates go bottom-to-top - _image = new Image + 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 WpfGlNativeContext()); + + public void DestroyContext() { - Source = _backBuffer, - RenderTransform = _scaleTransform - }; - Content = _image; - - Loaded += OnLoaded; - Unloaded += OnUnloaded; - SizeChanged += OnSizeChanged; - } + NativeMethods.wglDeleteContext(_glContext); + _glContext = default; + _hdc = default; + } - private void OnLoaded(object sender, RoutedEventArgs e) - { - SetupOpenGlContext(); - OnLoadedShared(); - } + public IDisposable MakeCurrent() + { + var glContext = NativeMethods.wglGetCurrentContext(); + var dc = NativeMethods.wglGetCurrentDC(); + NativeMethods.wglMakeCurrent(_hdc, _glContext); + return Disposable.Create(() => NativeMethods.wglMakeCurrent(dc, glContext)); + } - private void OnUnloaded(object sender, RoutedEventArgs e) => OnUnloadedShared(); + // https://sharovarskyi.com/blog/posts/csharp-win32-opengl-silknet/ + private class WpfGlNativeContext : INativeContext + { + private readonly UnmanagedLibrary _l; - private void SetupOpenGlContext() - { - _hwnd = WinRT.Interop.WindowNative.GetWindowHandle(_window); - - _hdc = NativeMethods.GetDC(_hwnd); - - NativeMethods.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.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 = NativeMethods.PFD_MAIN_PLANE; - - _pixelFormat = NativeMethods.ChoosePixelFormat(_hdc, ref pfd); - - // To inspect the chosen pixel format: - // NativeMethods.PIXELFORMATDESCRIPTOR temp_pfd = default; - // NativeMethods.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 (NativeMethods.SetPixelFormat(_hdc, _pixelFormat, ref pfd) == 0) - { - if (this.Log().IsEnabled(LogLevel.Error)) - { - this.Log().Error($"SetPixelFormat failed"); - } - throw new InvalidOperationException("ChoosePixelFormat failed"); - } - - _glContext = NativeMethods.wglCreateContext(_hdc); - - if (_glContext == IntPtr.Zero) - { - if (this.Log().IsEnabled(LogLevel.Error)) - { - this.Log().Error($"wglCreateContext failed"); - } - throw new InvalidOperationException("ChoosePixelFormat failed"); - } - - _gl = GL.GetApi(new WinUINativeContext()); - } + public WpfGlNativeContext() + { + _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 partial void Invalidate() => DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, Render); + public bool TryGetProcAddress(string proc, out nint addr, int? slot = null) + { + if (_l.TryLoadFunction(proc, out addr)) + { + return true; + } - private void OnSizeChanged(object sender, SizeChangedEventArgs args) - { - _scaleTransform.CenterY = args.NewSize.Height / 2; + addr = NativeMethods.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(); + } } // https://sharovarskyi.com/blog/posts/csharp-win32-opengl-silknet/ diff --git a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.shared.cs b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.shared.cs index 7866718965c9..4e53d1a34de6 100644 --- a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.shared.cs +++ b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.shared.cs @@ -1,16 +1,22 @@ using System; using System.Diagnostics; using System.Runtime.InteropServices; -using Windows.Foundation; +using System.Runtime.InteropServices.WindowsRuntime; 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.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; #endif namespace Uno.WinUI.Graphics; @@ -22,13 +28,17 @@ namespace Uno.WinUI.Graphics; /// 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 : UserControl +public abstract partial class GLCanvasElement : Grid { private const int BytesPerPixel = 4; + private 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; @@ -69,28 +79,59 @@ public abstract partial class GLCanvasElement : UserControl /// 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 calls in the next rendering cycle. + /// 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. /// - public partial void Invalidate(); - - private GLStateDisposable CreateGlStateDisposable() #if WINAPPSDK - => new GLStateDisposable(_gl!, _hdc, _glContext); + public void Invalidate() => DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, Render); #else - => new GLStateDisposable(_gl!); + public void Invalidate() => NativeDispatcher.Main.Enqueue(Render, NativeDispatcherPriority.Idle); #endif - private unsafe void OnLoadedShared() + private unsafe void OnLoaded(object sender, RoutedEventArgs routedEventArgs) { - Debug.Assert(_gl is not null); + _nativeOpenGlWrapper.CreateContext(this); + _gl = (GL)_nativeOpenGlWrapper.CreateGLSilkNETHandle(); _pixels = Marshal.AllocHGlobal((int)(_width * _height * BytesPerPixel)); - using (CreateGlStateDisposable()) + using (new GLStateDisposable(this)) { _framebuffer = _gl.GenBuffer(); _gl.BindFramebuffer(GLEnum.Framebuffer, _framebuffer); @@ -126,16 +167,16 @@ private unsafe void OnLoadedShared() Invalidate(); } - private void OnUnloadedShared() + private void OnUnloaded(object sender, RoutedEventArgs routedEventArgs) { Debug.Assert(_gl is not null); // because OnLoaded creates _gl Marshal.FreeHGlobal(_pixels); - using (CreateGlStateDisposable()) + using (new GLStateDisposable(this)) { #if WINAPPSDK - if (NativeMethods.wglMakeCurrent(_hdc, _glContext) != 1) + if (NativeMethods.wglGetCurrentContext() == 0) { if (this.Log().IsEnabled(LogLevel.Debug)) { @@ -156,11 +197,6 @@ private void OnUnloadedShared() _textureColorBuffer = default; _renderBuffer = default; _pixels = default; - -#if WINAPPSDK - NativeMethods.wglDeleteContext(_glContext); - _glContext = default; -#endif } private unsafe void Render() @@ -172,127 +208,45 @@ private unsafe void Render() Debug.Assert(_gl is not null); // because _gl exists if loaded - using var _ = CreateGlStateDisposable(); + using var _ = new GLStateDisposable(this); _gl!.BindFramebuffer(GLEnum.Framebuffer, _framebuffer); { - _gl.Viewport(new global::System.Drawing.Size((int)_width, (int)_height)); + _gl.Viewport(new System.Drawing.Size((int)_width, (int)_height)); RenderOverride(_gl); - // Can we do without this copy? _gl.ReadBuffer(GLEnum.ColorAttachment0); _gl.ReadPixels(0, 0, _width, _height, GLEnum.Bgra, GLEnum.UnsignedByte, (void*)_pixels); -#if WINAPPSDK using (var stream = _backBuffer.PixelBuffer.AsStream()) { stream.Write(new ReadOnlySpan((void*)_pixels, (int)(_width * _height * BytesPerPixel))); } _backBuffer.Invalidate(); -#endif - } - } - - /// - /// By default, uses all the given. Subclasses of - /// should override this method if they need something different. - /// - /// An exception will be thrown if availableSize is infinite (e.g. if inside a StackPanel). - protected override Size MeasureOverride(Size availableSize) - { - if (availableSize.Width == Double.PositiveInfinity || - availableSize.Height == Double.PositiveInfinity || - double.IsNaN(availableSize.Width) || - double.IsNaN(availableSize.Height)) - { - throw new ArgumentException($"{nameof(GLCanvasElement)} cannot be measured with infinite or NaN values, but received availableSize={availableSize}."); } - return availableSize; - } - - /// - /// By default, uses all the given. Subclasses of - /// should override this method if they need something different. - /// - /// An exception will be thrown if is infinite (e.g. if inside a StackPanel). - protected override Size ArrangeOverride(Size finalSize) - { - if (finalSize.Width == Double.PositiveInfinity || - finalSize.Height == Double.PositiveInfinity || - double.IsNaN(finalSize.Width) || - double.IsNaN(finalSize.Height)) - { - throw new ArgumentException($"{nameof(GLCanvasElement)} cannot be arranged with infinite or NaN values, but received finalSize={finalSize}."); - } -#if WINAPPSDK - _image.Arrange(new Rect(new Point(), finalSize)); -#endif - return finalSize; } private readonly struct GLStateDisposable : IDisposable { - private readonly GL _gl; - private readonly int _oldArrayBuffer; - private readonly int _oldVertexArray; - private readonly int _oldFramebuffer; - private readonly int _oldTextureColorBuffer; - private readonly int _oldRbo; - private readonly bool _depthTestEnabled; - private readonly bool _depthTestMask; - private readonly int[] _oldViewport = new int[4]; + private readonly GLCanvasElement _glCanvasElement; + private readonly IDisposable _contextDisposable; -#if WINAPPSDK - private readonly IntPtr _dc; - private readonly IntPtr _glContext; -#endif - -#if WINAPPSDK - public GLStateDisposable(GL gl, IntPtr dc, IntPtr glContext) -#else - public GLStateDisposable(GL gl) -#endif + public GLStateDisposable(GLCanvasElement glCanvasElement) { - _gl = gl; - -#if WINAPPSDK - _glContext = NativeMethods.wglGetCurrentContext(); - _dc = NativeMethods.wglGetCurrentDC(); - NativeMethods.wglMakeCurrent(dc, glContext); -#endif + _glCanvasElement = glCanvasElement; + var gl = _glCanvasElement._gl; + Debug.Assert(gl is not null); - _depthTestEnabled = gl.GetBoolean(GLEnum.DepthTest); - _depthTestMask = gl.GetBoolean(GLEnum.DepthWritemask); - _oldArrayBuffer = gl.GetInteger(GLEnum.ArrayBufferBinding); - _oldVertexArray = gl.GetInteger(GLEnum.VertexArrayBinding); - _oldFramebuffer = gl.GetInteger(GLEnum.FramebufferBinding); - _oldTextureColorBuffer = gl.GetInteger(GLEnum.TextureBinding2D); - _oldRbo = gl.GetInteger(GLEnum.RenderbufferBinding); - gl.GetInteger(GLEnum.Viewport, new Span(_oldViewport)); + _contextDisposable = _glCanvasElement._nativeOpenGlWrapper.MakeCurrent(); } public void Dispose() { - _gl.BindVertexArray((uint)_oldVertexArray); - _gl.BindBuffer(BufferTargetARB.ArrayBuffer, (uint)_oldArrayBuffer); - _gl.BindFramebuffer(GLEnum.Framebuffer, (uint)_oldFramebuffer); - _gl.BindTexture(GLEnum.Texture2D, (uint)_oldTextureColorBuffer); - _gl.BindRenderbuffer(GLEnum.Renderbuffer, (uint)_oldRbo); - _gl.Viewport(_oldViewport[0], _oldViewport[1], (uint)_oldViewport[2], (uint)_oldViewport[3]); - _gl.DepthMask(_depthTestMask); - if (_depthTestEnabled) - { - _gl.Enable(EnableCap.DepthTest); - } - else - { - _gl.Disable(EnableCap.DepthTest); - } + var gl = _glCanvasElement._gl; + Debug.Assert(gl is not null); -#if WINAPPSDK - NativeMethods.wglMakeCurrent(_dc, _glContext); -#endif + _contextDisposable.Dispose(); } } } diff --git a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.uno.cs b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.uno.cs deleted file mode 100644 index 5c60309769d7..000000000000 --- a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.uno.cs +++ /dev/null @@ -1,66 +0,0 @@ -#if !WINAPPSDK - -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; -using Windows.Foundation; -using Microsoft.UI.Xaml; -using Silk.NET.Core.Contexts; -using Silk.NET.OpenGL; -using Uno.Foundation.Extensibility; - -namespace Uno.WinUI.Graphics; - -/// -/// A that exposes the ability to draw 3D graphics using OpenGL and Silk.NET. -/// -/// -/// This is only available on skia-based targets and when running with hardware acceleration. -/// This is currently only available on the WPF and X11 targets. -/// -public abstract partial class GLCanvasElement -{ - private readonly GLVisual _glVisual; - - private bool _renderDirty = true; - - /// The width of the backing framebuffer. - /// The height of the backing framebuffer. - protected GLCanvasElement(uint width, uint height) - { - _width = width; - _height = height; - - _glVisual = new GLVisual(this, Visual.Compositor); - Visual.Children.InsertAtTop(_glVisual); - } - - public partial void Invalidate() - { - _renderDirty = true; - _glVisual.Compositor.InvalidateRender(_glVisual); - } - - private protected override unsafe void OnLoaded() - { - base.OnLoaded(); - - if (ApiExtensibility.CreateInstance(this, out var nativeContext)) - { - _gl = GL.GetApi(nativeContext); - } - else if (ApiExtensibility.CreateInstance(this, out var getProcAddress)) - { - _gl = GL.GetApi(getProcAddress.Invoke); - } - else - { - throw new InvalidOperationException($"Couldn't create a {nameof(GL)} object for {nameof(GLCanvasElement)}. Make sure you are running on a platform with {nameof(GLCanvasElement)} support."); - } - - OnLoadedShared(); - } - - private protected override void OnUnloaded() => OnUnloadedShared(); -} -#endif diff --git a/src/AddIns/Uno.WinUI.Graphics/Uno.WinUI.Graphics.csproj b/src/AddIns/Uno.WinUI.Graphics/Uno.WinUI.Graphics.csproj index 6b1f877d4e74..e943e9d1b4d5 100644 --- a/src/AddIns/Uno.WinUI.Graphics/Uno.WinUI.Graphics.csproj +++ b/src/AddIns/Uno.WinUI.Graphics/Uno.WinUI.Graphics.csproj @@ -14,6 +14,7 @@ + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs index b618e5d3b8d5..4ee29979cb62 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs @@ -21,9 +21,9 @@ namespace UITests.Shared.Windows_UI_Composition { #if WINAPPSDK - public class RotatingCubeGlCanvasElement() : GLCanvasElement(1200, 800, SamplesApp.App.MainWindow) + public class RotatingCubeGlCanvasElement() : GLCanvasElement(1200, 800, () => SamplesApp.App.MainWindow) #elif __SKIA__ - public class RotatingCubeGlCanvasElement() : GLCanvasElement(1200, 800) + public class RotatingCubeGlCanvasElement() : GLCanvasElement(1200, 800, null) #endif { private static BufferObject _vbo; diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs index 8adeeafa76f6..ac6df2a8629b 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs @@ -9,9 +9,9 @@ namespace UITests.Shared.Windows_UI_Composition // https://learnopengl.com/Getting-started/Hello-Triangle public class SimpleTriangleGlCanvasElement() #if __SKIA__ - : GLCanvasElement(1200, 800) + : GLCanvasElement(1200, 800, null) #elif WINAPPSDK - : GLCanvasElement(1200, 800, SamplesApp.App.MainWindow) + : GLCanvasElement(1200, 800, () => SamplesApp.App.MainWindow) #endif { private uint _vao; diff --git a/src/Uno.UI.Dispatching/AssemblyInfo.cs b/src/Uno.UI.Dispatching/AssemblyInfo.cs index 087f3221acdc..01cd0f05edcd 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.Graphics")] + [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 717a40b98bcf..f79162569d63 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfExtensionsRegistrar.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfExtensionsRegistrar.cs @@ -21,10 +21,10 @@ using Windows.System.Profile.Internal; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -using Silk.NET.Core.Contexts; 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; @@ -58,7 +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(INativeContext), _ => new WpfGlNativeContext()); + ApiExtensibility.Register(typeof(INativeOpenGLWrapper), _ => new WpfNativeOpenGLWrapper()); _registered = true; } diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfGlNativeContext.cs b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfGlNativeContext.cs deleted file mode 100644 index 00e84c9d5ad7..000000000000 --- a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfGlNativeContext.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using Silk.NET.Core.Contexts; -using Silk.NET.Core.Loader; -using Uno.UI.Runtime.Skia.Wpf.Rendering; -namespace Uno.UI.Runtime.Skia.Wpf.Extensions; - -// https://sharovarskyi.com/blog/posts/csharp-win32-opengl-silknet/ -internal class WpfGlNativeContext : INativeContext -{ - private readonly UnmanagedLibrary _l; - - public WpfGlNativeContext() - { - _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 = OpenGLWpfRenderer.NativeMethods.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/Extensions/WpfNativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs new file mode 100644 index 000000000000..1cf4984d0b0e --- /dev/null +++ b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs @@ -0,0 +1,137 @@ +#nullable enable + +using System; +using System.Runtime.InteropServices; +using System.Windows.Interop; +using Microsoft.UI.Xaml; +using Silk.NET.Core.Contexts; +using Silk.NET.Core.Loader; +using Silk.NET.OpenGL; +using Uno.Disposables; +using Uno.Foundation.Logging; +using Uno.Graphics; +using Uno.UI.Runtime.Skia.Wpf.Rendering; +using WpfWindow = System.Windows.Window; + +namespace Uno.UI.Runtime.Skia.Wpf.Extensions; + +internal class WpfNativeOpenGLWrapper : INativeOpenGLWrapper +{ + private nint _hdc; + private nint _glContext; + + public void CreateContext(UIElement element) + { + if (element.XamlRoot?.HostWindow?.NativeWindow is not WpfWindow wpfWindow) + { + throw new InvalidOperationException($"The XamlRoot and its NativeWindow must be initialzied on the element before calling {nameof(CreateContext)}."); + } + var hwnd = new WindowInteropHelper(wpfWindow).Handle; + + _hdc = WpfRenderingNativeMethods.GetDC(hwnd); + + WpfRenderingNativeMethods.PIXELFORMATDESCRIPTOR pfd = new(); + pfd.nSize = (ushort)Marshal.SizeOf(pfd); + pfd.nVersion = 1; + pfd.dwFlags = WpfRenderingNativeMethods.PFD_DRAW_TO_WINDOW | WpfRenderingNativeMethods.PFD_SUPPORT_OPENGL | WpfRenderingNativeMethods.PFD_DOUBLEBUFFER; + pfd.iPixelType = WpfRenderingNativeMethods.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 = WpfRenderingNativeMethods.PFD_MAIN_PLANE; + + var pixelFormat = WpfRenderingNativeMethods.ChoosePixelFormat(_hdc, ref pfd); + + // To inspect the chosen pixel format: + // WpfRenderingNativeMethods.PIXELFORMATDESCRIPTOR temp_pfd = default; + // WpfRenderingNativeMethods.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 (WpfRenderingNativeMethods.SetPixelFormat(_hdc, pixelFormat, ref pfd) == 0) + { + if (this.Log().IsEnabled(LogLevel.Error)) + { + this.Log().Error($"SetPixelFormat failed"); + } + throw new InvalidOperationException("ChoosePixelFormat failed"); + } + + _glContext = WpfRenderingNativeMethods.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 WpfGlNativeContext()); + + public void DestroyContext() + { + WpfRenderingNativeMethods.wglDeleteContext(_glContext); + _glContext = default; + _hdc = default; + } + + public IDisposable MakeCurrent() + { + var glContext = WpfRenderingNativeMethods.wglGetCurrentContext(); + var dc = WpfRenderingNativeMethods.wglGetCurrentDC(); + WpfRenderingNativeMethods.wglMakeCurrent(_hdc, _glContext); + return Disposable.Create(() => WpfRenderingNativeMethods.wglMakeCurrent(dc, glContext)); + } + + // https://sharovarskyi.com/blog/posts/csharp-win32-opengl-silknet/ + private class WpfGlNativeContext : INativeContext + { + private readonly UnmanagedLibrary _l; + + public WpfGlNativeContext() + { + _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 = WpfRenderingNativeMethods.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 2c0305c9bd0c..000000000000 --- a/src/Uno.UI.Runtime.Skia.Wpf/Rendering/OpenGLWpfRenderer.NativeMethods.cs +++ /dev/null @@ -1,151 +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", 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")] - 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..97b24b28eff1 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 = WpfRenderingNativeMethods.GetDC(_hwnd); // Set the pixel format - NativeMethods.PIXELFORMATDESCRIPTOR pfd = new(); + WpfRenderingNativeMethods.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 = WpfRenderingNativeMethods.PFD_DRAW_TO_WINDOW | WpfRenderingNativeMethods.PFD_SUPPORT_OPENGL | WpfRenderingNativeMethods.PFD_DOUBLEBUFFER; + pfd.iPixelType = WpfRenderingNativeMethods.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 = WpfRenderingNativeMethods.PFD_MAIN_PLANE; // Choose the best matching pixel format - _pixelFormat = NativeMethods.ChoosePixelFormat(_hdc, ref pfd); + _pixelFormat = WpfRenderingNativeMethods.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 (WpfRenderingNativeMethods.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 = WpfRenderingNativeMethods.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); + WpfRenderingNativeMethods.wglMakeCurrent(_hdc, _glContext); #pragma warning restore CA1806 // Do not ignore method results - var version = NativeMethods.GetOpenGLVersion(); + var version = WpfRenderingNativeMethods.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); + WpfRenderingNativeMethods.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); + WpfRenderingNativeMethods.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); + WpfRenderingNativeMethods.glClear(WpfRenderingNativeMethods.GL_COLOR_BUFFER_BIT | WpfRenderingNativeMethods.GL_STENCIL_BUFFER_BIT | WpfRenderingNativeMethods.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); + WpfRenderingNativeMethods.glReadPixels(0, 0, width, height, WpfRenderingNativeMethods.GL_BGRA_EXT, WpfRenderingNativeMethods.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); + WpfRenderingNativeMethods.glGetIntegerv(WpfRenderingNativeMethods.GL_FRAMEBUFFER_BINDING, out var framebuffer); + WpfRenderingNativeMethods.glGetIntegerv(WpfRenderingNativeMethods.GL_STENCIL_BITS, out var stencil); + WpfRenderingNativeMethods.glGetIntegerv(WpfRenderingNativeMethods.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); + WpfRenderingNativeMethods.wglDeleteContext(_glContext); + WpfRenderingNativeMethods.ReleaseDC(_hwnd, _hdc); #pragma warning restore CA1806 // Do not ignore method results _glContext = 0; diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Rendering/WpfRenderingNativeMethods.cs b/src/Uno.UI.Runtime.Skia.Wpf/Rendering/WpfRenderingNativeMethods.cs new file mode 100644 index 000000000000..1fa054e4ff9c --- /dev/null +++ b/src/Uno.UI.Runtime.Skia.Wpf/Rendering/WpfRenderingNativeMethods.cs @@ -0,0 +1,151 @@ +#nullable enable + +using System; +using System.Runtime.InteropServices; + +namespace Uno.UI.Runtime.Skia.Wpf.Rendering; + +internal static class WpfRenderingNativeMethods +{ + [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.X11/X11ApplicationHost.cs b/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs index 79e80ca17999..a6d6393c3436 100644 --- a/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs +++ b/src/Uno.UI.Runtime.Skia.X11/X11ApplicationHost.cs @@ -67,7 +67,7 @@ static X11ApplicationHost() ApiExtensibility.Register(typeof(Windows.ApplicationModel.DataTransfer.DragDrop.Core.IDragDropExtension), o => new X11DragDropExtension(o)); - ApiExtensibility.Register(typeof(Uno.Graphics.GLGetProcAddress), _ => new Uno.Graphics.GLGetProcAddress(GlxInterface.glXGetProcAddress)); + 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..c535acc6566a --- /dev/null +++ b/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs @@ -0,0 +1,16 @@ +using System; +using Microsoft.UI.Xaml; +using Uno.Graphics; + +namespace Uno.WinUI.Runtime.Skia.X11; + +internal class X11NativeOpenGLWrapper : INativeOpenGLWrapper +{ + public void CreateContext(UIElement element) => throw new NotImplementedException(); + + public object CreateGLSilkNETHandle() => throw new NotImplementedException(); + + public void DestroyContext() => throw new NotImplementedException(); + + public IDisposable MakeCurrent() => throw new NotImplementedException(); +} diff --git a/src/Uno.UI/Graphics/GLGetProcAddress.skia.cs b/src/Uno.UI/Graphics/GLGetProcAddress.skia.cs deleted file mode 100644 index 67c33e058f9d..000000000000 --- a/src/Uno.UI/Graphics/GLGetProcAddress.skia.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System; - -namespace Uno.Graphics -{ - internal delegate IntPtr GLGetProcAddress(string proc); -} diff --git a/src/Uno.UI/Graphics/NativeOpenGLWrapper.cs b/src/Uno.UI/Graphics/NativeOpenGLWrapper.cs new file mode 100644 index 000000000000..5873c7f31199 --- /dev/null +++ b/src/Uno.UI/Graphics/NativeOpenGLWrapper.cs @@ -0,0 +1,32 @@ +using System; +using Microsoft.UI.Xaml; + +namespace Uno.Graphics +{ + 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(); + } +} From baf26ed77cf71e2f26934a13c4621b3c8beab4fb Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 28 Aug 2024 19:12:12 +0300 Subject: [PATCH 72/94] chore: build error --- src/AddIns/Uno.WinUI.Graphics/Uno.WinUI.Graphics.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AddIns/Uno.WinUI.Graphics/Uno.WinUI.Graphics.csproj b/src/AddIns/Uno.WinUI.Graphics/Uno.WinUI.Graphics.csproj index e943e9d1b4d5..0ccfc248d5c5 100644 --- a/src/AddIns/Uno.WinUI.Graphics/Uno.WinUI.Graphics.csproj +++ b/src/AddIns/Uno.WinUI.Graphics/Uno.WinUI.Graphics.csproj @@ -14,7 +14,6 @@ - @@ -30,6 +29,7 @@ + From b24d069e9be08e96335baef6378f6449794fd5ee Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 28 Aug 2024 20:34:16 +0300 Subject: [PATCH 73/94] chore: implement INativeOpenGLWrapper for X11 --- .../Extensions/WpfNativeOpenGLWrapper.cs | 2 +- .../Uno.UI.Runtime.Skia.X11.csproj | 2 + .../X11NativeOpenGLWrapper.cs | 77 ++++++++++++++++++- .../X11XamlRootHost.cs | 32 ++++---- 4 files changed, 92 insertions(+), 21 deletions(-) diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs index 1cf4984d0b0e..396fa0e31ff8 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs @@ -24,7 +24,7 @@ public void CreateContext(UIElement element) { if (element.XamlRoot?.HostWindow?.NativeWindow is not WpfWindow wpfWindow) { - throw new InvalidOperationException($"The XamlRoot and its NativeWindow must be initialzied on the element before calling {nameof(CreateContext)}."); + throw new InvalidOperationException($"The XamlRoot and its NativeWindow must be initialized on the element before calling {nameof(CreateContext)}."); } var hwnd = new WindowInteropHelper(wpfWindow).Handle; 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/X11NativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs index c535acc6566a..91f815c29931 100644 --- a/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs +++ b/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs @@ -1,16 +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 { - public void CreateContext(UIElement element) => throw new NotImplementedException(); + private IntPtr _display; + private IntPtr _glContext; + private IntPtr _pbuffer; - public object CreateGLSilkNETHandle() => throw new NotImplementedException(); + 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)}."); + } - public void DestroyContext() => throw new NotImplementedException(); + _display = xamlRootHost.RootX11Window.Display; - public IDisposable MakeCurrent() => throw new NotImplementedException(); + 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."); From 8b05a86897a0083fd31faa83d35cc53828ef8e73 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 28 Aug 2024 20:39:07 +0300 Subject: [PATCH 74/94] chore: formatting --- .../GLCanvasElement.WinUI.cs | 147 +++++++++--------- 1 file changed, 70 insertions(+), 77 deletions(-) diff --git a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs index f027201039cd..a6bbe86a96ea 100644 --- a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs +++ b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs @@ -13,13 +13,6 @@ namespace Uno.WinUI.Graphics; -/// -/// A that exposes the ability to draw 3D graphics using OpenGL and Silk.NET. -/// -/// -/// This is only available on skia-based targets and when running with hardware acceleration. -/// This is currently only available on the WPF and X11 targets. -/// public abstract partial class GLCanvasElement { internal interface INativeOpenGLWrapper @@ -205,76 +198,76 @@ public nint GetProcAddress(string proc, int? slot = null) } private static class NativeMethods - { - [DllImport("user32.dll")] - internal static extern IntPtr GetDC(IntPtr hWnd); - - [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 int wglDeleteContext(IntPtr hglrc); - - [DllImport("opengl32.dll")] - internal static extern int wglMakeCurrent(IntPtr hdc, IntPtr hglrc); - - [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; - } + { + [DllImport("user32.dll")] + internal static extern IntPtr GetDC(IntPtr hWnd); + + [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 int wglDeleteContext(IntPtr hglrc); + + [DllImport("opengl32.dll")] + internal static extern int wglMakeCurrent(IntPtr hdc, IntPtr hglrc); + + [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; + } } #endif From 672261e56b5d718aa66757e1ca693eb05a923b7f Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 28 Aug 2024 20:47:21 +0300 Subject: [PATCH 75/94] chore: undo UnoMissingAssemblyAnalyzer changes --- src/Uno.Analyzers/UnoMissingAssemblyAnalyzer.cs | 5 ----- .../Composition/SkiaCompositionSurface.skia.cs | 7 ------- src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs | 10 +++++----- src/Uno.UI/Uno.UI.Reference.csproj | 5 ----- src/Uno.UI/Uno.UI.Wasm.csproj | 3 --- 5 files changed, 5 insertions(+), 25 deletions(-) diff --git a/src/Uno.Analyzers/UnoMissingAssemblyAnalyzer.cs b/src/Uno.Analyzers/UnoMissingAssemblyAnalyzer.cs index 201b75737ade..7ad29cae90cb 100644 --- a/src/Uno.Analyzers/UnoMissingAssemblyAnalyzer.cs +++ b/src/Uno.Analyzers/UnoMissingAssemblyAnalyzer.cs @@ -39,7 +39,6 @@ public override void Initialize(AnalysisContext context) { var assemblies = context.Compilation.ReferencedAssemblyNames.Select(a => a.Name).ToImmutableHashSet(); var progressRing = context.Compilation.GetTypeByMetadataName("Microsoft" /* UWP don't rename */ + ".UI.Xaml.Controls.ProgressRing"); - var glCanvasElement = context.Compilation.GetTypeByMetadataName("Microsoft.UI.Xaml.Controls.GLCanvasElement"); var mpe = context.Compilation.GetTypeByMetadataName("Microsoft.UI.Xaml.Controls.MediaPlayerElement"); _ = context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.UnoRuntimeIdentifier", out var unoRuntimeIdentifier); _ = context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.IsUnoHead", out var isUnoHead); @@ -67,10 +66,6 @@ public override void Initialize(AnalysisContext context) context.ReportDiagnostic(Diagnostic.Create(Rule, objectCreation.Syntax.GetLocation(), "ProgressRing", lottieNuGetPackageName)); } - else if (type.DerivesFrom(glCanvasElement) && !assemblies.Contains("Silk.NET.OpenGL")) - { - context.ReportDiagnostic(Diagnostic.Create(Rule, objectCreation.Syntax.GetLocation(), "GLCanvasElement", "Silk.NET.OpenGL")); - } else if (type.DerivesFrom(mpe)) { if (unoRuntimeIdentifier?.Equals("WebAssembly", StringComparison.OrdinalIgnoreCase) == true) diff --git a/src/Uno.UI.Composition/Composition/SkiaCompositionSurface.skia.cs b/src/Uno.UI.Composition/Composition/SkiaCompositionSurface.skia.cs index 04607efe9bfa..74a10d7c6eda 100644 --- a/src/Uno.UI.Composition/Composition/SkiaCompositionSurface.skia.cs +++ b/src/Uno.UI.Composition/Composition/SkiaCompositionSurface.skia.cs @@ -113,13 +113,6 @@ internal unsafe void CopyPixels(int pixelWidth, int pixelHeight, ReadOnlyMemory< } } - internal void CopyPixels(int pixelWidth, int pixelHeight, IntPtr data) - { - var info = new SKImageInfo(pixelWidth, pixelHeight, SKColorType.Bgra8888, SKAlphaType.Premul); - - SetFrameProviderAndOnFrameChanged(FrameProviderFactory.Create(SKImage.FromPixelCopy(info, data, pixelWidth * 4)), null); - } - ~SkiaCompositionSurface() { SetFrameProviderAndOnFrameChanged(null, null); diff --git a/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs index 91f815c29931..bd29323ece3d 100644 --- a/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs +++ b/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs @@ -10,7 +10,7 @@ internal class X11NativeOpenGLWrapper : INativeOpenGLWrapper { private IntPtr _display; private IntPtr _glContext; - private IntPtr _pbuffer; + private IntPtr _pBuffer; public unsafe void CreateContext(UIElement element) { @@ -58,7 +58,7 @@ public unsafe void CreateContext(UIElement element) } _glContext = GlxInterface.glXCreateNewContext(_display, bestFbc, GlxConsts.GLX_RGBA_TYPE, IntPtr.Zero, /* True */ 1); - _pbuffer = GlxInterface.glXCreatePbuffer(_display, bestFbc, new [] { (int)X11Helper.None }); + _pBuffer = GlxInterface.glXCreatePbuffer(_display, bestFbc, new [] { (int)X11Helper.None }); } public object CreateGLSilkNETHandle() => GL.GetApi(GlxInterface.glXGetProcAddress); @@ -67,19 +67,19 @@ public void DestroyContext() { using var lockDisposable = X11Helper.XLock(_display); - GlxInterface.glXDestroyPbuffer(_display, _pbuffer); + GlxInterface.glXDestroyPbuffer(_display, _pBuffer); GlxInterface.glXDestroyContext(_display, _glContext); _display = default; _glContext = default; - _pbuffer = default; + _pBuffer = default; } public IDisposable MakeCurrent() { var glContext = GlxInterface.glXGetCurrentContext(); var drawable = GlxInterface.glXGetCurrentDrawable(); - GlxInterface.glXMakeCurrent(_display, _pbuffer, _glContext); + GlxInterface.glXMakeCurrent(_display, _pBuffer, _glContext); return Disposable.Create(() => GlxInterface.glXMakeCurrent(_display, drawable, glContext)); } } diff --git a/src/Uno.UI/Uno.UI.Reference.csproj b/src/Uno.UI/Uno.UI.Reference.csproj index 517e50eaaf9d..2cdaf4c997dd 100644 --- a/src/Uno.UI/Uno.UI.Reference.csproj +++ b/src/Uno.UI/Uno.UI.Reference.csproj @@ -79,11 +79,6 @@ - - - - - $(AssemblyName).xml diff --git a/src/Uno.UI/Uno.UI.Wasm.csproj b/src/Uno.UI/Uno.UI.Wasm.csproj index eebf67292823..6c1261ccae5a 100644 --- a/src/Uno.UI/Uno.UI.Wasm.csproj +++ b/src/Uno.UI/Uno.UI.Wasm.csproj @@ -62,9 +62,6 @@ - - - From 436f47de0aebc21e7f0f57231951d05815d3092b Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 28 Aug 2024 21:08:44 +0300 Subject: [PATCH 76/94] chore: optimize copying gl output in uno only --- .../GLCanvasElement.shared.cs | 19 ++++++++++++++++++- src/Uno.UWP/AssemblyInfo.cs | 2 ++ src/Uno.UWP/Storage/Streams/Buffer.cs | 15 ++++++++++++++- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.shared.cs b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.shared.cs index 4e53d1a34de6..f535f6c3faeb 100644 --- a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.shared.cs +++ b/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.shared.cs @@ -7,6 +7,7 @@ using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media.Imaging; using Silk.NET.OpenGL; +using Buffer = Windows.Storage.Streams.Buffer; #if WINAPPSDK using Microsoft.Extensions.Logging; @@ -44,7 +45,9 @@ public abstract partial class GLCanvasElement : Grid 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. @@ -129,7 +132,9 @@ 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)) { @@ -171,7 +176,9 @@ 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)) { @@ -196,7 +203,9 @@ private void OnUnloaded(object sender, RoutedEventArgs routedEventArgs) _framebuffer = default; _textureColorBuffer = default; _renderBuffer = default; +#if WINAPPSDK _pixels = default; +#endif } private unsafe void Render() @@ -217,12 +226,20 @@ private unsafe void Render() RenderOverride(_gl); _gl.ReadBuffer(GLEnum.ColorAttachment0); - _gl.ReadPixels(0, 0, _width, _height, GLEnum.Bgra, GLEnum.UnsignedByte, (void*)_pixels); +#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(); } } diff --git a/src/Uno.UWP/AssemblyInfo.cs b/src/Uno.UWP/AssemblyInfo.cs index cc804c467095..87b8408fb584 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.Graphics")] + [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 From 7631bab6f28cdab67e5017b4a0560db5b8cf91d6 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 28 Aug 2024 21:55:22 +0300 Subject: [PATCH 77/94] chore: Uno.WinUI.Graphics -> Uno.WinUI.Graphics3D --- doc/articles/controls/GLCanvasElement.md | 4 ++-- .../GLCanvasElement.WinUI.cs | 2 +- .../GLCanvasElement.shared.cs | 2 +- .../Uno.WinUI.Graphics3D.csproj} | 0 src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj | 2 +- src/SamplesApp/SamplesApp.Windows/SamplesApp.Windows.csproj | 2 +- .../Windows_UI_Composition/RotatingCubeGlCanvasElement.cs | 2 +- .../Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs | 2 +- src/Uno.UI-Skia-only.slnf | 4 ++-- src/Uno.UI-Windows-only.slnf | 2 +- src/Uno.UI.Composition/AssemblyInfo.cs | 2 +- src/Uno.UI.Dispatching/AssemblyInfo.cs | 2 +- src/Uno.UI.sln | 2 +- src/Uno.UI/AssemblyInfo.cs | 2 +- src/Uno.UWP/AssemblyInfo.cs | 2 +- 15 files changed, 16 insertions(+), 16 deletions(-) rename src/AddIns/{Uno.WinUI.Graphics => Uno.WinUI.Graphics3D}/GLCanvasElement.WinUI.cs (99%) rename src/AddIns/{Uno.WinUI.Graphics => Uno.WinUI.Graphics3D}/GLCanvasElement.shared.cs (99%) rename src/AddIns/{Uno.WinUI.Graphics/Uno.WinUI.Graphics.csproj => Uno.WinUI.Graphics3D/Uno.WinUI.Graphics3D.csproj} (100%) diff --git a/doc/articles/controls/GLCanvasElement.md b/doc/articles/controls/GLCanvasElement.md index 04068e9890af..87d8ef478f39 100644 --- a/doc/articles/controls/GLCanvasElement.md +++ b/doc/articles/controls/GLCanvasElement.md @@ -2,12 +2,12 @@ uid: Uno.Controls.GLCanvasElement --- -## Uno.WinUI.Graphics.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.Graphics` package. +`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`. diff --git a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs b/src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.WinUI.cs similarity index 99% rename from src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs rename to src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.WinUI.cs index a6bbe86a96ea..2aba28ef8def 100644 --- a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.WinUI.cs +++ b/src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.WinUI.cs @@ -11,7 +11,7 @@ using Uno.Extensions; using Uno.Logging; -namespace Uno.WinUI.Graphics; +namespace Uno.WinUI.Graphics3D; public abstract partial class GLCanvasElement { diff --git a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.shared.cs b/src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.shared.cs similarity index 99% rename from src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.shared.cs rename to src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.shared.cs index f535f6c3faeb..b6c81841bb73 100644 --- a/src/AddIns/Uno.WinUI.Graphics/GLCanvasElement.shared.cs +++ b/src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.shared.cs @@ -20,7 +20,7 @@ using Uno.UI.Dispatching; #endif -namespace Uno.WinUI.Graphics; +namespace Uno.WinUI.Graphics3D; /// /// A that exposes the ability to draw 3D graphics using OpenGL and Silk.NET. diff --git a/src/AddIns/Uno.WinUI.Graphics/Uno.WinUI.Graphics.csproj b/src/AddIns/Uno.WinUI.Graphics3D/Uno.WinUI.Graphics3D.csproj similarity index 100% rename from src/AddIns/Uno.WinUI.Graphics/Uno.WinUI.Graphics.csproj rename to src/AddIns/Uno.WinUI.Graphics3D/Uno.WinUI.Graphics3D.csproj diff --git a/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj b/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj index 48b183000ba0..638a956cb47e 100644 --- a/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj +++ b/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj @@ -32,7 +32,7 @@ - + diff --git a/src/SamplesApp/SamplesApp.Windows/SamplesApp.Windows.csproj b/src/SamplesApp/SamplesApp.Windows/SamplesApp.Windows.csproj index b8ff65412697..b98591a265e3 100644 --- a/src/SamplesApp/SamplesApp.Windows/SamplesApp.Windows.csproj +++ b/src/SamplesApp/SamplesApp.Windows/SamplesApp.Windows.csproj @@ -76,7 +76,7 @@ - + {2569663d-293a-4a1d-bb84-aa8c7b4b7f92} Uno.UI.MSAL diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs index 4ee29979cb62..6ed584403a5f 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs @@ -16,7 +16,7 @@ using System.Diagnostics; using System.Numerics; using Silk.NET.OpenGL; -using Uno.WinUI.Graphics; +using Uno.WinUI.Graphics3D; namespace UITests.Shared.Windows_UI_Composition { diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs index ac6df2a8629b..7243a835caca 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs @@ -2,7 +2,7 @@ using System; using System.Drawing; using Silk.NET.OpenGL; -using Uno.WinUI.Graphics; +using Uno.WinUI.Graphics3D; namespace UITests.Shared.Windows_UI_Composition { diff --git a/src/Uno.UI-Skia-only.slnf b/src/Uno.UI-Skia-only.slnf index edf15b7c2e48..e64cf83ca352 100644 --- a/src/Uno.UI-Skia-only.slnf +++ b/src/Uno.UI-Skia-only.slnf @@ -6,7 +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.Graphics\\Uno.WinUI.Graphics.csproj", + "AddIns\\Uno.WinUI.Graphics3D\\Uno.WinUI.Graphics3D.csproj", "SamplesApp\\Benchmarks.Shared\\SamplesApp.Benchmarks.shproj", "SamplesApp\\SamplesApp.Shared\\SamplesApp.Shared.shproj", "SamplesApp\\SamplesApp.Skia.Generic\\SamplesApp.Skia.Generic.csproj", @@ -59,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 7b88489034a9..7610321c17f7 100644 --- a/src/Uno.UI-Windows-only.slnf +++ b/src/Uno.UI-Windows-only.slnf @@ -3,7 +3,7 @@ "path": "Uno.UI.sln", "projects": [ "AddIns\\Uno.UI.MSAL\\Uno.UI.MSAL.Windows.csproj", - "AddIns\\Uno.WinUI.Graphics\\Uno.WinUI.Graphics.csproj", + "AddIns\\Uno.WinUI.Graphics3D\\Uno.WinUI.Graphics3D.csproj", "SamplesApp\\Benchmarks.Shared\\SamplesApp.Benchmarks.shproj", "SamplesApp\\SamplesApp.Windows\\SamplesApp.Windows.csproj", "SamplesApp\\SamplesApp.Shared\\SamplesApp.Shared.shproj", diff --git a/src/Uno.UI.Composition/AssemblyInfo.cs b/src/Uno.UI.Composition/AssemblyInfo.cs index 1958a93eb34a..4d7f59fce5b3 100644 --- a/src/Uno.UI.Composition/AssemblyInfo.cs +++ b/src/Uno.UI.Composition/AssemblyInfo.cs @@ -16,7 +16,7 @@ [assembly: InternalsVisibleTo("SamplesApp.Wasm")] [assembly: InternalsVisibleTo("SamplesApp.Skia")] -[assembly: InternalsVisibleTo("Uno.WinUI.Graphics")] +[assembly: InternalsVisibleTo("Uno.WinUI.Graphics3D")] [assembly: InternalsVisibleTo("UnoIslandsSamplesApp")] [assembly: InternalsVisibleTo("UnoIslandsSamplesApp.Skia")] diff --git a/src/Uno.UI.Dispatching/AssemblyInfo.cs b/src/Uno.UI.Dispatching/AssemblyInfo.cs index 01cd0f05edcd..f64d35cdb8e4 100644 --- a/src/Uno.UI.Dispatching/AssemblyInfo.cs +++ b/src/Uno.UI.Dispatching/AssemblyInfo.cs @@ -16,7 +16,7 @@ [assembly: InternalsVisibleTo("SamplesApp.Wasm")] [assembly: InternalsVisibleTo("SamplesApp.Skia")] -[assembly: InternalsVisibleTo("Uno.WinUI.Graphics")] +[assembly: InternalsVisibleTo("Uno.WinUI.Graphics3D")] [assembly: InternalsVisibleTo("Uno")] diff --git a/src/Uno.UI.sln b/src/Uno.UI.sln index 773dd1078ba2..8f6ac696146c 100644 --- a/src/Uno.UI.sln +++ b/src/Uno.UI.sln @@ -315,7 +315,7 @@ 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.Graphics", "AddIns\Uno.WinUI.Graphics\Uno.WinUI.Graphics.csproj", "{0F62DA75-6AD9-4F58-B69C-D63CA9053E34}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Uno.WinUI.Graphics3D", "AddIns\Uno.WinUI.Graphics3D\Uno.WinUI.Graphics3D.csproj", "{0F62DA75-6AD9-4F58-B69C-D63CA9053E34}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/Uno.UI/AssemblyInfo.cs b/src/Uno.UI/AssemblyInfo.cs index 6385bf50077c..4d213d60830e 100644 --- a/src/Uno.UI/AssemblyInfo.cs +++ b/src/Uno.UI/AssemblyInfo.cs @@ -29,7 +29,7 @@ [assembly: InternalsVisibleTo("Uno.UI.MediaPlayer.Skia.Gtk")] [assembly: InternalsVisibleTo("Uno.UI.MediaPlayer.WebAssembly")] -[assembly: InternalsVisibleTo("Uno.WinUI.Graphics")] +[assembly: InternalsVisibleTo("Uno.WinUI.Graphics3D")] [assembly: AssemblyMetadata("IsTrimmable", "True")] diff --git a/src/Uno.UWP/AssemblyInfo.cs b/src/Uno.UWP/AssemblyInfo.cs index 87b8408fb584..25ba2f8285ee 100644 --- a/src/Uno.UWP/AssemblyInfo.cs +++ b/src/Uno.UWP/AssemblyInfo.cs @@ -16,7 +16,7 @@ [assembly: InternalsVisibleTo("Uno.UI.MediaPlayer.WebAssembly")] [assembly: InternalsVisibleTo("Uno.UI.XamlHost")] -[assembly: InternalsVisibleTo("Uno.WinUI.Graphics")] +[assembly: InternalsVisibleTo("Uno.WinUI.Graphics3D")] [assembly: InternalsVisibleTo("SamplesApp")] [assembly: InternalsVisibleTo("SamplesApp.Droid")] From 1980f69d80d47a10a41d3b5fff1bab02d62a7a55 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 28 Aug 2024 23:14:50 +0300 Subject: [PATCH 78/94] chore: consolodate more logic between wpf and winui --- .../GLCanvasElement.WinUI.cs | 273 ------------------ ...asElement.shared.cs => GLCanvasElement.cs} | 6 +- .../Uno.WinUI.Graphics3D.csproj | 4 + .../Extensions/WpfNativeOpenGLWrapper.cs | 70 +++-- .../Rendering/OpenGLWpfRenderer.cs | 38 +-- ...ds.cs => WindowsRenderingNativeMethods.cs} | 6 +- .../Uno.UI.Runtime.Skia.csproj | 4 + src/Uno.UI/Graphics/INativeOpenGLWrapper.cs | 35 +++ src/Uno.UI/Graphics/NativeOpenGLWrapper.cs | 32 -- 9 files changed, 119 insertions(+), 349 deletions(-) delete mode 100644 src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.WinUI.cs rename src/AddIns/Uno.WinUI.Graphics3D/{GLCanvasElement.shared.cs => GLCanvasElement.cs} (98%) rename src/Uno.UI.Runtime.Skia.Wpf/Rendering/{WpfRenderingNativeMethods.cs => WindowsRenderingNativeMethods.cs} (97%) create mode 100644 src/Uno.UI/Graphics/INativeOpenGLWrapper.cs delete mode 100644 src/Uno.UI/Graphics/NativeOpenGLWrapper.cs diff --git a/src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.WinUI.cs b/src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.WinUI.cs deleted file mode 100644 index 2aba28ef8def..000000000000 --- a/src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.WinUI.cs +++ /dev/null @@ -1,273 +0,0 @@ -#if WINAPPSDK - -using System; -using System.Runtime.InteropServices; -using Microsoft.Extensions.Logging; -using Microsoft.UI.Xaml; -using Silk.NET.Core.Contexts; -using Silk.NET.Core.Loader; -using Silk.NET.OpenGL; -using Uno.Disposables; -using Uno.Extensions; -using Uno.Logging; - -namespace Uno.WinUI.Graphics3D; - -public abstract partial class GLCanvasElement -{ - 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(); - } - - internal class WinUINativeOpenGLWrapper(Func getWindowFunc) : INativeOpenGLWrapper - { - private nint _hdc; - private nint _glContext; - - public void CreateContext(UIElement element) - { - var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(getWindowFunc()); - - _hdc = NativeMethods.GetDC(hwnd); - - NativeMethods.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.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 = NativeMethods.PFD_MAIN_PLANE; - - var pixelFormat = NativeMethods.ChoosePixelFormat(_hdc, ref pfd); - - // To inspect the chosen pixel format: - // WpfRenderingNativeMethods.PIXELFORMATDESCRIPTOR temp_pfd = default; - // WpfRenderingNativeMethods.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 (NativeMethods.SetPixelFormat(_hdc, pixelFormat, ref pfd) == 0) - { - if (this.Log().IsEnabled(LogLevel.Error)) - { - this.Log().Error($"SetPixelFormat failed"); - } - throw new InvalidOperationException("ChoosePixelFormat failed"); - } - - _glContext = NativeMethods.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 WpfGlNativeContext()); - - public void DestroyContext() - { - NativeMethods.wglDeleteContext(_glContext); - _glContext = default; - _hdc = default; - } - - public IDisposable MakeCurrent() - { - var glContext = NativeMethods.wglGetCurrentContext(); - var dc = NativeMethods.wglGetCurrentDC(); - NativeMethods.wglMakeCurrent(_hdc, _glContext); - return Disposable.Create(() => NativeMethods.wglMakeCurrent(dc, glContext)); - } - - // https://sharovarskyi.com/blog/posts/csharp-win32-opengl-silknet/ - private class WpfGlNativeContext : INativeContext - { - private readonly UnmanagedLibrary _l; - - public WpfGlNativeContext() - { - _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 = NativeMethods.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(); - } - } - - // https://sharovarskyi.com/blog/posts/csharp-win32-opengl-silknet/ - private class WinUINativeContext : INativeContext - { - private readonly UnmanagedLibrary _l; - - public WinUINativeContext() - { - _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 = NativeMethods.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(); - } - - private static class NativeMethods - { - [DllImport("user32.dll")] - internal static extern IntPtr GetDC(IntPtr hWnd); - - [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 int wglDeleteContext(IntPtr hglrc); - - [DllImport("opengl32.dll")] - internal static extern int wglMakeCurrent(IntPtr hdc, IntPtr hglrc); - - [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; - } -} - -#endif diff --git a/src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.shared.cs b/src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.cs similarity index 98% rename from src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.shared.cs rename to src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.cs index b6c81841bb73..aa6ca3566b20 100644 --- a/src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.shared.cs +++ b/src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.cs @@ -7,7 +7,6 @@ using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media.Imaging; using Silk.NET.OpenGL; -using Buffer = Windows.Storage.Streams.Buffer; #if WINAPPSDK using Microsoft.Extensions.Logging; @@ -18,6 +17,7 @@ using Uno.Foundation.Extensibility; using Uno.Graphics; using Uno.UI.Dispatching; +using Buffer = Windows.Storage.Streams.Buffer; #endif namespace Uno.WinUI.Graphics3D; @@ -33,7 +33,7 @@ public abstract partial class GLCanvasElement : Grid { private const int BytesPerPixel = 4; - private INativeOpenGLWrapper _nativeOpenGlWrapper; + private readonly INativeOpenGLWrapper _nativeOpenGlWrapper; private readonly uint _width; private readonly uint _height; @@ -183,7 +183,7 @@ private void OnUnloaded(object sender, RoutedEventArgs routedEventArgs) using (new GLStateDisposable(this)) { #if WINAPPSDK - if (NativeMethods.wglGetCurrentContext() == 0) + if (WindowsRenderingNativeMethods.wglGetCurrentContext() == 0) { if (this.Log().IsEnabled(LogLevel.Debug)) { diff --git a/src/AddIns/Uno.WinUI.Graphics3D/Uno.WinUI.Graphics3D.csproj b/src/AddIns/Uno.WinUI.Graphics3D/Uno.WinUI.Graphics3D.csproj index 0ccfc248d5c5..367099a959f9 100644 --- a/src/AddIns/Uno.WinUI.Graphics3D/Uno.WinUI.Graphics3D.csproj +++ b/src/AddIns/Uno.WinUI.Graphics3D/Uno.WinUI.Graphics3D.csproj @@ -30,6 +30,10 @@ + + + + diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs index 396fa0e31ff8..0025254a3b16 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs @@ -1,40 +1,68 @@ #nullable enable + using System; using System.Runtime.InteropServices; -using System.Windows.Interop; -using Microsoft.UI.Xaml; using Silk.NET.Core.Contexts; using Silk.NET.Core.Loader; using Silk.NET.OpenGL; + +#if 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 -namespace Uno.UI.Runtime.Skia.Wpf.Extensions; +#if WINAPPSDK +using WpfRenderingNativeMethods = Uno.WinUI.Graphics3D.WindowsRenderingNativeMethods; +#else +#endif -internal class WpfNativeOpenGLWrapper : INativeOpenGLWrapper +#if WINAPPSDK +namespace Uno.WinUI.Graphics3D; +#else +namespace Uno.UI.Runtime.Skia.Wpf.Extensions; +#endif + +#if WINAPPSDK +internal class WinUINativeOpenGLWrapper(Func getWindowFunc) +#else +internal class WpfNativeOpenGLWrapper +#endif + : INativeOpenGLWrapper { private nint _hdc; private nint _glContext; public void CreateContext(UIElement element) { +#if 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 = WpfRenderingNativeMethods.GetDC(hwnd); + _hdc = WindowsRenderingNativeMethods.GetDC(hwnd); - WpfRenderingNativeMethods.PIXELFORMATDESCRIPTOR pfd = new(); + WindowsRenderingNativeMethods.PIXELFORMATDESCRIPTOR pfd = new(); pfd.nSize = (ushort)Marshal.SizeOf(pfd); pfd.nVersion = 1; - pfd.dwFlags = WpfRenderingNativeMethods.PFD_DRAW_TO_WINDOW | WpfRenderingNativeMethods.PFD_SUPPORT_OPENGL | WpfRenderingNativeMethods.PFD_DOUBLEBUFFER; - pfd.iPixelType = WpfRenderingNativeMethods.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; @@ -42,9 +70,9 @@ public void CreateContext(UIElement element) pfd.cAlphaBits = 8; pfd.cDepthBits = 16; pfd.cStencilBits = 1; // anything > 0 is fine, we will most likely get 8 - pfd.iLayerType = WpfRenderingNativeMethods.PFD_MAIN_PLANE; + pfd.iLayerType = WindowsRenderingNativeMethods.PFD_MAIN_PLANE; - var pixelFormat = WpfRenderingNativeMethods.ChoosePixelFormat(_hdc, ref pfd); + var pixelFormat = WindowsRenderingNativeMethods.ChoosePixelFormat(_hdc, ref pfd); // To inspect the chosen pixel format: // WpfRenderingNativeMethods.PIXELFORMATDESCRIPTOR temp_pfd = default; @@ -59,7 +87,7 @@ public void CreateContext(UIElement element) throw new InvalidOperationException("ChoosePixelFormat failed"); } - if (WpfRenderingNativeMethods.SetPixelFormat(_hdc, pixelFormat, ref pfd) == 0) + if (WindowsRenderingNativeMethods.SetPixelFormat(_hdc, pixelFormat, ref pfd) == 0) { if (this.Log().IsEnabled(LogLevel.Error)) { @@ -68,7 +96,7 @@ public void CreateContext(UIElement element) throw new InvalidOperationException("ChoosePixelFormat failed"); } - _glContext = WpfRenderingNativeMethods.wglCreateContext(_hdc); + _glContext = WindowsRenderingNativeMethods.wglCreateContext(_hdc); if (_glContext == IntPtr.Zero) { @@ -80,29 +108,29 @@ public void CreateContext(UIElement element) } } - public object CreateGLSilkNETHandle() => GL.GetApi(new WpfGlNativeContext()); + public object CreateGLSilkNETHandle() => GL.GetApi(new WindowsGlNativeContext()); public void DestroyContext() { - WpfRenderingNativeMethods.wglDeleteContext(_glContext); + WindowsRenderingNativeMethods.wglDeleteContext(_glContext); _glContext = default; _hdc = default; } public IDisposable MakeCurrent() { - var glContext = WpfRenderingNativeMethods.wglGetCurrentContext(); - var dc = WpfRenderingNativeMethods.wglGetCurrentDC(); - WpfRenderingNativeMethods.wglMakeCurrent(_hdc, _glContext); - return Disposable.Create(() => WpfRenderingNativeMethods.wglMakeCurrent(dc, glContext)); + var glContext = WindowsRenderingNativeMethods.wglGetCurrentContext(); + var dc = WindowsRenderingNativeMethods.wglGetCurrentDC(); + WindowsRenderingNativeMethods.wglMakeCurrent(_hdc, _glContext); + return Disposable.Create(() => WindowsRenderingNativeMethods.wglMakeCurrent(dc, glContext)); } // https://sharovarskyi.com/blog/posts/csharp-win32-opengl-silknet/ - private class WpfGlNativeContext : INativeContext + private class WindowsGlNativeContext : INativeContext { private readonly UnmanagedLibrary _l; - public WpfGlNativeContext() + public WindowsGlNativeContext() { _l = new UnmanagedLibrary("opengl32.dll"); if (_l.Handle == IntPtr.Zero) @@ -118,7 +146,7 @@ public bool TryGetProcAddress(string proc, out nint addr, int? slot = null) return true; } - addr = WpfRenderingNativeMethods.wglGetProcAddress(proc); + addr = WindowsRenderingNativeMethods.wglGetProcAddress(proc); return addr != IntPtr.Zero; } diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Rendering/OpenGLWpfRenderer.cs b/src/Uno.UI.Runtime.Skia.Wpf/Rendering/OpenGLWpfRenderer.cs index 97b24b28eff1..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 = WpfRenderingNativeMethods.GetDC(_hwnd); + _hdc = WindowsRenderingNativeMethods.GetDC(_hwnd); // Set the pixel format - WpfRenderingNativeMethods.PIXELFORMATDESCRIPTOR pfd = new(); + WindowsRenderingNativeMethods.PIXELFORMATDESCRIPTOR pfd = new(); pfd.nSize = (ushort)Marshal.SizeOf(pfd); pfd.nVersion = 1; - pfd.dwFlags = WpfRenderingNativeMethods.PFD_DRAW_TO_WINDOW | WpfRenderingNativeMethods.PFD_SUPPORT_OPENGL | WpfRenderingNativeMethods.PFD_DOUBLEBUFFER; - pfd.iPixelType = WpfRenderingNativeMethods.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 = WpfRenderingNativeMethods.PFD_MAIN_PLANE; + pfd.iLayerType = WindowsRenderingNativeMethods.PFD_MAIN_PLANE; // Choose the best matching pixel format - _pixelFormat = WpfRenderingNativeMethods.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 (WpfRenderingNativeMethods.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 = WpfRenderingNativeMethods.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 - WpfRenderingNativeMethods.wglMakeCurrent(_hdc, _glContext); + WindowsRenderingNativeMethods.wglMakeCurrent(_hdc, _glContext); #pragma warning restore CA1806 // Do not ignore method results - var version = WpfRenderingNativeMethods.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 - WpfRenderingNativeMethods.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 - WpfRenderingNativeMethods.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) } } - WpfRenderingNativeMethods.glClear(WpfRenderingNativeMethods.GL_COLOR_BUFFER_BIT | WpfRenderingNativeMethods.GL_STENCIL_BUFFER_BIT | WpfRenderingNativeMethods.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(); - WpfRenderingNativeMethods.glReadPixels(0, 0, width, height, WpfRenderingNativeMethods.GL_BGRA_EXT, WpfRenderingNativeMethods.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() { - WpfRenderingNativeMethods.glGetIntegerv(WpfRenderingNativeMethods.GL_FRAMEBUFFER_BINDING, out var framebuffer); - WpfRenderingNativeMethods.glGetIntegerv(WpfRenderingNativeMethods.GL_STENCIL_BITS, out var stencil); - WpfRenderingNativeMethods.glGetIntegerv(WpfRenderingNativeMethods.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 - WpfRenderingNativeMethods.wglDeleteContext(_glContext); - WpfRenderingNativeMethods.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/WpfRenderingNativeMethods.cs b/src/Uno.UI.Runtime.Skia.Wpf/Rendering/WindowsRenderingNativeMethods.cs similarity index 97% rename from src/Uno.UI.Runtime.Skia.Wpf/Rendering/WpfRenderingNativeMethods.cs rename to src/Uno.UI.Runtime.Skia.Wpf/Rendering/WindowsRenderingNativeMethods.cs index 1fa054e4ff9c..c057cdb6de66 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Rendering/WpfRenderingNativeMethods.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Rendering/WindowsRenderingNativeMethods.cs @@ -3,9 +3,13 @@ using System; using System.Runtime.InteropServices; +#if WINAPPSDK +namespace Uno.WinUI.Graphics3D; +#else namespace Uno.UI.Runtime.Skia.Wpf.Rendering; +#endif -internal static class WpfRenderingNativeMethods +internal static class WindowsRenderingNativeMethods { [DllImport("user32.dll")] internal static extern IntPtr GetDC(IntPtr hWnd); diff --git a/src/Uno.UI.Runtime.Skia/Uno.UI.Runtime.Skia.csproj b/src/Uno.UI.Runtime.Skia/Uno.UI.Runtime.Skia.csproj index 9944a9e0d315..5aa339c2dc30 100644 --- a/src/Uno.UI.Runtime.Skia/Uno.UI.Runtime.Skia.csproj +++ b/src/Uno.UI.Runtime.Skia/Uno.UI.Runtime.Skia.csproj @@ -30,5 +30,9 @@ + + + + diff --git a/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs b/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs new file mode 100644 index 000000000000..fd61fff7e7fa --- /dev/null +++ b/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs @@ -0,0 +1,35 @@ +using System; +using Microsoft.UI.Xaml; + +#if WINAPPSDK +namespace Uno.WinUI.Graphics3D; +#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.UI/Graphics/NativeOpenGLWrapper.cs b/src/Uno.UI/Graphics/NativeOpenGLWrapper.cs deleted file mode 100644 index 5873c7f31199..000000000000 --- a/src/Uno.UI/Graphics/NativeOpenGLWrapper.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using Microsoft.UI.Xaml; - -namespace Uno.Graphics -{ - 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(); - } -} From 81592f479f8b317bad0a4ff3c6998e4ad7ad98d6 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 28 Aug 2024 23:27:32 +0300 Subject: [PATCH 79/94] chore: remove unnecessary modifications --- src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.cs | 6 +++--- src/Uno.UI.Composition/AssemblyInfo.cs | 2 -- src/Uno.UI.Runtime.Skia/Uno.UI.Runtime.Skia.csproj | 4 ---- src/Uno.UI/UI/Xaml/XamlRoot.crossruntime.cs | 2 +- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.cs b/src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.cs index aa6ca3566b20..788891e26611 100644 --- a/src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.cs +++ b/src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.cs @@ -1,7 +1,5 @@ using System; using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.WindowsRuntime; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; @@ -9,6 +7,8 @@ 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; @@ -123,7 +123,7 @@ protected GLCanvasElement(uint width, uint height, Func? getWindowFunc) /// #if WINAPPSDK public void Invalidate() => DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, Render); -#else +#else // WPF hangs if we attempt to enqueue on Low inside RenderOverride public void Invalidate() => NativeDispatcher.Main.Enqueue(Render, NativeDispatcherPriority.Idle); #endif diff --git a/src/Uno.UI.Composition/AssemblyInfo.cs b/src/Uno.UI.Composition/AssemblyInfo.cs index 4d7f59fce5b3..8d471f7539da 100644 --- a/src/Uno.UI.Composition/AssemblyInfo.cs +++ b/src/Uno.UI.Composition/AssemblyInfo.cs @@ -16,8 +16,6 @@ [assembly: InternalsVisibleTo("SamplesApp.Wasm")] [assembly: InternalsVisibleTo("SamplesApp.Skia")] -[assembly: InternalsVisibleTo("Uno.WinUI.Graphics3D")] - [assembly: InternalsVisibleTo("UnoIslandsSamplesApp")] [assembly: InternalsVisibleTo("UnoIslandsSamplesApp.Skia")] [assembly: System.Reflection.AssemblyMetadata("IsTrimmable", "True")] diff --git a/src/Uno.UI.Runtime.Skia/Uno.UI.Runtime.Skia.csproj b/src/Uno.UI.Runtime.Skia/Uno.UI.Runtime.Skia.csproj index 5aa339c2dc30..9944a9e0d315 100644 --- a/src/Uno.UI.Runtime.Skia/Uno.UI.Runtime.Skia.csproj +++ b/src/Uno.UI.Runtime.Skia/Uno.UI.Runtime.Skia.csproj @@ -30,9 +30,5 @@ - - - - diff --git a/src/Uno.UI/UI/Xaml/XamlRoot.crossruntime.cs b/src/Uno.UI/UI/Xaml/XamlRoot.crossruntime.cs index 45de4466a344..4e9c2a4d5fc5 100644 --- a/src/Uno.UI/UI/Xaml/XamlRoot.crossruntime.cs +++ b/src/Uno.UI/UI/Xaml/XamlRoot.crossruntime.cs @@ -51,7 +51,7 @@ private void DispatchQueueRender() _renderQueued = false; InvalidateRender(); } - }, NativeDispatcherPriority.Idle); + }); } /// From 0cb7946d1a951d1ceae95cde1f0931bd5eb65e2b Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Wed, 28 Aug 2024 23:42:14 +0300 Subject: [PATCH 80/94] chore: add to ci slnf --- build/filters/Uno.UI-packages-skia.slnf | 1 + build/filters/Uno.UI-packages-windows.slnf | 1 + 2 files changed, 2 insertions(+) diff --git a/build/filters/Uno.UI-packages-skia.slnf b/build/filters/Uno.UI-packages-skia.slnf index 276ae5d418b0..01f0e0faa00c 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.Graphics3D\\Uno.WinUI.Graphics3D.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/filters/Uno.UI-packages-windows.slnf b/build/filters/Uno.UI-packages-windows.slnf index 8375e1c74b01..990dfb97ef02 100644 --- a/build/filters/Uno.UI-packages-windows.slnf +++ b/build/filters/Uno.UI-packages-windows.slnf @@ -3,6 +3,7 @@ "path": "..\\..\\src\\Uno.UI.sln", "projects": [ "AddIns\\Uno.UI.MSAL\\Uno.UI.MSAL.Windows.csproj", + "AddIns\\Uno.WinUI.Graphics3D\\Uno.WinUI.Graphics3D.csproj", "Uno.UI.Toolkit\\Uno.UI.Toolkit.Windows.csproj" ] } From 8a86f8e78ed9b4594b03ef1cdf1d7f2f21accd38 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 29 Aug 2024 00:00:04 +0300 Subject: [PATCH 81/94] chore: formatting --- doc/articles/controls/GLCanvasElement.md | 4 +-- .../Extensions/WpfNativeOpenGLWrapper.cs | 27 ++++++++++++++++--- .../X11NativeOpenGLWrapper.cs | 2 +- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/doc/articles/controls/GLCanvasElement.md b/doc/articles/controls/GLCanvasElement.md index 87d8ef478f39..668504fe6059 100644 --- a/doc/articles/controls/GLCanvasElement.md +++ b/doc/articles/controls/GLCanvasElement.md @@ -84,10 +84,10 @@ public partial class GLCanvasElementExample : UserControl # __SKIA__ || WINAPPSDK public class SimpleTriangleGlCanvasElement() #if __SKIA__ - : GLCanvasElement(1200, 800, null) + : 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 */) + : GLCanvasElement(1200, 800, /* your getWindowFunc */) #endif { private uint _vao; diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs index 0025254a3b16..8afac3f75718 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs @@ -112,7 +112,13 @@ public void CreateContext(UIElement element) public void DestroyContext() { - WindowsRenderingNativeMethods.wglDeleteContext(_glContext); + if (WindowsRenderingNativeMethods.wglDeleteContext(_glContext) == 0) + { + if (this.Log().IsEnabled(LogLevel.Error)) + { + this.Log().Error($"{nameof(WindowsRenderingNativeMethods.wglDeleteContext)} failed."); + } + } _glContext = default; _hdc = default; } @@ -121,8 +127,23 @@ public IDisposable MakeCurrent() { var glContext = WindowsRenderingNativeMethods.wglGetCurrentContext(); var dc = WindowsRenderingNativeMethods.wglGetCurrentDC(); - WindowsRenderingNativeMethods.wglMakeCurrent(_hdc, _glContext); - return Disposable.Create(() => WindowsRenderingNativeMethods.wglMakeCurrent(dc, glContext)); + 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/ diff --git a/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs index bd29323ece3d..05b90001ad7f 100644 --- a/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs +++ b/src/Uno.UI.Runtime.Skia.X11/X11NativeOpenGLWrapper.cs @@ -58,7 +58,7 @@ public unsafe void CreateContext(UIElement element) } _glContext = GlxInterface.glXCreateNewContext(_display, bestFbc, GlxConsts.GLX_RGBA_TYPE, IntPtr.Zero, /* True */ 1); - _pBuffer = GlxInterface.glXCreatePbuffer(_display, bestFbc, new [] { (int)X11Helper.None }); + _pBuffer = GlxInterface.glXCreatePbuffer(_display, bestFbc, new[] { (int)X11Helper.None }); } public object CreateGLSilkNETHandle() => GL.GetApi(GlxInterface.glXGetProcAddress); From 4676bd4cd87e0315cb90a277d5618dd329492a88 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 29 Aug 2024 01:41:00 +0300 Subject: [PATCH 82/94] chore: build error --- src/AddIns/Uno.WinUI.Graphics3D/Uno.WinUI.Graphics3D.csproj | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/AddIns/Uno.WinUI.Graphics3D/Uno.WinUI.Graphics3D.csproj b/src/AddIns/Uno.WinUI.Graphics3D/Uno.WinUI.Graphics3D.csproj index 367099a959f9..bd82e197efef 100644 --- a/src/AddIns/Uno.WinUI.Graphics3D/Uno.WinUI.Graphics3D.csproj +++ b/src/AddIns/Uno.WinUI.Graphics3D/Uno.WinUI.Graphics3D.csproj @@ -19,7 +19,7 @@ - + win-x86;win-x64;win-arm64 $(DefineConstants);WINAPPSDK @@ -39,8 +39,6 @@ - - From f50340335caeed56d277dc4167d3df8b34b928ab Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Fri, 30 Aug 2024 16:58:51 +0300 Subject: [PATCH 83/94] chore: add reference to UnoIslandsSamplesApp.Skia.csproj --- .../UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj b/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj index 23c15e50a3d6..8d87ccee44d4 100644 --- a/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj +++ b/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj @@ -18,6 +18,7 @@ + From 47dcd7257915ddf0b911695ae6dd5130b8c0adda Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 2 Sep 2024 16:14:59 +0300 Subject: [PATCH 84/94] chore: Graphics3D -> Graphics3DGL --- build/filters/Uno.UI-packages-skia.slnf | 2 +- build/filters/Uno.UI-packages-windows.slnf | 2 +- .../GLCanvasElement.cs | 2 +- .../Uno.WinUI.Graphics3DGL.csproj} | 0 src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj | 2 +- src/SamplesApp/SamplesApp.Windows/SamplesApp.Windows.csproj | 2 +- .../Windows_UI_Composition/RotatingCubeGlCanvasElement.cs | 2 +- .../Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs | 2 +- .../UnoIslandsSamplesApp.Skia.csproj | 2 +- src/Uno.UI-Skia-only.slnf | 2 +- src/Uno.UI-Windows-only.slnf | 4 ++-- src/Uno.UI.Dispatching/AssemblyInfo.cs | 2 +- .../Extensions/WpfNativeOpenGLWrapper.cs | 4 ++-- .../Rendering/WindowsRenderingNativeMethods.cs | 2 +- src/Uno.UI.sln | 2 +- src/Uno.UI/AssemblyInfo.cs | 2 +- src/Uno.UI/Graphics/INativeOpenGLWrapper.cs | 2 +- src/Uno.UWP/AssemblyInfo.cs | 2 +- 18 files changed, 19 insertions(+), 19 deletions(-) rename src/AddIns/{Uno.WinUI.Graphics3D => Uno.WinUI.Graphics3DGL}/GLCanvasElement.cs (99%) rename src/AddIns/{Uno.WinUI.Graphics3D/Uno.WinUI.Graphics3D.csproj => Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj} (100%) diff --git a/build/filters/Uno.UI-packages-skia.slnf b/build/filters/Uno.UI-packages-skia.slnf index 01f0e0faa00c..acc27dca11a9 100644 --- a/build/filters/Uno.UI-packages-skia.slnf +++ b/build/filters/Uno.UI-packages-skia.slnf @@ -6,7 +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.Graphics3D\\Uno.WinUI.Graphics3D.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/filters/Uno.UI-packages-windows.slnf b/build/filters/Uno.UI-packages-windows.slnf index 990dfb97ef02..6370f0e35feb 100644 --- a/build/filters/Uno.UI-packages-windows.slnf +++ b/build/filters/Uno.UI-packages-windows.slnf @@ -3,7 +3,7 @@ "path": "..\\..\\src\\Uno.UI.sln", "projects": [ "AddIns\\Uno.UI.MSAL\\Uno.UI.MSAL.Windows.csproj", - "AddIns\\Uno.WinUI.Graphics3D\\Uno.WinUI.Graphics3D.csproj", + "AddIns\\Uno.WinUI.Graphics3DGL\\Uno.WinUI.Graphics3DGL.csproj", "Uno.UI.Toolkit\\Uno.UI.Toolkit.Windows.csproj" ] } diff --git a/src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.cs b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs similarity index 99% rename from src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.cs rename to src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs index 788891e26611..bdcf860cab63 100644 --- a/src/AddIns/Uno.WinUI.Graphics3D/GLCanvasElement.cs +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.cs @@ -20,7 +20,7 @@ using Buffer = Windows.Storage.Streams.Buffer; #endif -namespace Uno.WinUI.Graphics3D; +namespace Uno.WinUI.Graphics3DGL; /// /// A that exposes the ability to draw 3D graphics using OpenGL and Silk.NET. diff --git a/src/AddIns/Uno.WinUI.Graphics3D/Uno.WinUI.Graphics3D.csproj b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj similarity index 100% rename from src/AddIns/Uno.WinUI.Graphics3D/Uno.WinUI.Graphics3D.csproj rename to src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj diff --git a/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj b/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj index 638a956cb47e..bbc751c691f6 100644 --- a/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj +++ b/src/SamplesApp/SamplesApp.Skia/SamplesApp.Skia.csproj @@ -32,7 +32,7 @@ - + diff --git a/src/SamplesApp/SamplesApp.Windows/SamplesApp.Windows.csproj b/src/SamplesApp/SamplesApp.Windows/SamplesApp.Windows.csproj index b98591a265e3..fface3160006 100644 --- a/src/SamplesApp/SamplesApp.Windows/SamplesApp.Windows.csproj +++ b/src/SamplesApp/SamplesApp.Windows/SamplesApp.Windows.csproj @@ -76,7 +76,7 @@ - + {2569663d-293a-4a1d-bb84-aa8c7b4b7f92} Uno.UI.MSAL diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs index 6ed584403a5f..0ab7ad230cf1 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/RotatingCubeGlCanvasElement.cs @@ -16,7 +16,7 @@ using System.Diagnostics; using System.Numerics; using Silk.NET.OpenGL; -using Uno.WinUI.Graphics3D; +using Uno.WinUI.Graphics3DGL; namespace UITests.Shared.Windows_UI_Composition { diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs index 7243a835caca..27261b1eba4b 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SimpleTriangleGlCanvasElement.cs @@ -2,7 +2,7 @@ using System; using System.Drawing; using Silk.NET.OpenGL; -using Uno.WinUI.Graphics3D; +using Uno.WinUI.Graphics3DGL; namespace UITests.Shared.Windows_UI_Composition { diff --git a/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj b/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj index 8d87ccee44d4..726435ce45e0 100644 --- a/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj +++ b/src/SamplesApp/UnoIslandsSamplesApp.Skia/UnoIslandsSamplesApp.Skia.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Uno.UI-Skia-only.slnf b/src/Uno.UI-Skia-only.slnf index e64cf83ca352..1180c2e0875a 100644 --- a/src/Uno.UI-Skia-only.slnf +++ b/src/Uno.UI-Skia-only.slnf @@ -6,7 +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.Graphics3D\\Uno.WinUI.Graphics3D.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", diff --git a/src/Uno.UI-Windows-only.slnf b/src/Uno.UI-Windows-only.slnf index 7610321c17f7..fda7479bf4d5 100644 --- a/src/Uno.UI-Windows-only.slnf +++ b/src/Uno.UI-Windows-only.slnf @@ -3,7 +3,7 @@ "path": "Uno.UI.sln", "projects": [ "AddIns\\Uno.UI.MSAL\\Uno.UI.MSAL.Windows.csproj", - "AddIns\\Uno.WinUI.Graphics3D\\Uno.WinUI.Graphics3D.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", @@ -16,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 f64d35cdb8e4..0421ba0ee57d 100644 --- a/src/Uno.UI.Dispatching/AssemblyInfo.cs +++ b/src/Uno.UI.Dispatching/AssemblyInfo.cs @@ -16,7 +16,7 @@ [assembly: InternalsVisibleTo("SamplesApp.Wasm")] [assembly: InternalsVisibleTo("SamplesApp.Skia")] -[assembly: InternalsVisibleTo("Uno.WinUI.Graphics3D")] +[assembly: InternalsVisibleTo("Uno.WinUI.Graphics3DGL")] [assembly: InternalsVisibleTo("Uno")] diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs index 8afac3f75718..d127194c062f 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs @@ -24,12 +24,12 @@ #endif #if WINAPPSDK -using WpfRenderingNativeMethods = Uno.WinUI.Graphics3D.WindowsRenderingNativeMethods; +using WpfRenderingNativeMethods = Uno.WinUI.Graphics3DGL.WindowsRenderingNativeMethods; #else #endif #if WINAPPSDK -namespace Uno.WinUI.Graphics3D; +namespace Uno.WinUI.Graphics3DGL; #else namespace Uno.UI.Runtime.Skia.Wpf.Extensions; #endif diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Rendering/WindowsRenderingNativeMethods.cs b/src/Uno.UI.Runtime.Skia.Wpf/Rendering/WindowsRenderingNativeMethods.cs index c057cdb6de66..e7fd6f0611d7 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Rendering/WindowsRenderingNativeMethods.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Rendering/WindowsRenderingNativeMethods.cs @@ -4,7 +4,7 @@ using System.Runtime.InteropServices; #if WINAPPSDK -namespace Uno.WinUI.Graphics3D; +namespace Uno.WinUI.Graphics3DGL; #else namespace Uno.UI.Runtime.Skia.Wpf.Rendering; #endif diff --git a/src/Uno.UI.sln b/src/Uno.UI.sln index 8f6ac696146c..599d35388e04 100644 --- a/src/Uno.UI.sln +++ b/src/Uno.UI.sln @@ -315,7 +315,7 @@ 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.Graphics3D", "AddIns\Uno.WinUI.Graphics3D\Uno.WinUI.Graphics3D.csproj", "{0F62DA75-6AD9-4F58-B69C-D63CA9053E34}" +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 diff --git a/src/Uno.UI/AssemblyInfo.cs b/src/Uno.UI/AssemblyInfo.cs index 4d213d60830e..8b4faf31a0d1 100644 --- a/src/Uno.UI/AssemblyInfo.cs +++ b/src/Uno.UI/AssemblyInfo.cs @@ -29,7 +29,7 @@ [assembly: InternalsVisibleTo("Uno.UI.MediaPlayer.Skia.Gtk")] [assembly: InternalsVisibleTo("Uno.UI.MediaPlayer.WebAssembly")] -[assembly: InternalsVisibleTo("Uno.WinUI.Graphics3D")] +[assembly: InternalsVisibleTo("Uno.WinUI.Graphics3DGL")] [assembly: AssemblyMetadata("IsTrimmable", "True")] diff --git a/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs b/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs index fd61fff7e7fa..98433b996acf 100644 --- a/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs +++ b/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs @@ -2,7 +2,7 @@ using Microsoft.UI.Xaml; #if WINAPPSDK -namespace Uno.WinUI.Graphics3D; +namespace Uno.WinUI.Graphics3DGL; #else namespace Uno.Graphics; #endif diff --git a/src/Uno.UWP/AssemblyInfo.cs b/src/Uno.UWP/AssemblyInfo.cs index 25ba2f8285ee..2f199f5d6d22 100644 --- a/src/Uno.UWP/AssemblyInfo.cs +++ b/src/Uno.UWP/AssemblyInfo.cs @@ -16,7 +16,7 @@ [assembly: InternalsVisibleTo("Uno.UI.MediaPlayer.WebAssembly")] [assembly: InternalsVisibleTo("Uno.UI.XamlHost")] -[assembly: InternalsVisibleTo("Uno.WinUI.Graphics3D")] +[assembly: InternalsVisibleTo("Uno.WinUI.Graphics3DGL")] [assembly: InternalsVisibleTo("SamplesApp")] [assembly: InternalsVisibleTo("SamplesApp.Droid")] From c4e7a293de49aecad4d539cdf308d2c9da4f3cee Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 2 Sep 2024 16:18:57 +0300 Subject: [PATCH 85/94] chore: add an UnoFeature for GLCanvasElement --- src/Uno.Sdk/Sdk/Sdk.props.buildschema.json | 3 +++ src/Uno.Sdk/UnoFeature.cs | 5 ++++- src/Uno.Sdk/packages.json | 3 ++- .../Uno.Implicit.Packages.ProjectSystem.Desktop.targets | 4 ++++ .../Uno.Implicit.Packages.ProjectSystem.WinAppSdk.targets | 4 ++++ 5 files changed, 17 insertions(+), 2 deletions(-) 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 d8f77138e1ed..9b73026bf05d 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" /> + From cf7ab56535851e013a7e434a4c0cf7295b331d5c Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 2 Sep 2024 16:59:49 +0300 Subject: [PATCH 86/94] chore: don't build Graphics3DGL with the netX.0-windows10 SDK for skia --- build/ci/.azure-devops-package-netcoremobile.yml | 2 +- .../Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/build/ci/.azure-devops-package-netcoremobile.yml b/build/ci/.azure-devops-package-netcoremobile.yml index dc4975e5c3b1..e73f36d7894b 100644 --- a/build/ci/.azure-devops-package-netcoremobile.yml +++ b/build/ci/.azure-devops-package-netcoremobile.yml @@ -60,7 +60,7 @@ jobs: msbuildLocationMethod: version msbuildVersion: latest msbuildArchitecture: x86 - msbuildArguments: /r /m /v:m /p:Configuration=Release /detailedsummary /bl:$(build.artifactstagingdirectory)/build-$(GitVersion.FullSemVer)-windows-$(XAML_FLAVOR_BUILD)-binaries.binlog + msbuildArguments: /r /m /v:m /p:Configuration=Release /p:BuildGraphics3DGLForWindows=true /detailedsummary /bl:$(build.artifactstagingdirectory)/build-$(GitVersion.FullSemVer)-windows-$(XAML_FLAVOR_BUILD)-binaries.binlog clean: false restoreNugetPackages: false logProjectEvents: false diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj index bd82e197efef..206b1a84f03f 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj @@ -1,9 +1,7 @@  - - $(NetSkiaPreviousAndCurrent); - $(NetPrevious)-windows10.0.19041.0 - + $(NetSkiaPreviousAndCurrent) + $(NetPrevious)-windows10.0.19041.0 enable Uno.WinUI.GLCanvasElement From 94b01ba73c15782fec6ed54b33168983938c1b95 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 2 Sep 2024 17:01:43 +0300 Subject: [PATCH 87/94] chore: remove SKCanvasElement --- .../UITests.Shared/UITests.Shared.projitems | 8 -- .../SKCanvasElementImpl.skia.cs | 136 ------------------ .../SKCanvasElement_Simple.xaml | 24 ---- .../SKCanvasElement_Simple.xaml.cs | 18 --- .../Given_SKCanvasElement.skia.cs | 53 ------- .../SKCanvasElement.SKCanvasVisual.skia.cs | 26 ---- .../Graphics/SKCanvasElement.crossruntime.cs | 23 --- .../UI/Xaml/Graphics/SKCanvasElement.skia.cs | 77 ---------- 8 files changed, 365 deletions(-) delete mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElementImpl.skia.cs delete mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml delete mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml.cs delete mode 100644 src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs delete mode 100644 src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.SKCanvasVisual.skia.cs delete mode 100644 src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.crossruntime.cs delete mode 100644 src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.skia.cs diff --git a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems index 368015d7831e..13e2c051e4cd 100644 --- a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems +++ b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems @@ -4694,10 +4694,6 @@ Designer MSBuild:Compile - - Designer - MSBuild:Compile - Designer MSBuild:Compile @@ -6041,7 +6037,6 @@ - CloseRequestedTests.xaml @@ -8293,9 +8288,6 @@ VisualSurfaceTests.xaml - - SKCanvasElement_Simple.xaml - GLCanvasElement_Simple.xaml diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElementImpl.skia.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElementImpl.skia.cs deleted file mode 100644 index dd3c99d8c3f6..000000000000 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElementImpl.skia.cs +++ /dev/null @@ -1,136 +0,0 @@ -#if __SKIA__ -using System; -using Windows.Foundation; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -using SkiaSharp; - -namespace UITests.Shared.Windows_UI_Composition; - -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 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; - } - } -} -#endif diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml deleted file mode 100644 index 904ef6496384..000000000000 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml.cs deleted file mode 100644 index f6d3d39688d8..000000000000 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Composition/SKCanvasElement_Simple.xaml.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Uno.UI.Samples.Controls; -using Microsoft.UI.Xaml.Controls; - -namespace UITests.Shared.Windows_UI_Composition -{ - [Sample("Microsoft.UI.Composition")] - public sealed partial class SKCanvasElement_Simple : UserControl - { -#if __SKIA__ - public int MaxSampleIndex => SKCanvasElementImpl.SampleCount - 1; -#endif - - public SKCanvasElement_Simple() - { - this.InitializeComponent(); - } - } -} diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs deleted file mode 100644 index 37a2930177d7..000000000000 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_SKCanvasElement.skia.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Drawing; -using System.Threading.Tasks; -using Microsoft.UI; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -using Private.Infrastructure; -using SkiaSharp; -using Uno.UI.RuntimeTests.Helpers; -using Size = Windows.Foundation.Size; -namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls; - -[TestClass] -[RunsOnUIThread] -public class Given_SKCanvasElement -{ - [TestMethod] - public async Task When_Clipped_Inside_ScrollViewer() - { - var SUT = new BlueFillSKCanvasElement - { - Height = 400, - Width = 400 - }; - - var border = new Border - { - BorderBrush = Microsoft.UI.Colors.Green, - Height = 400, - Child = new ScrollViewer - { - VerticalAlignment = VerticalAlignment.Top, - Height = 100, - Background = Microsoft.UI.Colors.Red, - Content = SUT - } - }; - - await UITestHelper.Load(border); - - var bitmap = await UITestHelper.ScreenShot(border); - - ImageAssert.HasColorInRectangle(bitmap, new Rectangle(0, 0, 400, 300), Microsoft.UI.Colors.Blue); - ImageAssert.DoesNotHaveColorInRectangle(bitmap, new Rectangle(0, 101, 400, 299), Microsoft.UI.Colors.Blue); - } - - private class BlueFillSKCanvasElement : SKCanvasElement - { - protected override void RenderOverride(SKCanvas canvas, Size area) - { - canvas.DrawRect(new SKRect(0, 0, (float)area.Width, (float)area.Height), new SKPaint { Color = SKColors.Blue }); - } - } -} diff --git a/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.SKCanvasVisual.skia.cs b/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.SKCanvasVisual.skia.cs deleted file mode 100644 index d4dc8ce40189..000000000000 --- a/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.SKCanvasVisual.skia.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Numerics; -using Microsoft.UI.Composition; -using SkiaSharp; - -namespace Microsoft.UI.Xaml.Controls; - -/// -/// A that exposes the ability to draw directly on the window using SkiaSharp. -/// -/// This is only available on skia-based targets. -public abstract partial class SKCanvasElement -{ - private class SKCanvasVisual(SKCanvasElement owner, Compositor compositor) : Visual(compositor) - { - internal override void Paint(in PaintingSession session) - { - session.Canvas.Save(); - // clipping here guards against a naked canvas.Clear() call which would wipe out the entire window. - session.Canvas.ClipRect(new SKRect(0, 0, Size.X, Size.Y)); - owner.RenderOverride(session.Canvas, Size.ToSize()); - session.Canvas.Restore(); - } - - public void Invalidate() => Compositor.InvalidateRender(this); - } -} diff --git a/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.crossruntime.cs b/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.crossruntime.cs deleted file mode 100644 index 67247f69d27c..000000000000 --- a/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.crossruntime.cs +++ /dev/null @@ -1,23 +0,0 @@ -#if !__SKIA__ -using System; -using Windows.Foundation; -using SkiaSharp; - -namespace Microsoft.UI.Xaml.Controls; - -public abstract partial class SKCanvasElement : FrameworkElement -{ - protected SKCanvasElement() - { - throw new PlatformNotSupportedException($"${nameof(SKCanvasElement)} is only available on skia targets."); - } - - public void Invalidate() { } - - protected abstract void RenderOverride(SKCanvas canvas, Size area); - - protected override Size MeasureOverride(Size availableSize) => availableSize; - - protected override Size ArrangeOverride(Size finalSize) => finalSize; -} -#endif diff --git a/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.skia.cs b/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.skia.cs deleted file mode 100644 index b24f67adab39..000000000000 --- a/src/Uno.UI/UI/Xaml/Graphics/SKCanvasElement.skia.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Numerics; -using Windows.Foundation; -using Microsoft.UI.Xaml.Hosting; -using SkiaSharp; - -namespace Microsoft.UI.Xaml.Controls; - -/// -/// A that exposes the ability to draw directly on the window using SkiaSharp. -/// -/// This is only available on skia-based targets. -public abstract partial class SKCanvasElement : FrameworkElement -{ - private readonly SKCanvasVisual _skiaVisual; - - protected SKCanvasElement() - { - _skiaVisual = new SKCanvasVisual(this, ElementCompositionPreview.GetElementVisual(this).Compositor); - Visual.Children.InsertAtTop(_skiaVisual); - - SizeChanged += OnSizeChanged; - } - - /// - /// Queue a rendering cycle that will call . - /// - public void Invalidate() => _skiaVisual.Invalidate(); - - /// - /// The SkiaSharp drawing logic goes here. - /// - /// The SKCanvas that should be drawn on. - /// The dimensions of the clipping area. - /// - /// When called, the is already set up such that the origin (0,0) is at the top-left of the clipping area. - /// Drawing outside this area (i.e. outside the (0, 0, area.Width, area.Height rectangle) will be clipped out. - /// - protected abstract void RenderOverride(SKCanvas canvas, Size area); - - /// - /// By default, uses all the given. Subclasses of - /// should override this method if they need something different. - /// - /// An exception will be thrown if availableSize is infinite (e.g. if inside a StackPanel). - protected override Size MeasureOverride(Size availableSize) - { - if (availableSize.Width == Double.PositiveInfinity || - availableSize.Height == Double.PositiveInfinity || - double.IsNaN(availableSize.Width) || - double.IsNaN(availableSize.Height)) - { - throw new ArgumentException($"{nameof(SKCanvasElement)} cannot be measured with infinite or NaN values, but received availableSize={availableSize}."); - } - - return availableSize; - } - - /// - /// By default, uses all the given. Subclasses of - /// should override this method if they need something different. - /// - /// An exception will be thrown if is infinite (e.g. if inside a StackPanel). - protected override Size ArrangeOverride(Size finalSize) - { - if (finalSize.Width == double.PositiveInfinity || - finalSize.Height == double.PositiveInfinity || - double.IsNaN(finalSize.Width) || - double.IsNaN(finalSize.Height)) - { - throw new ArgumentException($"{nameof(SKCanvasElement)} cannot be arranged with infinite or NaN values, but received finalSize={finalSize}."); - } - return finalSize; - } - - private void OnSizeChanged(object sender, SizeChangedEventArgs args) => _skiaVisual.Size = args.NewSize.ToVector2(); -} From 3f540832ac52a8e86c5cf13049d71394bb428a81 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 2 Sep 2024 20:44:13 +0300 Subject: [PATCH 88/94] chore: adjust conditions for UWP --- .../Uno.WinUI.Graphics3DGL.csproj | 5 ++++- .../Extensions/WpfNativeOpenGLWrapper.cs | 17 ++++++----------- .../Rendering/WindowsRenderingNativeMethods.cs | 2 +- src/Uno.UI/Graphics/INativeOpenGLWrapper.cs | 2 +- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj index 206b1a84f03f..197eac748ea3 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj @@ -17,9 +17,12 @@ - + win-x86;win-x64;win-arm64 + + $(DefineConstants);WINDOWS_UWP + $(DefineConstants);WINAPPSDK $(DefineConstants);WINAPPSDK true None diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs index d127194c062f..b0ebe1220714 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeOpenGLWrapper.cs @@ -7,7 +7,7 @@ using Silk.NET.Core.Loader; using Silk.NET.OpenGL; -#if WINAPPSDK +#if WINDOWS_UWP || WINAPPSDK using Microsoft.UI.Xaml; using Microsoft.Extensions.Logging; using Uno.Disposables; @@ -23,18 +23,13 @@ using WpfWindow = System.Windows.Window; #endif -#if WINAPPSDK -using WpfRenderingNativeMethods = Uno.WinUI.Graphics3DGL.WindowsRenderingNativeMethods; -#else -#endif - -#if WINAPPSDK +#if WINDOWS_UWP || WINAPPSDK namespace Uno.WinUI.Graphics3DGL; #else namespace Uno.UI.Runtime.Skia.Wpf.Extensions; #endif -#if WINAPPSDK +#if WINDOWS_UWP || WINAPPSDK internal class WinUINativeOpenGLWrapper(Func getWindowFunc) #else internal class WpfNativeOpenGLWrapper @@ -46,7 +41,7 @@ internal class WpfNativeOpenGLWrapper public void CreateContext(UIElement element) { -#if WINAPPSDK +#if WINDOWS_UWP || WINAPPSDK var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(getWindowFunc()); #else if (element.XamlRoot?.HostWindow?.NativeWindow is not WpfWindow wpfWindow) @@ -75,8 +70,8 @@ public void CreateContext(UIElement element) var pixelFormat = WindowsRenderingNativeMethods.ChoosePixelFormat(_hdc, ref pfd); // To inspect the chosen pixel format: - // WpfRenderingNativeMethods.PIXELFORMATDESCRIPTOR temp_pfd = default; - // WpfRenderingNativeMethods.DescribePixelFormat(_hdc, _pixelFormat, (uint)Marshal.SizeOf(), ref temp_pfd); + // WindowsRenderingNativeMethods.PIXELFORMATDESCRIPTOR temp_pfd = default; + // WindowsRenderingNativeMethods.DescribePixelFormat(_hdc, _pixelFormat, (uint)Marshal.SizeOf(), ref temp_pfd); if (pixelFormat == 0) { diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Rendering/WindowsRenderingNativeMethods.cs b/src/Uno.UI.Runtime.Skia.Wpf/Rendering/WindowsRenderingNativeMethods.cs index e7fd6f0611d7..d18687b6cee2 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Rendering/WindowsRenderingNativeMethods.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Rendering/WindowsRenderingNativeMethods.cs @@ -3,7 +3,7 @@ using System; using System.Runtime.InteropServices; -#if WINAPPSDK +#if WINDOWS_UWP || WINAPPSDK namespace Uno.WinUI.Graphics3DGL; #else namespace Uno.UI.Runtime.Skia.Wpf.Rendering; diff --git a/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs b/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs index 98433b996acf..e9575a2b2ffb 100644 --- a/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs +++ b/src/Uno.UI/Graphics/INativeOpenGLWrapper.cs @@ -1,7 +1,7 @@ using System; using Microsoft.UI.Xaml; -#if WINAPPSDK +#if WINAPPSDK || WINDOWS_UWP namespace Uno.WinUI.Graphics3DGL; #else namespace Uno.Graphics; From 57891bd1223aeed7f86f1b382279724c83ae9626 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 2 Sep 2024 20:46:08 +0300 Subject: [PATCH 89/94] chore: add a BuildGraphics3DGLForWindows condition --- build/ci/.azure-devops-wasdk.yml | 2 +- build/test-scripts/run-net7-template-linux.ps1 | 3 +++ .../Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) 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/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/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj index 197eac748ea3..a0e3ad67d057 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj @@ -1,7 +1,7 @@  $(NetSkiaPreviousAndCurrent) - $(NetPrevious)-windows10.0.19041.0 + $(NetUWPOrWinUI) enable Uno.WinUI.GLCanvasElement From 1f029fa4a1778b42e928f3b728f8ca9cd69f6297 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 3 Sep 2024 00:30:45 +0300 Subject: [PATCH 90/94] chore: GeneratePackageOnBuild for Graphics3DGL --- .../Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj index a0e3ad67d057..a89707352f83 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj @@ -3,8 +3,10 @@ $(NetSkiaPreviousAndCurrent) $(NetUWPOrWinUI) - enable Uno.WinUI.GLCanvasElement + true + + enable true From 2a8cd8bc81c48cfb5abc44ba7602fff339b4aeef Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 3 Sep 2024 00:52:22 +0300 Subject: [PATCH 91/94] chore: don't reference WinAppSDK on UWP --- .../Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj index a89707352f83..bbb952662e83 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj @@ -30,8 +30,8 @@ None - - + + From 3c02cbbf348d9bc775967233ed71d2a165a23b74 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 3 Sep 2024 12:21:29 +0300 Subject: [PATCH 92/94] chore: add uno.png to None --- .../Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj index bbb952662e83..6e7ede31d5c4 100644 --- a/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj +++ b/src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj @@ -48,6 +48,10 @@ + + + +