diff --git a/.editorconfig b/.editorconfig
index 238e9887bd..1583d3e469 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -144,6 +144,8 @@ dotnet_diagnostic.CS1591.severity = suggestion
dotnet_diagnostic.CA1304.severity = warning
# CA1802: Use literals where appropriate
dotnet_diagnostic.CA1802.severity = warning
+# CA1815: Override equals and operator equals on value types
+dotnet_diagnostic.CA1815.severity = warning
# CA1820: Test for empty strings using string length
dotnet_diagnostic.CA1820.severity = warning
# CA1821: Remove empty finalizers
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index b95b455ca4..4a5f5bc96c 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -135,6 +135,9 @@
+
+
+
diff --git a/samples/ControlCatalog/Pages/RefreshContainerPage.axaml b/samples/ControlCatalog/Pages/RefreshContainerPage.axaml
new file mode 100644
index 0000000000..f3bf1724b4
--- /dev/null
+++ b/samples/ControlCatalog/Pages/RefreshContainerPage.axaml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/RefreshContainerPage.axaml.cs b/samples/ControlCatalog/Pages/RefreshContainerPage.axaml.cs
new file mode 100644
index 0000000000..f9d0328d9a
--- /dev/null
+++ b/samples/ControlCatalog/Pages/RefreshContainerPage.axaml.cs
@@ -0,0 +1,36 @@
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using ControlCatalog.ViewModels;
+
+namespace ControlCatalog.Pages
+{
+ public class RefreshContainerPage : UserControl
+ {
+ private RefreshContainerViewModel _viewModel;
+
+ public RefreshContainerPage()
+ {
+ this.InitializeComponent();
+
+ _viewModel = new RefreshContainerViewModel();
+
+ DataContext = _viewModel;
+ }
+
+ private async void RefreshContainerPage_RefreshRequested(object? sender, RefreshRequestedEventArgs e)
+ {
+ var deferral = e.GetDeferral();
+
+ await _viewModel.AddToTop();
+
+ deferral.Complete();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/ControlCatalog/ViewModels/RefreshContainerViewModel.cs b/samples/ControlCatalog/ViewModels/RefreshContainerViewModel.cs
new file mode 100644
index 0000000000..d4b43043be
--- /dev/null
+++ b/samples/ControlCatalog/ViewModels/RefreshContainerViewModel.cs
@@ -0,0 +1,26 @@
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reactive;
+using System.Threading.Tasks;
+using Avalonia.Controls.Notifications;
+using ControlCatalog.Pages;
+using MiniMvvm;
+
+namespace ControlCatalog.ViewModels
+{
+ public class RefreshContainerViewModel : ViewModelBase
+ {
+ public ObservableCollection Items { get; }
+
+ public RefreshContainerViewModel()
+ {
+ Items = new ObservableCollection(Enumerable.Range(1, 200).Select(i => $"Item {i}"));
+ }
+
+ public async Task AddToTop()
+ {
+ await Task.Delay(3000);
+ Items.Insert(0, $"Item {200 - Items.Count}");
+ }
+ }
+}
diff --git a/samples/RenderDemo/Pages/TextFormatterPage.axaml.cs b/samples/RenderDemo/Pages/TextFormatterPage.axaml.cs
index 57a5c7101f..8fbfa854b1 100644
--- a/samples/RenderDemo/Pages/TextFormatterPage.axaml.cs
+++ b/samples/RenderDemo/Pages/TextFormatterPage.axaml.cs
@@ -90,7 +90,7 @@ namespace RenderDemo.Pages
return new ControlRun(_control, _defaultProperties);
}
- return new TextCharacters(_text.AsMemory(), _defaultProperties);
+ return new TextCharacters(_text, _defaultProperties);
}
}
diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs
index 8d56086470..c885a7768c 100644
--- a/src/Android/Avalonia.Android/AndroidInputMethod.cs
+++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs
@@ -167,7 +167,7 @@ namespace Avalonia.Android
}
}
- public readonly struct ComposingRegion
+ public readonly record struct ComposingRegion
{
private readonly int _start = -1;
private readonly int _end = -1;
diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs
index 2b6d29e7c5..75856e4b52 100644
--- a/src/Android/Avalonia.Android/AndroidPlatform.cs
+++ b/src/Android/Avalonia.Android/AndroidPlatform.cs
@@ -32,6 +32,7 @@ namespace Avalonia.Android
public static AndroidPlatformOptions Options { get; private set; }
internal static Compositor Compositor { get; private set; }
+ internal static PlatformRenderInterfaceContextManager RenderInterface { get; private set; }
public static void Initialize()
{
@@ -51,15 +52,19 @@ namespace Avalonia.Android
if (Options.UseGpu)
{
- EglPlatformOpenGlInterface.TryInitialize();
+ EglPlatformGraphics.TryInitialize();
}
if (Options.UseCompositor)
{
Compositor = new Compositor(
AvaloniaLocator.Current.GetRequiredService(),
- AvaloniaLocator.Current.GetService());
+ AvaloniaLocator.Current.GetService());
}
+ else
+ RenderInterface =
+ new PlatformRenderInterfaceContextManager(AvaloniaLocator.Current
+ .GetService());
}
}
diff --git a/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs b/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs
deleted file mode 100644
index e85ed11028..0000000000
--- a/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using Avalonia.OpenGL;
-using Avalonia.OpenGL.Egl;
-using Avalonia.OpenGL.Surfaces;
-
-namespace Avalonia.Android.OpenGL
-{
- internal sealed class GlPlatformSurface : EglGlPlatformSurfaceBase
- {
- private readonly EglPlatformOpenGlInterface _egl;
- private readonly IEglWindowGlPlatformSurfaceInfo _info;
-
- private GlPlatformSurface(EglPlatformOpenGlInterface egl, IEglWindowGlPlatformSurfaceInfo info)
- {
- _egl = egl;
- _info = info;
- }
-
- public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() =>
- new GlRenderTarget(_egl, _info, _egl.CreateWindowSurface(_info.Handle), _info.Handle);
-
- public static GlPlatformSurface TryCreate(IEglWindowGlPlatformSurfaceInfo info)
- {
- var feature = AvaloniaLocator.Current.GetService();
- if (feature is EglPlatformOpenGlInterface egl)
- {
- return new GlPlatformSurface(egl, info);
- }
-
- return null;
- }
- }
-}
diff --git a/src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs b/src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs
deleted file mode 100644
index f9071d9b27..0000000000
--- a/src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System;
-
-using Avalonia.OpenGL.Egl;
-using Avalonia.OpenGL.Surfaces;
-
-namespace Avalonia.Android.OpenGL
-{
- internal sealed class GlRenderTarget : EglPlatformSurfaceRenderTargetBase, IGlPlatformSurfaceRenderTargetWithCorruptionInfo
- {
- private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info;
- private readonly EglSurface _surface;
- private readonly IntPtr _handle;
-
- public GlRenderTarget(
- EglPlatformOpenGlInterface egl,
- EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info,
- EglSurface surface,
- IntPtr handle)
- : base(egl)
- {
- _info = info;
- _surface = surface;
- _handle = handle;
- }
-
- public bool IsCorrupted => _handle != _info.Handle;
-
- public override IGlPlatformSurfaceRenderingSession BeginDraw() => BeginDraw(_surface, _info);
- }
-}
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index 4150b52946..56dbadca03 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -8,7 +8,6 @@ using Android.Runtime;
using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
-using Avalonia.Android.OpenGL;
using Avalonia.Android.Platform.Specific;
using Avalonia.Android.Platform.Specific.Helpers;
using Avalonia.Android.Platform.Storage;
@@ -30,7 +29,7 @@ using AndroidRect = Android.Graphics.Rect;
namespace Avalonia.Android.Platform.SkiaPlatform
{
- class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo,
+ class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo,
ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider
{
private readonly IGlPlatformSurface _gl;
@@ -47,7 +46,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_textInputMethod = new AndroidInputMethod(_view);
_keyboardHelper = new AndroidKeyboardEventsHelper(this);
_pointerHelper = new AndroidMotionEventsHelper(this);
- _gl = GlPlatformSurface.TryCreate(this);
+ _gl = new EglGlPlatformSurface(this);
_framebuffer = new FramebufferManager(this);
RenderScaling = _view.Scaling;
@@ -106,10 +105,15 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IRenderer CreateRenderer(IRenderRoot root) =>
AndroidPlatform.Options.UseCompositor
- ? new CompositingRenderer(root, AndroidPlatform.Compositor)
+ ? new CompositingRenderer(root, AndroidPlatform.Compositor, () => Surfaces)
: AndroidPlatform.Options.UseDeferredRendering
- ? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService()) { RenderOnlyOnRenderThread = true }
- : new ImmediateRenderer((Visual)root);
+ ? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService(),
+ () => AndroidPlatform.RenderInterface.CreateRenderTarget(Surfaces),
+ AndroidPlatform.RenderInterface)
+ { RenderOnlyOnRenderThread = true }
+ : new ImmediateRenderer((Visual)root,
+ () => AndroidPlatform.RenderInterface.CreateRenderTarget(Surfaces),
+ AndroidPlatform.RenderInterface);
public virtual void Hide()
{
@@ -283,7 +287,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);
- IntPtr EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo.Handle => ((IPlatformHandle)_view).Handle;
+ IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => ((IPlatformHandle)_view).Handle;
public PixelSize Size => _view.Size;
diff --git a/src/Avalonia.Base/Animation/Cue.cs b/src/Avalonia.Base/Animation/Cue.cs
index 6578148b07..c48f2ab6b0 100644
--- a/src/Avalonia.Base/Animation/Cue.cs
+++ b/src/Avalonia.Base/Animation/Cue.cs
@@ -8,7 +8,7 @@ namespace Avalonia.Animation
/// Determines the time index for a .
///
[TypeConverter(typeof(CueTypeConverter))]
- public readonly struct Cue : IEquatable, IEquatable
+ public readonly record struct Cue : IEquatable, IEquatable
{
///
/// The normalized percent value, ranging from 0.0 to 1.0
@@ -49,15 +49,6 @@ namespace Avalonia.Animation
}
}
- ///
- /// Checks for equality between two s.
- ///
- /// The second cue.
- public bool Equals(Cue other)
- {
- return CueValue == other.CueValue;
- }
-
///
/// Checks for equality between a
/// and a value.
diff --git a/src/Avalonia.Base/Data/BindingValue.cs b/src/Avalonia.Base/Data/BindingValue.cs
index 4e07ebf445..3bc172f596 100644
--- a/src/Avalonia.Base/Data/BindingValue.cs
+++ b/src/Avalonia.Base/Data/BindingValue.cs
@@ -80,7 +80,7 @@ namespace Avalonia.Data
/// - For an unset value, use or simply `default`
/// - For other types, call one of the static factory methods
///
- public readonly struct BindingValue
+ public readonly record struct BindingValue
{
private readonly T _value;
diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs
new file mode 100644
index 0000000000..fedd07ec32
--- /dev/null
+++ b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs
@@ -0,0 +1,152 @@
+using Avalonia.Input.GestureRecognizers;
+
+namespace Avalonia.Input
+{
+ public class PullGestureRecognizer : StyledElement, IGestureRecognizer
+ {
+ private IInputElement? _target;
+ private IGestureRecognizerActionsDispatcher? _actions;
+ private Point _initialPosition;
+ private int _gestureId;
+ private IPointer? _tracking;
+ private PullDirection _pullDirection;
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly DirectProperty PullDirectionProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(PullDirection),
+ o => o.PullDirection,
+ (o, v) => o.PullDirection = v);
+
+ public PullDirection PullDirection
+ {
+ get => _pullDirection;
+ set => SetAndRaise(PullDirectionProperty, ref _pullDirection, value);
+ }
+
+ public PullGestureRecognizer(PullDirection pullDirection)
+ {
+ PullDirection = pullDirection;
+ }
+
+ public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
+ {
+ _target = target;
+ _actions = actions;
+
+ _target?.AddHandler(InputElement.PointerPressedEvent, OnPointerPressed, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble);
+ _target?.AddHandler(InputElement.PointerReleasedEvent, OnPointerReleased, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble);
+ }
+
+ private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
+ {
+ PointerPressed(e);
+ }
+
+ private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
+ {
+ PointerReleased(e);
+ }
+
+ public void PointerCaptureLost(IPointer pointer)
+ {
+ if (_tracking == pointer)
+ {
+ EndPull();
+ }
+ }
+
+ public void PointerMoved(PointerEventArgs e)
+ {
+ if (_tracking == e.Pointer && _target is Visual visual)
+ {
+ var currentPosition = e.GetPosition(visual);
+ _actions!.Capture(e.Pointer, this);
+
+ Vector delta = default;
+ switch (PullDirection)
+ {
+ case PullDirection.TopToBottom:
+ if (currentPosition.Y > _initialPosition.Y)
+ {
+ delta = new Vector(0, currentPosition.Y - _initialPosition.Y);
+ }
+ break;
+ case PullDirection.BottomToTop:
+ if (currentPosition.Y < _initialPosition.Y)
+ {
+ delta = new Vector(0, _initialPosition.Y - currentPosition.Y);
+ }
+ break;
+ case PullDirection.LeftToRight:
+ if (currentPosition.X > _initialPosition.X)
+ {
+ delta = new Vector(currentPosition.X - _initialPosition.X, 0);
+ }
+ break;
+ case PullDirection.RightToLeft:
+ if (currentPosition.X < _initialPosition.X)
+ {
+ delta = new Vector(_initialPosition.X - currentPosition.X, 0);
+ }
+ break;
+ }
+
+ _target?.RaiseEvent(new PullGestureEventArgs(_gestureId, delta, PullDirection));
+ }
+ }
+
+ public void PointerPressed(PointerPressedEventArgs e)
+ {
+ if (_target != null && _target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
+ {
+ var position = e.GetPosition(visual);
+
+ var canPull = false;
+
+ var bounds = visual.Bounds;
+
+ switch (PullDirection)
+ {
+ case PullDirection.TopToBottom:
+ canPull = position.Y < bounds.Height * 0.1;
+ break;
+ case PullDirection.BottomToTop:
+ canPull = position.Y > bounds.Height - (bounds.Height * 0.1);
+ break;
+ case PullDirection.LeftToRight:
+ canPull = position.X < bounds.Width * 0.1;
+ break;
+ case PullDirection.RightToLeft:
+ canPull = position.X > bounds.Width - (bounds.Width * 0.1);
+ break;
+ }
+
+ if (canPull)
+ {
+ _gestureId = PullGestureEventArgs.GetNextFreeId();
+ _tracking = e.Pointer;
+ _initialPosition = position;
+ }
+ }
+ }
+
+ public void PointerReleased(PointerReleasedEventArgs e)
+ {
+ if (_tracking == e.Pointer)
+ {
+ EndPull();
+ }
+ }
+
+ private void EndPull()
+ {
+ _tracking = null;
+ _initialPosition = default;
+
+ _target?.RaiseEvent(new PullGestureEndedEventArgs(_gestureId, PullDirection));
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs
index 496052ad35..1ea88fe824 100644
--- a/src/Avalonia.Base/Input/Gestures.cs
+++ b/src/Avalonia.Base/Input/Gestures.cs
@@ -46,6 +46,14 @@ namespace Avalonia.Input
private static readonly WeakReference
- public struct AcrylicPlatformCompensationLevels
+ public record struct AcrylicPlatformCompensationLevels
{
public AcrylicPlatformCompensationLevels(double transparent, double blurred, double acrylic)
{
diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs
index 604f2369e7..a100d38d78 100644
--- a/src/Avalonia.Controls/AppBuilderBase.cs
+++ b/src/Avalonia.Controls/AppBuilderBase.cs
@@ -229,8 +229,8 @@ namespace Avalonia.Controls
s_setupWasAlreadyCalled = true;
_optionsInitializers?.Invoke();
RuntimePlatformServicesInitializer();
- WindowingSubsystemInitializer();
RenderingSubsystemInitializer();
+ WindowingSubsystemInitializer();
AfterPlatformServicesSetupCallback(Self);
Instance = _appFactory();
Instance.ApplicationLifetime = _lifetime;
diff --git a/src/Avalonia.Controls/Documents/LineBreak.cs b/src/Avalonia.Controls/Documents/LineBreak.cs
index 108a38d86b..ee31b85be9 100644
--- a/src/Avalonia.Controls/Documents/LineBreak.cs
+++ b/src/Avalonia.Controls/Documents/LineBreak.cs
@@ -4,7 +4,7 @@ using System.Text;
using Avalonia.Media.TextFormatting;
using Avalonia.Metadata;
-namespace Avalonia.Controls.Documents
+namespace Avalonia.Controls.Documents
{
///
/// LineBreak element that forces a line breaking.
@@ -21,7 +21,7 @@ namespace Avalonia.Controls.Documents
internal override void BuildTextRun(IList textRuns)
{
- var text = Environment.NewLine.AsMemory();
+ var text = Environment.NewLine;
var textRunProperties = CreateTextRunProperties();
diff --git a/src/Avalonia.Controls/Documents/Run.cs b/src/Avalonia.Controls/Documents/Run.cs
index 2bd66b8a64..5d7b8998e6 100644
--- a/src/Avalonia.Controls/Documents/Run.cs
+++ b/src/Avalonia.Controls/Documents/Run.cs
@@ -52,7 +52,7 @@ namespace Avalonia.Controls.Documents
internal override void BuildTextRun(IList textRuns)
{
- var text = (Text ?? "").AsMemory();
+ var text = Text ?? "";
var textRunProperties = CreateTextRunProperties();
diff --git a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
index 5ff0fd1feb..b79fef55b9 100644
--- a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
+++ b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
@@ -4,7 +4,6 @@ using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia.Styling;
-using JetBrains.Annotations;
namespace Avalonia.Controls.Embedding
{
@@ -12,7 +11,6 @@ namespace Avalonia.Controls.Embedding
{
public EmbeddableControlRoot(ITopLevelImpl impl) : base(impl)
{
-
}
public EmbeddableControlRoot() : base(PlatformManager.CreateEmbeddableWindow())
diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
index 4f80892857..4a227a0c00 100644
--- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
+++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
@@ -13,6 +13,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
{
private double _scaling = 1;
private Size _clientSize;
+ private PlatformRenderInterfaceContextManager _renderInterface = new(null);
public IInputRoot? InputRoot { get; private set; }
public bool IsDisposed { get; private set; }
@@ -22,7 +23,8 @@ namespace Avalonia.Controls.Embedding.Offscreen
IsDisposed = true;
}
- public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer((Visual)root);
+ public IRenderer CreateRenderer(IRenderRoot root) =>
+ new ImmediateRenderer((Visual)root, () => _renderInterface.CreateRenderTarget(Surfaces), _renderInterface);
public abstract void Invalidate(Rect rect);
public abstract IEnumerable Surfaces { get; }
diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
index 17dfec118f..2e70947457 100644
--- a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
+++ b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
@@ -64,7 +64,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
/// surface.
///
[Unstable]
- public struct PopupPositionerParameters
+ public record struct PopupPositionerParameters
{
private PopupGravity _gravity;
private PopupAnchor _anchor;
diff --git a/src/Avalonia.Controls/PullToRefresh/RefreshCompletionDeferral.cs b/src/Avalonia.Controls/PullToRefresh/RefreshCompletionDeferral.cs
new file mode 100644
index 0000000000..a18b3c2934
--- /dev/null
+++ b/src/Avalonia.Controls/PullToRefresh/RefreshCompletionDeferral.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Threading;
+
+namespace Avalonia.Controls
+{
+ ///
+ /// Deferral class for notify that a work done in RefreshRequested event is done.
+ ///
+ public class RefreshCompletionDeferral
+ {
+ private Action _deferredAction;
+ private int _deferCount;
+
+ public RefreshCompletionDeferral(Action deferredAction)
+ {
+ _deferredAction = deferredAction;
+ }
+
+ public void Complete()
+ {
+ Interlocked.Decrement(ref _deferCount);
+
+ if (_deferCount == 0)
+ {
+ _deferredAction?.Invoke();
+ }
+ }
+
+ public RefreshCompletionDeferral Get()
+ {
+ Interlocked.Increment(ref _deferCount);
+
+ return this;
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/PullToRefresh/RefreshContainer.cs b/src/Avalonia.Controls/PullToRefresh/RefreshContainer.cs
new file mode 100644
index 0000000000..d0b8178add
--- /dev/null
+++ b/src/Avalonia.Controls/PullToRefresh/RefreshContainer.cs
@@ -0,0 +1,252 @@
+using System;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.PullToRefresh;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+
+namespace Avalonia.Controls
+{
+ ///
+ /// Represents a container control that provides a and pull-to-refresh functionality for scrollable content.
+ ///
+ public class RefreshContainer : ContentControl
+ {
+ internal const int DefaultPullDimensionSize = 100;
+
+ private bool _hasDefaultRefreshInfoProviderAdapter;
+
+ private ScrollViewerIRefreshInfoProviderAdapter? _refreshInfoProviderAdapter;
+ private RefreshInfoProvider? _refreshInfoProvider;
+ private IDisposable? _visualizerSizeSubscription;
+ private Grid? _visualizerPresenter;
+ private RefreshVisualizer? _refreshVisualizer;
+ private bool _hasDefaultRefreshVisualizer;
+
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent RefreshRequestedEvent =
+ RoutedEvent.Register(nameof(RefreshRequested), RoutingStrategies.Bubble);
+
+ internal static readonly DirectProperty RefreshInfoProviderAdapterProperty =
+ AvaloniaProperty.RegisterDirect(nameof(RefreshInfoProviderAdapter),
+ (s) => s.RefreshInfoProviderAdapter, (s, o) => s.RefreshInfoProviderAdapter = o);
+
+ ///
+ /// Defines the event.
+ ///
+ public static readonly DirectProperty VisualizerProperty =
+ AvaloniaProperty.RegisterDirect(nameof(Visualizer),
+ s => s.Visualizer, (s, o) => s.Visualizer = o);
+
+ ///
+ /// Defines the event.
+ ///
+ public static readonly StyledProperty PullDirectionProperty =
+ AvaloniaProperty.Register(nameof(PullDirection), PullDirection.TopToBottom);
+
+ internal ScrollViewerIRefreshInfoProviderAdapter? RefreshInfoProviderAdapter
+ {
+ get => _refreshInfoProviderAdapter; set
+ {
+ _hasDefaultRefreshInfoProviderAdapter = false;
+ SetAndRaise(RefreshInfoProviderAdapterProperty, ref _refreshInfoProviderAdapter, value);
+ }
+ }
+
+ ///
+ /// Gets or sets the for this container.
+ ///
+ public RefreshVisualizer? Visualizer
+ {
+ get => _refreshVisualizer; set
+ {
+ if (_refreshVisualizer != null)
+ {
+ _visualizerSizeSubscription?.Dispose();
+ _refreshVisualizer.RefreshRequested -= Visualizer_RefreshRequested;
+ }
+
+ SetAndRaise(VisualizerProperty, ref _refreshVisualizer, value);
+ }
+ }
+
+ ///
+ /// Gets or sets a value that specifies the direction to pull to initiate a refresh.
+ ///
+ public PullDirection PullDirection
+ {
+ get => GetValue(PullDirectionProperty);
+ set => SetValue(PullDirectionProperty, value);
+ }
+
+ ///
+ /// Occurs when an update of the content has been initiated.
+ ///
+ public event EventHandler? RefreshRequested
+ {
+ add => AddHandler(RefreshRequestedEvent, value);
+ remove => RemoveHandler(RefreshRequestedEvent, value);
+ }
+
+ public RefreshContainer()
+ {
+ _hasDefaultRefreshInfoProviderAdapter = true;
+ _refreshInfoProviderAdapter = new ScrollViewerIRefreshInfoProviderAdapter(PullDirection);
+ RaisePropertyChanged(RefreshInfoProviderAdapterProperty, null, _refreshInfoProviderAdapter);
+ }
+
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ base.OnApplyTemplate(e);
+
+ _visualizerPresenter = e.NameScope.Find("PART_RefreshVisualizerPresenter");
+
+ if (_refreshVisualizer == null)
+ {
+ _hasDefaultRefreshVisualizer = true;
+ Visualizer = new RefreshVisualizer();
+ }
+ else
+ {
+ _hasDefaultRefreshVisualizer = false;
+ RaisePropertyChanged(VisualizerProperty, default, _refreshVisualizer);
+ }
+
+ OnPullDirectionChanged();
+ }
+
+ private void OnVisualizerSizeChanged(Rect obj)
+ {
+ if (_hasDefaultRefreshInfoProviderAdapter)
+ {
+ _refreshInfoProviderAdapter = new ScrollViewerIRefreshInfoProviderAdapter(PullDirection);
+ RaisePropertyChanged(RefreshInfoProviderAdapterProperty, null, _refreshInfoProviderAdapter);
+ }
+ }
+
+ private void Visualizer_RefreshRequested(object? sender, RefreshRequestedEventArgs e)
+ {
+ var ev = new RefreshRequestedEventArgs(e.GetDeferral(), RefreshRequestedEvent);
+ RaiseEvent(ev);
+ ev.DecrementCount();
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property == RefreshInfoProviderAdapterProperty)
+ {
+ if (_refreshVisualizer != null)
+ {
+ if (_refreshInfoProvider != null)
+ {
+ _refreshVisualizer.RefreshInfoProvider = _refreshInfoProvider;
+ }
+ else
+ {
+ if (RefreshInfoProviderAdapter != null && _refreshVisualizer != null)
+ {
+ _refreshInfoProvider = RefreshInfoProviderAdapter?.AdaptFromTree(this, _refreshVisualizer.Bounds.Size);
+
+ if (_refreshInfoProvider != null)
+ {
+ _refreshVisualizer.RefreshInfoProvider = _refreshInfoProvider;
+ RefreshInfoProviderAdapter?.SetAnimations(_refreshVisualizer);
+ }
+ }
+ }
+ }
+ }
+ else if (change.Property == VisualizerProperty)
+ {
+ if (_visualizerPresenter != null)
+ {
+ _visualizerPresenter.Children.Clear();
+ if (_refreshVisualizer != null)
+ {
+ _visualizerPresenter.Children.Add(_refreshVisualizer);
+ }
+ }
+
+ if (_refreshVisualizer != null)
+ {
+ _refreshVisualizer.RefreshRequested += Visualizer_RefreshRequested;
+ _visualizerSizeSubscription = _refreshVisualizer.GetObservable(Control.BoundsProperty).Subscribe(OnVisualizerSizeChanged);
+ }
+ }
+ else if (change.Property == PullDirectionProperty)
+ {
+ OnPullDirectionChanged();
+ }
+ }
+
+ private void OnPullDirectionChanged()
+ {
+ if (_visualizerPresenter != null && _refreshVisualizer != null)
+ {
+ switch (PullDirection)
+ {
+ case PullDirection.TopToBottom:
+ _visualizerPresenter.VerticalAlignment = Layout.VerticalAlignment.Top;
+ _visualizerPresenter.HorizontalAlignment = Layout.HorizontalAlignment.Stretch;
+ if (_hasDefaultRefreshVisualizer)
+ {
+ _refreshVisualizer.PullDirection = PullDirection.TopToBottom;
+ _refreshVisualizer.Height = DefaultPullDimensionSize;
+ _refreshVisualizer.Width = double.NaN;
+ }
+ break;
+ case PullDirection.BottomToTop:
+ _visualizerPresenter.VerticalAlignment = Layout.VerticalAlignment.Bottom;
+ _visualizerPresenter.HorizontalAlignment = Layout.HorizontalAlignment.Stretch;
+ if (_hasDefaultRefreshVisualizer)
+ {
+ _refreshVisualizer.PullDirection = PullDirection.BottomToTop;
+ _refreshVisualizer.Height = DefaultPullDimensionSize;
+ _refreshVisualizer.Width = double.NaN;
+ }
+ break;
+ case PullDirection.LeftToRight:
+ _visualizerPresenter.VerticalAlignment = Layout.VerticalAlignment.Stretch;
+ _visualizerPresenter.HorizontalAlignment = Layout.HorizontalAlignment.Left;
+ if (_hasDefaultRefreshVisualizer)
+ {
+ _refreshVisualizer.PullDirection = PullDirection.LeftToRight;
+ _refreshVisualizer.Width = DefaultPullDimensionSize;
+ _refreshVisualizer.Height = double.NaN;
+ }
+ break;
+ case PullDirection.RightToLeft:
+ _visualizerPresenter.VerticalAlignment = Layout.VerticalAlignment.Stretch;
+ _visualizerPresenter.HorizontalAlignment = Layout.HorizontalAlignment.Right;
+ if (_hasDefaultRefreshVisualizer)
+ {
+ _refreshVisualizer.PullDirection = PullDirection.RightToLeft;
+ _refreshVisualizer.Width = DefaultPullDimensionSize;
+ _refreshVisualizer.Height = double.NaN;
+ }
+ break;
+ }
+
+ if (_hasDefaultRefreshInfoProviderAdapter &&
+ _hasDefaultRefreshVisualizer &&
+ _refreshVisualizer.Bounds.Height == DefaultPullDimensionSize &&
+ _refreshVisualizer.Bounds.Width == DefaultPullDimensionSize)
+ {
+ _refreshInfoProviderAdapter = new ScrollViewerIRefreshInfoProviderAdapter(PullDirection);
+ RaisePropertyChanged(RefreshInfoProviderAdapterProperty, null, _refreshInfoProviderAdapter);
+ }
+ }
+ }
+
+ ///
+ /// Initiates an update of the content.
+ ///
+ public void RequestRefresh()
+ {
+ _refreshVisualizer?.RequestRefresh();
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/PullToRefresh/RefreshInfoProvider.cs b/src/Avalonia.Controls/PullToRefresh/RefreshInfoProvider.cs
new file mode 100644
index 0000000000..847f011fa9
--- /dev/null
+++ b/src/Avalonia.Controls/PullToRefresh/RefreshInfoProvider.cs
@@ -0,0 +1,141 @@
+using System;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Rendering.Composition;
+
+namespace Avalonia.Controls.PullToRefresh
+{
+ internal class RefreshInfoProvider : Interactive
+ {
+ internal const double DefaultExecutionRatio = 0.8;
+
+ private readonly PullDirection _refreshPullDirection;
+ private readonly Size _refreshVisualizerSize;
+
+ private readonly CompositionVisual? _visual;
+ private bool _isInteractingForRefresh;
+ private double _interactionRatio;
+ private bool _entered;
+
+ public DirectProperty IsInteractingForRefreshProperty =
+ AvaloniaProperty.RegisterDirect(nameof(IsInteractingForRefresh),
+ s => s.IsInteractingForRefresh, (s, o) => s.IsInteractingForRefresh = o);
+
+
+ public DirectProperty ExecutionRatioProperty =
+ AvaloniaProperty.RegisterDirect(nameof(ExecutionRatio),
+ s => s.ExecutionRatio);
+
+ public DirectProperty InteractionRatioProperty =
+ AvaloniaProperty.RegisterDirect(nameof(InteractionRatio),
+ s => s.InteractionRatio, (s, o) => s.InteractionRatio = o);
+
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent RefreshStartedEvent =
+ RoutedEvent.Register(nameof(RefreshStarted), RoutingStrategies.Bubble);
+
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent RefreshCompletedEvent =
+ RoutedEvent.Register(nameof(RefreshCompleted), RoutingStrategies.Bubble);
+
+ public bool PeekingMode { get; internal set; }
+
+ public bool IsInteractingForRefresh
+ {
+ get => _isInteractingForRefresh;
+ internal set
+ {
+ var isInteractingForRefresh = value && !PeekingMode;
+
+ if (isInteractingForRefresh != _isInteractingForRefresh)
+ {
+ SetAndRaise(IsInteractingForRefreshProperty, ref _isInteractingForRefresh, isInteractingForRefresh);
+ }
+ }
+ }
+
+ public double InteractionRatio
+ {
+ get => _interactionRatio;
+ set
+ {
+ SetAndRaise(InteractionRatioProperty, ref _interactionRatio, value);
+ }
+ }
+
+ public double ExecutionRatio
+ {
+ get => DefaultExecutionRatio;
+ }
+
+ internal CompositionVisual? Visual => _visual;
+
+ public event EventHandler RefreshStarted
+ {
+ add => AddHandler(RefreshStartedEvent, value);
+ remove => RemoveHandler(RefreshStartedEvent, value);
+ }
+
+ public event EventHandler RefreshCompleted
+ {
+ add => AddHandler(RefreshCompletedEvent, value);
+ remove => RemoveHandler(RefreshCompletedEvent, value);
+ }
+
+ internal void InteractingStateEntered(object? sender, PullGestureEventArgs e)
+ {
+ if (!_entered)
+ {
+ IsInteractingForRefresh = true;
+ _entered = true;
+ }
+
+ ValuesChanged(e.Delta);
+ }
+
+ internal void InteractingStateExited(object? sender, PullGestureEndedEventArgs e)
+ {
+ IsInteractingForRefresh = false;
+ _entered = false;
+
+ ValuesChanged(default);
+ }
+
+
+ public RefreshInfoProvider(PullDirection refreshPullDirection, Size? refreshVIsualizerSize, CompositionVisual? visual)
+ {
+ _refreshPullDirection = refreshPullDirection;
+ _refreshVisualizerSize = refreshVIsualizerSize ?? default;
+ _visual = visual;
+ }
+
+ public void OnRefreshStarted()
+ {
+ RaiseEvent(new RoutedEventArgs(RefreshStartedEvent));
+ }
+
+ public void OnRefreshCompleted()
+ {
+ RaiseEvent(new RoutedEventArgs(RefreshCompletedEvent));
+ }
+
+ internal void ValuesChanged(Vector value)
+ {
+ switch (_refreshPullDirection)
+ {
+ case PullDirection.TopToBottom:
+ case PullDirection.BottomToTop:
+ InteractionRatio = _refreshVisualizerSize.Height == 0 ? 1 : Math.Min(1, value.Y / _refreshVisualizerSize.Height);
+ break;
+ case PullDirection.LeftToRight:
+ case PullDirection.RightToLeft:
+ InteractionRatio = _refreshVisualizerSize.Height == 0 ? 1 : Math.Min(1, value.X / _refreshVisualizerSize.Width);
+ break;
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/PullToRefresh/RefreshRequestedEventArgs.cs b/src/Avalonia.Controls/PullToRefresh/RefreshRequestedEventArgs.cs
new file mode 100644
index 0000000000..4bb25d3b2c
--- /dev/null
+++ b/src/Avalonia.Controls/PullToRefresh/RefreshRequestedEventArgs.cs
@@ -0,0 +1,42 @@
+using System;
+using Avalonia.Interactivity;
+
+namespace Avalonia.Controls
+{
+ ///
+ /// Provides event data for RefreshRequested events.
+ ///
+ public class RefreshRequestedEventArgs : RoutedEventArgs
+ {
+ private RefreshCompletionDeferral _refreshCompletionDeferral;
+
+ ///
+ /// Gets a deferral object for managing the work done in the RefreshRequested event handler.
+ ///
+ /// A object
+ public RefreshCompletionDeferral GetDeferral()
+ {
+ return _refreshCompletionDeferral.Get();
+ }
+
+ public RefreshRequestedEventArgs(Action deferredAction, RoutedEvent? routedEvent) : base(routedEvent)
+ {
+ _refreshCompletionDeferral = new RefreshCompletionDeferral(deferredAction);
+ }
+
+ public RefreshRequestedEventArgs(RefreshCompletionDeferral completionDeferral, RoutedEvent? routedEvent) : base(routedEvent)
+ {
+ _refreshCompletionDeferral = completionDeferral;
+ }
+
+ internal void IncrementCount()
+ {
+ _refreshCompletionDeferral?.Get();
+ }
+
+ internal void DecrementCount()
+ {
+ _refreshCompletionDeferral?.Complete();
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs
new file mode 100644
index 0000000000..f2f735aaa9
--- /dev/null
+++ b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs
@@ -0,0 +1,553 @@
+using System;
+using System.Numerics;
+using System.Reactive.Linq;
+using Avalonia.Animation.Easings;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.PullToRefresh;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+using Avalonia.Rendering.Composition;
+using Avalonia.Rendering.Composition.Animations;
+
+namespace Avalonia.Controls
+{
+ public class RefreshVisualizer : ContentControl
+ {
+ private const int DefaultIndicatorSize = 24;
+ private const float MinimumIndicatorOpacity = 0.4f;
+ private const float ParallaxPositionRatio = 0.5f;
+ private double _executingRatio = 0.8;
+
+ private RefreshVisualizerState _refreshVisualizerState;
+ private RefreshInfoProvider? _refreshInfoProvider;
+ private IDisposable? _isInteractingSubscription;
+ private IDisposable? _interactionRatioSubscription;
+ private bool _isInteractingForRefresh;
+ private Grid? _root;
+ private Control? _content;
+ private RefreshVisualizerOrientation _orientation;
+ private float _startingRotationAngle;
+ private double _interactionRatio;
+ private bool _played;
+ private ScalarKeyFrameAnimation? _rotateAnimation;
+
+ private bool IsPullDirectionVertical => PullDirection == PullDirection.TopToBottom || PullDirection == PullDirection.BottomToTop;
+ private bool IsPullDirectionFar => PullDirection == PullDirection.BottomToTop || PullDirection == PullDirection.RightToLeft;
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty PullDirectionProperty =
+ AvaloniaProperty.Register(nameof(PullDirection), PullDirection.TopToBottom);
+
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent RefreshRequestedEvent =
+ RoutedEvent.Register(nameof(RefreshRequested), RoutingStrategies.Bubble);
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly DirectProperty RefreshVisualizerStateProperty =
+ AvaloniaProperty.RegisterDirect(nameof(RefreshVisualizerState),
+ s => s.RefreshVisualizerState);
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly DirectProperty OrientationProperty =
+ AvaloniaProperty.RegisterDirect(nameof(Orientation),
+ s => s.Orientation, (s, o) => s.Orientation = o);
+
+ ///
+ /// Defines the property.
+ ///
+ internal DirectProperty RefreshInfoProviderProperty =
+ AvaloniaProperty.RegisterDirect(nameof(RefreshInfoProvider),
+ s => s.RefreshInfoProvider, (s, o) => s.RefreshInfoProvider = o);
+
+ ///
+ /// Gets or sets a value that indicates the refresh state of the visualizer.
+ ///
+ protected RefreshVisualizerState RefreshVisualizerState
+ {
+ get
+ {
+ return _refreshVisualizerState;
+ }
+ private set
+ {
+ SetAndRaise(RefreshVisualizerStateProperty, ref _refreshVisualizerState, value);
+ UpdateContent();
+ }
+ }
+
+ ///
+ /// Gets or sets a value that indicates the orientation of the visualizer.
+ ///
+ public RefreshVisualizerOrientation Orientation
+ {
+ get
+ {
+ return _orientation;
+ }
+ set
+ {
+ SetAndRaise(OrientationProperty, ref _orientation, value);
+ }
+ }
+
+ internal PullDirection PullDirection
+ {
+ get => GetValue(PullDirectionProperty);
+ set => SetValue(PullDirectionProperty, value);
+ }
+
+ internal RefreshInfoProvider? RefreshInfoProvider
+ {
+ get => _refreshInfoProvider;
+ set
+ {
+ if (_refreshInfoProvider != null)
+ {
+ _refreshInfoProvider.RenderTransform = null;
+ }
+ SetAndRaise(RefreshInfoProviderProperty, ref _refreshInfoProvider, value);
+ }
+ }
+
+ ///
+ /// Occurs when an update of the content has been initiated.
+ ///
+ public event EventHandler? RefreshRequested
+ {
+ add => AddHandler(RefreshRequestedEvent, value);
+ remove => RemoveHandler(RefreshRequestedEvent, value);
+ }
+
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ base.OnApplyTemplate(e);
+
+ this.ClipToBounds = false;
+
+ _root = e.NameScope.Find("PART_Root");
+
+ if (_root != null)
+ {
+ _content = Content as Control;
+
+ if (_content == null)
+ {
+ _content = new PathIcon()
+ {
+ Height = DefaultIndicatorSize,
+ Width = DefaultIndicatorSize,
+ Name = "PART_Icon"
+ };
+
+ _content.Loaded += (s, e) =>
+ {
+ var composition = ElementComposition.GetElementVisual(_content);
+ var compositor = composition!.Compositor;
+ composition.Opacity = 0;
+
+ var smoothRotationAnimation
+ = compositor.CreateScalarKeyFrameAnimation();
+ smoothRotationAnimation.Target = "RotationAngle";
+ smoothRotationAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue", new LinearEasing());
+ smoothRotationAnimation.Duration = TimeSpan.FromMilliseconds(100);
+
+ var opacityAnimation
+ = compositor.CreateScalarKeyFrameAnimation();
+ opacityAnimation.Target = "Opacity";
+ opacityAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue", new LinearEasing());
+ opacityAnimation.Duration = TimeSpan.FromMilliseconds(100);
+
+ var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();
+ offsetAnimation.Target = "Offset";
+ offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue", new LinearEasing());
+ offsetAnimation.Duration = TimeSpan.FromMilliseconds(150);
+
+ var scaleAnimation
+ = compositor.CreateVector3KeyFrameAnimation();
+ scaleAnimation.Target = "Scale";
+ scaleAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue", new LinearEasing());
+ scaleAnimation.Duration = TimeSpan.FromMilliseconds(100);
+
+ var animation = compositor.CreateImplicitAnimationCollection();
+ animation["RotationAngle"] = smoothRotationAnimation;
+ animation["Offset"] = offsetAnimation;
+ animation["Scale"] = scaleAnimation;
+ animation["Opacity"] = opacityAnimation;
+
+ composition.ImplicitAnimations = animation;
+
+ UpdateContent();
+ };
+
+ Content = _content;
+ }
+ else
+ {
+ RaisePropertyChanged(ContentProperty, null, Content, Data.BindingPriority.Style, false);
+ }
+ }
+
+ OnOrientationChanged();
+
+ UpdateContent();
+ }
+
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToVisualTree(e);
+
+ UpdateContent();
+ }
+
+ private void UpdateContent()
+ {
+ if (_content != null && _root != null)
+ {
+ var root = _root;
+ var visual = _refreshInfoProvider?.Visual;
+ var contentVisual = ElementComposition.GetElementVisual(_content);
+ var visualizerVisual = ElementComposition.GetElementVisual(this);
+ if (visual != null && contentVisual != null && visualizerVisual != null)
+ {
+ contentVisual.CenterPoint = new Vector3((float)(_content.Bounds.Width / 2), (float)(_content.Bounds.Height / 2), 0);
+ switch (RefreshVisualizerState)
+ {
+ case RefreshVisualizerState.Idle:
+ _played = false;
+ if(_rotateAnimation != null)
+ {
+ _rotateAnimation.IterationBehavior = AnimationIterationBehavior.Count;
+ _rotateAnimation = null;
+ }
+ contentVisual.Opacity = MinimumIndicatorOpacity;
+ contentVisual.RotationAngle = _startingRotationAngle;
+ visualizerVisual.Offset = IsPullDirectionVertical ?
+ new Vector3(visualizerVisual.Offset.X, 0, 0) :
+ new Vector3(0, visualizerVisual.Offset.Y, 0);
+ visual.Offset = default;
+ _content.InvalidateMeasure();
+ break;
+ case RefreshVisualizerState.Interacting:
+ _played = false;
+ contentVisual.Opacity = MinimumIndicatorOpacity;
+ contentVisual.RotationAngle = (float)(_startingRotationAngle + _interactionRatio * 2 * Math.PI);
+ Vector3 offset = default;
+ if (IsPullDirectionVertical)
+ {
+ offset = new Vector3(0, (float)(_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Height), 0);
+ }
+ else
+ {
+ offset = new Vector3((float)(_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Width), 0, 0);
+ }
+ visual.Offset = offset;
+ visualizerVisual.Offset = IsPullDirectionVertical ?
+ new Vector3(visualizerVisual.Offset.X, offset.Y, 0) :
+ new Vector3(offset.X, visualizerVisual.Offset.Y, 0);
+ break;
+ case RefreshVisualizerState.Pending:
+ contentVisual.Opacity = 1;
+ contentVisual.RotationAngle = _startingRotationAngle + (float)(2 * Math.PI);
+ if (IsPullDirectionVertical)
+ {
+ offset = new Vector3(0, (float)(_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Height), 0);
+ }
+ else
+ {
+ offset = new Vector3((float)(_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Width), 0, 0);
+ }
+ visual.Offset = offset;
+ visualizerVisual.Offset = IsPullDirectionVertical ?
+ new Vector3(visualizerVisual.Offset.X, offset.Y, 0) :
+ new Vector3(offset.X, visualizerVisual.Offset.Y, 0);
+
+ if (!_played)
+ {
+ _played = true;
+ var scaleAnimation = contentVisual.Compositor!.CreateVector3KeyFrameAnimation();
+ scaleAnimation.Target = "Scale";
+ scaleAnimation.InsertKeyFrame(0.5f, new Vector3(1.5f, 1.5f, 1));
+ scaleAnimation.InsertKeyFrame(1f, new Vector3(1f, 1f, 1));
+ scaleAnimation.Duration = TimeSpan.FromSeconds(0.3);
+
+ contentVisual.StartAnimation("Scale", scaleAnimation);
+ }
+ break;
+ case RefreshVisualizerState.Refreshing:
+ _rotateAnimation = contentVisual.Compositor!.CreateScalarKeyFrameAnimation();
+ _rotateAnimation.Target = "RotationAngle";
+ _rotateAnimation.InsertKeyFrame(0, _startingRotationAngle, new LinearEasing());
+ _rotateAnimation.InsertKeyFrame(1, _startingRotationAngle + (float)(2 * Math.PI), new LinearEasing());
+ _rotateAnimation.IterationBehavior = AnimationIterationBehavior.Forever;
+ _rotateAnimation.StopBehavior = AnimationStopBehavior.LeaveCurrentValue;
+ _rotateAnimation.Duration = TimeSpan.FromSeconds(0.5);
+
+ contentVisual.StartAnimation("RotationAngle", _rotateAnimation);
+ contentVisual.Opacity = 1;
+ float translationRatio = (float)(_refreshInfoProvider != null ? (1.0f - _refreshInfoProvider.ExecutionRatio) * ParallaxPositionRatio : 1.0f)
+ * (IsPullDirectionFar ? -1f : 1f);
+ if (IsPullDirectionVertical)
+ {
+ offset = new Vector3(0, (float)(_executingRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Height), 0);
+ }
+ else
+ {
+ offset = new Vector3((float)(_executingRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Width), 0, 0);
+ }
+ visual.Offset = offset;
+ contentVisual.Offset += IsPullDirectionVertical ? new Vector3(0, (float)(translationRatio * root.Bounds.Height), 0) :
+ new Vector3((float)(translationRatio * root.Bounds.Width), 0, 0);
+ visualizerVisual.Offset = IsPullDirectionVertical ?
+ new Vector3(visualizerVisual.Offset.X, offset.Y, 0) :
+ new Vector3(offset.X, visualizerVisual.Offset.Y, 0);
+ break;
+ case RefreshVisualizerState.Peeking:
+ contentVisual.Opacity = 1;
+ contentVisual.RotationAngle = _startingRotationAngle;
+ break;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Initiates an update of the content.
+ ///
+ public void RequestRefresh()
+ {
+ RefreshVisualizerState = RefreshVisualizerState.Refreshing;
+ RefreshInfoProvider?.OnRefreshStarted();
+
+ RaiseRefreshRequested();
+ }
+
+ private void RefreshCompleted()
+ {
+ RefreshVisualizerState = RefreshVisualizerState.Idle;
+
+ RefreshInfoProvider?.OnRefreshCompleted();
+ }
+
+ private void RaiseRefreshRequested()
+ {
+ var refreshArgs = new RefreshRequestedEventArgs(RefreshCompleted, RefreshRequestedEvent);
+
+ refreshArgs.IncrementCount();
+
+ RaiseEvent(refreshArgs);
+
+ refreshArgs.DecrementCount();
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property == RefreshInfoProviderProperty)
+ {
+ OnRefreshInfoProviderChanged();
+ }
+ else if (change.Property == ContentProperty)
+ {
+ if (_root != null && _content != null)
+ {
+ _root.Children.Insert(0, _content);
+ _content.VerticalAlignment = Layout.VerticalAlignment.Center;
+ _content.HorizontalAlignment = Layout.HorizontalAlignment.Center;
+ }
+
+ UpdateContent();
+ }
+ else if (change.Property == OrientationProperty)
+ {
+ OnOrientationChanged();
+
+ UpdateContent();
+ }
+ else if (change.Property == BoundsProperty)
+ {
+ switch (PullDirection)
+ {
+ case PullDirection.TopToBottom:
+ RenderTransform = new TranslateTransform(0, -Bounds.Height);
+ break;
+ case PullDirection.BottomToTop:
+ RenderTransform = new TranslateTransform(0, Bounds.Height);
+ break;
+ case PullDirection.LeftToRight:
+ RenderTransform = new TranslateTransform(-Bounds.Width, 0);
+ break;
+ case PullDirection.RightToLeft:
+ RenderTransform = new TranslateTransform(Bounds.Width, 0);
+ break;
+ }
+
+ UpdateContent();
+ }
+ else if(change.Property == PullDirectionProperty)
+ {
+ OnOrientationChanged();
+
+ UpdateContent();
+ }
+ }
+
+ private void OnOrientationChanged()
+ {
+ switch (_orientation)
+ {
+ case RefreshVisualizerOrientation.Auto:
+ switch (PullDirection)
+ {
+ case PullDirection.TopToBottom:
+ case PullDirection.BottomToTop:
+ _startingRotationAngle = 0.0f;
+ break;
+ case PullDirection.LeftToRight:
+ _startingRotationAngle = (float)(-Math.PI / 2);
+ break;
+ case PullDirection.RightToLeft:
+ _startingRotationAngle = (float)(Math.PI / 2);
+ break;
+ }
+ break;
+ case RefreshVisualizerOrientation.Normal:
+ _startingRotationAngle = 0.0f;
+ break;
+ case RefreshVisualizerOrientation.Rotate90DegreesCounterclockwise:
+ _startingRotationAngle = (float)(Math.PI / 2);
+ break;
+ case RefreshVisualizerOrientation.Rotate270DegreesCounterclockwise:
+ _startingRotationAngle = (float)(-Math.PI / 2);
+ break;
+ }
+ }
+
+ private void OnRefreshInfoProviderChanged()
+ {
+ _isInteractingSubscription?.Dispose();
+ _isInteractingSubscription = null;
+ _interactionRatioSubscription?.Dispose();
+ _interactionRatioSubscription = null;
+
+ if (RefreshInfoProvider != null)
+ {
+ _isInteractingSubscription = RefreshInfoProvider.GetObservable(RefreshInfoProvider.IsInteractingForRefreshProperty)
+ .Subscribe(InteractingForRefreshObserver);
+
+ _interactionRatioSubscription = RefreshInfoProvider.GetObservable(RefreshInfoProvider.InteractionRatioProperty)
+ .Subscribe(InteractionRatioObserver);
+
+ var visual = RefreshInfoProvider.Visual;
+
+ _executingRatio = RefreshInfoProvider.ExecutionRatio;
+ }
+ else
+ {
+ _executingRatio = 1;
+ }
+ }
+
+ private void InteractionRatioObserver(double obj)
+ {
+ var wasAtZero = _interactionRatio == 0.0;
+ _interactionRatio = obj;
+
+ if (_isInteractingForRefresh)
+ {
+ if (RefreshVisualizerState == RefreshVisualizerState.Idle)
+ {
+ if (wasAtZero)
+ {
+ if (_interactionRatio > _executingRatio)
+ {
+ RefreshVisualizerState = RefreshVisualizerState.Pending;
+ }
+ else if (_interactionRatio > 0)
+ {
+ RefreshVisualizerState = RefreshVisualizerState.Interacting;
+ }
+ }
+ else if (_interactionRatio > 0)
+ {
+ RefreshVisualizerState = RefreshVisualizerState.Peeking;
+ }
+ }
+ else if (RefreshVisualizerState == RefreshVisualizerState.Interacting)
+ {
+ if (_interactionRatio <= 0)
+ {
+ RefreshVisualizerState = RefreshVisualizerState.Idle;
+ }
+ else if (_interactionRatio > _executingRatio)
+ {
+ RefreshVisualizerState = RefreshVisualizerState.Pending;
+ }
+ else
+ {
+ UpdateContent();
+ }
+ }
+ else if (RefreshVisualizerState == RefreshVisualizerState.Pending)
+ {
+ if (_interactionRatio <= _executingRatio)
+ {
+ RefreshVisualizerState = RefreshVisualizerState.Interacting;
+ }
+ else if (_interactionRatio <= 0)
+ {
+ RefreshVisualizerState = RefreshVisualizerState.Idle;
+ }
+ else
+ {
+ UpdateContent();
+ }
+ }
+ }
+ else
+ {
+ if (RefreshVisualizerState != RefreshVisualizerState.Refreshing)
+ {
+ if (_interactionRatio > 0)
+ {
+ RefreshVisualizerState = RefreshVisualizerState.Peeking;
+ }
+ else
+ {
+ RefreshVisualizerState = RefreshVisualizerState.Idle;
+ }
+ }
+ }
+ }
+
+ private void InteractingForRefreshObserver(bool obj)
+ {
+ _isInteractingForRefresh = obj;
+
+ if (!_isInteractingForRefresh)
+ {
+ switch (_refreshVisualizerState)
+ {
+ case RefreshVisualizerState.Pending:
+ RequestRefresh();
+ break;
+ case RefreshVisualizerState.Refreshing:
+ // We don't want to interrupt a currently executing refresh.
+ break;
+ default:
+ RefreshVisualizerState = RefreshVisualizerState.Idle;
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizerOrientation.cs b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizerOrientation.cs
new file mode 100644
index 0000000000..1ea37f67b9
--- /dev/null
+++ b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizerOrientation.cs
@@ -0,0 +1,13 @@
+namespace Avalonia.Controls
+{
+ ///
+ /// Defines constants that specify the orientation of a RefreshVisualizer.
+ ///
+ public enum RefreshVisualizerOrientation
+ {
+ Auto,
+ Normal,
+ Rotate90DegreesCounterclockwise,
+ Rotate270DegreesCounterclockwise
+ }
+}
diff --git a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizerState.cs b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizerState.cs
new file mode 100644
index 0000000000..5ab52f4de6
--- /dev/null
+++ b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizerState.cs
@@ -0,0 +1,14 @@
+namespace Avalonia.Controls
+{
+ ///
+ /// Defines constants that specify the state of a RefreshVisualizer
+ ///
+ public enum RefreshVisualizerState
+ {
+ Idle,
+ Peeking,
+ Interacting,
+ Pending,
+ Refreshing
+ }
+}
diff --git a/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs b/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs
new file mode 100644
index 0000000000..c3aebc82c5
--- /dev/null
+++ b/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs
@@ -0,0 +1,274 @@
+using System;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Rendering.Composition;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Controls.PullToRefresh
+{
+ internal class ScrollViewerIRefreshInfoProviderAdapter
+ {
+ private const int MaxSearchDepth = 10;
+ private const int InitialOffsetThreshold = 1;
+
+ private PullDirection _refreshPullDirection;
+ private ScrollViewer? _scrollViewer;
+ private RefreshInfoProvider? _refreshInfoProvider;
+ private PullGestureRecognizer? _pullGestureRecognizer;
+ private InputElement? _interactionSource;
+ private bool _isVisualizerInteractionSourceAttached;
+
+ public ScrollViewerIRefreshInfoProviderAdapter(PullDirection pullDirection)
+ {
+ _refreshPullDirection = pullDirection;
+ }
+
+ public RefreshInfoProvider? AdaptFromTree(Visual root, Size? refreshVIsualizerSize)
+ {
+ if (root is ScrollViewer scrollViewer)
+ {
+ return Adapt(scrollViewer, refreshVIsualizerSize);
+ }
+ else
+ {
+ int depth = 0;
+ while (depth < MaxSearchDepth)
+ {
+ var scroll = AdaptFromTreeRecursiveHelper(root, depth);
+
+ if (scroll != null)
+ {
+ return Adapt(scroll, refreshVIsualizerSize);
+ }
+
+ depth++;
+ }
+ }
+
+ ScrollViewer? AdaptFromTreeRecursiveHelper(Visual root, int depth)
+ {
+ if (depth == 0)
+ {
+ foreach (var child in root.VisualChildren)
+ {
+ if (child is ScrollViewer viewer)
+ {
+ return viewer;
+ }
+ }
+ }
+ else
+ {
+ foreach (var child in root.VisualChildren)
+ {
+ var viewer = AdaptFromTreeRecursiveHelper(child, depth - 1);
+ if (viewer != null)
+ {
+ return viewer;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ return null;
+ }
+
+ public RefreshInfoProvider Adapt(ScrollViewer adaptee, Size? refreshVIsualizerSize)
+ {
+ if (adaptee == null)
+ {
+ throw new ArgumentNullException(nameof(adaptee), "Adaptee cannot be null");
+ }
+
+ if (_scrollViewer != null)
+ {
+ CleanUpScrollViewer();
+ }
+
+ if (_refreshInfoProvider != null && _interactionSource != null)
+ {
+ _interactionSource.RemoveHandler(Gestures.PullGestureEvent, _refreshInfoProvider.InteractingStateEntered);
+ _interactionSource.RemoveHandler(Gestures.PullGestureEndedEvent, _refreshInfoProvider.InteractingStateExited);
+ }
+
+ _refreshInfoProvider = null;
+ _scrollViewer = adaptee;
+
+ if (_scrollViewer.Content == null)
+ {
+ throw new ArgumentException(nameof(adaptee), "Adaptee's content property cannot be null.");
+ }
+
+ var content = adaptee.Content as Visual;
+
+ if (content == null)
+ {
+ throw new ArgumentException(nameof(adaptee), "Adaptee's content property must be a Visual");
+ }
+
+ if (content.GetVisualParent() == null)
+ {
+ _scrollViewer.Loaded += ScrollViewer_Loaded;
+ }
+ else
+ {
+ ScrollViewer_Loaded(null, null);
+
+ if (content.Parent is not InputElement)
+ {
+ throw new ArgumentException(nameof(adaptee), "Adaptee's content's parent must be a InputElement");
+ }
+ }
+
+ _refreshInfoProvider = new RefreshInfoProvider(_refreshPullDirection, refreshVIsualizerSize, ElementComposition.GetElementVisual(content));
+
+ _pullGestureRecognizer = new PullGestureRecognizer(_refreshPullDirection);
+
+ if (_interactionSource != null)
+ {
+ _interactionSource.GestureRecognizers.Add(_pullGestureRecognizer);
+ _interactionSource.AddHandler(Gestures.PullGestureEvent, _refreshInfoProvider.InteractingStateEntered);
+ _interactionSource.AddHandler(Gestures.PullGestureEndedEvent, _refreshInfoProvider.InteractingStateExited);
+ _isVisualizerInteractionSourceAttached = true;
+ }
+
+ _scrollViewer.PointerPressed += ScrollViewer_PointerPressed;
+ _scrollViewer.PointerReleased += ScrollViewer_PointerReleased;
+ _scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged;
+
+ return _refreshInfoProvider;
+ }
+
+ private void ScrollViewer_ScrollChanged(object? sender, ScrollChangedEventArgs e)
+ {
+ if (_isVisualizerInteractionSourceAttached && _refreshInfoProvider != null && _refreshInfoProvider.IsInteractingForRefresh)
+ {
+ if (!IsWithinOffsetThreashold())
+ {
+ _refreshInfoProvider.IsInteractingForRefresh = false;
+ }
+ }
+ }
+
+ public void SetAnimations(RefreshVisualizer refreshVisualizer)
+ {
+ var visualizerComposition = ElementComposition.GetElementVisual(refreshVisualizer);
+ if (visualizerComposition != null)
+ {
+ var compositor = visualizerComposition.Compositor;
+
+ var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();
+ offsetAnimation.Target = "Offset";
+ offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue");
+ offsetAnimation.Duration = TimeSpan.FromMilliseconds(150);
+
+ var animation = compositor.CreateImplicitAnimationCollection();
+ animation["Offset"] = offsetAnimation;
+ visualizerComposition.ImplicitAnimations = animation;
+ }
+
+ if(_scrollViewer != null && _scrollViewer.Content is Visual visual)
+ {
+ var scollContentComposition = ElementComposition.GetElementVisual(visual);
+
+ if(scollContentComposition != null)
+ {
+ var compositor = scollContentComposition.Compositor;
+
+ var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();
+ offsetAnimation.Target = "Offset";
+ offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue");
+ offsetAnimation.Duration = TimeSpan.FromMilliseconds(150);
+
+ var animation = compositor.CreateImplicitAnimationCollection();
+ animation["Offset"] = offsetAnimation;
+ scollContentComposition.ImplicitAnimations = animation;
+ }
+ }
+ }
+
+ private void ScrollViewer_Loaded(object? sender, RoutedEventArgs? e)
+ {
+ var content = _scrollViewer?.Content as Visual;
+ if (content == null)
+ {
+ throw new ArgumentException(nameof(_scrollViewer), "Adaptee's content property must be a Visual");
+ }
+
+ if (content.Parent is not InputElement)
+ {
+ throw new ArgumentException(nameof(_scrollViewer), "Adaptee's content parent must be an InputElement");
+ }
+
+ MakeInteractionSource(content.Parent as InputElement);
+
+ if (_scrollViewer != null)
+ {
+ _scrollViewer.Loaded -= ScrollViewer_Loaded;
+ }
+ }
+
+ private void MakeInteractionSource(InputElement? element)
+ {
+ _interactionSource = element;
+
+ if (_pullGestureRecognizer != null && _refreshInfoProvider != null)
+ {
+ element?.GestureRecognizers.Add(_pullGestureRecognizer);
+ _interactionSource?.AddHandler(Gestures.PullGestureEvent, _refreshInfoProvider.InteractingStateEntered);
+ _interactionSource?.AddHandler(Gestures.PullGestureEndedEvent, _refreshInfoProvider.InteractingStateExited);
+ _isVisualizerInteractionSourceAttached = true;
+ }
+ }
+
+ private void ScrollViewer_PointerReleased(object? sender, PointerReleasedEventArgs e)
+ {
+ if (_refreshInfoProvider != null)
+ {
+ _refreshInfoProvider.IsInteractingForRefresh = false;
+ }
+ }
+
+ private void ScrollViewer_PointerPressed(object? sender, PointerPressedEventArgs e)
+ {
+ if (_refreshInfoProvider != null)
+ {
+ _refreshInfoProvider.PeekingMode = !IsWithinOffsetThreashold();
+ }
+ }
+
+ private bool IsWithinOffsetThreashold()
+ {
+ if (_scrollViewer != null)
+ {
+ var offset = _scrollViewer.Offset;
+
+ switch (_refreshPullDirection)
+ {
+ case PullDirection.TopToBottom:
+ return offset.Y < InitialOffsetThreshold;
+ case PullDirection.LeftToRight:
+ return offset.X < InitialOffsetThreshold;
+ case PullDirection.RightToLeft:
+ return offset.X > _scrollViewer.Extent.Width - _scrollViewer.Viewport.Width - InitialOffsetThreshold;
+ case PullDirection.BottomToTop:
+ return offset.Y > _scrollViewer.Extent.Height - _scrollViewer.Viewport.Height - InitialOffsetThreshold;
+ }
+ }
+
+ return false;
+ }
+
+ private void CleanUpScrollViewer()
+ {
+ if (_scrollViewer != null)
+ {
+ _scrollViewer.PointerPressed -= ScrollViewer_PointerPressed;
+ _scrollViewer.PointerReleased -= ScrollViewer_PointerReleased;
+ _scrollViewer.ScrollChanged -= ScrollViewer_ScrollChanged;
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/Selection/ISelectionModel.cs b/src/Avalonia.Controls/Selection/ISelectionModel.cs
index 3f4ae48263..4c2a355bb5 100644
--- a/src/Avalonia.Controls/Selection/ISelectionModel.cs
+++ b/src/Avalonia.Controls/Selection/ISelectionModel.cs
@@ -39,7 +39,7 @@ namespace Avalonia.Controls.Selection
return new BatchUpdateOperation(model);
}
- public struct BatchUpdateOperation : IDisposable
+ public record struct BatchUpdateOperation : IDisposable
{
private readonly ISelectionModel _owner;
private bool _isDisposed;
diff --git a/src/Avalonia.Controls/Selection/SelectionModel.cs b/src/Avalonia.Controls/Selection/SelectionModel.cs
index 154f1868f3..9bbddfcbb2 100644
--- a/src/Avalonia.Controls/Selection/SelectionModel.cs
+++ b/src/Avalonia.Controls/Selection/SelectionModel.cs
@@ -738,7 +738,7 @@ namespace Avalonia.Controls.Selection
}
}
- public struct BatchUpdateOperation : IDisposable
+ public record struct BatchUpdateOperation : IDisposable
{
private readonly SelectionModel _owner;
private bool _isDisposed;
diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs
index c8e05e5cb3..7a0da1ecc5 100644
--- a/src/Avalonia.Controls/TextBlock.cs
+++ b/src/Avalonia.Controls/TextBlock.cs
@@ -630,7 +630,7 @@ namespace Avalonia.Controls
}
else
{
- textSource = new SimpleTextSource((text ?? "").AsMemory(), defaultProperties);
+ textSource = new SimpleTextSource(text ?? "", defaultProperties);
}
return new TextLayout(
@@ -827,14 +827,14 @@ namespace Avalonia.Controls
InvalidateTextLayout();
}
- protected readonly struct SimpleTextSource : ITextSource
+ protected readonly record struct SimpleTextSource : ITextSource
{
- private readonly ReadOnlySlice _text;
+ private readonly CharacterBufferRange _text;
private readonly TextRunProperties _defaultProperties;
- public SimpleTextSource(ReadOnlySlice text, TextRunProperties defaultProperties)
+ public SimpleTextSource(string text, TextRunProperties defaultProperties)
{
- _text = text;
+ _text = new CharacterBufferRange(new CharacterBufferReference(text), text.Length);
_defaultProperties = defaultProperties;
}
@@ -852,7 +852,7 @@ namespace Avalonia.Controls
return new TextEndOfParagraph();
}
- return new TextCharacters(runText, _defaultProperties);
+ return new TextCharacters(runText.CharacterBufferReference, runText.Length, _defaultProperties);
}
}
@@ -873,21 +873,28 @@ namespace Avalonia.Controls
foreach (var textRun in _textRuns)
{
- if (textRun.TextSourceLength == 0)
+ if (textRun.Length == 0)
{
continue;
}
- if (textSourceIndex >= currentPosition + textRun.TextSourceLength)
+ if (textSourceIndex >= currentPosition + textRun.Length)
{
- currentPosition += textRun.TextSourceLength;
+ currentPosition += textRun.Length;
continue;
}
- if (textRun is TextCharacters)
+ if (textRun is TextCharacters)
{
- return new TextCharacters(textRun.Text.Skip(Math.Max(0, textSourceIndex - currentPosition)), textRun.Properties!);
+ var characterBufferReference = textRun.CharacterBufferReference;
+
+ var skip = Math.Max(0, textSourceIndex - currentPosition);
+
+ return new TextCharacters(
+ new CharacterBufferReference(characterBufferReference.CharacterBuffer, characterBufferReference.OffsetToFirstChar + skip),
+ textRun.Length - skip,
+ textRun.Properties!);
}
return textRun;
diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs
index 699170df5a..1bdec878d9 100644
--- a/src/Avalonia.Controls/TextBox.cs
+++ b/src/Avalonia.Controls/TextBox.cs
@@ -961,7 +961,9 @@ namespace Avalonia.Controls
var length = 0;
- var graphemeEnumerator = new GraphemeEnumerator(input.AsMemory());
+ var inputRange = new CharacterBufferRange(new CharacterBufferReference(input), input.Length);
+
+ var graphemeEnumerator = new GraphemeEnumerator(inputRange);
while (graphemeEnumerator.MoveNext())
{
diff --git a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
index 6a79760011..52815b943d 100644
--- a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
+++ b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
@@ -77,12 +77,14 @@ namespace Avalonia.Controls
foreach (var run in textLine.TextRuns)
{
- if(run.Text.Length > 0)
+ if(run.Length > 0)
{
+ var characterBufferRange = new CharacterBufferRange(run.CharacterBufferReference, run.Length);
+
#if NET6_0
- builder.Append(run.Text.Span);
+ builder.Append(characterBufferRange.Span);
#else
- builder.Append(run.Text.Span.ToArray());
+ builder.Append(characterBufferRange.Span.ToArray());
#endif
}
}
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index d2d5a8309c..2cd55dc3ab 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -323,17 +323,7 @@ namespace Avalonia.Controls
??= AvaloniaLocator.Current.GetService()?.CreateProvider(this)
?? (PlatformImpl as ITopLevelImplWithStorageProvider)?.StorageProvider
?? throw new InvalidOperationException("StorageProvider platform implementation is not available.");
-
- IRenderTarget IRenderRoot.CreateRenderTarget() => CreateRenderTarget();
-
- ///
- protected virtual IRenderTarget CreateRenderTarget()
- {
- if(PlatformImpl == null)
- throw new InvalidOperationException("Can't create render target, PlatformImpl is null (might be already disposed)");
- return _renderInterface!.CreateRenderTarget(PlatformImpl.Surfaces);
- }
-
+
///
void IRenderRoot.Invalidate(Rect rect)
{
diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs
index 90e8a60828..0655af72a1 100644
--- a/src/Avalonia.Controls/TreeView.cs
+++ b/src/Avalonia.Controls/TreeView.cs
@@ -179,6 +179,26 @@ namespace Avalonia.Controls
}
}
+ ///
+ /// Collapse the specified all descendent s.
+ ///
+ /// The item to collapse.
+ public void CollapseSubTree(TreeViewItem item)
+ {
+ item.IsExpanded = false;
+
+ if (item.Presenter?.Panel != null)
+ {
+ foreach (var child in item.Presenter.Panel.Children)
+ {
+ if (child is TreeViewItem treeViewItem)
+ {
+ CollapseSubTree(treeViewItem);
+ }
+ }
+ }
+ }
+
///
/// Selects all items in the .
///
diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs
index 538e91053f..fb20a7ee14 100644
--- a/src/Avalonia.Controls/TreeViewItem.cs
+++ b/src/Avalonia.Controls/TreeViewItem.cs
@@ -1,3 +1,5 @@
+using System;
+using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Metadata;
@@ -147,30 +149,94 @@ namespace Avalonia.Controls
{
if (!e.Handled)
{
- switch (e.Key)
+ Func? handler =
+ e.Key switch
+ {
+ Key.Left => ApplyToItemOrRecursivelyIfCtrl(FocusAwareCollapseItem, e.KeyModifiers),
+ Key.Right => ApplyToItemOrRecursivelyIfCtrl(ExpandItem, e.KeyModifiers),
+ Key.Enter or Key.Space => ApplyToItemOrRecursivelyIfCtrl(IsExpanded ? CollapseItem : ExpandItem, e.KeyModifiers),
+
+ // do not handle CTRL with numpad keys
+ Key.Subtract => FocusAwareCollapseItem,
+ Key.Add => ExpandItem,
+ Key.Divide => ApplyToSubtree(CollapseItem),
+ Key.Multiply => ApplyToSubtree(ExpandItem),
+ _ => null,
+ };
+
+ if (handler is not null)
+ {
+ e.Handled = handler(this);
+ }
+
+ // NOTE: these local functions do not use the TreeView.Expand/CollapseSubtree
+ // function because we want to know if any items were in fact expanded to set the
+ // event handled status. Also the handling here avoids a potential infinite recursion/stack overflow.
+ static Func ApplyToSubtree(Func f)
+ {
+ // Calling toList enumerates all items before applying functions. This avoids a
+ // potential infinite loop if there is an infinite tree (the control catalog is
+ // lazily infinite). But also means a lazily loaded tree will not be expanded completely.
+ return t => SubTree(t)
+ .ToList()
+ .Select(treeViewItem => f(treeViewItem))
+ .Aggregate(false, (p, c) => p || c);
+ }
+
+ static Func ApplyToItemOrRecursivelyIfCtrl(Func f, KeyModifiers keyModifiers)
+ {
+ if (keyModifiers.HasAllFlags(KeyModifiers.Control))
+ {
+ return ApplyToSubtree(f);
+ }
+
+ return f;
+ }
+
+ static bool ExpandItem(TreeViewItem treeViewItem)
+ {
+ if (treeViewItem.ItemCount > 0 && !treeViewItem.IsExpanded)
+ {
+ treeViewItem.IsExpanded = true;
+ return true;
+ }
+
+ return false;
+ }
+
+ static bool CollapseItem(TreeViewItem treeViewItem)
{
- case Key.Right:
- if (ItemsView.Count > 0 && !IsExpanded)
+ if (treeViewItem.ItemCount > 0 && treeViewItem.IsExpanded)
+ {
+ treeViewItem.IsExpanded = false;
+ return true;
+ }
+
+ return false;
+ }
+
+ static bool FocusAwareCollapseItem(TreeViewItem treeViewItem)
+ {
+ if (treeViewItem.ItemCount > 0 && treeViewItem.IsExpanded)
+ {
+ if (treeViewItem.IsFocused)
{
- IsExpanded = true;
- e.Handled = true;
+ treeViewItem.IsExpanded = false;
}
- break;
-
- case Key.Left:
- if (ItemsView.Count > 0 && IsExpanded)
+ else
{
- if (IsFocused)
- {
- IsExpanded = false;
- }
- else
- {
- FocusManager.Instance?.Focus(this, NavigationMethod.Directional);
- }
- e.Handled = true;
+ FocusManager.Instance?.Focus(treeViewItem, NavigationMethod.Directional);
}
- break;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ static IEnumerable SubTree(TreeViewItem treeViewItem)
+ {
+ return new[] { treeViewItem }.Concat(treeViewItem.LogicalChildren.OfType().SelectMany(child => SubTree(child)));
}
}
@@ -179,8 +245,19 @@ namespace Avalonia.Controls
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
+ if (_header is InputElement previousInputMethod)
+ {
+ previousInputMethod.DoubleTapped -= HeaderDoubleTapped;
+ }
+
_header = e.NameScope.Find("PART_Header");
_templateApplied = true;
+
+ if (_header is InputElement im)
+ {
+ im.DoubleTapped += HeaderDoubleTapped;
+ }
+
if (_deferredBringIntoViewFlag)
{
_deferredBringIntoViewFlag = false;
@@ -200,5 +277,14 @@ namespace Avalonia.Controls
return logical != null ? result : @default;
}
+
+ private void HeaderDoubleTapped(object? sender, TappedEventArgs e)
+ {
+ if (ItemCount > 0)
+ {
+ IsExpanded = !IsExpanded;
+ e.Handled = true;
+ }
+ }
}
}
diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs
index 559d674c02..a893c74324 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/src/Avalonia.Controls/Window.cs
@@ -176,6 +176,8 @@ namespace Avalonia.Controls
private object? _dialogResult;
private readonly Size _maxPlatformClientSize;
private WindowStartupLocation _windowStartupLocation;
+ private bool _shown;
+ private bool _showingAsDialog;
///
/// Initializes static members of the class.
@@ -508,6 +510,8 @@ namespace Avalonia.Controls
Owner = null;
PlatformImpl?.Dispose();
+
+ _showingAsDialog = false;
}
private bool ShouldCancelClose(CancelEventArgs? args = null)
@@ -563,29 +567,33 @@ namespace Avalonia.Controls
///
public override void Hide()
{
- if (!IsVisible)
+ using (FreezeVisibilityChangeHandling())
{
- return;
- }
+ if (!_shown)
+ {
+ return;
+ }
- Renderer?.Stop();
+ Renderer?.Stop();
- if (Owner is Window owner)
- {
- owner.RemoveChild(this);
- }
+ if (Owner is Window owner)
+ {
+ owner.RemoveChild(this);
+ }
- if (_children.Count > 0)
- {
- foreach (var child in _children.ToArray())
+ if (_children.Count > 0)
{
- child.child.Hide();
+ foreach (var child in _children.ToArray())
+ {
+ child.child.Hide();
+ }
}
- }
- Owner = null;
- PlatformImpl?.Hide();
- IsVisible = false;
+ Owner = null;
+ PlatformImpl?.Hide();
+ IsVisible = false;
+ _shown = false;
+ }
}
///
@@ -599,81 +607,124 @@ namespace Avalonia.Controls
ShowCore(null);
}
+ protected override void IsVisibleChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ if (!IgnoreVisibilityChanges)
+ {
+ var isVisible = e.GetNewValue();
+
+ if (_shown != isVisible)
+ {
+ if(!_shown)
+ {
+ Show();
+ }
+ else
+ {
+ if (_showingAsDialog)
+ {
+ Close(false);
+ }
+ else
+ {
+ Hide();
+ }
+ }
+ }
+ }
+ }
+
///
- /// Shows the window as a child of .
+ /// Shows the window as a child of .
///
- /// Window that will be a parent of the shown window.
+ /// Window that will be the owner of the shown window.
///
/// The window has already been closed.
///
- public void Show(Window parent)
+ public void Show(Window owner)
{
- if (parent is null)
+ if (owner is null)
{
- throw new ArgumentNullException(nameof(parent), "Showing a child window requires valid parent.");
+ throw new ArgumentNullException(nameof(owner), "Showing a child window requires valid parent.");
}
- ShowCore(parent);
+ ShowCore(owner);
}
- private void ShowCore(Window? parent)
+ private void EnsureStateBeforeShow()
{
if (PlatformImpl == null)
{
throw new InvalidOperationException("Cannot re-show a closed window.");
}
+ }
+
+ private void EnsureParentStateBeforeShow(Window owner)
+ {
+ if (owner.PlatformImpl == null)
+ {
+ throw new InvalidOperationException("Cannot show a window with a closed owner.");
+ }
- if (parent != null)
+ if (owner == this)
{
- if (parent.PlatformImpl == null)
- {
- throw new InvalidOperationException("Cannot show a window with a closed parent.");
- }
- else if (parent == this)
- {
- throw new InvalidOperationException("A Window cannot be its own parent.");
- }
- else if (!parent.IsVisible)
- {
- throw new InvalidOperationException("Cannot show window with non-visible parent.");
- }
+ throw new InvalidOperationException("A Window cannot be its own owner.");
}
- if (IsVisible)
+ if (!owner.IsVisible)
{
- return;
+ throw new InvalidOperationException("Cannot show window with non-visible owner.");
}
+ }
- RaiseEvent(new RoutedEventArgs(WindowOpenedEvent));
+ private void ShowCore(Window? owner)
+ {
+ using (FreezeVisibilityChangeHandling())
+ {
+ EnsureStateBeforeShow();
- EnsureInitialized();
- ApplyStyling();
- IsVisible = true;
+ if (owner != null)
+ {
+ EnsureParentStateBeforeShow(owner);
+ }
- var initialSize = new Size(
- double.IsNaN(Width) ? Math.Max(MinWidth, ClientSize.Width) : Width,
- double.IsNaN(Height) ? Math.Max(MinHeight, ClientSize.Height) : Height);
+ if (_shown)
+ {
+ return;
+ }
- if (initialSize != ClientSize)
- {
- PlatformImpl?.Resize(initialSize, PlatformResizeReason.Layout);
- }
+ RaiseEvent(new RoutedEventArgs(WindowOpenedEvent));
- LayoutManager.ExecuteInitialLayoutPass();
+ EnsureInitialized();
+ ApplyStyling();
+ _shown = true;
+ IsVisible = true;
- if (PlatformImpl != null && parent?.PlatformImpl is not null)
- {
- PlatformImpl.SetParent(parent.PlatformImpl);
- }
+ var initialSize = new Size(
+ double.IsNaN(Width) ? Math.Max(MinWidth, ClientSize.Width) : Width,
+ double.IsNaN(Height) ? Math.Max(MinHeight, ClientSize.Height) : Height);
- Owner = parent;
- parent?.AddChild(this, false);
+ if (initialSize != ClientSize)
+ {
+ PlatformImpl?.Resize(initialSize, PlatformResizeReason.Layout);
+ }
- SetWindowStartupLocation(parent?.PlatformImpl);
+ LayoutManager.ExecuteInitialLayoutPass();
- PlatformImpl?.Show(ShowActivated, false);
- Renderer?.Start();
- OnOpened(EventArgs.Empty);
+ if (PlatformImpl != null && owner?.PlatformImpl is not null)
+ {
+ PlatformImpl.SetParent(owner.PlatformImpl);
+ }
+
+ Owner = owner;
+ owner?.AddChild(this, false);
+
+ SetWindowStartupLocation(owner?.PlatformImpl);
+
+ PlatformImpl?.Show(ShowActivated, false);
+ Renderer?.Start();
+ OnOpened(EventArgs.Empty);
+ }
}
///
@@ -703,68 +754,66 @@ namespace Avalonia.Controls
///
public Task ShowDialog(Window owner)
{
- if (owner == null)
- {
- throw new ArgumentNullException(nameof(owner));
- }
- else if (owner.PlatformImpl == null)
- {
- throw new InvalidOperationException("Cannot show a window with a closed owner.");
- }
- else if (owner == this)
+ using (FreezeVisibilityChangeHandling())
{
- throw new InvalidOperationException("A Window cannot be its own owner.");
- }
- else if (IsVisible)
- {
- throw new InvalidOperationException("The window is already being shown.");
- }
- else if (!owner.IsVisible)
- {
- throw new InvalidOperationException("Cannot show window with non-visible parent.");
- }
+ EnsureStateBeforeShow();
- RaiseEvent(new RoutedEventArgs(WindowOpenedEvent));
+ if (owner == null)
+ {
+ throw new ArgumentNullException(nameof(owner));
+ }
- EnsureInitialized();
- ApplyStyling();
- IsVisible = true;
+ EnsureParentStateBeforeShow(owner);
- var initialSize = new Size(
- double.IsNaN(Width) ? ClientSize.Width : Width,
- double.IsNaN(Height) ? ClientSize.Height : Height);
+ if (_shown)
+ {
+ throw new InvalidOperationException("The window is already being shown.");
+ }
- if (initialSize != ClientSize)
- {
- PlatformImpl?.Resize(initialSize, PlatformResizeReason.Layout);
- }
+ RaiseEvent(new RoutedEventArgs(WindowOpenedEvent));
- LayoutManager.ExecuteInitialLayoutPass();
+ EnsureInitialized();
+ ApplyStyling();
+ _shown = true;
+ _showingAsDialog = true;
+ IsVisible = true;
- var result = new TaskCompletionSource();
+ var initialSize = new Size(
+ double.IsNaN(Width) ? ClientSize.Width : Width,
+ double.IsNaN(Height) ? ClientSize.Height : Height);
- PlatformImpl?.SetParent(owner.PlatformImpl);
- Owner = owner;
- owner.AddChild(this, true);
+ if (initialSize != ClientSize)
+ {
+ PlatformImpl?.Resize(initialSize, PlatformResizeReason.Layout);
+ }
- SetWindowStartupLocation(owner.PlatformImpl);
+ LayoutManager.ExecuteInitialLayoutPass();
- PlatformImpl?.Show(ShowActivated, true);
+ var result = new TaskCompletionSource();
- Renderer?.Start();
+ PlatformImpl?.SetParent(owner.PlatformImpl!);
+ Owner = owner;
+ owner.AddChild(this, true);
- Observable.FromEventPattern(
- x => Closed += x,
- x => Closed -= x)
- .Take(1)
- .Subscribe(_ =>
- {
- owner.Activate();
- result.SetResult((TResult)(_dialogResult ?? default(TResult)!));
- });
+ SetWindowStartupLocation(owner.PlatformImpl);
- OnOpened(EventArgs.Empty);
- return result.Task;
+ PlatformImpl?.Show(ShowActivated, true);
+
+ Renderer?.Start();
+
+ Observable.FromEventPattern(
+ x => Closed += x,
+ x => Closed -= x)
+ .Take(1)
+ .Subscribe(_ =>
+ {
+ owner.Activate();
+ result.SetResult((TResult)(_dialogResult ?? default(TResult)!));
+ });
+
+ OnOpened(EventArgs.Empty);
+ return result.Task;
+ }
}
private void UpdateEnabled()
diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs
index 8f1b2198ad..b71dc6df44 100644
--- a/src/Avalonia.Controls/WindowBase.cs
+++ b/src/Avalonia.Controls/WindowBase.cs
@@ -42,9 +42,11 @@ namespace Avalonia.Controls
private bool _hasExecutedInitialLayoutPass;
private bool _isActive;
- private bool _ignoreVisibilityChange;
+ private int _ignoreVisibilityChanges;
private WindowBase? _owner;
+ protected bool IgnoreVisibilityChanges => _ignoreVisibilityChanges > 0;
+
static WindowBase()
{
IsVisibleProperty.OverrideDefaultValue(false);
@@ -66,6 +68,11 @@ namespace Avalonia.Controls
impl.PositionChanged = HandlePositionChanged;
}
+ protected IDisposable FreezeVisibilityChangeHandling()
+ {
+ return new IgnoreVisibilityChangesDisposable(this);
+ }
+
///
/// Fired when the window is activated.
///
@@ -125,18 +132,12 @@ namespace Avalonia.Controls
///
public virtual void Hide()
{
- _ignoreVisibilityChange = true;
-
- try
+ using (FreezeVisibilityChangeHandling())
{
Renderer?.Stop();
PlatformImpl?.Hide();
IsVisible = false;
}
- finally
- {
- _ignoreVisibilityChange = false;
- }
}
///
@@ -144,9 +145,7 @@ namespace Avalonia.Controls
///
public virtual void Show()
{
- _ignoreVisibilityChange = true;
-
- try
+ using (FreezeVisibilityChangeHandling())
{
EnsureInitialized();
ApplyStyling();
@@ -157,14 +156,11 @@ namespace Avalonia.Controls
LayoutManager.ExecuteInitialLayoutPass();
_hasExecutedInitialLayoutPass = true;
}
+
PlatformImpl?.Show(true, false);
Renderer?.Start();
OnOpened(EventArgs.Empty);
}
- finally
- {
- _ignoreVisibilityChange = false;
- }
}
///
@@ -202,23 +198,17 @@ namespace Avalonia.Controls
protected override void HandleClosed()
{
- _ignoreVisibilityChange = true;
-
- try
+ using (FreezeVisibilityChangeHandling())
{
IsVisible = false;
-
+
if (this is IFocusScope scope)
{
FocusManager.Instance?.RemoveFocusScope(scope);
}
-
+
base.HandleClosed();
}
- finally
- {
- _ignoreVisibilityChange = false;
- }
}
///
@@ -318,9 +308,9 @@ namespace Avalonia.Controls
Deactivated?.Invoke(this, EventArgs.Empty);
}
- private void IsVisibleChanged(AvaloniaPropertyChangedEventArgs e)
+ protected virtual void IsVisibleChanged(AvaloniaPropertyChangedEventArgs e)
{
- if (!_ignoreVisibilityChange)
+ if (_ignoreVisibilityChanges == 0)
{
if ((bool)e.NewValue!)
{
@@ -332,5 +322,21 @@ namespace Avalonia.Controls
}
}
}
+
+ private readonly struct IgnoreVisibilityChangesDisposable : IDisposable
+ {
+ private readonly WindowBase _windowBase;
+
+ public IgnoreVisibilityChangesDisposable(WindowBase windowBase)
+ {
+ _windowBase = windowBase;
+ _windowBase._ignoreVisibilityChanges++;
+ }
+
+ public void Dispose()
+ {
+ _windowBase._ignoreVisibilityChanges--;
+ }
+ }
}
}
diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs
index 71389be1a6..80a4c7d897 100644
--- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs
+++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs
@@ -61,7 +61,10 @@ namespace Avalonia.DesignerSupport.Remote
}));
}
- public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer((Visual)root);
+ public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer((Visual)root, () =>
+ new PlatformRenderInterfaceContextManager(null)
+ .CreateRenderTarget(Surfaces));
+
public void Dispose()
{
}
diff --git a/src/Avalonia.FreeDesktop/IX11InputMethod.cs b/src/Avalonia.FreeDesktop/IX11InputMethod.cs
index 5d91118978..9fa9c1809e 100644
--- a/src/Avalonia.FreeDesktop/IX11InputMethod.cs
+++ b/src/Avalonia.FreeDesktop/IX11InputMethod.cs
@@ -11,7 +11,9 @@ namespace Avalonia.FreeDesktop
(ITextInputMethodImpl method, IX11InputMethodControl control) CreateClient(IntPtr xid);
}
+#pragma warning disable CA1815 // Override equals and operator equals on value types
public struct X11InputMethodForwardedKey
+#pragma warning restore CA1815 // Override equals and operator equals on value types
{
public int KeyVal { get; set; }
public KeyModifiers Modifiers { get; set; }
diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
index 7abc0ca131..be0c7ed7a7 100644
--- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
+++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
@@ -12,7 +12,7 @@ using Avalonia.Media.Imaging;
namespace Avalonia.Headless
{
- internal class HeadlessPlatformRenderInterface : IPlatformRenderInterface
+ internal class HeadlessPlatformRenderInterface : IPlatformRenderInterface, IPlatformRenderInterfaceContext
{
public static void Initialize()
{
@@ -22,6 +22,8 @@ namespace Avalonia.Headless
public IEnumerable InstalledFontNames { get; } = new[] { "Tahoma" };
+ public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) => this;
+
public bool SupportsIndividualRoundRects => false;
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
@@ -47,6 +49,7 @@ namespace Avalonia.Headless
public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) => throw new NotImplementedException();
public IRenderTarget CreateRenderTarget(IEnumerable surfaces) => new HeadlessRenderTarget();
+ public object TryGetFeature(Type featureType) => null;
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)
{
@@ -313,6 +316,8 @@ namespace Avalonia.Headless
return new HeadlessDrawingContextStub();
}
+ public bool IsCorrupted => false;
+
public void Blit(IDrawingContextImpl context)
{
@@ -474,6 +479,13 @@ namespace Avalonia.Headless
{
return new HeadlessDrawingContextStub();
}
+
+ public bool IsCorrupted => false;
+ }
+
+ public void Dispose()
+ {
+
}
}
}
diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs
index 688b8e0398..1cc0fa73bb 100644
--- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs
+++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs
@@ -145,13 +145,15 @@ namespace Avalonia.Headless
class HeadlessTextShaperStub : ITextShaperImpl
{
- public ShapedBuffer ShapeText(ReadOnlySlice text, TextShaperOptions options)
+ public ShapedBuffer ShapeText(CharacterBufferReference text, int length, TextShaperOptions options)
{
var typeface = options.Typeface;
var fontRenderingEmSize = options.FontRenderingEmSize;
var bidiLevel = options.BidiLevel;
- return new ShapedBuffer(text, text.Length, typeface, fontRenderingEmSize, bidiLevel);
+ var characterBufferRange = new CharacterBufferRange(text, length);
+
+ return new ShapedBuffer(characterBufferRange, length, typeface, fontRenderingEmSize, bidiLevel);
}
}
diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs
index 742df3324b..725fab1eaa 100644
--- a/src/Avalonia.Headless/HeadlessWindowImpl.cs
+++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs
@@ -56,8 +56,9 @@ namespace Avalonia.Headless
public IRenderer CreateRenderer(IRenderRoot root)
=> AvaloniaHeadlessPlatform.Compositor != null
- ? new CompositingRenderer(root, AvaloniaHeadlessPlatform.Compositor)
- : new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService());
+ ? new CompositingRenderer(root, AvaloniaHeadlessPlatform.Compositor, () => Surfaces)
+ : new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService(),
+ () => new PlatformRenderInterfaceContextManager(null).CreateRenderTarget(Surfaces), null);
public void Invalidate(Rect rect)
{
diff --git a/src/Avalonia.Native/AvaloniaNativePlatformOpenGlInterface.cs b/src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs
similarity index 75%
rename from src/Avalonia.Native/AvaloniaNativePlatformOpenGlInterface.cs
rename to src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs
index 14d27a90e9..8e40960af6 100644
--- a/src/Avalonia.Native/AvaloniaNativePlatformOpenGlInterface.cs
+++ b/src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using Avalonia.OpenGL;
using Avalonia.Native.Interop;
using System.Drawing;
@@ -8,18 +9,18 @@ using Avalonia.Threading;
namespace Avalonia.Native
{
- class AvaloniaNativePlatformOpenGlInterface : IPlatformOpenGlInterface
+ class AvaloniaNativeGlPlatformGraphics : IPlatformGraphics
{
private readonly IAvnGlDisplay _display;
- public AvaloniaNativePlatformOpenGlInterface(IAvnGlDisplay display)
+ public AvaloniaNativeGlPlatformGraphics(IAvnGlDisplay display)
{
_display = display;
- var immediate = display.CreateContext(null);
+ var context = display.CreateContext(null);
int major, minor;
GlInterface glInterface;
- using (immediate.MakeCurrent())
+ using (context.MakeCurrent())
{
var basic = new GlBasicInfoInterface(display.GetProcAddress);
basic.GetIntegerv(GlConsts.GL_MAJOR_VERSION, out major);
@@ -32,24 +33,23 @@ namespace Avalonia.Native
});
}
- GlDisplay = new GlDisplay(display, glInterface, immediate.SampleCount, immediate.StencilSize);
- MainContext = new GlContext(GlDisplay, null, immediate, _version);
+ GlDisplay = new GlDisplay(display, glInterface, context.SampleCount, context.StencilSize);
+ SharedContext =(GlContext)CreateContext();
}
- internal GlContext MainContext { get; }
- public IGlContext PrimaryContext => MainContext;
- IPlatformGpuContext IPlatformGpu.PrimaryContext => PrimaryContext;
+
+ public bool UsesSharedContext => true;
+ public IPlatformGraphicsContext CreateContext() => new GlContext(GlDisplay,
+ null, _display.CreateContext(null), _version);
+
+ public IPlatformGraphicsContext GetSharedContext() => SharedContext;
+
public bool CanShareContexts => true;
public bool CanCreateContexts => true;
internal GlDisplay GlDisplay;
private readonly GlVersion _version;
-
- public IGlContext CreateSharedContext() => new GlContext(GlDisplay,
- MainContext, _display.CreateContext(MainContext.Context), _version);
-
- public IGlContext CreateContext() => new GlContext(GlDisplay,
- null, _display.CreateContext(null), _version);
+ internal GlContext SharedContext { get; }
}
class GlDisplay
@@ -71,6 +71,10 @@ namespace Avalonia.Native
public int StencilSize { get; }
public void ClearContext() => _display.LegacyClearCurrentContext();
+
+ public GlContext CreateSharedContext(GlContext share) =>
+ new GlContext(this, share, _display.CreateContext(share.Context), share.Version);
+
}
class GlContext : IGlContext
@@ -91,7 +95,14 @@ namespace Avalonia.Native
public GlInterface GlInterface => _display.GlInterface;
public int SampleCount => _display.SampleCount;
public int StencilSize => _display.StencilSize;
- public IDisposable MakeCurrent() => Context.MakeCurrent();
+ public IDisposable MakeCurrent()
+ {
+ if (IsLost)
+ throw new PlatformGraphicsContextLostException();
+ return Context.MakeCurrent();
+ }
+
+ public bool IsLost => Context == null;
public IDisposable EnsureCurrent() => MakeCurrent();
public bool IsSharedWith(IGlContext context)
@@ -103,12 +114,18 @@ namespace Avalonia.Native
|| _sharedWith != null && _sharedWith == c._sharedWith;
}
+ public bool CanCreateSharedContext => true;
+
+ public IGlContext CreateSharedContext(IEnumerable preferredVersions = null) =>
+ _display.CreateSharedContext(_sharedWith ?? this);
public void Dispose()
{
Context.Dispose();
Context = null;
}
+
+ public object TryGetFeature(Type featureType) => null;
}
@@ -125,7 +142,6 @@ namespace Avalonia.Native
public IGlPlatformSurfaceRenderingSession BeginDraw()
{
- var feature = (AvaloniaNativePlatformOpenGlInterface)AvaloniaLocator.Current.GetService();
return new GlPlatformSurfaceRenderingSession(_context, _target.BeginDrawing());
}
@@ -172,16 +188,14 @@ namespace Avalonia.Native
class GlPlatformSurface : IGlPlatformSurface
{
private readonly IAvnWindowBase _window;
- private readonly IGlContext _context;
-
- public GlPlatformSurface(IAvnWindowBase window, IGlContext context)
+ public GlPlatformSurface(IAvnWindowBase window)
{
_window = window;
- _context = context;
}
- public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
+ public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context)
{
- return new GlPlatformSurfaceRenderTarget(_window.CreateGlRenderTarget(), _context);
+ var avnContext = (GlContext)context;
+ return new GlPlatformSurfaceRenderTarget(_window.CreateGlRenderTarget(), avnContext);
}
}
diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs
index db6b6f57cd..a5b2ea30cc 100644
--- a/src/Avalonia.Native/AvaloniaNativePlatform.cs
+++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs
@@ -18,13 +18,14 @@ namespace Avalonia.Native
{
private readonly IAvaloniaNativeFactory _factory;
private AvaloniaNativePlatformOptions _options;
- private AvaloniaNativePlatformOpenGlInterface _platformGl;
+ private AvaloniaNativeGlPlatformGraphics _platformGl;
[DllImport("libAvaloniaNative")]
static extern IntPtr CreateAvaloniaNative();
internal static readonly KeyboardDevice KeyboardDevice = new KeyboardDevice();
[CanBeNull] internal static Compositor Compositor { get; private set; }
+ [CanBeNull] internal static PlatformRenderInterfaceContextManager RenderInterface { get; private set; }
public static AvaloniaNativePlatform Initialize(IntPtr factory, AvaloniaNativePlatformOptions options)
{
@@ -126,10 +127,9 @@ namespace Avalonia.Native
{
try
{
- _platformGl = new AvaloniaNativePlatformOpenGlInterface(_factory.ObtainGlDisplay());
+ _platformGl = new AvaloniaNativeGlPlatformGraphics(_factory.ObtainGlDisplay());
AvaloniaLocator.CurrentMutable
- .Bind().ToConstant(_platformGl)
- .Bind().ToConstant(_platformGl);
+ .Bind().ToConstant(_platformGl);
}
catch (Exception)
@@ -137,12 +137,14 @@ namespace Avalonia.Native
// ignored
}
}
-
+
if (_options.UseDeferredRendering && _options.UseCompositor)
{
Compositor = new Compositor(renderLoop, _platformGl);
}
+ else
+ RenderInterface = new PlatformRenderInterfaceContextManager(_platformGl);
}
public ITrayIconImpl CreateTrayIcon()
diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs
index 76d3905b47..0953527284 100644
--- a/src/Avalonia.Native/PopupImpl.cs
+++ b/src/Avalonia.Native/PopupImpl.cs
@@ -8,12 +8,12 @@ namespace Avalonia.Native
class PopupImpl : WindowBaseImpl, IPopupImpl
{
private readonly AvaloniaNativePlatformOptions _opts;
- private readonly AvaloniaNativePlatformOpenGlInterface _glFeature;
+ private readonly AvaloniaNativeGlPlatformGraphics _glFeature;
private readonly IWindowBaseImpl _parent;
public PopupImpl(IAvaloniaNativeFactory factory,
AvaloniaNativePlatformOptions opts,
- AvaloniaNativePlatformOpenGlInterface glFeature,
+ AvaloniaNativeGlPlatformGraphics glFeature,
IWindowBaseImpl parent) : base(factory, opts, glFeature)
{
_opts = opts;
@@ -21,8 +21,7 @@ namespace Avalonia.Native
_parent = parent;
using (var e = new PopupEvents(this))
{
- var context = _opts.UseGpu ? glFeature?.MainContext : null;
- Init(factory.CreatePopup(e, context?.Context), factory.CreateScreens(), context);
+ Init(factory.CreatePopup(e, _glFeature.SharedContext.Context), factory.CreateScreens());
}
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize));
}
diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs
index 9de794ed53..2201503168 100644
--- a/src/Avalonia.Native/WindowImpl.cs
+++ b/src/Avalonia.Native/WindowImpl.cs
@@ -14,14 +14,14 @@ namespace Avalonia.Native
internal class WindowImpl : WindowBaseImpl, IWindowImpl, ITopLevelImplWithNativeMenuExporter
{
private readonly AvaloniaNativePlatformOptions _opts;
- private readonly AvaloniaNativePlatformOpenGlInterface _glFeature;
+ private readonly AvaloniaNativeGlPlatformGraphics _glFeature;
IAvnWindow _native;
private double _extendTitleBarHeight = -1;
private DoubleClickHelper _doubleClickHelper;
internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts,
- AvaloniaNativePlatformOpenGlInterface glFeature) : base(factory, opts, glFeature)
+ AvaloniaNativeGlPlatformGraphics glFeature) : base(factory, opts, glFeature)
{
_opts = opts;
_glFeature = glFeature;
@@ -29,8 +29,7 @@ namespace Avalonia.Native
using (var e = new WindowEvents(this))
{
- var context = _opts.UseGpu ? glFeature?.MainContext : null;
- Init(_native = factory.CreateWindow(e, context?.Context), factory.CreateScreens(), context);
+ Init(_native = factory.CreateWindow(e, glFeature.SharedContext.Context), factory.CreateScreens());
}
NativeMenuExporter = new AvaloniaNativeMenuExporter(_native, factory);
diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs
index 4744a20107..5d66590a2b 100644
--- a/src/Avalonia.Native/WindowImplBase.cs
+++ b/src/Avalonia.Native/WindowImplBase.cs
@@ -66,7 +66,7 @@ namespace Avalonia.Native
private IGlContext _glContext;
internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts,
- AvaloniaNativePlatformOpenGlInterface glFeature)
+ AvaloniaNativeGlPlatformGraphics glFeature)
{
_factory = factory;
_gpu = opts.UseGpu && glFeature != null;
@@ -78,14 +78,13 @@ namespace Avalonia.Native
StorageProvider = new SystemDialogs(this, _factory.CreateSystemDialogs());
}
- protected void Init(IAvnWindowBase window, IAvnScreens screens, IGlContext glContext)
+ protected void Init(IAvnWindowBase window, IAvnScreens screens)
{
_native = window;
- _glContext = glContext;
Handle = new MacOSTopLevelWindowHandle(window);
if (_gpu)
- _glSurface = new GlPlatformSurface(window, _glContext);
+ _glSurface = new GlPlatformSurface(window);
Screen = new ScreenImpl(screens);
_savedLogicalSize = ClientSize;
@@ -375,14 +374,17 @@ namespace Avalonia.Native
if (_deferredRendering)
{
if (AvaloniaNativePlatform.Compositor != null)
- return new CompositingRenderer(root, AvaloniaNativePlatform.Compositor)
+ return new CompositingRenderer(root, AvaloniaNativePlatform.Compositor, () => Surfaces)
{
RenderOnlyOnRenderThread = false
};
- return new DeferredRenderer(root, loop);
+ return new DeferredRenderer(root, loop,
+ () => AvaloniaNativePlatform.RenderInterface!.CreateRenderTarget(Surfaces),
+ AvaloniaNativePlatform.RenderInterface);
}
- return new ImmediateRenderer((Visual)root);
+ return new ImmediateRenderer((Visual)root,
+ () => AvaloniaNativePlatform.RenderInterface!.CreateRenderTarget(Surfaces), AvaloniaNativePlatform.RenderInterface);
}
public virtual void Dispose()
diff --git a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs b/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs
deleted file mode 100644
index 3a8fdb8491..0000000000
--- a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs
+++ /dev/null
@@ -1,95 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Runtime.InteropServices;
-using Avalonia.OpenGL.Egl;
-using static Avalonia.OpenGL.Egl.EglConsts;
-
-namespace Avalonia.OpenGL.Angle
-{
- public class AngleWin32EglDisplay : EglDisplay
- {
- struct AngleInfo
- {
- public IntPtr Display { get; set; }
- public AngleOptions.PlatformApi PlatformApi { get; set; }
- }
-
- static AngleInfo CreateAngleDisplay(EglInterface _egl)
- {
- if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- throw new PlatformNotSupportedException();
- var display = IntPtr.Zero;
- AngleOptions.PlatformApi angleApi = default;
- {
- if (!_egl.IsGetPlatformDisplayExtAvailable)
- throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl.dll");
-
- var allowedApis = AvaloniaLocator.Current.GetService()?.AllowedPlatformApis
- ?? new [] { AngleOptions.PlatformApi.DirectX11, AngleOptions.PlatformApi.DirectX9 };
-
- foreach (var platformApi in allowedApis)
- {
- int dapi;
- if (platformApi == AngleOptions.PlatformApi.DirectX9)
- dapi = EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE;
- else if (platformApi == AngleOptions.PlatformApi.DirectX11)
- dapi = EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE;
- else
- continue;
-
- display = _egl.GetPlatformDisplayExt(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero,
- new[] { EGL_PLATFORM_ANGLE_TYPE_ANGLE, dapi, EGL_NONE });
- if (display != IntPtr.Zero)
- {
- angleApi = platformApi;
- break;
- }
- }
-
- if (display == IntPtr.Zero)
- throw new OpenGlException("Unable to create ANGLE display");
- return new AngleInfo { Display = display, PlatformApi = angleApi };
- }
- }
-
- private AngleWin32EglDisplay(EglInterface egl, AngleInfo info) : base(egl, false, info.Display)
- {
- PlatformApi = info.PlatformApi;
- }
-
- public AngleWin32EglDisplay(EglInterface egl) : this(egl, CreateAngleDisplay(egl))
- {
-
- }
-
- public AngleWin32EglDisplay() : this(new AngleEglInterface())
- {
-
- }
-
- public AngleOptions.PlatformApi PlatformApi { get; }
-
- public IntPtr GetDirect3DDevice()
- {
- if (!EglInterface.QueryDisplayAttribExt(Handle, EglConsts.EGL_DEVICE_EXT, out var eglDevice))
- throw new OpenGlException("Unable to get EGL_DEVICE_EXT");
- if (!EglInterface.QueryDeviceAttribExt(eglDevice, PlatformApi == AngleOptions.PlatformApi.DirectX9 ? EGL_D3D9_DEVICE_ANGLE : EGL_D3D11_DEVICE_ANGLE, out var d3dDeviceHandle))
- throw new OpenGlException("Unable to get EGL_D3D9_DEVICE_ANGLE");
- return d3dDeviceHandle;
- }
-
- public EglSurface WrapDirect3D11Texture(EglPlatformOpenGlInterface egl, IntPtr handle)
- {
- if (PlatformApi != AngleOptions.PlatformApi.DirectX11)
- throw new InvalidOperationException("Current platform API is " + PlatformApi);
- return egl.CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_NONE, EGL_NONE });
- }
-
- public EglSurface WrapDirect3D11Texture(EglPlatformOpenGlInterface egl, IntPtr handle, int offsetX, int offsetY, int width, int height)
- {
- if (PlatformApi != AngleOptions.PlatformApi.DirectX11)
- throw new InvalidOperationException("Current platform API is " + PlatformApi);
- return egl.CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_WIDTH, width, EGL_HEIGHT, height, EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE, EGL_TRUE, EGL_TEXTURE_OFFSET_X_ANGLE, offsetX, EGL_TEXTURE_OFFSET_Y_ANGLE, offsetY, EGL_NONE });
- }
- }
-}
diff --git a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
index e13ee80864..c62a3d8d2f 100644
--- a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
+++ b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
@@ -1,8 +1,10 @@
using System;
+using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Logging;
using Avalonia.Media;
using Avalonia.OpenGL.Imaging;
+using Avalonia.VisualTree;
using static Avalonia.OpenGL.GlConsts;
namespace Avalonia.OpenGL.Controls
@@ -14,8 +16,10 @@ namespace Avalonia.OpenGL.Controls
private OpenGlBitmap _bitmap;
private IOpenGlBitmapAttachment _attachment;
private PixelSize _depthBufferSize;
- private bool _glFailed;
- private bool _initialized;
+
+ private Task _initialization;
+ private IOpenGlTextureSharingRenderInterfaceContextFeature _feature;
+
protected GlVersion GlVersion { get; private set; }
public sealed override void Render(DrawingContext context)
{
@@ -38,13 +42,6 @@ namespace Avalonia.OpenGL.Controls
base.Render(context);
}
- private static void CheckError(GlInterface gl)
- {
- int err;
- while ((err = gl.GetError()) != GL_NO_ERROR)
- Console.WriteLine(err);
- }
-
void EnsureTextureAttachment()
{
_context.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, _fb);
@@ -54,7 +51,7 @@ namespace Avalonia.OpenGL.Controls
_attachment = null;
_bitmap?.Dispose();
_bitmap = null;
- _bitmap = new OpenGlBitmap(GetPixelSize(), new Vector(96, 96));
+ _bitmap = new OpenGlBitmap(_feature, GetPixelSize(), new Vector(96, 96));
_attachment = _bitmap.CreateFramebufferAttachment(_context);
}
}
@@ -87,9 +84,11 @@ namespace Avalonia.OpenGL.Controls
gl.ActiveTexture(GL_TEXTURE0);
gl.BindTexture(GL_TEXTURE_2D, 0);
gl.BindFramebuffer(GL_FRAMEBUFFER, 0);
- gl.DeleteFramebuffer(_fb);
+ if (_fb != 0)
+ gl.DeleteFramebuffer(_fb);
_fb = 0;
- gl.DeleteRenderbuffer(_depthBuffer);
+ if (_depthBuffer != 0)
+ gl.DeleteRenderbuffer(_depthBuffer);
_depthBuffer = 0;
_attachment?.Dispose();
_attachment = null;
@@ -98,10 +97,10 @@ namespace Avalonia.OpenGL.Controls
try
{
- if (_initialized)
+ if (_initialization is { Status: TaskStatus.RanToCompletion, Result: true })
{
- _initialized = false;
OnOpenGlDeinit(_context.GlInterface, _fb);
+ _initialization = null;
}
}
finally
@@ -111,6 +110,11 @@ namespace Avalonia.OpenGL.Controls
}
}
}
+
+ _fb = _depthBuffer = 0;
+ _attachment = null;
+ _bitmap = null;
+ _feature = null;
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
@@ -119,26 +123,12 @@ namespace Avalonia.OpenGL.Controls
base.OnDetachedFromVisualTree(e);
}
- private bool EnsureInitializedCore()
+ private bool EnsureInitializedCore(IOpenGlTextureSharingRenderInterfaceContextFeature feature)
{
- if (_context != null)
- return true;
-
- if (_glFailed)
- return false;
-
- var feature = AvaloniaLocator.Current.GetService();
- if (feature == null)
- return false;
- if (!feature.CanShareContexts)
- {
- Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
- "Unable to initialize OpenGL: current platform does not support multithreaded context sharing");
- return false;
- }
try
{
_context = feature.CreateSharedContext();
+ _feature = feature;
}
catch (Exception e)
{
@@ -157,7 +147,7 @@ namespace Avalonia.OpenGL.Controls
GlVersion = _context.Version;
try
{
- _bitmap = new OpenGlBitmap(GetPixelSize(), new Vector(96, 96));
+ _bitmap = new OpenGlBitmap(_feature, GetPixelSize(), new Vector(96, 96));
if (!_bitmap.SupportsContext(_context))
{
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
@@ -212,15 +202,68 @@ namespace Avalonia.OpenGL.Controls
return true;
}
+ void ContextLost()
+ {
+ _context = null;
+ _feature = null;
+ _initialization = null;
+ _attachment = null;
+ _bitmap = null;
+ _fb = 0;
+ _depthBuffer = 0;
+ _depthBufferSize = default;
+ OnOpenGlLost();
+ }
+
private bool EnsureInitialized()
{
- if (_initialized)
- return true;
- _glFailed = !(_initialized = EnsureInitializedCore());
- if (_glFailed)
+ if (_initialization != null)
+ {
+ // Check if we've previously failed to initialize OpenGL on this platform
+ if (_initialization is { IsCompleted: true, Result: false } ||
+ _initialization?.IsFaulted == true)
+ return false;
+
+ // Check if we are still waiting for init to complete
+ if (_initialization is { IsCompleted: false })
+ return false;
+
+ if (_context.IsLost)
+ ContextLost();
+ else
+ return true;
+ }
+
+ _initialization = InitializeAsync();
+ return false;
+
+ }
+
+ private async Task InitializeAsync()
+ {
+ var contextSharingFeature =
+ (IOpenGlTextureSharingRenderInterfaceContextFeature)
+ await this.GetVisualRoot()!.Renderer.TryGetRenderInterfaceFeature(
+ typeof(IOpenGlTextureSharingRenderInterfaceContextFeature));
+
+ if (contextSharingFeature == null || !contextSharingFeature.CanCreateSharedContext)
+ {
+ Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
+ "Unable to initialize OpenGL: current platform does not support multithreaded context sharing");
return false;
+ }
+
+ if (!EnsureInitializedCore(contextSharingFeature))
+ {
+ DoCleanup();
+ return false;
+ }
+
using (_context.MakeCurrent())
OnOpenGlInit(_context.GlInterface, _fb);
+
+ InvalidateVisual();
+
return true;
}
@@ -242,6 +285,11 @@ namespace Avalonia.OpenGL.Controls
}
+ protected virtual void OnOpenGlLost()
+ {
+
+ }
+
protected abstract void OnOpenGlRender(GlInterface gl, int fb);
}
}
diff --git a/src/Avalonia.OpenGL/Egl/EglConsts.cs b/src/Avalonia.OpenGL/Egl/EglConsts.cs
index 1268845f0f..428d11857f 100644
--- a/src/Avalonia.OpenGL/Egl/EglConsts.cs
+++ b/src/Avalonia.OpenGL/Egl/EglConsts.cs
@@ -196,6 +196,11 @@ namespace Avalonia.OpenGL.Egl
// public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_WARP_ANGLE = 0x320B;
// public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_REFERENCE_ANGLE = 0x320C;
+//
+ // EXT_platform_device
+ public const int EGL_PLATFORM_DEVICE_EXT = 0x313F;
+
+
//EXT_device_query
public const int EGL_DEVICE_EXT = 0x322C;
diff --git a/src/Avalonia.OpenGL/Egl/EglContext.cs b/src/Avalonia.OpenGL/Egl/EglContext.cs
index e7b5dc7c83..4d75a776c3 100644
--- a/src/Avalonia.OpenGL/Egl/EglContext.cs
+++ b/src/Avalonia.OpenGL/Egl/EglContext.cs
@@ -1,6 +1,8 @@
using System;
+using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Threading;
+using Avalonia.Platform;
using static Avalonia.OpenGL.Egl.EglConsts;
namespace Avalonia.OpenGL.Egl
@@ -10,24 +12,32 @@ namespace Avalonia.OpenGL.Egl
private readonly EglDisplay _disp;
private readonly EglInterface _egl;
private readonly EglContext _sharedWith;
- private readonly object _lock = new object();
+ private bool _isLost;
+ private IntPtr _context;
+ private readonly Action _disposeCallback;
+ private readonly Dictionary _features;
+ private readonly object _lock;
- public EglContext(EglDisplay display, EglInterface egl, EglContext sharedWith, IntPtr ctx, Func offscreenSurface,
- GlVersion version, int sampleCount, int stencilSize)
+ internal EglContext(EglDisplay display, EglInterface egl, EglContext sharedWith, IntPtr ctx, EglSurface offscreenSurface,
+ GlVersion version, int sampleCount, int stencilSize, Action disposeCallback, Dictionary features)
{
_disp = display;
_egl = egl;
_sharedWith = sharedWith;
- Context = ctx;
- OffscreenSurface = offscreenSurface(this);
+ _context = ctx;
+ _disposeCallback = disposeCallback;
+ _features = features;
+ OffscreenSurface = offscreenSurface;
Version = version;
SampleCount = sampleCount;
StencilSize = stencilSize;
+ _lock = display.ContextSharedSyncRoot ?? new object();
using (MakeCurrent())
GlInterface = GlInterface.FromNativeUtf8GetProcAddress(version, _egl.GetProcAddress);
}
- public IntPtr Context { get; }
+ public IntPtr Context =>
+ _context == IntPtr.Zero ? throw new ObjectDisposedException(nameof(EglContext)) : _context;
public EglSurface OffscreenSurface { get; }
public GlVersion Version { get; }
public GlInterface GlInterface { get; }
@@ -40,7 +50,7 @@ namespace Avalonia.OpenGL.Egl
private readonly EglInterface _egl;
private readonly object _l;
private readonly IntPtr _display;
- private IntPtr _context, _read, _draw;
+ public IntPtr _context, _read, _draw;
public RestoreContext(EglInterface egl, IntPtr defDisplay, object l)
{
@@ -66,6 +76,9 @@ namespace Avalonia.OpenGL.Egl
public IDisposable MakeCurrent(EglSurface surface)
{
+ if (IsLost)
+ throw new PlatformGraphicsContextLostException();
+
Monitor.Enter(_lock);
var success = false;
try
@@ -74,8 +87,18 @@ namespace Avalonia.OpenGL.Egl
var surf = surface ?? OffscreenSurface;
_egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
if (!_egl.MakeCurrent(_disp.Handle, surf?.DangerousGetHandle() ?? IntPtr.Zero,
- surf?.DangerousGetHandle() ?? IntPtr.Zero, Context))
- throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl);
+ surf?.DangerousGetHandle() ?? IntPtr.Zero, Context))
+ {
+ var error = _egl.GetError();
+ if (error == EGL_CONTEXT_LOST)
+ {
+ NotifyContextLost();
+ throw new PlatformGraphicsContextLostException();
+ }
+
+ throw OpenGlException.GetFormattedEglException("eglMakeCurrent", error);
+ }
+
success = true;
return old;
}
@@ -85,7 +108,15 @@ namespace Avalonia.OpenGL.Egl
Monitor.Exit(_lock);
}
}
+
+ public void NotifyContextLost()
+ {
+ _isLost = true;
+ _disp.OnContextLost(this);
+ }
+ public bool IsLost => _isLost || _disp.IsLost || Context == IntPtr.Zero;
+
public IDisposable EnsureCurrent()
{
if(IsCurrent)
@@ -110,12 +141,32 @@ namespace Avalonia.OpenGL.Egl
|| _sharedWith != null && _sharedWith == c._sharedWith;
}
+ public bool CanCreateSharedContext => _disp.SupportsSharing;
+
+ public IGlContext CreateSharedContext(IEnumerable preferredVersions = null) =>
+ _disp.CreateContext(new EglContextOptions
+ {
+ ShareWith = _sharedWith ?? this
+ });
+
public bool IsCurrent => _egl.GetCurrentDisplay() == _disp.Handle && _egl.GetCurrentContext() == Context;
public void Dispose()
{
+ if(_context == IntPtr.Zero)
+ return;
_egl.DestroyContext(_disp.Handle, Context);
OffscreenSurface?.Dispose();
+ _context = IntPtr.Zero;
+ _disp.OnContextDisposed(this);
+ _disposeCallback?.Invoke();
+ }
+
+ public object TryGetFeature(Type featureType)
+ {
+ if (_features?.TryGetValue(featureType, out var feature) == true)
+ return feature;
+ return null;
}
}
}
diff --git a/src/Avalonia.OpenGL/Egl/EglDisplay.cs b/src/Avalonia.OpenGL/Egl/EglDisplay.cs
index d3d85e8c83..eea2587587 100644
--- a/src/Avalonia.OpenGL/Egl/EglDisplay.cs
+++ b/src/Avalonia.OpenGL/Egl/EglDisplay.cs
@@ -1,193 +1,179 @@
using System;
+using System.Collections.Generic;
using System.Linq;
+using System.Reactive.Disposables;
+using System.Threading;
using static Avalonia.OpenGL.Egl.EglConsts;
namespace Avalonia.OpenGL.Egl
{
- public class EglDisplay
+ public class EglDisplay : IDisposable
{
private readonly EglInterface _egl;
+ private IntPtr _display;
+ private readonly EglDisplayOptions _options;
+ private EglConfigInfo _config;
+ private bool _isLost;
+ private object _lock = new();
+
public bool SupportsSharing { get; }
- private readonly IntPtr _display;
- private readonly IntPtr _config;
- private readonly int[] _contextAttributes;
- private readonly int _surfaceType;
public IntPtr Handle => _display;
- public IntPtr Config => _config;
- private int _sampleCount;
- private int _stencilSize;
- private GlVersion _version;
-
- public EglDisplay(EglInterface egl, bool supportsSharing) : this(egl, supportsSharing, -1, IntPtr.Zero, null)
+ public IntPtr Config => _config.Config;
+ internal bool SingleContext => !_options.SupportsMultipleContexts;
+ private List _contexts = new();
+
+ public EglDisplay() : this(new EglDisplayCreationOptions
{
-
- }
-
- static IntPtr CreateDisplay(EglInterface egl, int platformType, IntPtr platformDisplay, int[] attrs)
+ Egl = new EglInterface()
+ })
{
- var display = IntPtr.Zero;
- if (platformType == -1 && platformDisplay == IntPtr.Zero)
- {
- if (display == IntPtr.Zero)
- display = egl.GetDisplay(IntPtr.Zero);
- }
- else
- {
- if (!egl.IsGetPlatformDisplayExtAvailable)
- throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl");
- display = egl.GetPlatformDisplayExt(platformType, platformDisplay, attrs);
- }
-
- if (display == IntPtr.Zero)
- throw OpenGlException.GetFormattedException("eglGetDisplay", egl);
- return display;
+
}
- public EglDisplay(EglInterface egl, bool supportsSharing, int platformType, IntPtr platformDisplay, int[] attrs)
- : this(egl, supportsSharing, CreateDisplay(egl, platformType, platformDisplay, attrs))
+ public EglDisplay(EglDisplayCreationOptions options) : this(EglDisplayUtils.CreateDisplay(options), options)
{
-
+
}
-
- public EglDisplay(EglInterface egl, bool supportsSharing, IntPtr display)
+
+ public EglDisplay(IntPtr display, EglDisplayOptions options)
{
- _egl = egl;
- SupportsSharing = supportsSharing;
+ _egl = options.Egl;
+ SupportsSharing = options.SupportsContextSharing;
_display = display;
+ _options = options;
if(_display == IntPtr.Zero)
throw new ArgumentException();
-
-
- if (!_egl.Initialize(_display, out var major, out var minor))
- throw OpenGlException.GetFormattedException("eglInitialize", _egl);
-
- var glProfiles = AvaloniaLocator.Current.GetService()?.GlProfiles
- ?? new[]
- {
- new GlVersion(GlProfileType.OpenGLES, 3, 0),
- new GlVersion(GlProfileType.OpenGLES, 2, 0)
- };
- var cfgs = glProfiles.Select(x =>
+ _config = EglDisplayUtils.InitializeAndGetConfig(_egl, display, options.GlVersions);
+ }
+
+ public EglInterface EglInterface => _egl;
+ public EglContext CreateContext(EglContextOptions options)
+ {
+ if (SingleContext && _contexts.Any())
+ throw new OpenGlException("This EGLDisplay can only have one active context");
+
+ options ??= new EglContextOptions();
+ lock (_lock)
{
- var typeBit = EGL_OPENGL_ES3_BIT;
+ var share = options.ShareWith;
+ if (share != null && !SupportsSharing)
+ throw new NotSupportedException("Context sharing is not supported by this display");
- switch (x.Major)
- {
- case 2:
- typeBit = EGL_OPENGL_ES2_BIT;
- break;
+ var offscreenSurface = options.OffscreenSurface;
- case 1:
- typeBit = EGL_OPENGL_ES_BIT;
- break;
- }
-
- return new
+ if (offscreenSurface == null)
{
- Attributes = new[]
+ // Check if eglMakeCurrent can work with EGL_NONE as read-write surfaces
+ var extensions = _egl.QueryString(Handle, EGL_EXTENSIONS);
+ if (extensions?.Contains("EGL_KHR_surfaceless_context") != true)
{
- EGL_CONTEXT_MAJOR_VERSION, x.Major,
- EGL_CONTEXT_MINOR_VERSION, x.Minor,
- EGL_NONE
- },
- Api = EGL_OPENGL_ES_API,
- RenderableTypeBit = typeBit,
- Version = x
- };
- });
-
- foreach (var cfg in cfgs)
- {
- if (!_egl.BindApi(cfg.Api))
- continue;
- foreach(var surfaceType in new[]{EGL_PBUFFER_BIT|EGL_WINDOW_BIT, EGL_WINDOW_BIT})
- foreach(var stencilSize in new[]{8, 1, 0})
- foreach (var depthSize in new []{8, 1, 0})
+ // Attempt to create a PBuffer as a surface for offscreen rendering
+ if ((_config.SurfaceType | EGL_PBUFFER_BIT) == 0)
+ throw new InvalidOperationException(
+ "Platform doesn't support EGL_KHR_surfaceless_context and PBUFFER surfaces");
+
+ var pBufferSurface = _egl.CreatePBufferSurface(_display, Config,
+ new[] { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE });
+ if (pBufferSurface == IntPtr.Zero)
+ throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl);
+
+ offscreenSurface = new EglSurface(this, pBufferSurface);
+ }
+ }
+
+ var ctx = _egl.CreateContext(_display, Config, share?.Context ?? IntPtr.Zero, _config.Attributes);
+ if (ctx == IntPtr.Zero)
{
- var attribs = new[]
- {
- EGL_SURFACE_TYPE, surfaceType,
- EGL_RENDERABLE_TYPE, cfg.RenderableTypeBit,
- EGL_RED_SIZE, 8,
- EGL_GREEN_SIZE, 8,
- EGL_BLUE_SIZE, 8,
- EGL_ALPHA_SIZE, 8,
- EGL_STENCIL_SIZE, stencilSize,
- EGL_DEPTH_SIZE, depthSize,
- EGL_NONE
- };
- if (!_egl.ChooseConfig(_display, attribs, out _config, 1, out int numConfigs))
- continue;
- if (numConfigs == 0)
- continue;
- _contextAttributes = cfg.Attributes;
- _surfaceType = surfaceType;
- _version = cfg.Version;
- _egl.GetConfigAttrib(_display, _config, EGL_SAMPLES, out _sampleCount);
- _egl.GetConfigAttrib(_display, _config, EGL_STENCIL_SIZE, out _stencilSize);
- goto Found;
+ var ex = OpenGlException.GetFormattedException("eglCreateContext", _egl);
+ offscreenSurface?.Dispose();
+ throw ex;
}
+ var rv = new EglContext(this, _egl, share, ctx, offscreenSurface,
+ _config.Version, _config.SampleCount, _config.StencilSize,
+ options.DisposeCallback, options.ExtraFeatures);
+ _contexts.Add(rv);
+ return rv;
}
- Found:
- if (_contextAttributes == null)
- throw new OpenGlException("No suitable EGL config was found");
}
- public EglDisplay() : this(false)
+ public EglSurface CreateWindowSurface(IntPtr window)
{
-
+ if (window == IntPtr.Zero)
+ throw new OpenGlException($"Window {window} is invalid.");
+
+ using (Lock())
+ {
+ var s = EglInterface.CreateWindowSurface(Handle, Config, window,
+ new[] { EGL_NONE, EGL_NONE });
+ if (s == IntPtr.Zero)
+ throw OpenGlException.GetFormattedException("eglCreateWindowSurface", EglInterface);
+ return new EglSurface(this, s);
+ }
}
- public EglDisplay(bool supportsSharing) : this(new EglInterface(), supportsSharing)
+ public EglSurface CreatePBufferFromClientBuffer (int bufferType, IntPtr handle, int[] attribs)
{
-
+ using (Lock())
+ {
+ var s = EglInterface.CreatePbufferFromClientBuffer(Handle, bufferType, handle,
+ Config, attribs);
+
+ if (s == IntPtr.Zero)
+ throw OpenGlException.GetFormattedException("eglCreatePbufferFromClientBuffer", EglInterface);
+ return new EglSurface(this, s);
+ }
}
+
+ protected virtual bool DisplayLockIsSharedWithContexts => false;
- public EglInterface EglInterface => _egl;
- public EglContext CreateContext(IGlContext share)
+ internal object ContextSharedSyncRoot => DisplayLockIsSharedWithContexts ? _lock : null;
+
+ internal void OnContextLost(EglContext context)
{
- if (share != null && !SupportsSharing)
- throw new NotSupportedException("Context sharing is not supported by this display");
-
- if((_surfaceType|EGL_PBUFFER_BIT) == 0)
- throw new InvalidOperationException("Platform doesn't support PBUFFER surfaces");
- var shareCtx = (EglContext)share;
- var ctx = _egl.CreateContext(_display, _config, shareCtx?.Context ?? IntPtr.Zero, _contextAttributes);
- if (ctx == IntPtr.Zero)
- throw OpenGlException.GetFormattedException("eglCreateContext", _egl);
-
- var extensions = _egl.QueryString(Handle, EGL_EXTENSIONS);
-
- IntPtr surf = IntPtr.Zero;
- if (extensions?.Contains("EGL_KHR_surfaceless_context") != true)
+ if (_options.ContextLossIsDisplayLoss)
+ _isLost = true;
+ }
+
+ internal void OnContextDisposed(EglContext context)
+ {
+ lock (_lock)
+ _contexts.Remove(context);
+ }
+
+ public bool IsLost
+ {
+ get
{
- surf = _egl.CreatePBufferSurface(_display, _config,
- new[] { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE });
- if (surf == IntPtr.Zero)
- throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl);
+ if (_isLost || _display == IntPtr.Zero)
+ return true;
+ if (_options.DeviceLostCheckCallback?.Invoke() == true)
+ return _isLost = true;
+ return false;
}
-
- var rv = new EglContext(this, _egl, shareCtx, ctx,
- context =>
- surf == IntPtr.Zero ? null : new EglSurface(this, context, surf),
- _version, _sampleCount, _stencilSize);
- return rv;
+ }
+
+ public IDisposable Lock()
+ {
+ Monitor.Enter(_lock);
+ return Disposable.Create(() => { Monitor.Exit(_lock); });
}
- public EglContext CreateContext(EglContext share, EglSurface offscreenSurface)
+ public void Dispose()
{
- if (share != null && !SupportsSharing)
- throw new NotSupportedException("Context sharing is not supported by this display");
-
- var ctx = _egl.CreateContext(_display, _config, share?.Context ?? IntPtr.Zero, _contextAttributes);
- if (ctx == IntPtr.Zero)
- throw OpenGlException.GetFormattedException("eglCreateContext", _egl);
- var rv = new EglContext(this, _egl, share, ctx, _ => offscreenSurface, _version, _sampleCount, _stencilSize);
- rv.MakeCurrent(null);
- return rv;
+ lock (_lock)
+ {
+ foreach(var ctx in _contexts)
+ ctx.Dispose();
+ _contexts.Clear();
+ if (_display != IntPtr.Zero)
+ _egl.Terminate(_display);
+ _display = IntPtr.Zero;
+ _config = null;
+ _options.DisposeCallback?.Invoke();
+ }
}
}
}
diff --git a/src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs b/src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs
new file mode 100644
index 0000000000..5648645c54
--- /dev/null
+++ b/src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+
+namespace Avalonia.OpenGL.Egl;
+
+public class EglDisplayOptions
+{
+ public EglInterface Egl { get; set; }
+ public bool SupportsContextSharing { get; set; }
+ public bool SupportsMultipleContexts { get; set; }
+ public bool ContextLossIsDisplayLoss { get; set; }
+ public Func DeviceLostCheckCallback { get; set; }
+ public Action DisposeCallback { get; set; }
+ public IEnumerable GlVersions { get; set; }
+}
+
+public class EglContextOptions
+{
+ public EglContext ShareWith { get; set; }
+ public EglSurface OffscreenSurface { get; set; }
+ public Action DisposeCallback { get; set; }
+ public Dictionary ExtraFeatures { get; set; }
+}
+
+public class EglDisplayCreationOptions : EglDisplayOptions
+{
+ public int? PlatformType { get; set; }
+ public IntPtr PlatformDisplay { get; set; }
+ public int[] PlatformDisplayAttrs { get; set; }
+}
diff --git a/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs b/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs
new file mode 100644
index 0000000000..fbfaf1bd3d
--- /dev/null
+++ b/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reactive.Disposables;
+using System.Threading;
+using static Avalonia.OpenGL.Egl.EglConsts;
+namespace Avalonia.OpenGL.Egl;
+
+static class EglDisplayUtils
+{
+ public static IntPtr CreateDisplay(EglDisplayCreationOptions options)
+ {
+ var egl = options.Egl;
+ var display = IntPtr.Zero;
+ if (options.PlatformType == null)
+ {
+ if (display == IntPtr.Zero)
+ display = egl.GetDisplay(IntPtr.Zero);
+ }
+ else
+ {
+ if (!egl.IsGetPlatformDisplayExtAvailable)
+ throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl");
+
+ display = egl.GetPlatformDisplayExt(options.PlatformType.Value, options.PlatformDisplay,
+ options.PlatformDisplayAttrs);
+ }
+
+ if (display == IntPtr.Zero)
+ throw OpenGlException.GetFormattedException("eglGetDisplay", egl);
+ return display;
+ }
+
+ public static EglConfigInfo InitializeAndGetConfig(EglInterface egl, IntPtr display, IEnumerable versions)
+ {
+ if (!egl.Initialize(display, out var major, out var minor))
+ throw OpenGlException.GetFormattedException("eglInitialize", egl);
+
+ // TODO: AvaloniaLocator.Current.GetService()?.GlProfiles
+ versions ??= new[]
+ {
+ new GlVersion(GlProfileType.OpenGLES, 3, 0),
+ new GlVersion(GlProfileType.OpenGLES, 2, 0)
+ };
+
+ var cfgs = versions
+ .Where(x => x.Type == GlProfileType.OpenGLES)
+ .Select(x =>
+ {
+ var typeBit = EGL_OPENGL_ES3_BIT;
+
+ switch (x.Major)
+ {
+ case 2:
+ typeBit = EGL_OPENGL_ES2_BIT;
+ break;
+
+ case 1:
+ typeBit = EGL_OPENGL_ES_BIT;
+ break;
+ }
+
+ return new
+ {
+ Attributes = new[]
+ {
+ EGL_CONTEXT_MAJOR_VERSION, x.Major,
+ EGL_CONTEXT_MINOR_VERSION, x.Minor,
+ EGL_NONE
+ },
+ Api = EGL_OPENGL_ES_API,
+ RenderableTypeBit = typeBit,
+ Version = x
+ };
+ });
+
+ foreach (var cfg in cfgs)
+ {
+ if (!egl.BindApi(cfg.Api))
+ continue;
+ foreach (var surfaceType in new[] { EGL_PBUFFER_BIT | EGL_WINDOW_BIT, EGL_WINDOW_BIT })
+ foreach (var stencilSize in new[] { 8, 1, 0 })
+ foreach (var depthSize in new[] { 8, 1, 0 })
+ {
+ var attribs = new[]
+ {
+ EGL_SURFACE_TYPE, surfaceType,
+ EGL_RENDERABLE_TYPE, cfg.RenderableTypeBit,
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_ALPHA_SIZE, 8,
+ EGL_STENCIL_SIZE, stencilSize,
+ EGL_DEPTH_SIZE, depthSize,
+ EGL_NONE
+ };
+ if (!egl.ChooseConfig(display, attribs, out var config, 1, out int numConfigs))
+ continue;
+ if (numConfigs == 0)
+ continue;
+
+
+ egl.GetConfigAttrib(display, config, EGL_SAMPLES, out var sampleCount);
+ egl.GetConfigAttrib(display, config, EGL_STENCIL_SIZE, out var returnedStencilSize);
+ return new EglConfigInfo(config, cfg.Version, surfaceType, cfg.Attributes, sampleCount,
+ returnedStencilSize);
+ }
+ }
+
+ throw new OpenGlException("No suitable EGL config was found");
+ }
+
+
+}
+
+class EglConfigInfo
+{
+ public IntPtr Config { get; }
+ public GlVersion Version { get; }
+ public int SurfaceType { get; }
+ public int[] Attributes { get; }
+ public int SampleCount { get; }
+ public int StencilSize { get; }
+
+ public EglConfigInfo(IntPtr config, GlVersion version, int surfaceType, int[] attributes, int sampleCount,
+ int stencilSize)
+ {
+ Config = config;
+ Version = version;
+ SurfaceType = surfaceType;
+ Attributes = attributes;
+ SampleCount = sampleCount;
+ StencilSize = stencilSize;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs
index 3d58660d47..348028bd08 100644
--- a/src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs
+++ b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs
@@ -1,52 +1,61 @@
+using System;
using Avalonia.OpenGL.Surfaces;
namespace Avalonia.OpenGL.Egl
{
public class EglGlPlatformSurface : EglGlPlatformSurfaceBase
{
- private readonly EglPlatformOpenGlInterface _egl;
+ public interface IEglWindowGlPlatformSurfaceInfo
+ {
+ IntPtr Handle { get; }
+ PixelSize Size { get; }
+ double Scaling { get; }
+ }
+
private readonly IEglWindowGlPlatformSurfaceInfo _info;
- public EglGlPlatformSurface(EglPlatformOpenGlInterface egl, IEglWindowGlPlatformSurfaceInfo info) : base()
+ public EglGlPlatformSurface(IEglWindowGlPlatformSurfaceInfo info)
{
- _egl = egl;
_info = info;
}
- public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
+ public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context)
{
- var glSurface = _egl.CreateWindowSurface(_info.Handle);
- return new RenderTarget(_egl, glSurface, _info);
+ var eglContext = (EglContext)context;
+
+ var glSurface = eglContext.Display.CreateWindowSurface(_info.Handle);
+ return new RenderTarget(glSurface, eglContext, _info);
}
class RenderTarget : EglPlatformSurfaceRenderTargetBase
{
- private readonly EglPlatformOpenGlInterface _egl;
private EglSurface _glSurface;
private readonly IEglWindowGlPlatformSurfaceInfo _info;
private PixelSize _currentSize;
+ private readonly IntPtr _handle;
- public RenderTarget(EglPlatformOpenGlInterface egl,
- EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info) : base(egl)
+ public RenderTarget(EglSurface glSurface, EglContext context, IEglWindowGlPlatformSurfaceInfo info) : base(context)
{
- _egl = egl;
_glSurface = glSurface;
_info = info;
_currentSize = info.Size;
+ _handle = _info.Handle;
}
public override void Dispose() => _glSurface.Dispose();
-
- public override IGlPlatformSurfaceRenderingSession BeginDraw()
+
+ public override IGlPlatformSurfaceRenderingSession BeginDrawCore()
{
- if (_info.Size != _currentSize || _glSurface == null)
+ if (_info.Size != _currentSize
+ || _handle != _info.Handle
+ || _glSurface == null)
{
_glSurface?.Dispose();
_glSurface = null;
- _glSurface = _egl.CreateWindowSurface(_info.Handle);
+ _glSurface = Context.Display.CreateWindowSurface(_info.Handle);
_currentSize = _info.Size;
}
- return base.BeginDraw(_glSurface, _info);
+ return base.BeginDraw(_glSurface, _info.Size, _info.Scaling);
}
}
}
diff --git a/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs
index 4ea6766de2..f66f630556 100644
--- a/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs
+++ b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs
@@ -5,23 +5,16 @@ namespace Avalonia.OpenGL.Egl
{
public abstract class EglGlPlatformSurfaceBase : IGlPlatformSurface
{
- public interface IEglWindowGlPlatformSurfaceInfo
- {
- IntPtr Handle { get; }
- PixelSize Size { get; }
- double Scaling { get; }
- }
-
- public abstract IGlPlatformSurfaceRenderTarget CreateGlRenderTarget();
+ public abstract IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context);
}
- public abstract class EglPlatformSurfaceRenderTargetBase : IGlPlatformSurfaceRenderTarget
+ public abstract class EglPlatformSurfaceRenderTargetBase : IGlPlatformSurfaceRenderTargetWithCorruptionInfo
{
- private readonly EglPlatformOpenGlInterface _egl;
+ protected EglContext Context { get; }
- protected EglPlatformSurfaceRenderTargetBase(EglPlatformOpenGlInterface egl)
+ protected EglPlatformSurfaceRenderTargetBase(EglContext context)
{
- _egl = egl;
+ Context = context;
}
public virtual void Dispose()
@@ -29,25 +22,33 @@ namespace Avalonia.OpenGL.Egl
}
- public abstract IGlPlatformSurfaceRenderingSession BeginDraw();
+ public IGlPlatformSurfaceRenderingSession BeginDraw()
+ {
+ if (Context.IsLost)
+ throw new RenderTargetCorruptedException();
+
+ return BeginDrawCore();
+ }
+
+ public abstract IGlPlatformSurfaceRenderingSession BeginDrawCore();
protected IGlPlatformSurfaceRenderingSession BeginDraw(EglSurface surface,
- EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info, Action onFinish = null, bool isYFlipped = false)
+ PixelSize size, double scaling, Action onFinish = null, bool isYFlipped = false)
{
- var restoreContext = _egl.PrimaryEglContext.MakeCurrent(surface);
+ var restoreContext = Context.MakeCurrent(surface);
var success = false;
try
{
- var egli = _egl.Display.EglInterface;
+ var egli = Context.Display.EglInterface;
egli.WaitClient();
egli.WaitGL();
egli.WaitNative(EglConsts.EGL_CORE_NATIVE_ENGINE);
- _egl.PrimaryContext.GlInterface.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, 0);
+ Context.GlInterface.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, 0);
success = true;
- return new Session(_egl.Display, _egl.PrimaryEglContext, surface, info, restoreContext, onFinish, isYFlipped);
+ return new Session(Context.Display, Context, surface, size, scaling, restoreContext, onFinish, isYFlipped);
}
finally
{
@@ -60,21 +61,21 @@ namespace Avalonia.OpenGL.Egl
{
private readonly EglContext _context;
private readonly EglSurface _glSurface;
- private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info;
private readonly EglDisplay _display;
private readonly IDisposable _restoreContext;
private readonly Action _onFinish;
public Session(EglDisplay display, EglContext context,
- EglSurface glSurface, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info,
+ EglSurface glSurface, PixelSize size, double scaling,
IDisposable restoreContext, Action onFinish, bool isYFlipped)
{
+ Size = size;
+ Scaling = scaling;
IsYFlipped = isYFlipped;
_context = context;
_display = display;
_glSurface = glSurface;
- _info = info;
_restoreContext = restoreContext;
_onFinish = onFinish;
}
@@ -92,9 +93,11 @@ namespace Avalonia.OpenGL.Egl
}
public IGlContext Context => _context;
- public PixelSize Size => _info.Size;
- public double Scaling => _info.Scaling;
+ public PixelSize Size { get; }
+ public double Scaling { get; }
public bool IsYFlipped { get; }
}
+
+ public virtual bool IsCorrupted => Context.IsLost;
}
}
diff --git a/src/Avalonia.OpenGL/Egl/EglInterface.cs b/src/Avalonia.OpenGL/Egl/EglInterface.cs
index 4fec8e5356..ad4b55a686 100644
--- a/src/Avalonia.OpenGL/Egl/EglInterface.cs
+++ b/src/Avalonia.OpenGL/Egl/EglInterface.cs
@@ -53,6 +53,9 @@ namespace Avalonia.OpenGL.Egl
[GetProcAddress("eglInitialize")]
public partial bool Initialize(IntPtr display, out int major, out int minor);
+
+ [GetProcAddress("eglTerminate")]
+ public partial void Terminate(IntPtr display);
[GetProcAddress("eglGetProcAddress")]
public partial IntPtr GetProcAddress(IntPtr proc);
diff --git a/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs b/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs
new file mode 100644
index 0000000000..faa9f279a6
--- /dev/null
+++ b/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs
@@ -0,0 +1,48 @@
+using System;
+using Avalonia.Logging;
+using Avalonia.Platform;
+using static Avalonia.OpenGL.Egl.EglConsts;
+
+namespace Avalonia.OpenGL.Egl
+{
+ public sealed class EglPlatformGraphics : IPlatformGraphics
+ {
+ private readonly EglDisplay _display;
+ public bool UsesSharedContext => false;
+ public IPlatformGraphicsContext CreateContext() => _display.CreateContext(null);
+ public IPlatformGraphicsContext GetSharedContext() => throw new NotSupportedException();
+
+ public EglPlatformGraphics(EglDisplay display)
+ {
+ _display = display;
+ }
+
+ public static void TryInitialize()
+ {
+ var feature = TryCreate();
+ if (feature != null)
+ AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature);
+ }
+
+ public static EglPlatformGraphics TryCreate() => TryCreate(() => new EglDisplay(new EglDisplayCreationOptions
+ {
+ Egl = new EglInterface(),
+ // Those are expected to be supported by most EGL implementations
+ SupportsMultipleContexts = true,
+ SupportsContextSharing = true
+ }));
+
+ public static EglPlatformGraphics TryCreate(Func displayFactory)
+ {
+ try
+ {
+ return new EglPlatformGraphics(displayFactory());
+ }
+ catch(Exception e)
+ {
+ Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log(null, "Unable to initialize EGL-based rendering: {0}", e);
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.OpenGL/Egl/EglPlatformOpenGlInterface.cs b/src/Avalonia.OpenGL/Egl/EglPlatformOpenGlInterface.cs
deleted file mode 100644
index a1ac2a9d37..0000000000
--- a/src/Avalonia.OpenGL/Egl/EglPlatformOpenGlInterface.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-using System;
-using Avalonia.Logging;
-using Avalonia.Platform;
-using static Avalonia.OpenGL.Egl.EglConsts;
-
-namespace Avalonia.OpenGL.Egl
-{
- public class EglPlatformOpenGlInterface : IPlatformOpenGlInterface
- {
- public EglDisplay Display { get; private set; }
- public bool CanCreateContexts => true;
- public bool CanShareContexts => Display.SupportsSharing;
-
- public EglContext PrimaryEglContext { get; }
- public IGlContext PrimaryContext => PrimaryEglContext;
- IPlatformGpuContext IPlatformGpu.PrimaryContext => PrimaryContext;
-
- public EglPlatformOpenGlInterface(EglDisplay display)
- {
- Display = display;
- PrimaryEglContext = display.CreateContext(null);
- }
-
- public static void TryInitialize()
- {
- var feature = TryCreate();
- if (feature != null)
- AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature);
- }
-
- public static EglPlatformOpenGlInterface TryCreate() => TryCreate(() => new EglDisplay());
- public static EglPlatformOpenGlInterface TryCreate(Func displayFactory)
- {
- try
- {
- return new EglPlatformOpenGlInterface(displayFactory());
- }
- catch(Exception e)
- {
- Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log(null, "Unable to initialize EGL-based rendering: {0}", e);
- return null;
- }
- }
-
- public IGlContext CreateContext() => Display.CreateContext(null);
- public IGlContext CreateSharedContext() => Display.CreateContext(PrimaryEglContext);
-
-
- public EglSurface CreateWindowSurface(IntPtr window)
- {
- if (window == IntPtr.Zero)
- throw new OpenGlException($"Window {window} is invalid.");
-
- using (PrimaryContext.MakeCurrent())
- {
- var s = Display.EglInterface.CreateWindowSurface(Display.Handle, Display.Config, window,
- new[] { EGL_NONE, EGL_NONE });
- if (s == IntPtr.Zero)
- throw OpenGlException.GetFormattedException("eglCreateWindowSurface", Display.EglInterface);
- return new EglSurface(Display, PrimaryEglContext, s);
- }
- }
-
- public EglSurface CreatePBufferFromClientBuffer (int bufferType, IntPtr handle, int[] attribs)
- {
- using (PrimaryContext.MakeCurrent())
- {
- var s = Display.EglInterface.CreatePbufferFromClientBuffer(Display.Handle, bufferType, handle,
- Display.Config, attribs);
-
- if (s == IntPtr.Zero)
- throw OpenGlException.GetFormattedException("eglCreatePbufferFromClientBuffer", Display.EglInterface);
- return new EglSurface(Display, PrimaryEglContext, s);
- }
- }
- }
-}
diff --git a/src/Avalonia.OpenGL/Egl/EglSurface.cs b/src/Avalonia.OpenGL/Egl/EglSurface.cs
index a93751ca9e..f3ee85b3f6 100644
--- a/src/Avalonia.OpenGL/Egl/EglSurface.cs
+++ b/src/Avalonia.OpenGL/Egl/EglSurface.cs
@@ -6,19 +6,17 @@ namespace Avalonia.OpenGL.Egl
public class EglSurface : SafeHandle
{
private readonly EglDisplay _display;
- private readonly EglContext _context;
private readonly EglInterface _egl;
- public EglSurface(EglDisplay display, EglContext context, IntPtr surface) : base(surface, true)
+ public EglSurface(EglDisplay display, IntPtr surface) : base(surface, true)
{
_display = display;
- _context = context;
_egl = display.EglInterface;
}
protected override bool ReleaseHandle()
{
- using (_context.MakeCurrent())
+ using (_display.Lock())
_egl.DestroySurface(_display.Handle, handle);
return true;
}
diff --git a/src/Avalonia.OpenGL/GlVersion.cs b/src/Avalonia.OpenGL/GlVersion.cs
index 042ff4c2f0..16ed18e45a 100644
--- a/src/Avalonia.OpenGL/GlVersion.cs
+++ b/src/Avalonia.OpenGL/GlVersion.cs
@@ -6,7 +6,7 @@ namespace Avalonia.OpenGL
OpenGLES
}
- public struct GlVersion
+ public record struct GlVersion
{
public GlProfileType Type { get; }
public int Major { get; }
diff --git a/src/Avalonia.OpenGL/IGlContext.cs b/src/Avalonia.OpenGL/IGlContext.cs
index a52a6535da..d45ea931d0 100644
--- a/src/Avalonia.OpenGL/IGlContext.cs
+++ b/src/Avalonia.OpenGL/IGlContext.cs
@@ -1,9 +1,11 @@
using System;
+using System.Collections.Generic;
+using Avalonia.OpenGL.Surfaces;
using Avalonia.Platform;
namespace Avalonia.OpenGL
{
- public interface IGlContext : IPlatformGpuContext
+ public interface IGlContext : IPlatformGraphicsContext
{
GlVersion Version { get; }
GlInterface GlInterface { get; }
@@ -12,5 +14,13 @@ namespace Avalonia.OpenGL
IDisposable MakeCurrent();
IDisposable EnsureCurrent();
bool IsSharedWith(IGlContext context);
+ bool CanCreateSharedContext { get; }
+ IGlContext CreateSharedContext(IEnumerable preferredVersions = null);
+ }
+
+ public interface IGlPlatformSurfaceRenderTargetFactory
+ {
+ bool CanRenderToSurface(IGlContext context, object surface);
+ IGlPlatformSurfaceRenderTarget CreateRenderTarget(IGlContext context, object surface);
}
}
diff --git a/src/Avalonia.OpenGL/IOpenGlAwarePlatformRenderInterface.cs b/src/Avalonia.OpenGL/IOpenGlAwarePlatformRenderInterface.cs
deleted file mode 100644
index fdb9162164..0000000000
--- a/src/Avalonia.OpenGL/IOpenGlAwarePlatformRenderInterface.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Avalonia.OpenGL.Imaging;
-
-namespace Avalonia.OpenGL
-{
- public interface IOpenGlAwarePlatformRenderInterface
- {
- IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi);
- }
-}
diff --git a/src/Avalonia.OpenGL/IOpenGlTextureSharingRenderInterfaceContextFeature.cs b/src/Avalonia.OpenGL/IOpenGlTextureSharingRenderInterfaceContextFeature.cs
new file mode 100644
index 0000000000..9c22d446ef
--- /dev/null
+++ b/src/Avalonia.OpenGL/IOpenGlTextureSharingRenderInterfaceContextFeature.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+using Avalonia.OpenGL.Imaging;
+using Avalonia.Platform;
+
+namespace Avalonia.OpenGL
+{
+ public interface IOpenGlTextureSharingRenderInterfaceContextFeature
+ {
+ bool CanCreateSharedContext { get; }
+ IGlContext CreateSharedContext(IEnumerable preferredVersions = null);
+ IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi);
+ }
+}
diff --git a/src/Avalonia.OpenGL/IPlatformOpenGlInterface.cs b/src/Avalonia.OpenGL/IPlatformOpenGlInterface.cs
deleted file mode 100644
index 4ff7997b03..0000000000
--- a/src/Avalonia.OpenGL/IPlatformOpenGlInterface.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using Avalonia.Platform;
-
-namespace Avalonia.OpenGL
-{
- public interface IPlatformOpenGlInterface : IPlatformGpu
- {
- new IGlContext PrimaryContext { get; }
- IGlContext CreateSharedContext();
- bool CanShareContexts { get; }
- bool CanCreateContexts { get; }
- IGlContext CreateContext();
- /*IGlContext TryCreateContext(GlVersion version);
- */
- }
-}
diff --git a/src/Avalonia.OpenGL/Imaging/OpenGlBitmap.cs b/src/Avalonia.OpenGL/Imaging/OpenGlBitmap.cs
index 7af44cd624..53013ae5a3 100644
--- a/src/Avalonia.OpenGL/Imaging/OpenGlBitmap.cs
+++ b/src/Avalonia.OpenGL/Imaging/OpenGlBitmap.cs
@@ -10,19 +10,15 @@ namespace Avalonia.OpenGL.Imaging
{
private IOpenGlBitmapImpl _impl;
- public OpenGlBitmap(PixelSize size, Vector dpi)
- : base(CreateOrThrow(size, dpi))
+ public OpenGlBitmap(IOpenGlTextureSharingRenderInterfaceContextFeature feature,
+ PixelSize size, Vector dpi)
+ : base(CreateOrThrow(feature, size, dpi))
{
_impl = (IOpenGlBitmapImpl)PlatformImpl.Item;
}
-
- static IOpenGlBitmapImpl CreateOrThrow(PixelSize size, Vector dpi)
- {
- if (!(AvaloniaLocator.Current.GetService() is IOpenGlAwarePlatformRenderInterface
- glAware))
- throw new PlatformNotSupportedException("Rendering platform does not support OpenGL integration");
- return glAware.CreateOpenGlBitmap(size, dpi);
- }
+
+ static IOpenGlBitmapImpl CreateOrThrow(IOpenGlTextureSharingRenderInterfaceContextFeature feature,
+ PixelSize size, Vector dpi) => feature.CreateOpenGlBitmap(size, dpi);
public IOpenGlBitmapAttachment CreateFramebufferAttachment(IGlContext context) =>
_impl.CreateFramebufferAttachment(context, SetIsDirty);
diff --git a/src/Avalonia.OpenGL/OpenGlException.cs b/src/Avalonia.OpenGL/OpenGlException.cs
index 196f507ad8..efe305dba5 100644
--- a/src/Avalonia.OpenGL/OpenGlException.cs
+++ b/src/Avalonia.OpenGL/OpenGlException.cs
@@ -18,25 +18,33 @@ namespace Avalonia.OpenGL
public static OpenGlException GetFormattedException(string funcName, EglInterface egl)
{
- return GetFormattedException(typeof(EglErrors), funcName, egl.GetError());
+ return GetFormattedEglException(funcName, egl.GetError());
}
public static OpenGlException GetFormattedException(string funcName, GlInterface gl)
{
- return GetFormattedException(typeof(GlErrors), funcName, gl.GetError());
+ var err = gl.GetError();
+ return GetFormattedException(funcName, (GlErrors)err, err);
}
- private static OpenGlException GetFormattedException(Type consts, string funcName, int errorCode)
+ public static OpenGlException GetFormattedEglException(string funcName, int errorCode) =>
+ GetFormattedException(funcName, (EglErrors)errorCode,errorCode);
+
+ private static OpenGlException GetFormattedException(string funcName, T errorCode, int intErrorCode) where T : struct, Enum
{
try
{
- string errorName = Enum.GetName(consts, errorCode);
+#if NET6_0_OR_GREATER
+ var errorName = Enum.GetName(errorCode);
+#else
+ var errorName = Enum.GetName(typeof(T), errorCode);
+#endif
return new OpenGlException(
- $"{funcName} failed with error {errorName} (0x{errorCode.ToString("X")})", errorCode);
+ $"{funcName} failed with error {errorName} (0x{errorCode.ToString("X")})", intErrorCode);
}
catch (ArgumentException)
{
- return new OpenGlException($"{funcName} failed with error 0x{errorCode.ToString("X")}", errorCode);
+ return new OpenGlException($"{funcName} failed with error 0x{errorCode.ToString("X")}", intErrorCode);
}
}
}
diff --git a/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurface.cs b/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurface.cs
index 875c215336..d80e72e4e7 100644
--- a/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurface.cs
+++ b/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurface.cs
@@ -2,6 +2,6 @@ namespace Avalonia.OpenGL.Surfaces
{
public interface IGlPlatformSurface
{
- IGlPlatformSurfaceRenderTarget CreateGlRenderTarget();
+ IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context);
}
}
diff --git a/src/Avalonia.Themes.Fluent/Accents/Base.xaml b/src/Avalonia.Themes.Fluent/Accents/Base.xaml
index 259d107b5c..479bcd8531 100644
--- a/src/Avalonia.Themes.Fluent/Accents/Base.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/Base.xaml
@@ -22,6 +22,7 @@
10,6,6,5
20
20
+ 8,5,8,6
3
diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
index 71a8bc3a3c..810065fc9b 100644
--- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
@@ -636,4 +636,8 @@
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
index d764e1616c..bccc47b9b8 100644
--- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
@@ -631,4 +631,8 @@
-
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Avalonia.Themes.Fluent/Controls/Button.xaml b/src/Avalonia.Themes.Fluent/Controls/Button.xaml
index 7828fd52ed..126f2c22e0 100644
--- a/src/Avalonia.Themes.Fluent/Controls/Button.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/Button.xaml
@@ -8,8 +8,6 @@
-
- 8,5,8,6
diff --git a/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml b/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml
index f8b4854553..67e70d154f 100644
--- a/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml
@@ -1,6 +1,7 @@
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
index a029be6b8d..2c3550a72f 100644
--- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
@@ -4,70 +4,71 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/RefreshContainer.xaml b/src/Avalonia.Themes.Fluent/Controls/RefreshContainer.xaml
new file mode 100644
index 0000000000..8e29e6208f
--- /dev/null
+++ b/src/Avalonia.Themes.Fluent/Controls/RefreshContainer.xaml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/RefreshVisualizer.xaml b/src/Avalonia.Themes.Fluent/Controls/RefreshVisualizer.xaml
new file mode 100644
index 0000000000..4e76bee9f5
--- /dev/null
+++ b/src/Avalonia.Themes.Fluent/Controls/RefreshVisualizer.xaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml b/src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml
index a54187104b..fd04c85fed 100644
--- a/src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml
@@ -10,8 +10,6 @@
- 8,5,8,6
-
diff --git a/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml b/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml
index 7a46f21534..da2021790a 100644
--- a/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml
@@ -10,8 +10,6 @@
- 8,5,8,6
-
diff --git a/src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml b/src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml
index b2dc8615a9..b5f1220bc8 100644
--- a/src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml
@@ -75,6 +75,7 @@
MinHeight="{TemplateBinding MinHeight}"
TemplatedControl.IsTemplateFocusTarget="True">
-
-
+
+
diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs b/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs
index eea3d3ad08..a8297953a8 100644
--- a/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs
+++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs
@@ -99,15 +99,15 @@ namespace Avalonia.Themes.Fluent
var themeVariantResource1 = Mode == FluentThemeMode.Dark ? _baseDark : _baseLight;
var themeVariantResource2 = Mode == FluentThemeMode.Dark ? _fluentDark : _fluentLight;
var dict = Resources.MergedDictionaries;
- if (dict.Count == 2)
+ if (dict.Count == 0)
{
- dict.Insert(1, themeVariantResource1);
+ dict.Add(themeVariantResource1);
dict.Add(themeVariantResource2);
}
else
{
- dict[1] = themeVariantResource1;
- dict[3] = themeVariantResource2;
+ dict[0] = themeVariantResource1;
+ dict[1] = themeVariantResource2;
}
}
diff --git a/src/Avalonia.Themes.Simple/Accents/BaseDark.xaml b/src/Avalonia.Themes.Simple/Accents/BaseDark.xaml
index 9ad9f70c98..88c2681f65 100644
--- a/src/Avalonia.Themes.Simple/Accents/BaseDark.xaml
+++ b/src/Avalonia.Themes.Simple/Accents/BaseDark.xaml
@@ -31,5 +31,8 @@
+
+
+
diff --git a/src/Avalonia.Themes.Simple/Accents/BaseLight.xaml b/src/Avalonia.Themes.Simple/Accents/BaseLight.xaml
index f96425cf06..77166a9d8a 100644
--- a/src/Avalonia.Themes.Simple/Accents/BaseLight.xaml
+++ b/src/Avalonia.Themes.Simple/Accents/BaseLight.xaml
@@ -31,5 +31,7 @@
-
+
+
+
diff --git a/src/Avalonia.Themes.Simple/Controls/EmbeddableControlRoot.xaml b/src/Avalonia.Themes.Simple/Controls/EmbeddableControlRoot.xaml
index 79d6c6d917..3d52464858 100644
--- a/src/Avalonia.Themes.Simple/Controls/EmbeddableControlRoot.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/EmbeddableControlRoot.xaml
@@ -2,6 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+
diff --git a/src/Avalonia.Themes.Simple/Controls/RefreshContainer.xaml b/src/Avalonia.Themes.Simple/Controls/RefreshContainer.xaml
new file mode 100644
index 0000000000..04b2a1bf29
--- /dev/null
+++ b/src/Avalonia.Themes.Simple/Controls/RefreshContainer.xaml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Simple/Controls/RefreshVisualizer.xaml b/src/Avalonia.Themes.Simple/Controls/RefreshVisualizer.xaml
new file mode 100644
index 0000000000..bd7e43530a
--- /dev/null
+++ b/src/Avalonia.Themes.Simple/Controls/RefreshVisualizer.xaml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml b/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml
index 4aefa0136c..093adaeab2 100644
--- a/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml
@@ -3,68 +3,70 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Simple/Controls/TreeViewItem.xaml b/src/Avalonia.Themes.Simple/Controls/TreeViewItem.xaml
index b75da2dd7b..b4499f2087 100644
--- a/src/Avalonia.Themes.Simple/Controls/TreeViewItem.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/TreeViewItem.xaml
@@ -44,6 +44,7 @@
Focusable="True"
TemplatedControl.IsTemplateFocusTarget="True">
-
+
diff --git a/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs b/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs
index 56c4cbeac6..42dfafd7e0 100644
--- a/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs
+++ b/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs
@@ -58,13 +58,13 @@ namespace Avalonia.Themes.Simple
{
var themeVariantResource = Mode == SimpleThemeMode.Dark ? _simpleDark : _simpleLight;
var dict = Resources.MergedDictionaries;
- if (dict.Count == 1)
+ if (dict.Count == 0)
{
dict.Add(themeVariantResource);
}
else
{
- dict[1] = themeVariantResource;
+ dict[0] = themeVariantResource;
}
}
}
diff --git a/src/Avalonia.X11/Glx/GlxContext.cs b/src/Avalonia.X11/Glx/GlxContext.cs
index e9cb88cb8f..def5228e94 100644
--- a/src/Avalonia.X11/Glx/GlxContext.cs
+++ b/src/Avalonia.X11/Glx/GlxContext.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Threading;
using Avalonia.OpenGL;
@@ -70,6 +71,8 @@ namespace Avalonia.X11.Glx
}
public IDisposable MakeCurrent() => MakeCurrent(_defaultXid);
+ public bool IsLost => false;
+
public IDisposable EnsureCurrent()
{
if(IsCurrent)
@@ -86,6 +89,11 @@ namespace Avalonia.X11.Glx
|| _sharedWith != null && _sharedWith == c._sharedWith;
}
+ public bool CanCreateSharedContext => true;
+
+ public IGlContext CreateSharedContext(IEnumerable preferredVersions = null) =>
+ Display.CreateContext(_sharedWith ?? this);
+
public IDisposable MakeCurrent(IntPtr xid)
{
Monitor.Enter(_lock);
@@ -114,5 +122,7 @@ namespace Avalonia.X11.Glx
if (_ownsPBuffer)
Glx.DestroyPbuffer(_x11.Display, _defaultXid);
}
+
+ public object TryGetFeature(Type featureType) => null;
}
}
diff --git a/src/Avalonia.X11/Glx/GlxDisplay.cs b/src/Avalonia.X11/Glx/GlxDisplay.cs
index 1e70608168..9b8a8f0b5b 100644
--- a/src/Avalonia.X11/Glx/GlxDisplay.cs
+++ b/src/Avalonia.X11/Glx/GlxDisplay.cs
@@ -111,8 +111,8 @@ namespace Avalonia.X11.Glx
return Glx.CreatePbuffer(_x11.Display, _fbconfig, new[] { GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, 0 });
}
-
- public GlxContext CreateContext() => CreateContext();
+ public GlxContext CreateContext() => CreateContext(CreatePBuffer(), null, DeferredContext.SampleCount,
+ DeferredContext.StencilSize, true);
public GlxContext CreateContext(IGlContext share) => CreateContext(CreatePBuffer(), share,
share.SampleCount, share.StencilSize, true);
diff --git a/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs b/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs
index cb4ab4aca0..ebb1e18723 100644
--- a/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs
+++ b/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs
@@ -9,20 +9,16 @@ namespace Avalonia.X11.Glx
class GlxGlPlatformSurface: IGlPlatformSurface
{
- private readonly GlxDisplay _display;
- private readonly GlxContext _context;
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
- public GlxGlPlatformSurface(GlxDisplay display, GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info)
+ public GlxGlPlatformSurface(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info)
{
- _display = display;
- _context = context;
_info = info;
}
- public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
+ public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context)
{
- return new RenderTarget(_context, _info);
+ return new RenderTarget((GlxContext)context, _info);
}
class RenderTarget : IGlPlatformSurfaceRenderTarget
diff --git a/src/Avalonia.X11/Glx/GlxPlatformFeature.cs b/src/Avalonia.X11/Glx/GlxPlatformFeature.cs
index 0968adc799..bc50fa61aa 100644
--- a/src/Avalonia.X11/Glx/GlxPlatformFeature.cs
+++ b/src/Avalonia.X11/Glx/GlxPlatformFeature.cs
@@ -6,38 +6,36 @@ using Avalonia.Platform;
namespace Avalonia.X11.Glx
{
- class GlxPlatformOpenGlInterface : IPlatformOpenGlInterface
+ class GlxPlatformGraphics : IPlatformGraphics
{
public GlxDisplay Display { get; private set; }
public bool CanCreateContexts => true;
public bool CanShareContexts => true;
- public IGlContext CreateContext() => Display.CreateContext();
- public IGlContext CreateSharedContext() => Display.CreateContext(PrimaryContext);
- public GlxContext DeferredContext { get; private set; }
- public IGlContext PrimaryContext => DeferredContext;
- IPlatformGpuContext IPlatformGpu.PrimaryContext => PrimaryContext;
+ public bool UsesSharedContext => false;
+ IPlatformGraphicsContext IPlatformGraphics.CreateContext() => Display.CreateContext();
+
+ public IPlatformGraphicsContext GetSharedContext() => throw new NotSupportedException();
public static bool TryInitialize(X11Info x11, IList glProfiles)
{
var feature = TryCreate(x11, glProfiles);
if (feature != null)
{
- AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature);
+ AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature);
return true;
}
return false;
}
- public static GlxPlatformOpenGlInterface TryCreate(X11Info x11, IList glProfiles)
+ public static GlxPlatformGraphics TryCreate(X11Info x11, IList glProfiles)
{
try
{
var disp = new GlxDisplay(x11, glProfiles);
- return new GlxPlatformOpenGlInterface
+ return new GlxPlatformGraphics
{
- Display = disp,
- DeferredContext = disp.DeferredContext
+ Display = disp
};
}
catch(Exception e)
diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs
index 6041b53a62..16de10e163 100644
--- a/src/Avalonia.X11/X11CursorFactory.cs
+++ b/src/Avalonia.X11/X11CursorFactory.cs
@@ -113,7 +113,8 @@ namespace Avalonia.X11
image->yhot = hotSpot.Y;
image->pixels = (IntPtr)(image + 1);
- using (var renderTarget = platformRenderInterface.CreateRenderTarget(new[] { this }))
+ using (var cpuContext = platformRenderInterface.CreateBackendContext(null))
+ using (var renderTarget = cpuContext.CreateRenderTarget(new[] { this }))
using (var ctx = renderTarget.CreateDrawingContext(null))
{
var r = new Rect(_pixelSize.ToSize(1));
diff --git a/src/Avalonia.X11/X11IconLoader.cs b/src/Avalonia.X11/X11IconLoader.cs
index 4ae1c1599f..bf59d72c0f 100644
--- a/src/Avalonia.X11/X11IconLoader.cs
+++ b/src/Avalonia.X11/X11IconLoader.cs
@@ -41,7 +41,8 @@ namespace Avalonia.X11
_width = Math.Min(bitmap.PixelSize.Width, 128);
_height = Math.Min(bitmap.PixelSize.Height, 128);
_bdata = new uint[_width * _height];
- using(var rt = AvaloniaLocator.Current.GetService().CreateRenderTarget(new[]{this}))
+ using(var cpuContext = AvaloniaLocator.Current.GetRequiredService().CreateBackendContext(null))
+ using(var rt = cpuContext.CreateRenderTarget(new[]{this}))
using (var ctx = rt.CreateDrawingContext(null))
ctx.DrawBitmap(bitmap.PlatformImpl, 1, new Rect(bitmap.Size),
new Rect(0, 0, _width, _height));
diff --git a/src/Avalonia.X11/X11ImmediateRendererProxy.cs b/src/Avalonia.X11/X11ImmediateRendererProxy.cs
index 293e5110f7..ef061cbe5c 100644
--- a/src/Avalonia.X11/X11ImmediateRendererProxy.cs
+++ b/src/Avalonia.X11/X11ImmediateRendererProxy.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
+using System.Threading.Tasks;
+using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.VisualTree;
@@ -14,11 +16,11 @@ namespace Avalonia.X11
private bool _running;
private object _lock = new object();
- public X11ImmediateRendererProxy(Visual root, IRenderLoop loop)
+ public X11ImmediateRendererProxy(Visual root, IRenderLoop loop, Func renderTargetFactory,
+ PlatformRenderInterfaceContextManager renderContext)
{
_loop = loop;
- _renderer = new ImmediateRenderer(root);
-
+ _renderer = new ImmediateRenderer(root, renderTargetFactory, renderContext);
}
public void Dispose()
@@ -92,6 +94,9 @@ namespace Avalonia.X11
_renderer.Stop();
}
+ public ValueTask TryGetRenderInterfaceFeature(Type featureType) =>
+ _renderer.TryGetRenderInterfaceFeature(featureType);
+
public bool NeedsUpdate => false;
public void Update(TimeSpan time)
{
diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs
index cbb782edd0..e44b5ded14 100644
--- a/src/Avalonia.X11/X11Platform.cs
+++ b/src/Avalonia.X11/X11Platform.cs
@@ -30,6 +30,7 @@ namespace Avalonia.X11
public X11Info Info { get; private set; }
public IX11Screens X11Screens { get; private set; }
public Compositor Compositor { get; private set; }
+ public PlatformRenderInterfaceContextManager RenderInterface { get; private set; }
public IScreenImpl Screens { get; private set; }
public X11PlatformOptions Options { get; private set; }
public IntPtr OrphanedWindow { get; private set; }
@@ -96,17 +97,17 @@ namespace Avalonia.X11
if (options.UseGpu)
{
if (options.UseEGL)
- EglPlatformOpenGlInterface.TryInitialize();
+ EglPlatformGraphics.TryInitialize();
else
- GlxPlatformOpenGlInterface.TryInitialize(Info, Options.GlProfiles);
+ GlxPlatformGraphics.TryInitialize(Info, Options.GlProfiles);
}
- var gl = AvaloniaLocator.Current.GetService();
- if (gl != null)
- AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl);
-
+ var gl = AvaloniaLocator.Current.GetService();
+
if (options.UseCompositor)
Compositor = new Compositor(AvaloniaLocator.Current.GetService()!, gl);
+ else
+ RenderInterface = new(gl);
}
diff --git a/src/Avalonia.X11/X11Structs.cs b/src/Avalonia.X11/X11Structs.cs
index 7e46606c36..26515762b4 100644
--- a/src/Avalonia.X11/X11Structs.cs
+++ b/src/Avalonia.X11/X11Structs.cs
@@ -1768,7 +1768,9 @@ namespace Avalonia.X11 {
}
[StructLayout(LayoutKind.Sequential)]
+#pragma warning disable CA1815 // Override equals and operator equals on value types
public unsafe struct XImage
+#pragma warning restore CA1815 // Override equals and operator equals on value types
{
public int width, height; /* size of image */
public int xoffset; /* number of pixels offset in X direction */
@@ -1799,7 +1801,7 @@ namespace Avalonia.X11 {
internal IntPtr green_mask;
internal IntPtr blue_mask;
internal int colormap_size;
- internal int bits_per_rgb;
+ internal int bits_per_rgb;
}
internal enum XIMFeedback
diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs
index 2f84e15b32..160401c5fc 100644
--- a/src/Avalonia.X11/X11Window.cs
+++ b/src/Avalonia.X11/X11Window.cs
@@ -74,7 +74,7 @@ namespace Avalonia.X11
_touch = new TouchDevice();
_keyboard = platform.KeyboardDevice;
- var glfeature = AvaloniaLocator.Current.GetService();
+ var glfeature = AvaloniaLocator.Current.GetService();
XSetWindowAttributes attr = new XSetWindowAttributes();
var valueMask = default(SetWindowValuemask);
@@ -96,13 +96,13 @@ namespace Avalonia.X11
// OpenGL seems to be do weird things to it's current window which breaks resize sometimes
_useRenderWindow = glfeature != null;
- var glx = glfeature as GlxPlatformOpenGlInterface;
+ var glx = glfeature as GlxPlatformGraphics;
if (glx != null)
visualInfo = *glx.Display.VisualInfo;
else if (glfeature == null)
visualInfo = _x11.TransparentVisualInfo;
- var egl = glfeature as EglPlatformOpenGlInterface;
+ var egl = glfeature as EglPlatformGraphics;
var visual = IntPtr.Zero;
var depth = 24;
@@ -176,11 +176,9 @@ namespace Avalonia.X11
if (egl != null)
surfaces.Insert(0,
- new EglGlPlatformSurface(egl,
- new SurfaceInfo(this, _x11.DeferredDisplay, _handle, _renderHandle)));
+ new EglGlPlatformSurface(new SurfaceInfo(this, _x11.DeferredDisplay, _handle, _renderHandle)));
if (glx != null)
- surfaces.Insert(0, new GlxGlPlatformSurface(glx.Display, glx.DeferredContext,
- new SurfaceInfo(this, _x11.Display, _handle, _renderHandle)));
+ surfaces.Insert(0, new GlxGlPlatformSurface(new SurfaceInfo(this, _x11.Display, _handle, _renderHandle)));
surfaces.Add(Handle);
@@ -391,12 +389,14 @@ namespace Avalonia.X11
return _platform.Options.UseDeferredRendering
? _platform.Options.UseCompositor
- ? new CompositingRenderer(root, this._platform.Compositor)
- : new DeferredRenderer(root, loop)
+ ? new CompositingRenderer(root, this._platform.Compositor, () => Surfaces)
+ : new DeferredRenderer(root, loop, () => _platform.RenderInterface.CreateRenderTarget(Surfaces), _platform.RenderInterface)
{
RenderOnlyOnRenderThread = true
}
- : (IRenderer)new X11ImmediateRendererProxy((Visual)root, loop);
+ : new X11ImmediateRendererProxy((Visual)root, loop,
+ () => _platform.RenderInterface.CreateRenderTarget(Surfaces),
+ _platform.RenderInterface);
}
void OnEvent(ref XEvent ev)
diff --git a/src/Browser/Avalonia.Browser/AvaloniaView.cs b/src/Browser/Avalonia.Browser/AvaloniaView.cs
index a407e1e4d8..abda618b0d 100644
--- a/src/Browser/Avalonia.Browser/AvaloniaView.cs
+++ b/src/Browser/Avalonia.Browser/AvaloniaView.cs
@@ -11,6 +11,7 @@ using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
+using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
using SkiaSharp;
@@ -106,7 +107,7 @@ namespace Avalonia.Browser
_dpi = DomHelper.ObserveDpi(OnDpiChanged);
- _useGL = skiaOptions?.CustomGpuFactory != null;
+ _useGL = AvaloniaLocator.Current.GetRequiredService() != null;
if (_useGL)
{
diff --git a/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs b/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs
index ee4f6eca9b..b6c766b2a5 100644
--- a/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs
+++ b/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs
@@ -48,7 +48,6 @@ public static class WebAppBuilder
{
return builder
.UseWindowingSubsystem(BrowserWindowingPlatform.Register)
- .UseSkia()
- .With(new SkiaOptions { CustomGpuFactory = () => new BrowserSkiaGpu() });
+ .UseSkia();
}
}
diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
index 69e2d27181..85725e4cf3 100644
--- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
+++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
@@ -166,7 +166,8 @@ namespace Avalonia.Browser
public IRenderer CreateRenderer(IRenderRoot root)
{
var loop = AvaloniaLocator.Current.GetRequiredService();
- return new CompositingRenderer(root, new Compositor(loop, null));
+ return new CompositingRenderer(root,
+ new Compositor(loop, AvaloniaLocator.Current.GetRequiredService()), () => Surfaces);
}
public void Invalidate(Rect rect)
diff --git a/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs
index a96ead93cb..e56efdb4a8 100644
--- a/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs
+++ b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs
@@ -1,4 +1,7 @@
+using System;
using System.Collections.Generic;
+using System.Reactive.Disposables;
+using Avalonia.Platform;
using Avalonia.Skia;
namespace Avalonia.Browser.Skia
@@ -22,5 +25,28 @@ namespace Avalonia.Browser.Skia
{
return null;
}
+
+ public void Dispose()
+ {
+
+ }
+
+ public object? TryGetFeature(Type t) => null;
+
+ public bool IsLost => false;
+
+ public IDisposable EnsureCurrent()
+ {
+ return Disposable.Empty;
+ }
+ }
+
+ class BrowserSkiaGraphics : IPlatformGraphics
+ {
+ private BrowserSkiaGpu _skia = new();
+ public bool UsesSharedContext => true;
+ public IPlatformGraphicsContext CreateContext() => throw new NotSupportedException();
+
+ public IPlatformGraphicsContext GetSharedContext() => _skia;
}
}
diff --git a/src/Browser/Avalonia.Browser/WindowingPlatform.cs b/src/Browser/Avalonia.Browser/WindowingPlatform.cs
index 6535e9534c..6493374a50 100644
--- a/src/Browser/Avalonia.Browser/WindowingPlatform.cs
+++ b/src/Browser/Avalonia.Browser/WindowingPlatform.cs
@@ -1,5 +1,6 @@
using System;
using System.Threading;
+using Avalonia.Browser.Skia;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Platform;
@@ -43,6 +44,7 @@ namespace Avalonia.Browser
.Bind().ToConstant(new RenderLoop())
.Bind().ToConstant(ManualTriggerRenderTimer.Instance)
.Bind().ToConstant(instance)
+ .Bind().ToConstant(new BrowserSkiaGraphics())
.Bind().ToSingleton()
.Bind().ToSingleton();
}
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
index b64423ec10..ac54365f51 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
@@ -1,4 +1,4 @@
-using System;
+ using System;
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Input;
@@ -33,7 +33,7 @@ namespace Avalonia.LinuxFramebuffer
{
var factory = AvaloniaLocator.Current.GetService();
var renderLoop = AvaloniaLocator.Current.GetService();
- return factory?.Create(root, renderLoop) ?? new CompositingRenderer(root, LinuxFramebufferPlatform.Compositor);
+ return factory?.Create(root, renderLoop) ?? new CompositingRenderer(root, LinuxFramebufferPlatform.Compositor, () => Surfaces);
}
public void Dispose()
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
index d881a97af2..38498951f8 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
@@ -41,7 +41,7 @@ namespace Avalonia.LinuxFramebuffer
{
Threading = new InternalPlatformThreadingInterface();
if (_fb is IGlOutputBackend gl)
- AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl.PlatformOpenGlInterface);
+ AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl.PlatformGraphics);
var opts = AvaloniaLocator.Current.GetService() ?? new LinuxFramebufferPlatformOptions();
@@ -56,7 +56,7 @@ namespace Avalonia.LinuxFramebuffer
Compositor = new Compositor(
AvaloniaLocator.Current.GetRequiredService(),
- AvaloniaLocator.Current.GetService());
+ AvaloniaLocator.Current.GetService());
}
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
index a4d7d8b1eb..22dd407791 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
@@ -6,6 +6,7 @@ using System.Runtime.InteropServices;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
+using Avalonia.Platform;
using Avalonia.Platform.Interop;
using JetBrains.Annotations;
using static Avalonia.LinuxFramebuffer.NativeUnsafeMethods;
@@ -25,10 +26,22 @@ namespace Avalonia.LinuxFramebuffer.Output
get => _outputOptions.Scaling;
set => _outputOptions.Scaling = value;
}
- public IGlContext PrimaryContext => _deferredContext;
- private EglPlatformOpenGlInterface _platformGl;
- public IPlatformOpenGlInterface PlatformOpenGlInterface => _platformGl;
+ class SharedContextGraphics : IPlatformGraphics
+ {
+ private readonly IPlatformGraphicsContext _context;
+
+ public SharedContextGraphics(IPlatformGraphicsContext context)
+ {
+ _context = context;
+ }
+ public bool UsesSharedContext => true;
+ public IPlatformGraphicsContext CreateContext() => throw new NotSupportedException();
+
+ public IPlatformGraphicsContext GetSharedContext() => _context;
+ }
+
+ public IPlatformGraphics PlatformGraphics { get; private set; }
public DrmOutput(DrmCard card, DrmResources resources, DrmConnector connector, DrmModeInfo modeInfo,
DrmOutputOptions? options = null)
@@ -161,11 +174,22 @@ namespace Avalonia.LinuxFramebuffer.Output
if(_gbmTargetSurface == IntPtr.Zero)
throw new InvalidOperationException("Unable to create GBM surface");
- _eglDisplay = new EglDisplay(new EglInterface(eglGetProcAddress), false, 0x31D7, device, null);
- _platformGl = new EglPlatformOpenGlInterface(_eglDisplay);
- _eglSurface = _platformGl.CreateWindowSurface(_gbmTargetSurface);
+ _eglDisplay = new EglDisplay(
+ new EglDisplayCreationOptions
+ {
+ Egl = new EglInterface(eglGetProcAddress),
+ PlatformType = 0x31D7,
+ PlatformDisplay = device,
+ SupportsMultipleContexts = true,
+ SupportsContextSharing = true
+ });
+
+ var surface = _eglDisplay.EglInterface.CreateWindowSurface(_eglDisplay.Handle, _eglDisplay.Config, _gbmTargetSurface, new[] { EglConsts.EGL_NONE, EglConsts.EGL_NONE });
+
+ _eglSurface = new EglSurface(_eglDisplay, surface);
- _deferredContext = _platformGl.PrimaryEglContext;
+ _deferredContext = _eglDisplay.CreateContext(null);
+ PlatformGraphics = new SharedContextGraphics(_deferredContext);
var initialBufferSwappingColorR = _outputOptions.InitialBufferSwappingColor.R / 255.0f;
var initialBufferSwappingColorG = _outputOptions.InitialBufferSwappingColor.G / 255.0f;
@@ -206,11 +230,17 @@ namespace Avalonia.LinuxFramebuffer.Output
}
- public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
+ public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() => new RenderTarget(this);
+
+
+ public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context)
{
- return new RenderTarget(this);
+ if (context != _deferredContext)
+ throw new InvalidOperationException(
+ "This platform backend can only create render targets for its primary context");
+ return CreateGlRenderTarget();
}
-
+
class RenderTarget : IGlPlatformSurfaceRenderTarget
{
private readonly DrmOutput _parent;
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/IGlOutputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/IGlOutputBackend.cs
index 7bc73d590c..e415b1782e 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/Output/IGlOutputBackend.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/IGlOutputBackend.cs
@@ -1,9 +1,10 @@
using Avalonia.OpenGL;
+using Avalonia.Platform;
namespace Avalonia.LinuxFramebuffer.Output
{
public interface IGlOutputBackend : IOutputBackend
{
- public IPlatformOpenGlInterface PlatformOpenGlInterface { get; }
+ public IPlatformGraphics PlatformGraphics { get; }
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs
index b4c951fc5e..6f6420f66d 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs
@@ -56,8 +56,8 @@ namespace Avalonia.Markup.Xaml
///
/// Collection of documents.
/// Xaml loader configuration.
- /// The loaded objects per each input document.
- public static IReadOnlyList LoadGroup(IReadOnlyCollection documents, RuntimeXamlLoaderConfiguration? configuration = null)
+ /// The loaded objects per each input document. If document was removed, the element by index is null.
+ public static IReadOnlyList LoadGroup(IReadOnlyCollection documents, RuntimeXamlLoaderConfiguration? configuration = null)
=> AvaloniaXamlIlRuntimeCompiler.LoadGroup(documents, configuration ?? new RuntimeXamlLoaderConfiguration());
///
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
index b8350c3f11..aaaee39b0d 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
@@ -85,6 +85,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
GroupTransformers = new()
{
+ new XamlMergeResourceGroupTransformer(),
new AvaloniaXamlIncludeTransformer()
};
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs
index 32bf37431f..eeb5293325 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs
@@ -18,7 +18,7 @@ internal class AstGroupTransformationContext : AstTransformationContext
public IXamlDocumentResource CurrentDocument { get; set; }
public IReadOnlyCollection Documents { get; }
-
+
public new IXamlAstNode ParseError(string message, IXamlAstNode node) =>
Error(node, new XamlDocumentParseException(CurrentDocument?.FileSource?.FilePath, message, node));
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs
index 1bb05e238c..afc7b569a8 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs
@@ -15,6 +15,7 @@ using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers;
+#nullable enable
internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
{
public IXamlAstNode Transform(AstGroupTransformationContext context, IXamlAstNode node)
@@ -34,40 +35,26 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
{
throw new InvalidOperationException($"\"{nodeTypeName}\".Loaded property is expected to be defined");
}
-
+
if (valueNode.Manipulation is not XamlObjectInitializationNode
{
Manipulation: XamlPropertyAssignmentNode { Property: { Name: "Source" } } sourceProperty
})
{
- return context.ParseError($"Source property must be set on the \"{nodeTypeName}\" node.", node);
+ throw new XamlDocumentParseException(context.CurrentDocument,
+ $"Source property must be set on the \"{nodeTypeName}\" node.", valueNode);
}
- // We expect that AvaloniaXamlIlLanguageParseIntrinsics has already parsed the Uri and created node like: `new Uri(assetPath, uriKind)`.
- if (sourceProperty.Values.OfType().FirstOrDefault() is not { } sourceUriNode
- || sourceUriNode.Type.GetClrType() != context.GetAvaloniaTypes().Uri
- || sourceUriNode.Arguments.FirstOrDefault() is not XamlConstantNode { Constant: string originalAssetPath }
- || sourceUriNode.Arguments.Skip(1).FirstOrDefault() is not XamlConstantNode { Constant: int uriKind })
+ var (assetPathUri, sourceUriNode) = ResolveSourceFromXamlInclude(context, nodeTypeName, sourceProperty, false);
+ if (assetPathUri is null)
{
- // TODO: make it a compiler warning
- // Source value can be set with markup extension instead of the Uri object node, we don't support it here yet.
return node;
}
-
- var uriPath = new Uri(originalAssetPath, (UriKind)uriKind);
- if (!uriPath.IsAbsoluteUri)
+ else
{
- var baseUrl = context.CurrentDocument.Uri ?? throw new InvalidOperationException("CurrentDocument URI is null.");
- uriPath = new Uri(new Uri(baseUrl, UriKind.Absolute), uriPath);
- }
- else if (!uriPath.Scheme.Equals("avares", StringComparison.CurrentCultureIgnoreCase))
- {
- return context.ParseError(
- $"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri.",
- sourceUriNode, node);
+ sourceUriNode ??= valueNode;
}
- var assetPathUri = Uri.UnescapeDataString(uriPath.AbsoluteUri);
var assetPath = assetPathUri.Replace("avares://", "");
var assemblyNameSeparator = assetPath.IndexOf('/');
var assembly = assetPath.Substring(0, assemblyNameSeparator);
@@ -119,7 +106,7 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
$"Unable to resolve XAML resource \"{assetPathUri}\" in the \"{assembly}\" assembly.",
sourceUriNode, node);
}
-
+
private static IXamlAstNode FromType(AstTransformationContext context, IXamlType type, IXamlAstNode li,
IXamlType expectedLoadedType, IXamlAstNode fallbackNode, string assetPathUri, string assembly)
{
@@ -151,7 +138,49 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
new[] { new NewServiceProviderNode(sp, li) });
}
- internal class NewServiceProviderNode : XamlAstNode, IXamlAstValueNode,IXamlAstNodeNeedsParentStack,
+ internal static (string?, IXamlAstNode?) ResolveSourceFromXamlInclude(
+ AstGroupTransformationContext context, string nodeTypeName, XamlPropertyAssignmentNode sourceProperty,
+ bool strictSourceValueType)
+ {
+ // We expect that AvaloniaXamlIlLanguageParseIntrinsics has already parsed the Uri and created node like: `new Uri(assetPath, uriKind)`.
+ if (sourceProperty.Values.OfType().FirstOrDefault() is not { } sourceUriNode
+ || sourceUriNode.Type.GetClrType() != context.GetAvaloniaTypes().Uri
+ || sourceUriNode.Arguments.FirstOrDefault() is not XamlConstantNode { Constant: string originalAssetPath }
+ || sourceUriNode.Arguments.Skip(1).FirstOrDefault() is not XamlConstantNode { Constant: int uriKind })
+ {
+ // Source value can be set with markup extension instead of the Uri object node, we don't support it here yet.
+ var anyPropValue = sourceProperty.Values.FirstOrDefault();
+ if (strictSourceValueType)
+ {
+ context.Error(anyPropValue,
+ new XamlDocumentParseException(context.CurrentDocument,
+ $"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri.", anyPropValue));
+ }
+ else
+ {
+ // TODO: make it a compiler warning
+ }
+ return (null, anyPropValue);
+ }
+
+ var uriPath = new Uri(originalAssetPath, (UriKind)uriKind);
+ if (!uriPath.IsAbsoluteUri)
+ {
+ var baseUrl = context.CurrentDocument.Uri ?? throw new InvalidOperationException("CurrentDocument URI is null.");
+ uriPath = new Uri(new Uri(baseUrl, UriKind.Absolute), uriPath);
+ }
+ else if (!uriPath.Scheme.Equals("avares", StringComparison.CurrentCultureIgnoreCase))
+ {
+ context.Error(sourceUriNode,
+ new XamlDocumentParseException(context.CurrentDocument,
+ $"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri.", sourceUriNode));
+ return (null, sourceUriNode);
+ }
+
+ return (Uri.UnescapeDataString(uriPath.AbsoluteUri), sourceUriNode);
+ }
+
+ private class NewServiceProviderNode : XamlAstNode, IXamlAstValueNode,IXamlAstNodeNeedsParentStack,
IXamlAstEmitableNode
{
public NewServiceProviderNode(IXamlType type, IXamlLineInfo lineInfo) : base(lineInfo)
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs
new file mode 100644
index 0000000000..8c83c74248
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+using XamlX.Ast;
+using XamlX.IL.Emitters;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers;
+#nullable enable
+
+internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
+{
+ public IXamlAstNode Transform(AstGroupTransformationContext context, IXamlAstNode node)
+ {
+ var resourceDictionaryType = context.GetAvaloniaTypes().ResourceDictionary;
+ if (node is not XamlObjectInitializationNode resourceDictionaryNode
+ || resourceDictionaryNode.Type != resourceDictionaryType
+ || resourceDictionaryNode.Manipulation is not XamlManipulationGroupNode resourceDictionaryManipulation)
+ {
+ return node;
+ }
+
+ var mergeResourceIncludeType = context.GetAvaloniaTypes().MergeResourceInclude;
+ var mergeSourceNodes = new List();
+ var hasAnyNonMergedResource = false;
+ foreach (var manipulationNode in resourceDictionaryManipulation.Children.ToArray())
+ {
+ void ProcessXamlPropertyAssignmentNode(XamlManipulationGroupNode parent, XamlPropertyAssignmentNode assignmentNode)
+ {
+ if (assignmentNode.Property.Name == "MergedDictionaries"
+ && assignmentNode.Values.FirstOrDefault() is XamlValueWithManipulationNode valueNode)
+ {
+ if (valueNode.Type.GetClrType() == mergeResourceIncludeType)
+ {
+ if (valueNode.Manipulation is XamlObjectInitializationNode objectInitialization
+ && objectInitialization.Manipulation is XamlPropertyAssignmentNode sourceAssignmentNode)
+ {
+ parent.Children.Remove(assignmentNode);
+ mergeSourceNodes.Add(sourceAssignmentNode);
+ }
+ else
+ {
+ throw new XamlDocumentParseException(context.CurrentDocument,
+ "Invalid MergeResourceInclude node found. Make sure that Source property is set.",
+ valueNode);
+ }
+ }
+ else
+ {
+ hasAnyNonMergedResource = true;
+ }
+
+ if (hasAnyNonMergedResource && mergeSourceNodes.Any())
+ {
+ throw new XamlDocumentParseException(context.CurrentDocument,
+ "Mix of MergeResourceInclude and other dictionaries inside of the ResourceDictionary.MergedDictionaries is not allowed",
+ valueNode);
+ }
+ }
+ }
+
+ if (manipulationNode is XamlPropertyAssignmentNode singleValueAssignment)
+ {
+ ProcessXamlPropertyAssignmentNode(resourceDictionaryManipulation, singleValueAssignment);
+ }
+ else if (manipulationNode is XamlManipulationGroupNode groupNodeValues)
+ {
+ foreach (var groupNodeValue in groupNodeValues.Children.OfType().ToArray())
+ {
+ ProcessXamlPropertyAssignmentNode(groupNodeValues, groupNodeValue);
+ }
+ }
+ }
+
+ var manipulationGroup = new XamlManipulationGroupNode(node, new List());
+ foreach (var sourceNode in mergeSourceNodes)
+ {
+ var (originalAssetPath, propertyNode) =
+ AvaloniaXamlIncludeTransformer.ResolveSourceFromXamlInclude(context, "MergeResourceInclude", sourceNode, true);
+ if (originalAssetPath is null)
+ {
+ return node;
+ }
+
+ var targetDocument = context.Documents.FirstOrDefault(d =>
+ string.Equals(d.Uri, originalAssetPath, StringComparison.InvariantCultureIgnoreCase))
+ ?.XamlDocument.Root as XamlValueWithManipulationNode;
+ if (targetDocument is null)
+ {
+ return context.ParseError(
+ $"Node MergeResourceInclude is unable to resolve \"{originalAssetPath}\" path.", propertyNode, node);
+ }
+
+ var singleRootObject = ((XamlManipulationGroupNode)targetDocument.Manipulation)
+ .Children.OfType().Single();
+ if (singleRootObject.Type != resourceDictionaryType)
+ {
+ return context.ParseError(
+ $"MergeResourceInclude can only include another ResourceDictionary", propertyNode, node);
+ }
+
+ manipulationGroup.Children.Add(singleRootObject.Manipulation);
+ }
+
+ if (manipulationGroup.Children.Any())
+ {
+ // MergedDictionaries are read first, so we need ot inject our merged values in the beginning.
+ resourceDictionaryManipulation.Children.Insert(0, manipulationGroup);
+ }
+
+ return node;
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
index c5bfdd8687..aab6239a35 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
@@ -104,6 +104,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType IStyle { get; }
public IXamlType StyleInclude { get; }
public IXamlType ResourceInclude { get; }
+ public IXamlType MergeResourceInclude { get; }
public IXamlType IResourceDictionary { get; }
public IXamlType ResourceDictionary { get; }
public IXamlMethod ResourceDictionaryDeferredAdd { get; }
@@ -236,6 +237,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
IStyle = cfg.TypeSystem.GetType("Avalonia.Styling.IStyle");
StyleInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.StyleInclude");
ResourceInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.ResourceInclude");
+ MergeResourceInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.MergeResourceInclude");
IResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.IResourceDictionary");
ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary");
ResourceDictionaryDeferredAdd = ResourceDictionary.FindMethod("AddDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object,
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs
index d031a6086b..0532287a67 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs
@@ -18,4 +18,9 @@ internal class XamlDocumentParseException : XamlParseException
{
FilePath = path;
}
+
+ public XamlDocumentParseException(IXamlDocumentResource document, string message, IXamlLineInfo lineInfo)
+ : this(document.FileSource?.FilePath, message, lineInfo)
+ {
+ }
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
index c59542c0f3..267bd2520e 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
+++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
@@ -43,6 +43,7 @@
+
diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/MergeResourceInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/MergeResourceInclude.cs
new file mode 100644
index 0000000000..d231a1d221
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml/Styling/MergeResourceInclude.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using Avalonia.Controls;
+
+#nullable enable
+
+namespace Avalonia.Markup.Xaml.Styling;
+
+///
+/// Loads a resource dictionary from a specified URL.
+///
+///
+/// If used from the XAML code, it is merged into the parent dictionary in the compile time.
+/// When used in runtime, this type behaves like .
+///
+[RequiresUnreferencedCode(TrimmingMessages.StyleResourceIncludeRequiresUnreferenceCodeMessage)]
+public class MergeResourceInclude : ResourceInclude
+{
+ public MergeResourceInclude(Uri? baseUri) : base(baseUri)
+ {
+ }
+
+ public MergeResourceInclude(IServiceProvider serviceProvider) : base(serviceProvider)
+ {
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs
index 3d8a61364a..595b37f7d1 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs
@@ -9,6 +9,10 @@ namespace Avalonia.Markup.Xaml.Styling
///
/// Loads a resource dictionary from a specified URL.
///
+ ///
+ /// If used from the XAML code, it is replaced with direct resource dictionary reference.
+ /// When used in runtime, this type might be unsafe with trimming and AOT.
+ ///
[RequiresUnreferencedCode(TrimmingMessages.StyleResourceIncludeRequiresUnreferenceCodeMessage)]
public class ResourceInclude : IResourceProvider
{
diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
index bdde6e725c..b87aa64297 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
@@ -11,6 +11,10 @@ namespace Avalonia.Markup.Xaml.Styling
///
/// Includes a style from a URL.
///
+ ///
+ /// If used from the XAML code, it is replaced with direct style reference.
+ /// When used in runtime, this type might be unsafe with trimming and AOT.
+ ///
[RequiresUnreferencedCode(TrimmingMessages.StyleResourceIncludeRequiresUnreferenceCodeMessage)]
public class StyleInclude : IStyle, IResourceProvider
{
diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
index 9dbfd0f8d9..1ae47c8b7a 100644
--- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
+++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
@@ -61,6 +61,8 @@ namespace Avalonia.Skia
return new DrawingContextImpl(createInfo, _preFramebufferCopyHandler, canvas, framebuffer);
}
+ public bool IsCorrupted => false;
+
///
/// Check if two images info are compatible.
///
diff --git a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs
index 4a7d4d4ac8..b064445a0b 100644
--- a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs
+++ b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
+using Avalonia.OpenGL;
using Avalonia.OpenGL.Imaging;
+using Avalonia.Platform;
using SkiaSharp;
namespace Avalonia.Skia
@@ -8,7 +10,7 @@ namespace Avalonia.Skia
///
/// Custom Skia gpu instance.
///
- public interface ISkiaGpu
+ public interface ISkiaGpu : IPlatformGraphicsContext
{
///
/// Attempts to create custom render target from given surfaces.
diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs
index 5cc338a469..e19379df09 100644
--- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs
+++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs
@@ -1,11 +1,13 @@
using System;
using Avalonia.OpenGL;
+using Avalonia.Platform;
using SkiaSharp;
using static Avalonia.OpenGL.GlConsts;
namespace Avalonia.Skia
{
- public class FboSkiaSurface : ISkiaSurface
+ internal class FboSkiaSurface : ISkiaSurface
{
+ private readonly GlSkiaGpu _gpu;
private readonly GRContext _grContext;
private readonly IGlContext _glContext;
private readonly PixelSize _pixelSize;
@@ -14,8 +16,9 @@ namespace Avalonia.Skia
private int _texture;
private static readonly bool[] TrueFalse = new[] { true, false };
- public FboSkiaSurface(GRContext grContext, IGlContext glContext, PixelSize pixelSize, GRSurfaceOrigin surfaceOrigin)
+ public FboSkiaSurface(GlSkiaGpu gpu, GRContext grContext, IGlContext glContext, PixelSize pixelSize, GRSurfaceOrigin surfaceOrigin)
{
+ _gpu = gpu;
_grContext = grContext;
_glContext = glContext;
_pixelSize = pixelSize;
@@ -93,19 +96,33 @@ namespace Avalonia.Skia
public void Dispose()
{
- using (_glContext.EnsureCurrent())
+ try
{
- Surface?.Dispose();
- Surface = null;
- var gl = _glContext.GlInterface;
- if (_fbo != 0)
+ using (_glContext.EnsureCurrent())
{
- gl.DeleteFramebuffer(_fbo);
- gl.DeleteTexture(_texture);
- gl.DeleteRenderbuffer(_depthStencil);
- _fbo = _texture = _depthStencil = 0;
+ Surface?.Dispose();
+ Surface = null;
+ var gl = _glContext.GlInterface;
+ if (_fbo != 0)
+ {
+ gl.DeleteFramebuffer(_fbo);
+ gl.DeleteTexture(_texture);
+ gl.DeleteRenderbuffer(_depthStencil);
+ }
}
}
+ catch (PlatformGraphicsContextLostException)
+ {
+ if (Surface != null)
+ // We need to dispose SKSurface _after_ GRContext.Abandon was called,
+ // otherwise it will try to do OpenGL calls without a proper context
+ _gpu.AddPostDispose(Surface.Dispose);
+ Surface = null;
+ }
+ finally
+ {
+ _fbo = _texture = _depthStencil = 0;
+ }
}
public SKSurface Surface { get; private set; }
diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs
index 476ecc4a39..5bf1272c2f 100644
--- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs
+++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs
@@ -14,10 +14,11 @@ namespace Avalonia.Skia
private readonly GRContext _grContext;
private IGlPlatformSurfaceRenderTarget _surface;
- public GlRenderTarget(GRContext grContext, IGlPlatformSurface glSurface)
+ public GlRenderTarget(GRContext grContext, IGlContext glContext, IGlPlatformSurface glSurface)
{
_grContext = grContext;
- _surface = glSurface.CreateGlRenderTarget();
+ using (glContext.EnsureCurrent())
+ _surface = glSurface.CreateGlRenderTarget(glContext);
}
public void Dispose() => _surface.Dispose();
diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs
index 08e0fbd808..cdba3b9ea2 100644
--- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs
+++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs
@@ -5,21 +5,22 @@ using Avalonia.Logging;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Imaging;
using Avalonia.OpenGL.Surfaces;
+using Avalonia.Platform;
using SkiaSharp;
namespace Avalonia.Skia
{
- class GlSkiaGpu : IOpenGlAwareSkiaGpu
+ class GlSkiaGpu : IOpenGlAwareSkiaGpu, IOpenGlTextureSharingRenderInterfaceContextFeature
{
private GRContext _grContext;
private IGlContext _glContext;
+ private List _postDisposeCallbacks = new();
private bool? _canCreateSurfaces;
- public GlSkiaGpu(IPlatformOpenGlInterface openGl, long? maxResourceBytes)
+ public GlSkiaGpu(IGlContext context, long? maxResourceBytes)
{
- var context = openGl.PrimaryContext;
_glContext = context;
- using (context.MakeCurrent())
+ using (_glContext.EnsureCurrent())
{
using (var iface = context.Version.Type == GlProfileType.OpenGL ?
GRGlInterface.CreateOpenGl(proc => context.GlInterface.GetProcAddress(proc)) :
@@ -34,13 +35,34 @@ namespace Avalonia.Skia
}
}
+ class SurfaceWrapper : IGlPlatformSurface
+ {
+ private readonly object _surface;
+
+ public SurfaceWrapper( object surface)
+ {
+ _surface = surface;
+ }
+
+ public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context)
+ {
+ var feature = context.TryGetFeature()!;
+ return feature.CreateRenderTarget(context, _surface);
+ }
+ }
+
public ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable surfaces)
{
+ var customRenderTargetFactory = _glContext.TryGetFeature();
foreach (var surface in surfaces)
{
+ if (customRenderTargetFactory?.CanRenderToSurface(_glContext, surface) == true)
+ {
+ return new GlRenderTarget(_grContext, _glContext, new SurfaceWrapper(surface));
+ }
if (surface is IGlPlatformSurface glSurface)
{
- return new GlRenderTarget(_grContext, glSurface);
+ return new GlRenderTarget(_grContext, _glContext, glSurface);
}
}
@@ -62,7 +84,8 @@ namespace Avalonia.Skia
return null;
try
{
- var surface = new FboSkiaSurface(_grContext, _glContext, size, session?.SurfaceOrigin ?? GRSurfaceOrigin.TopLeft);
+ var surface = new FboSkiaSurface(this, _grContext, _glContext, size,
+ session?.SurfaceOrigin ?? GRSurfaceOrigin.TopLeft);
_canCreateSurfaces = true;
return surface;
}
@@ -75,6 +98,40 @@ namespace Avalonia.Skia
}
}
+ public bool CanCreateSharedContext => _glContext.CanCreateSharedContext;
+
+ public IGlContext CreateSharedContext(IEnumerable preferredVersions = null) =>
+ _glContext.CreateSharedContext(preferredVersions);
+
public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) => new GlOpenGlBitmapImpl(_glContext, size, dpi);
+
+ public void Dispose()
+ {
+ if (_glContext.IsLost)
+ _grContext.AbandonContext();
+ else
+ _grContext.AbandonContext(true);
+ _grContext.Dispose();
+
+ lock(_postDisposeCallbacks)
+ foreach (var cb in _postDisposeCallbacks)
+ cb();
+ }
+
+ public bool IsLost => _glContext.IsLost;
+ public IDisposable EnsureCurrent() => _glContext.EnsureCurrent();
+
+ public object TryGetFeature(Type featureType)
+ {
+ if (featureType == typeof(IOpenGlTextureSharingRenderInterfaceContextFeature))
+ return this;
+ return null;
+ }
+
+ public void AddPostDispose(Action dispose)
+ {
+ lock (_postDisposeCallbacks)
+ _postDisposeCallbacks.Add(dispose);
+ }
}
}
diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs
index 5e02500bf0..d8bff7cfc8 100644
--- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs
+++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs
@@ -109,7 +109,9 @@ namespace Avalonia.Skia
using (_context.EnsureCurrent())
{
var glVersion = _context.Version;
- InternalFormat = glVersion.Type == GlProfileType.OpenGLES ? GL_RGBA : GL_RGBA8;
+ InternalFormat = glVersion.Type == GlProfileType.OpenGLES && glVersion.Major == 2
+ ? GL_RGBA
+ : GL_RGBA8;
_context.GlInterface.GetIntegerv(GL_FRAMEBUFFER_BINDING, out _fbo);
if (_fbo == 0)
@@ -145,7 +147,7 @@ namespace Avalonia.Skia
public void Present()
{
- using (_context.MakeCurrent())
+ using (_context.EnsureCurrent())
{
if (_disposed)
throw new ObjectDisposedException(nameof(SharedOpenGlBitmapAttachment));
diff --git a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs
index 6626546c0c..6b4a7a3409 100644
--- a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs
+++ b/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs
@@ -6,7 +6,7 @@ namespace Avalonia.Skia
///
/// Adapts to be used within our rendering pipeline.
///
- internal class SkiaGpuRenderTarget : IRenderTargetWithCorruptionInfo
+ internal class SkiaGpuRenderTarget : IRenderTarget
{
private readonly ISkiaGpu _skiaGpu;
private readonly ISkiaGpuRenderTarget _renderTarget;
diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
index f34e25299c..b202b60cdf 100644
--- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
+++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
@@ -18,26 +18,28 @@ namespace Avalonia.Skia
///
/// Skia platform render interface.
///
- internal class PlatformRenderInterface : IPlatformRenderInterface, IOpenGlAwarePlatformRenderInterface
+ internal class PlatformRenderInterface : IPlatformRenderInterface
{
- private readonly ISkiaGpu _skiaGpu;
+ private readonly long? _maxResourceBytes;
- public PlatformRenderInterface(ISkiaGpu skiaGpu, long? maxResourceBytes = null)
+ public PlatformRenderInterface(long? maxResourceBytes = null)
{
+ _maxResourceBytes = maxResourceBytes;
DefaultPixelFormat = SKImageInfo.PlatformColorType.ToPixelFormat();
+ }
- if (skiaGpu != null)
- {
- _skiaGpu = skiaGpu;
- return;
- }
- var gl = AvaloniaLocator.Current.GetService();
- if (gl != null)
- _skiaGpu = new GlSkiaGpu(gl, maxResourceBytes);
+ public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext)
+ {
+ if (graphicsContext == null)
+ return new SkiaContext(null);
+ if (graphicsContext is ISkiaGpu skiaGpu)
+ return new SkiaContext(skiaGpu);
+ if (graphicsContext is IGlContext gl)
+ return new SkiaContext(new GlSkiaGpu(gl, _maxResourceBytes));
+ throw new ArgumentException("Graphics context of type is not supported");
}
-
public bool SupportsIndividualRoundRects => true;
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
@@ -205,43 +207,12 @@ namespace Avalonia.Skia
return new SurfaceRenderTarget(createInfo);
}
- ///
- public IRenderTarget CreateRenderTarget(IEnumerable surfaces)
- {
- if (!(surfaces is IList))
- surfaces = surfaces.ToList();
- var gpuRenderTarget = _skiaGpu?.TryCreateRenderTarget(surfaces);
- if (gpuRenderTarget != null)
- {
- return new SkiaGpuRenderTarget(_skiaGpu, gpuRenderTarget);
- }
-
- foreach (var surface in surfaces)
- {
- if (surface is IFramebufferPlatformSurface framebufferSurface)
- return new FramebufferRenderTarget(framebufferSurface);
- }
-
- throw new NotSupportedException(
- "Don't know how to create a Skia render target from any of provided surfaces");
- }
-
///
public IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat format, AlphaFormat alphaFormat)
{
return new WriteableBitmapImpl(size, dpi, format, alphaFormat);
}
- public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi)
- {
- if (_skiaGpu is IOpenGlAwareSkiaGpu glAware)
- return glAware.CreateOpenGlBitmap(size, dpi);
- if (_skiaGpu == null)
- throw new PlatformNotSupportedException("GPU acceleration is not available");
- throw new PlatformNotSupportedException(
- "Current GPU acceleration backend does not support OpenGL integration");
- }
-
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices,
IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets)
{
diff --git a/src/Skia/Avalonia.Skia/SkiaBackendContext.cs b/src/Skia/Avalonia.Skia/SkiaBackendContext.cs
new file mode 100644
index 0000000000..4949f4a50d
--- /dev/null
+++ b/src/Skia/Avalonia.Skia/SkiaBackendContext.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Controls.Platform.Surfaces;
+using Avalonia.Platform;
+
+namespace Avalonia.Skia;
+
+internal class SkiaContext : IPlatformRenderInterfaceContext
+{
+ private ISkiaGpu _gpu;
+
+ public SkiaContext(ISkiaGpu gpu)
+ {
+ _gpu = gpu;
+ }
+
+ public void Dispose()
+ {
+ _gpu?.Dispose();
+ _gpu = null;
+ }
+
+ ///
+ public IRenderTarget CreateRenderTarget(IEnumerable surfaces)
+ {
+ if (!(surfaces is IList))
+ surfaces = surfaces.ToList();
+ var gpuRenderTarget = _gpu?.TryCreateRenderTarget(surfaces);
+ if (gpuRenderTarget != null)
+ {
+ return new SkiaGpuRenderTarget(_gpu, gpuRenderTarget);
+ }
+
+ foreach (var surface in surfaces)
+ {
+ if (surface is IFramebufferPlatformSurface framebufferSurface)
+ return new FramebufferRenderTarget(framebufferSurface);
+ }
+
+ throw new NotSupportedException(
+ "Don't know how to create a Skia render target from any of provided surfaces");
+ }
+
+ public object TryGetFeature(Type featureType) => _gpu?.TryGetFeature(featureType);
+}
\ No newline at end of file
diff --git a/src/Skia/Avalonia.Skia/SkiaOptions.cs b/src/Skia/Avalonia.Skia/SkiaOptions.cs
index 493263677d..b3c3056a58 100644
--- a/src/Skia/Avalonia.Skia/SkiaOptions.cs
+++ b/src/Skia/Avalonia.Skia/SkiaOptions.cs
@@ -8,11 +8,6 @@ namespace Avalonia
///
public class SkiaOptions
{
- ///
- /// Custom gpu factory to use. Can be used to customize behavior of Skia renderer.
- ///
- public Func CustomGpuFactory { get; set; }
-
///
/// The maximum number of bytes for video memory to store textures and resources.
///
diff --git a/src/Skia/Avalonia.Skia/SkiaPlatform.cs b/src/Skia/Avalonia.Skia/SkiaPlatform.cs
index 9a5725e06f..27f2631db8 100644
--- a/src/Skia/Avalonia.Skia/SkiaPlatform.cs
+++ b/src/Skia/Avalonia.Skia/SkiaPlatform.cs
@@ -17,8 +17,7 @@ namespace Avalonia.Skia
public static void Initialize(SkiaOptions options)
{
- var customGpu = options.CustomGpuFactory?.Invoke();
- var renderInterface = new PlatformRenderInterface(customGpu, options.MaxGpuResourceSizeBytes);
+ var renderInterface = new PlatformRenderInterface(options.MaxGpuResourceSizeBytes);
AvaloniaLocator.CurrentMutable
.Bind().ToConstant(renderInterface)
diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
index 74e3ebc51d..a7998353d9 100644
--- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
+++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
@@ -107,6 +107,8 @@ namespace Avalonia.Skia
return new DrawingContextImpl(createInfo, Disposable.Create(() => Version++));
}
+ public bool IsCorrupted => _gpu?.IsLost == true;
+
///
public Vector Dpi { get; }
diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs
index eaf588c27d..98eb35d5c5 100644
--- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs
+++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs
@@ -3,7 +3,6 @@ using System.Globalization;
using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform;
-using Avalonia.Utilities;
using HarfBuzzSharp;
using Buffer = HarfBuzzSharp.Buffer;
using GlyphInfo = HarfBuzzSharp.GlyphInfo;
@@ -12,8 +11,9 @@ namespace Avalonia.Skia
{
internal class TextShaperImpl : ITextShaperImpl
{
- public ShapedBuffer ShapeText(ReadOnlySlice text, TextShaperOptions options)
+ public ShapedBuffer ShapeText(CharacterBufferReference characterBufferReference, int length, TextShaperOptions options)
{
+ var text = new CharacterBufferRange(characterBufferReference, length);
var typeface = options.Typeface;
var fontRenderingEmSize = options.FontRenderingEmSize;
var bidiLevel = options.BidiLevel;
@@ -21,21 +21,21 @@ namespace Avalonia.Skia
using (var buffer = new Buffer())
{
- buffer.AddUtf16(text.Buffer.Span, text.BufferOffset, text.Length);
+ buffer.AddUtf16(characterBufferReference.CharacterBuffer.Span, characterBufferReference.OffsetToFirstChar, length);
MergeBreakPair(buffer);
-
+
buffer.GuessSegmentProperties();
buffer.Direction = (bidiLevel & 1) == 0 ? Direction.LeftToRight : Direction.RightToLeft;
- buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
+ buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
var font = ((GlyphTypefaceImpl)typeface).Font;
font.Shape(buffer);
- if(buffer.Direction == Direction.RightToLeft)
+ if (buffer.Direction == Direction.RightToLeft)
{
buffer.Reverse();
}
@@ -64,12 +64,12 @@ namespace Avalonia.Skia
var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale);
- if(text.Buffer.Span[glyphCluster] == '\t')
+ if (text[i] == '\t')
{
glyphIndex = typeface.GetGlyph(' ');
- glyphAdvance = options.IncrementalTabWidth > 0 ?
- options.IncrementalTabWidth :
+ glyphAdvance = options.IncrementalTabWidth > 0 ?
+ options.IncrementalTabWidth :
4 * typeface.GetGlyphAdvance(glyphIndex) * textScale;
}
@@ -87,7 +87,7 @@ namespace Avalonia.Skia
var length = buffer.Length;
var glyphInfos = buffer.GetGlyphInfoSpan();
-
+
var second = glyphInfos[length - 1];
if (!new Codepoint(second.Codepoint).IsBreakChar)
@@ -98,7 +98,7 @@ namespace Avalonia.Skia
if (length > 1 && glyphInfos[length - 2].Codepoint == '\r' && second.Codepoint == '\n')
{
var first = glyphInfos[length - 2];
-
+
first.Codepoint = '\u200C';
second.Codepoint = '\u200C';
second.Cluster = first.Cluster;
@@ -109,7 +109,7 @@ namespace Avalonia.Skia
{
*p = first;
}
-
+
fixed (GlyphInfo* p = &glyphInfos[length - 1])
{
*p = second;
diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
index d437f514bb..dcb267b2a3 100644
--- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
+++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
@@ -217,8 +217,7 @@ namespace Avalonia.Skia
public int RowBytes => _bitmap.RowBytes;
///
- public Vector Dpi { get; } = SkiaPlatform.DefaultDpi;
-
+ public Vector Dpi => _parent.Dpi;
///
public PixelFormat Format => _bitmap.ColorType.ToPixelFormat();
}
diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
index 4d307c9762..a5f77230b7 100644
--- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
+++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
@@ -113,7 +113,7 @@ namespace Avalonia.Direct2D1
SharpDX.Configuration.EnableReleaseOnFinalizer = true;
}
- public IRenderTarget CreateRenderTarget(IEnumerable surfaces)
+ private IRenderTarget CreateRenderTarget(IEnumerable surfaces)
{
foreach (var s in surfaces)
{
@@ -223,6 +223,26 @@ namespace Avalonia.Direct2D1
return new GlyphRunImpl(run);
}
+ class D2DApi : IPlatformRenderInterfaceContext
+ {
+ private readonly Direct2D1Platform _platform;
+
+ public D2DApi(Direct2D1Platform platform)
+ {
+ _platform = platform;
+ }
+ public object TryGetFeature(Type featureType) => null;
+
+ public void Dispose()
+ {
+ }
+
+ public IRenderTarget CreateRenderTarget(IEnumerable surfaces) => _platform.CreateRenderTarget(surfaces);
+ }
+
+ public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) =>
+ new D2DApi(this);
+
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{
if (glyphRun.GlyphTypeface is not GlyphTypefaceImpl glyphTypeface)
diff --git a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs
index 2c0adcac32..02932c52da 100644
--- a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs
+++ b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs
@@ -38,6 +38,8 @@ namespace Avalonia.Direct2D1
});
}
+ public bool IsCorrupted => false;
+
public IDrawingContextLayerImpl CreateLayer(Size size)
{
var renderTarget = _externalRenderTargetProvider.GetOrCreateRenderTarget();
diff --git a/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
index f7e7ed3dc2..984a24fb30 100644
--- a/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
+++ b/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
@@ -35,6 +35,8 @@ namespace Avalonia.Direct2D1
.CreateDrawingContext(visualBrushRenderer);
}
+ public bool IsCorrupted => false;
+
class FramebufferShim : WicRenderTargetBitmapImpl
{
private readonly ILockedFramebuffer _target;
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
index c5f8e837ce..84f11acdd7 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
@@ -37,6 +37,8 @@ namespace Avalonia.Direct2D1.Media.Imaging
return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget, null, () => Version++);
}
+ public bool IsCorrupted => false;
+
public void Blit(IDrawingContextImpl context) => throw new NotSupportedException();
public bool CanBlit => false;
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs
index 8c9d01f37d..9f0d48dbc7 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs
@@ -39,6 +39,8 @@ namespace Avalonia.Direct2D1.Media
public virtual IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
=> CreateDrawingContext(visualBrushRenderer, null);
+ public bool IsCorrupted => false;
+
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer, Action finishedCallback)
{
return new DrawingContextImpl(visualBrushRenderer, null, _renderTarget, finishedCallback: () =>
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs
index 885be132a4..2e40bdd9d1 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs
@@ -52,7 +52,7 @@ namespace Avalonia.Direct2D1.Media.Imaging
public IntPtr Address => _lock.Data.DataPointer;
public PixelSize Size => _lock.Size.ToAvalonia();
public int RowBytes => _lock.Stride;
- public Vector Dpi { get; } = new Vector(96, 96);
+ public Vector Dpi => _parent.Dpi;
public PixelFormat Format => _format;
}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
index 7f2cbc6182..6685dd00b9 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
@@ -3,7 +3,6 @@ using System.Globalization;
using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform;
-using Avalonia.Utilities;
using HarfBuzzSharp;
using Buffer = HarfBuzzSharp.Buffer;
using GlyphInfo = HarfBuzzSharp.GlyphInfo;
@@ -12,7 +11,7 @@ namespace Avalonia.Direct2D1.Media
{
internal class TextShaperImpl : ITextShaperImpl
{
- public ShapedBuffer ShapeText(ReadOnlySlice text, TextShaperOptions options)
+ public ShapedBuffer ShapeText(CharacterBufferReference characterBufferReference, int length, TextShaperOptions options)
{
var typeface = options.Typeface;
var fontRenderingEmSize = options.FontRenderingEmSize;
@@ -21,7 +20,7 @@ namespace Avalonia.Direct2D1.Media
using (var buffer = new Buffer())
{
- buffer.AddUtf16(text.Buffer.Span, text.BufferOffset, text.Length);
+ buffer.AddUtf16(characterBufferReference.CharacterBuffer.Span, characterBufferReference.OffsetToFirstChar, length);
MergeBreakPair(buffer);
@@ -46,7 +45,9 @@ namespace Avalonia.Direct2D1.Media
var bufferLength = buffer.Length;
- var shapedBuffer = new ShapedBuffer(text, bufferLength, typeface, fontRenderingEmSize, bidiLevel);
+ var characterBufferRange = new CharacterBufferRange(characterBufferReference, length);
+
+ var shapedBuffer = new ShapedBuffer(characterBufferRange, bufferLength, typeface, fontRenderingEmSize, bidiLevel);
var glyphInfos = buffer.GetGlyphInfoSpan();
@@ -64,7 +65,7 @@ namespace Avalonia.Direct2D1.Media
var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale);
- if (text.Buffer.Span[glyphCluster] == '\t')
+ if (characterBufferRange[i] == '\t')
{
glyphIndex = typeface.GetGlyph(' ');
diff --git a/src/Windows/Avalonia.Direct2D1/OptionalDispose.cs b/src/Windows/Avalonia.Direct2D1/OptionalDispose.cs
index e302e71102..1cdf7661df 100644
--- a/src/Windows/Avalonia.Direct2D1/OptionalDispose.cs
+++ b/src/Windows/Avalonia.Direct2D1/OptionalDispose.cs
@@ -2,7 +2,7 @@
namespace Avalonia.Direct2D1
{
- public readonly struct OptionalDispose : IDisposable where T : IDisposable
+ public readonly record struct OptionalDispose : IDisposable where T : IDisposable
{
private readonly bool _dispose;
diff --git a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs
index d04c616bd9..1a749c1a7f 100644
--- a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs
+++ b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs
@@ -30,6 +30,8 @@ namespace Avalonia.Direct2D1
return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget);
}
+ public bool IsCorrupted => false;
+
public IDrawingContextLayerImpl CreateLayer(Size size)
{
return D2DRenderTargetBitmapImpl.CreateCompatible(_renderTarget, size);
diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs
index f319cfae03..4935e3db48 100644
--- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs
+++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs
@@ -35,6 +35,8 @@ namespace Avalonia.Direct2D1
return new DrawingContextImpl(visualBrushRenderer, this, _deviceContext, _swapChain);
}
+ public bool IsCorrupted => false;
+
public IDrawingContextLayerImpl CreateLayer(Size size)
{
if (_deviceContext == null)
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
index 1dcf45aa4e..16a20c855f 100644
--- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
@@ -87,9 +87,11 @@ namespace Avalonia.Win32.Interop.Wpf
_ttl.ScalingChanged?.Invoke(_ttl.RenderScaling);
}
+
public IRenderer CreateRenderer(IRenderRoot root)
{
- return new ImmediateRenderer((Visual)root);
+ var mgr = new PlatformRenderInterfaceContextManager(null);
+ return new ImmediateRenderer((Visual)root, () => mgr.CreateRenderTarget(_surfaces), mgr);
}
public void Dispose()
diff --git a/src/Avalonia.OpenGL/AngleOptions.cs b/src/Windows/Avalonia.Win32/AngleOptions.cs
similarity index 90%
rename from src/Avalonia.OpenGL/AngleOptions.cs
rename to src/Windows/Avalonia.Win32/AngleOptions.cs
index 0807eb7ab4..076ddd2a5d 100644
--- a/src/Avalonia.OpenGL/AngleOptions.cs
+++ b/src/Windows/Avalonia.Win32/AngleOptions.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
+using Avalonia.OpenGL;
-namespace Avalonia.OpenGL
+namespace Avalonia.Win32
{
public class AngleOptions
{
diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
index e69e0ee219..eb51b7fd07 100644
--- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
+++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
@@ -16,11 +16,13 @@
+
+
+
+
-
-
$(NoWarn);CA1416
diff --git a/src/Windows/Avalonia.Win32/DirectX/DirectXEnums.cs b/src/Windows/Avalonia.Win32/DirectX/DirectXEnums.cs
index 3a67da530f..a749ed1b45 100644
--- a/src/Windows/Avalonia.Win32/DirectX/DirectXEnums.cs
+++ b/src/Windows/Avalonia.Win32/DirectX/DirectXEnums.cs
@@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace Avalonia.Win32.DxgiSwapchain
+namespace Avalonia.Win32.DirectX
{
internal enum D3D_FEATURE_LEVEL
{
@@ -136,4 +136,53 @@ namespace Avalonia.Win32.DxgiSwapchain
DXGI_ALPHA_MODE_FORCE_DWORD = (unchecked((int)0xffffffff)),
}
+
+ internal enum D3D_DRIVER_TYPE
+ {
+ D3D_DRIVER_TYPE_UNKNOWN = 0,
+ D3D_DRIVER_TYPE_HARDWARE,
+ D3D_DRIVER_TYPE_REFERENCE,
+ D3D_DRIVER_TYPE_NULL,
+ D3D_DRIVER_TYPE_SOFTWARE,
+ D3D_DRIVER_TYPE_WARP
+ }
+
+ internal enum DXGI_ERROR : uint
+ {
+ DXGI_ERROR_ACCESS_DENIED = 0x887A002B,
+ DXGI_ERROR_ACCESS_LOST = 0x887A0026,
+ DXGI_ERROR_ALREADY_EXISTS = 0x887A0036,
+ DXGI_ERROR_CANNOT_PROTECT_CONTENT = 0x887A002A,
+ DXGI_ERROR_DEVICE_HUNG = 0x887A0006,
+ DXGI_ERROR_DEVICE_REMOVED = 0x887A0005,
+ DXGI_ERROR_DEVICE_RESET = 0x887A0007,
+ DXGI_ERROR_DRIVER_INTERNAL_ERROR = 0x887A0020,
+ DXGI_ERROR_FRAME_STATISTICS_DISJOINT = 0x887A000B,
+ DXGI_ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE = 0x887A000C,
+ DXGI_ERROR_INVALID_CALL = 0x887A0001,
+ DXGI_ERROR_MORE_DATA = 0x887A0003,
+ DXGI_ERROR_NAME_ALREADY_EXISTS = 0x887A002C,
+ DXGI_ERROR_NONEXCLUSIVE = 0x887A0021,
+ DXGI_ERROR_NOT_CURRENTLY_AVAILABLE = 0x887A0022,
+ DXGI_ERROR_NOT_FOUND = 0x887A0002,
+ DXGI_ERROR_REMOTE_CLIENT_DISCONNECTED = 0x887A0023,
+ DXGI_ERROR_REMOTE_OUTOFMEMORY = 0x887A0024,
+ DXGI_ERROR_RESTRICT_TO_OUTPUT_STALE = 0x887A0029,
+ DXGI_ERROR_SDK_COMPONENT_MISSING = 0x887A002D,
+ DXGI_ERROR_SESSION_DISCONNECTED = 0x887A0028,
+ DXGI_ERROR_UNSUPPORTED = 0x887A0004,
+ DXGI_ERROR_WAIT_TIMEOUT = 0x887A0027,
+ DXGI_ERROR_WAS_STILL_DRAWING = 0x887A000A
+ }
+
+ internal static class DxgiErrorExtensions
+ {
+ public static bool IsDeviceLostError(this DXGI_ERROR error)
+ {
+ return error is DXGI_ERROR.DXGI_ERROR_DEVICE_REMOVED
+ or DXGI_ERROR.DXGI_ERROR_DEVICE_HUNG
+ or DXGI_ERROR.DXGI_ERROR_DEVICE_RESET
+ or DXGI_ERROR.DXGI_ERROR_NOT_CURRENTLY_AVAILABLE;
+ }
+ }
}
diff --git a/src/Windows/Avalonia.Win32/DirectX/DirectXStructs.cs b/src/Windows/Avalonia.Win32/DirectX/DirectXStructs.cs
index d606b00109..47451831a6 100644
--- a/src/Windows/Avalonia.Win32/DirectX/DirectXStructs.cs
+++ b/src/Windows/Avalonia.Win32/DirectX/DirectXStructs.cs
@@ -6,8 +6,10 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using static Avalonia.Win32.Interop.UnmanagedMethods;
+// ReSharper disable InconsistentNaming
+#pragma warning disable CS0649
-namespace Avalonia.Win32.DxgiSwapchain
+namespace Avalonia.Win32.DirectX
{
#nullable enable
public unsafe struct HANDLE
@@ -36,1092 +38,13 @@ namespace Avalonia.Win32.DxgiSwapchain
public override string ToString() => ((IntPtr)Value).ToString();
}
- internal unsafe partial struct MONITORINFOEXW
+ internal unsafe struct MONITORINFOEXW
{
internal MONITORINFO Base;
internal fixed ushort szDevice[32];
}
-
- internal unsafe struct DXGI_GAMMA_CONTROL
- {
- public DXGI_RGB Scale;
-
- public DXGI_RGB Offset;
-
- public _GammaCurve_e__FixedBuffer GammaCurve;
-
- public partial struct _GammaCurve_e__FixedBuffer
- {
- public DXGI_RGB e0;
- public DXGI_RGB e1;
- public DXGI_RGB e2;
- public DXGI_RGB e3;
- public DXGI_RGB e4;
- public DXGI_RGB e5;
- public DXGI_RGB e6;
- public DXGI_RGB e7;
- public DXGI_RGB e8;
- public DXGI_RGB e9;
- public DXGI_RGB e10;
- public DXGI_RGB e11;
- public DXGI_RGB e12;
- public DXGI_RGB e13;
- public DXGI_RGB e14;
- public DXGI_RGB e15;
- public DXGI_RGB e16;
- public DXGI_RGB e17;
- public DXGI_RGB e18;
- public DXGI_RGB e19;
- public DXGI_RGB e20;
- public DXGI_RGB e21;
- public DXGI_RGB e22;
- public DXGI_RGB e23;
- public DXGI_RGB e24;
- public DXGI_RGB e25;
- public DXGI_RGB e26;
- public DXGI_RGB e27;
- public DXGI_RGB e28;
- public DXGI_RGB e29;
- public DXGI_RGB e30;
- public DXGI_RGB e31;
- public DXGI_RGB e32;
- public DXGI_RGB e33;
- public DXGI_RGB e34;
- public DXGI_RGB e35;
- public DXGI_RGB e36;
- public DXGI_RGB e37;
- public DXGI_RGB e38;
- public DXGI_RGB e39;
- public DXGI_RGB e40;
- public DXGI_RGB e41;
- public DXGI_RGB e42;
- public DXGI_RGB e43;
- public DXGI_RGB e44;
- public DXGI_RGB e45;
- public DXGI_RGB e46;
- public DXGI_RGB e47;
- public DXGI_RGB e48;
- public DXGI_RGB e49;
- public DXGI_RGB e50;
- public DXGI_RGB e51;
- public DXGI_RGB e52;
- public DXGI_RGB e53;
- public DXGI_RGB e54;
- public DXGI_RGB e55;
- public DXGI_RGB e56;
- public DXGI_RGB e57;
- public DXGI_RGB e58;
- public DXGI_RGB e59;
- public DXGI_RGB e60;
- public DXGI_RGB e61;
- public DXGI_RGB e62;
- public DXGI_RGB e63;
- public DXGI_RGB e64;
- public DXGI_RGB e65;
- public DXGI_RGB e66;
- public DXGI_RGB e67;
- public DXGI_RGB e68;
- public DXGI_RGB e69;
- public DXGI_RGB e70;
- public DXGI_RGB e71;
- public DXGI_RGB e72;
- public DXGI_RGB e73;
- public DXGI_RGB e74;
- public DXGI_RGB e75;
- public DXGI_RGB e76;
- public DXGI_RGB e77;
- public DXGI_RGB e78;
- public DXGI_RGB e79;
- public DXGI_RGB e80;
- public DXGI_RGB e81;
- public DXGI_RGB e82;
- public DXGI_RGB e83;
- public DXGI_RGB e84;
- public DXGI_RGB e85;
- public DXGI_RGB e86;
- public DXGI_RGB e87;
- public DXGI_RGB e88;
- public DXGI_RGB e89;
- public DXGI_RGB e90;
- public DXGI_RGB e91;
- public DXGI_RGB e92;
- public DXGI_RGB e93;
- public DXGI_RGB e94;
- public DXGI_RGB e95;
- public DXGI_RGB e96;
- public DXGI_RGB e97;
- public DXGI_RGB e98;
- public DXGI_RGB e99;
- public DXGI_RGB e100;
- public DXGI_RGB e101;
- public DXGI_RGB e102;
- public DXGI_RGB e103;
- public DXGI_RGB e104;
- public DXGI_RGB e105;
- public DXGI_RGB e106;
- public DXGI_RGB e107;
- public DXGI_RGB e108;
- public DXGI_RGB e109;
- public DXGI_RGB e110;
- public DXGI_RGB e111;
- public DXGI_RGB e112;
- public DXGI_RGB e113;
- public DXGI_RGB e114;
- public DXGI_RGB e115;
- public DXGI_RGB e116;
- public DXGI_RGB e117;
- public DXGI_RGB e118;
- public DXGI_RGB e119;
- public DXGI_RGB e120;
- public DXGI_RGB e121;
- public DXGI_RGB e122;
- public DXGI_RGB e123;
- public DXGI_RGB e124;
- public DXGI_RGB e125;
- public DXGI_RGB e126;
- public DXGI_RGB e127;
- public DXGI_RGB e128;
- public DXGI_RGB e129;
- public DXGI_RGB e130;
- public DXGI_RGB e131;
- public DXGI_RGB e132;
- public DXGI_RGB e133;
- public DXGI_RGB e134;
- public DXGI_RGB e135;
- public DXGI_RGB e136;
- public DXGI_RGB e137;
- public DXGI_RGB e138;
- public DXGI_RGB e139;
- public DXGI_RGB e140;
- public DXGI_RGB e141;
- public DXGI_RGB e142;
- public DXGI_RGB e143;
- public DXGI_RGB e144;
- public DXGI_RGB e145;
- public DXGI_RGB e146;
- public DXGI_RGB e147;
- public DXGI_RGB e148;
- public DXGI_RGB e149;
- public DXGI_RGB e150;
- public DXGI_RGB e151;
- public DXGI_RGB e152;
- public DXGI_RGB e153;
- public DXGI_RGB e154;
- public DXGI_RGB e155;
- public DXGI_RGB e156;
- public DXGI_RGB e157;
- public DXGI_RGB e158;
- public DXGI_RGB e159;
- public DXGI_RGB e160;
- public DXGI_RGB e161;
- public DXGI_RGB e162;
- public DXGI_RGB e163;
- public DXGI_RGB e164;
- public DXGI_RGB e165;
- public DXGI_RGB e166;
- public DXGI_RGB e167;
- public DXGI_RGB e168;
- public DXGI_RGB e169;
- public DXGI_RGB e170;
- public DXGI_RGB e171;
- public DXGI_RGB e172;
- public DXGI_RGB e173;
- public DXGI_RGB e174;
- public DXGI_RGB e175;
- public DXGI_RGB e176;
- public DXGI_RGB e177;
- public DXGI_RGB e178;
- public DXGI_RGB e179;
- public DXGI_RGB e180;
- public DXGI_RGB e181;
- public DXGI_RGB e182;
- public DXGI_RGB e183;
- public DXGI_RGB e184;
- public DXGI_RGB e185;
- public DXGI_RGB e186;
- public DXGI_RGB e187;
- public DXGI_RGB e188;
- public DXGI_RGB e189;
- public DXGI_RGB e190;
- public DXGI_RGB e191;
- public DXGI_RGB e192;
- public DXGI_RGB e193;
- public DXGI_RGB e194;
- public DXGI_RGB e195;
- public DXGI_RGB e196;
- public DXGI_RGB e197;
- public DXGI_RGB e198;
- public DXGI_RGB e199;
- public DXGI_RGB e200;
- public DXGI_RGB e201;
- public DXGI_RGB e202;
- public DXGI_RGB e203;
- public DXGI_RGB e204;
- public DXGI_RGB e205;
- public DXGI_RGB e206;
- public DXGI_RGB e207;
- public DXGI_RGB e208;
- public DXGI_RGB e209;
- public DXGI_RGB e210;
- public DXGI_RGB e211;
- public DXGI_RGB e212;
- public DXGI_RGB e213;
- public DXGI_RGB e214;
- public DXGI_RGB e215;
- public DXGI_RGB e216;
- public DXGI_RGB e217;
- public DXGI_RGB e218;
- public DXGI_RGB e219;
- public DXGI_RGB e220;
- public DXGI_RGB e221;
- public DXGI_RGB e222;
- public DXGI_RGB e223;
- public DXGI_RGB e224;
- public DXGI_RGB e225;
- public DXGI_RGB e226;
- public DXGI_RGB e227;
- public DXGI_RGB e228;
- public DXGI_RGB e229;
- public DXGI_RGB e230;
- public DXGI_RGB e231;
- public DXGI_RGB e232;
- public DXGI_RGB e233;
- public DXGI_RGB e234;
- public DXGI_RGB e235;
- public DXGI_RGB e236;
- public DXGI_RGB e237;
- public DXGI_RGB e238;
- public DXGI_RGB e239;
- public DXGI_RGB e240;
- public DXGI_RGB e241;
- public DXGI_RGB e242;
- public DXGI_RGB e243;
- public DXGI_RGB e244;
- public DXGI_RGB e245;
- public DXGI_RGB e246;
- public DXGI_RGB e247;
- public DXGI_RGB e248;
- public DXGI_RGB e249;
- public DXGI_RGB e250;
- public DXGI_RGB e251;
- public DXGI_RGB e252;
- public DXGI_RGB e253;
- public DXGI_RGB e254;
- public DXGI_RGB e255;
- public DXGI_RGB e256;
- public DXGI_RGB e257;
- public DXGI_RGB e258;
- public DXGI_RGB e259;
- public DXGI_RGB e260;
- public DXGI_RGB e261;
- public DXGI_RGB e262;
- public DXGI_RGB e263;
- public DXGI_RGB e264;
- public DXGI_RGB e265;
- public DXGI_RGB e266;
- public DXGI_RGB e267;
- public DXGI_RGB e268;
- public DXGI_RGB e269;
- public DXGI_RGB e270;
- public DXGI_RGB e271;
- public DXGI_RGB e272;
- public DXGI_RGB e273;
- public DXGI_RGB e274;
- public DXGI_RGB e275;
- public DXGI_RGB e276;
- public DXGI_RGB e277;
- public DXGI_RGB e278;
- public DXGI_RGB e279;
- public DXGI_RGB e280;
- public DXGI_RGB e281;
- public DXGI_RGB e282;
- public DXGI_RGB e283;
- public DXGI_RGB e284;
- public DXGI_RGB e285;
- public DXGI_RGB e286;
- public DXGI_RGB e287;
- public DXGI_RGB e288;
- public DXGI_RGB e289;
- public DXGI_RGB e290;
- public DXGI_RGB e291;
- public DXGI_RGB e292;
- public DXGI_RGB e293;
- public DXGI_RGB e294;
- public DXGI_RGB e295;
- public DXGI_RGB e296;
- public DXGI_RGB e297;
- public DXGI_RGB e298;
- public DXGI_RGB e299;
- public DXGI_RGB e300;
- public DXGI_RGB e301;
- public DXGI_RGB e302;
- public DXGI_RGB e303;
- public DXGI_RGB e304;
- public DXGI_RGB e305;
- public DXGI_RGB e306;
- public DXGI_RGB e307;
- public DXGI_RGB e308;
- public DXGI_RGB e309;
- public DXGI_RGB e310;
- public DXGI_RGB e311;
- public DXGI_RGB e312;
- public DXGI_RGB e313;
- public DXGI_RGB e314;
- public DXGI_RGB e315;
- public DXGI_RGB e316;
- public DXGI_RGB e317;
- public DXGI_RGB e318;
- public DXGI_RGB e319;
- public DXGI_RGB e320;
- public DXGI_RGB e321;
- public DXGI_RGB e322;
- public DXGI_RGB e323;
- public DXGI_RGB e324;
- public DXGI_RGB e325;
- public DXGI_RGB e326;
- public DXGI_RGB e327;
- public DXGI_RGB e328;
- public DXGI_RGB e329;
- public DXGI_RGB e330;
- public DXGI_RGB e331;
- public DXGI_RGB e332;
- public DXGI_RGB e333;
- public DXGI_RGB e334;
- public DXGI_RGB e335;
- public DXGI_RGB e336;
- public DXGI_RGB e337;
- public DXGI_RGB e338;
- public DXGI_RGB e339;
- public DXGI_RGB e340;
- public DXGI_RGB e341;
- public DXGI_RGB e342;
- public DXGI_RGB e343;
- public DXGI_RGB e344;
- public DXGI_RGB e345;
- public DXGI_RGB e346;
- public DXGI_RGB e347;
- public DXGI_RGB e348;
- public DXGI_RGB e349;
- public DXGI_RGB e350;
- public DXGI_RGB e351;
- public DXGI_RGB e352;
- public DXGI_RGB e353;
- public DXGI_RGB e354;
- public DXGI_RGB e355;
- public DXGI_RGB e356;
- public DXGI_RGB e357;
- public DXGI_RGB e358;
- public DXGI_RGB e359;
- public DXGI_RGB e360;
- public DXGI_RGB e361;
- public DXGI_RGB e362;
- public DXGI_RGB e363;
- public DXGI_RGB e364;
- public DXGI_RGB e365;
- public DXGI_RGB e366;
- public DXGI_RGB e367;
- public DXGI_RGB e368;
- public DXGI_RGB e369;
- public DXGI_RGB e370;
- public DXGI_RGB e371;
- public DXGI_RGB e372;
- public DXGI_RGB e373;
- public DXGI_RGB e374;
- public DXGI_RGB e375;
- public DXGI_RGB e376;
- public DXGI_RGB e377;
- public DXGI_RGB e378;
- public DXGI_RGB e379;
- public DXGI_RGB e380;
- public DXGI_RGB e381;
- public DXGI_RGB e382;
- public DXGI_RGB e383;
- public DXGI_RGB e384;
- public DXGI_RGB e385;
- public DXGI_RGB e386;
- public DXGI_RGB e387;
- public DXGI_RGB e388;
- public DXGI_RGB e389;
- public DXGI_RGB e390;
- public DXGI_RGB e391;
- public DXGI_RGB e392;
- public DXGI_RGB e393;
- public DXGI_RGB e394;
- public DXGI_RGB e395;
- public DXGI_RGB e396;
- public DXGI_RGB e397;
- public DXGI_RGB e398;
- public DXGI_RGB e399;
- public DXGI_RGB e400;
- public DXGI_RGB e401;
- public DXGI_RGB e402;
- public DXGI_RGB e403;
- public DXGI_RGB e404;
- public DXGI_RGB e405;
- public DXGI_RGB e406;
- public DXGI_RGB e407;
- public DXGI_RGB e408;
- public DXGI_RGB e409;
- public DXGI_RGB e410;
- public DXGI_RGB e411;
- public DXGI_RGB e412;
- public DXGI_RGB e413;
- public DXGI_RGB e414;
- public DXGI_RGB e415;
- public DXGI_RGB e416;
- public DXGI_RGB e417;
- public DXGI_RGB e418;
- public DXGI_RGB e419;
- public DXGI_RGB e420;
- public DXGI_RGB e421;
- public DXGI_RGB e422;
- public DXGI_RGB e423;
- public DXGI_RGB e424;
- public DXGI_RGB e425;
- public DXGI_RGB e426;
- public DXGI_RGB e427;
- public DXGI_RGB e428;
- public DXGI_RGB e429;
- public DXGI_RGB e430;
- public DXGI_RGB e431;
- public DXGI_RGB e432;
- public DXGI_RGB e433;
- public DXGI_RGB e434;
- public DXGI_RGB e435;
- public DXGI_RGB e436;
- public DXGI_RGB e437;
- public DXGI_RGB e438;
- public DXGI_RGB e439;
- public DXGI_RGB e440;
- public DXGI_RGB e441;
- public DXGI_RGB e442;
- public DXGI_RGB e443;
- public DXGI_RGB e444;
- public DXGI_RGB e445;
- public DXGI_RGB e446;
- public DXGI_RGB e447;
- public DXGI_RGB e448;
- public DXGI_RGB e449;
- public DXGI_RGB e450;
- public DXGI_RGB e451;
- public DXGI_RGB e452;
- public DXGI_RGB e453;
- public DXGI_RGB e454;
- public DXGI_RGB e455;
- public DXGI_RGB e456;
- public DXGI_RGB e457;
- public DXGI_RGB e458;
- public DXGI_RGB e459;
- public DXGI_RGB e460;
- public DXGI_RGB e461;
- public DXGI_RGB e462;
- public DXGI_RGB e463;
- public DXGI_RGB e464;
- public DXGI_RGB e465;
- public DXGI_RGB e466;
- public DXGI_RGB e467;
- public DXGI_RGB e468;
- public DXGI_RGB e469;
- public DXGI_RGB e470;
- public DXGI_RGB e471;
- public DXGI_RGB e472;
- public DXGI_RGB e473;
- public DXGI_RGB e474;
- public DXGI_RGB e475;
- public DXGI_RGB e476;
- public DXGI_RGB e477;
- public DXGI_RGB e478;
- public DXGI_RGB e479;
- public DXGI_RGB e480;
- public DXGI_RGB e481;
- public DXGI_RGB e482;
- public DXGI_RGB e483;
- public DXGI_RGB e484;
- public DXGI_RGB e485;
- public DXGI_RGB e486;
- public DXGI_RGB e487;
- public DXGI_RGB e488;
- public DXGI_RGB e489;
- public DXGI_RGB e490;
- public DXGI_RGB e491;
- public DXGI_RGB e492;
- public DXGI_RGB e493;
- public DXGI_RGB e494;
- public DXGI_RGB e495;
- public DXGI_RGB e496;
- public DXGI_RGB e497;
- public DXGI_RGB e498;
- public DXGI_RGB e499;
- public DXGI_RGB e500;
- public DXGI_RGB e501;
- public DXGI_RGB e502;
- public DXGI_RGB e503;
- public DXGI_RGB e504;
- public DXGI_RGB e505;
- public DXGI_RGB e506;
- public DXGI_RGB e507;
- public DXGI_RGB e508;
- public DXGI_RGB e509;
- public DXGI_RGB e510;
- public DXGI_RGB e511;
- public DXGI_RGB e512;
- public DXGI_RGB e513;
- public DXGI_RGB e514;
- public DXGI_RGB e515;
- public DXGI_RGB e516;
- public DXGI_RGB e517;
- public DXGI_RGB e518;
- public DXGI_RGB e519;
- public DXGI_RGB e520;
- public DXGI_RGB e521;
- public DXGI_RGB e522;
- public DXGI_RGB e523;
- public DXGI_RGB e524;
- public DXGI_RGB e525;
- public DXGI_RGB e526;
- public DXGI_RGB e527;
- public DXGI_RGB e528;
- public DXGI_RGB e529;
- public DXGI_RGB e530;
- public DXGI_RGB e531;
- public DXGI_RGB e532;
- public DXGI_RGB e533;
- public DXGI_RGB e534;
- public DXGI_RGB e535;
- public DXGI_RGB e536;
- public DXGI_RGB e537;
- public DXGI_RGB e538;
- public DXGI_RGB e539;
- public DXGI_RGB e540;
- public DXGI_RGB e541;
- public DXGI_RGB e542;
- public DXGI_RGB e543;
- public DXGI_RGB e544;
- public DXGI_RGB e545;
- public DXGI_RGB e546;
- public DXGI_RGB e547;
- public DXGI_RGB e548;
- public DXGI_RGB e549;
- public DXGI_RGB e550;
- public DXGI_RGB e551;
- public DXGI_RGB e552;
- public DXGI_RGB e553;
- public DXGI_RGB e554;
- public DXGI_RGB e555;
- public DXGI_RGB e556;
- public DXGI_RGB e557;
- public DXGI_RGB e558;
- public DXGI_RGB e559;
- public DXGI_RGB e560;
- public DXGI_RGB e561;
- public DXGI_RGB e562;
- public DXGI_RGB e563;
- public DXGI_RGB e564;
- public DXGI_RGB e565;
- public DXGI_RGB e566;
- public DXGI_RGB e567;
- public DXGI_RGB e568;
- public DXGI_RGB e569;
- public DXGI_RGB e570;
- public DXGI_RGB e571;
- public DXGI_RGB e572;
- public DXGI_RGB e573;
- public DXGI_RGB e574;
- public DXGI_RGB e575;
- public DXGI_RGB e576;
- public DXGI_RGB e577;
- public DXGI_RGB e578;
- public DXGI_RGB e579;
- public DXGI_RGB e580;
- public DXGI_RGB e581;
- public DXGI_RGB e582;
- public DXGI_RGB e583;
- public DXGI_RGB e584;
- public DXGI_RGB e585;
- public DXGI_RGB e586;
- public DXGI_RGB e587;
- public DXGI_RGB e588;
- public DXGI_RGB e589;
- public DXGI_RGB e590;
- public DXGI_RGB e591;
- public DXGI_RGB e592;
- public DXGI_RGB e593;
- public DXGI_RGB e594;
- public DXGI_RGB e595;
- public DXGI_RGB e596;
- public DXGI_RGB e597;
- public DXGI_RGB e598;
- public DXGI_RGB e599;
- public DXGI_RGB e600;
- public DXGI_RGB e601;
- public DXGI_RGB e602;
- public DXGI_RGB e603;
- public DXGI_RGB e604;
- public DXGI_RGB e605;
- public DXGI_RGB e606;
- public DXGI_RGB e607;
- public DXGI_RGB e608;
- public DXGI_RGB e609;
- public DXGI_RGB e610;
- public DXGI_RGB e611;
- public DXGI_RGB e612;
- public DXGI_RGB e613;
- public DXGI_RGB e614;
- public DXGI_RGB e615;
- public DXGI_RGB e616;
- public DXGI_RGB e617;
- public DXGI_RGB e618;
- public DXGI_RGB e619;
- public DXGI_RGB e620;
- public DXGI_RGB e621;
- public DXGI_RGB e622;
- public DXGI_RGB e623;
- public DXGI_RGB e624;
- public DXGI_RGB e625;
- public DXGI_RGB e626;
- public DXGI_RGB e627;
- public DXGI_RGB e628;
- public DXGI_RGB e629;
- public DXGI_RGB e630;
- public DXGI_RGB e631;
- public DXGI_RGB e632;
- public DXGI_RGB e633;
- public DXGI_RGB e634;
- public DXGI_RGB e635;
- public DXGI_RGB e636;
- public DXGI_RGB e637;
- public DXGI_RGB e638;
- public DXGI_RGB e639;
- public DXGI_RGB e640;
- public DXGI_RGB e641;
- public DXGI_RGB e642;
- public DXGI_RGB e643;
- public DXGI_RGB e644;
- public DXGI_RGB e645;
- public DXGI_RGB e646;
- public DXGI_RGB e647;
- public DXGI_RGB e648;
- public DXGI_RGB e649;
- public DXGI_RGB e650;
- public DXGI_RGB e651;
- public DXGI_RGB e652;
- public DXGI_RGB e653;
- public DXGI_RGB e654;
- public DXGI_RGB e655;
- public DXGI_RGB e656;
- public DXGI_RGB e657;
- public DXGI_RGB e658;
- public DXGI_RGB e659;
- public DXGI_RGB e660;
- public DXGI_RGB e661;
- public DXGI_RGB e662;
- public DXGI_RGB e663;
- public DXGI_RGB e664;
- public DXGI_RGB e665;
- public DXGI_RGB e666;
- public DXGI_RGB e667;
- public DXGI_RGB e668;
- public DXGI_RGB e669;
- public DXGI_RGB e670;
- public DXGI_RGB e671;
- public DXGI_RGB e672;
- public DXGI_RGB e673;
- public DXGI_RGB e674;
- public DXGI_RGB e675;
- public DXGI_RGB e676;
- public DXGI_RGB e677;
- public DXGI_RGB e678;
- public DXGI_RGB e679;
- public DXGI_RGB e680;
- public DXGI_RGB e681;
- public DXGI_RGB e682;
- public DXGI_RGB e683;
- public DXGI_RGB e684;
- public DXGI_RGB e685;
- public DXGI_RGB e686;
- public DXGI_RGB e687;
- public DXGI_RGB e688;
- public DXGI_RGB e689;
- public DXGI_RGB e690;
- public DXGI_RGB e691;
- public DXGI_RGB e692;
- public DXGI_RGB e693;
- public DXGI_RGB e694;
- public DXGI_RGB e695;
- public DXGI_RGB e696;
- public DXGI_RGB e697;
- public DXGI_RGB e698;
- public DXGI_RGB e699;
- public DXGI_RGB e700;
- public DXGI_RGB e701;
- public DXGI_RGB e702;
- public DXGI_RGB e703;
- public DXGI_RGB e704;
- public DXGI_RGB e705;
- public DXGI_RGB e706;
- public DXGI_RGB e707;
- public DXGI_RGB e708;
- public DXGI_RGB e709;
- public DXGI_RGB e710;
- public DXGI_RGB e711;
- public DXGI_RGB e712;
- public DXGI_RGB e713;
- public DXGI_RGB e714;
- public DXGI_RGB e715;
- public DXGI_RGB e716;
- public DXGI_RGB e717;
- public DXGI_RGB e718;
- public DXGI_RGB e719;
- public DXGI_RGB e720;
- public DXGI_RGB e721;
- public DXGI_RGB e722;
- public DXGI_RGB e723;
- public DXGI_RGB e724;
- public DXGI_RGB e725;
- public DXGI_RGB e726;
- public DXGI_RGB e727;
- public DXGI_RGB e728;
- public DXGI_RGB e729;
- public DXGI_RGB e730;
- public DXGI_RGB e731;
- public DXGI_RGB e732;
- public DXGI_RGB e733;
- public DXGI_RGB e734;
- public DXGI_RGB e735;
- public DXGI_RGB e736;
- public DXGI_RGB e737;
- public DXGI_RGB e738;
- public DXGI_RGB e739;
- public DXGI_RGB e740;
- public DXGI_RGB e741;
- public DXGI_RGB e742;
- public DXGI_RGB e743;
- public DXGI_RGB e744;
- public DXGI_RGB e745;
- public DXGI_RGB e746;
- public DXGI_RGB e747;
- public DXGI_RGB e748;
- public DXGI_RGB e749;
- public DXGI_RGB e750;
- public DXGI_RGB e751;
- public DXGI_RGB e752;
- public DXGI_RGB e753;
- public DXGI_RGB e754;
- public DXGI_RGB e755;
- public DXGI_RGB e756;
- public DXGI_RGB e757;
- public DXGI_RGB e758;
- public DXGI_RGB e759;
- public DXGI_RGB e760;
- public DXGI_RGB e761;
- public DXGI_RGB e762;
- public DXGI_RGB e763;
- public DXGI_RGB e764;
- public DXGI_RGB e765;
- public DXGI_RGB e766;
- public DXGI_RGB e767;
- public DXGI_RGB e768;
- public DXGI_RGB e769;
- public DXGI_RGB e770;
- public DXGI_RGB e771;
- public DXGI_RGB e772;
- public DXGI_RGB e773;
- public DXGI_RGB e774;
- public DXGI_RGB e775;
- public DXGI_RGB e776;
- public DXGI_RGB e777;
- public DXGI_RGB e778;
- public DXGI_RGB e779;
- public DXGI_RGB e780;
- public DXGI_RGB e781;
- public DXGI_RGB e782;
- public DXGI_RGB e783;
- public DXGI_RGB e784;
- public DXGI_RGB e785;
- public DXGI_RGB e786;
- public DXGI_RGB e787;
- public DXGI_RGB e788;
- public DXGI_RGB e789;
- public DXGI_RGB e790;
- public DXGI_RGB e791;
- public DXGI_RGB e792;
- public DXGI_RGB e793;
- public DXGI_RGB e794;
- public DXGI_RGB e795;
- public DXGI_RGB e796;
- public DXGI_RGB e797;
- public DXGI_RGB e798;
- public DXGI_RGB e799;
- public DXGI_RGB e800;
- public DXGI_RGB e801;
- public DXGI_RGB e802;
- public DXGI_RGB e803;
- public DXGI_RGB e804;
- public DXGI_RGB e805;
- public DXGI_RGB e806;
- public DXGI_RGB e807;
- public DXGI_RGB e808;
- public DXGI_RGB e809;
- public DXGI_RGB e810;
- public DXGI_RGB e811;
- public DXGI_RGB e812;
- public DXGI_RGB e813;
- public DXGI_RGB e814;
- public DXGI_RGB e815;
- public DXGI_RGB e816;
- public DXGI_RGB e817;
- public DXGI_RGB e818;
- public DXGI_RGB e819;
- public DXGI_RGB e820;
- public DXGI_RGB e821;
- public DXGI_RGB e822;
- public DXGI_RGB e823;
- public DXGI_RGB e824;
- public DXGI_RGB e825;
- public DXGI_RGB e826;
- public DXGI_RGB e827;
- public DXGI_RGB e828;
- public DXGI_RGB e829;
- public DXGI_RGB e830;
- public DXGI_RGB e831;
- public DXGI_RGB e832;
- public DXGI_RGB e833;
- public DXGI_RGB e834;
- public DXGI_RGB e835;
- public DXGI_RGB e836;
- public DXGI_RGB e837;
- public DXGI_RGB e838;
- public DXGI_RGB e839;
- public DXGI_RGB e840;
- public DXGI_RGB e841;
- public DXGI_RGB e842;
- public DXGI_RGB e843;
- public DXGI_RGB e844;
- public DXGI_RGB e845;
- public DXGI_RGB e846;
- public DXGI_RGB e847;
- public DXGI_RGB e848;
- public DXGI_RGB e849;
- public DXGI_RGB e850;
- public DXGI_RGB e851;
- public DXGI_RGB e852;
- public DXGI_RGB e853;
- public DXGI_RGB e854;
- public DXGI_RGB e855;
- public DXGI_RGB e856;
- public DXGI_RGB e857;
- public DXGI_RGB e858;
- public DXGI_RGB e859;
- public DXGI_RGB e860;
- public DXGI_RGB e861;
- public DXGI_RGB e862;
- public DXGI_RGB e863;
- public DXGI_RGB e864;
- public DXGI_RGB e865;
- public DXGI_RGB e866;
- public DXGI_RGB e867;
- public DXGI_RGB e868;
- public DXGI_RGB e869;
- public DXGI_RGB e870;
- public DXGI_RGB e871;
- public DXGI_RGB e872;
- public DXGI_RGB e873;
- public DXGI_RGB e874;
- public DXGI_RGB e875;
- public DXGI_RGB e876;
- public DXGI_RGB e877;
- public DXGI_RGB e878;
- public DXGI_RGB e879;
- public DXGI_RGB e880;
- public DXGI_RGB e881;
- public DXGI_RGB e882;
- public DXGI_RGB e883;
- public DXGI_RGB e884;
- public DXGI_RGB e885;
- public DXGI_RGB e886;
- public DXGI_RGB e887;
- public DXGI_RGB e888;
- public DXGI_RGB e889;
- public DXGI_RGB e890;
- public DXGI_RGB e891;
- public DXGI_RGB e892;
- public DXGI_RGB e893;
- public DXGI_RGB e894;
- public DXGI_RGB e895;
- public DXGI_RGB e896;
- public DXGI_RGB e897;
- public DXGI_RGB e898;
- public DXGI_RGB e899;
- public DXGI_RGB e900;
- public DXGI_RGB e901;
- public DXGI_RGB e902;
- public DXGI_RGB e903;
- public DXGI_RGB e904;
- public DXGI_RGB e905;
- public DXGI_RGB e906;
- public DXGI_RGB e907;
- public DXGI_RGB e908;
- public DXGI_RGB e909;
- public DXGI_RGB e910;
- public DXGI_RGB e911;
- public DXGI_RGB e912;
- public DXGI_RGB e913;
- public DXGI_RGB e914;
- public DXGI_RGB e915;
- public DXGI_RGB e916;
- public DXGI_RGB e917;
- public DXGI_RGB e918;
- public DXGI_RGB e919;
- public DXGI_RGB e920;
- public DXGI_RGB e921;
- public DXGI_RGB e922;
- public DXGI_RGB e923;
- public DXGI_RGB e924;
- public DXGI_RGB e925;
- public DXGI_RGB e926;
- public DXGI_RGB e927;
- public DXGI_RGB e928;
- public DXGI_RGB e929;
- public DXGI_RGB e930;
- public DXGI_RGB e931;
- public DXGI_RGB e932;
- public DXGI_RGB e933;
- public DXGI_RGB e934;
- public DXGI_RGB e935;
- public DXGI_RGB e936;
- public DXGI_RGB e937;
- public DXGI_RGB e938;
- public DXGI_RGB e939;
- public DXGI_RGB e940;
- public DXGI_RGB e941;
- public DXGI_RGB e942;
- public DXGI_RGB e943;
- public DXGI_RGB e944;
- public DXGI_RGB e945;
- public DXGI_RGB e946;
- public DXGI_RGB e947;
- public DXGI_RGB e948;
- public DXGI_RGB e949;
- public DXGI_RGB e950;
- public DXGI_RGB e951;
- public DXGI_RGB e952;
- public DXGI_RGB e953;
- public DXGI_RGB e954;
- public DXGI_RGB e955;
- public DXGI_RGB e956;
- public DXGI_RGB e957;
- public DXGI_RGB e958;
- public DXGI_RGB e959;
- public DXGI_RGB e960;
- public DXGI_RGB e961;
- public DXGI_RGB e962;
- public DXGI_RGB e963;
- public DXGI_RGB e964;
- public DXGI_RGB e965;
- public DXGI_RGB e966;
- public DXGI_RGB e967;
- public DXGI_RGB e968;
- public DXGI_RGB e969;
- public DXGI_RGB e970;
- public DXGI_RGB e971;
- public DXGI_RGB e972;
- public DXGI_RGB e973;
- public DXGI_RGB e974;
- public DXGI_RGB e975;
- public DXGI_RGB e976;
- public DXGI_RGB e977;
- public DXGI_RGB e978;
- public DXGI_RGB e979;
- public DXGI_RGB e980;
- public DXGI_RGB e981;
- public DXGI_RGB e982;
- public DXGI_RGB e983;
- public DXGI_RGB e984;
- public DXGI_RGB e985;
- public DXGI_RGB e986;
- public DXGI_RGB e987;
- public DXGI_RGB e988;
- public DXGI_RGB e989;
- public DXGI_RGB e990;
- public DXGI_RGB e991;
- public DXGI_RGB e992;
- public DXGI_RGB e993;
- public DXGI_RGB e994;
- public DXGI_RGB e995;
- public DXGI_RGB e996;
- public DXGI_RGB e997;
- public DXGI_RGB e998;
- public DXGI_RGB e999;
- public DXGI_RGB e1000;
- public DXGI_RGB e1001;
- public DXGI_RGB e1002;
- public DXGI_RGB e1003;
- public DXGI_RGB e1004;
- public DXGI_RGB e1005;
- public DXGI_RGB e1006;
- public DXGI_RGB e1007;
- public DXGI_RGB e1008;
- public DXGI_RGB e1009;
- public DXGI_RGB e1010;
- public DXGI_RGB e1011;
- public DXGI_RGB e1012;
- public DXGI_RGB e1013;
- public DXGI_RGB e1014;
- public DXGI_RGB e1015;
- public DXGI_RGB e1016;
- public DXGI_RGB e1017;
- public DXGI_RGB e1018;
- public DXGI_RGB e1019;
- public DXGI_RGB e1020;
- public DXGI_RGB e1021;
- public DXGI_RGB e1022;
- public DXGI_RGB e1023;
- public DXGI_RGB e1024;
-#if NET6_0_OR_GREATER
- public ref DXGI_RGB this[int index]
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get
- {
- return ref AsSpan()[index];
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Span AsSpan() => MemoryMarshal.CreateSpan(ref e0, 1025);
-#else
- // there is no way to do this outside of terrible unsafe code. Don't do this in .Net Standard 2.0
-
- public DXGI_RGB this[int index]
- {
- get
- {
- if ((uint)index > 1025)
- throw new ArgumentOutOfRangeException("index");
-
- fixed (DXGI_RGB* basePtr = &e0)
- {
- DXGI_RGB* newPtr = basePtr + index;
- return *newPtr;
- }
- }
- set
- {
- if ((uint)index > 1025)
- throw new ArgumentOutOfRangeException("index");
-
- fixed (DXGI_RGB* basePtr = &e0)
- {
- DXGI_RGB* newPtr = basePtr + index;
- *newPtr = value;
- }
- }
- }
-#endif
- }
- }
-
+
internal unsafe struct DEVMODEW
{
public fixed ushort dmDeviceName[32];
@@ -1196,7 +119,7 @@ namespace Avalonia.Win32.DxgiSwapchain
public uint Flags;
}
- internal unsafe struct DXGI_FRAME_STATISTICS
+ internal struct DXGI_FRAME_STATISTICS
{
public uint PresentCount;
@@ -1225,11 +148,10 @@ namespace Avalonia.Win32.DxgiSwapchain
internal unsafe struct DXGI_MAPPED_RECT
{
public int Pitch;
-
public byte* pBits;
}
- internal unsafe partial struct DXGI_MODE_DESC
+ internal struct DXGI_MODE_DESC
{
public ushort Width;
public ushort Height;
@@ -1239,7 +161,7 @@ namespace Avalonia.Win32.DxgiSwapchain
public DXGI_MODE_SCALING Scaling;
}
- internal unsafe partial struct DXGI_OUTPUT_DESC
+ internal unsafe struct DXGI_OUTPUT_DESC
{
internal fixed ushort DeviceName[32];
@@ -1263,13 +185,13 @@ namespace Avalonia.Win32.DxgiSwapchain
public POINT* pScrollOffset;
}
- internal unsafe partial struct DXGI_RATIONAL
+ internal struct DXGI_RATIONAL
{
public ushort Numerator;
public ushort Denominator;
}
- internal partial struct DXGI_RGB
+ internal struct DXGI_RGB
{
public float Red;
@@ -1278,7 +200,7 @@ namespace Avalonia.Win32.DxgiSwapchain
public float Blue;
}
- internal partial struct DXGI_RGBA
+ internal struct DXGI_RGBA
{
public float r;
@@ -1295,7 +217,7 @@ namespace Avalonia.Win32.DxgiSwapchain
public uint Quality;
}
- internal unsafe struct DXGI_SURFACE_DESC
+ internal struct DXGI_SURFACE_DESC
{
public uint Width;
@@ -1306,7 +228,7 @@ namespace Avalonia.Win32.DxgiSwapchain
public DXGI_SAMPLE_DESC SampleDesc;
}
- internal unsafe partial struct DXGI_SWAP_CHAIN_DESC
+ internal struct DXGI_SWAP_CHAIN_DESC
{
public DXGI_MODE_DESC BufferDesc;
public DXGI_SAMPLE_DESC SampleDesc;
@@ -1333,7 +255,7 @@ namespace Avalonia.Win32.DxgiSwapchain
public uint Flags;
}
- internal unsafe struct DXGI_SWAP_CHAIN_FULLSCREEN_DESC
+ internal struct DXGI_SWAP_CHAIN_FULLSCREEN_DESC
{
public DXGI_RATIONAL RefreshRate;
@@ -1344,7 +266,7 @@ namespace Avalonia.Win32.DxgiSwapchain
public int Windowed;
}
- internal partial struct D3D11_TEXTURE2D_DESC
+ internal struct D3D11_TEXTURE2D_DESC
{
public uint Width;
diff --git a/src/Windows/Avalonia.Win32/DirectX/DirectXUnmanagedMethods.cs b/src/Windows/Avalonia.Win32/DirectX/DirectXUnmanagedMethods.cs
index 1a739ce3d1..14a9ff1277 100644
--- a/src/Windows/Avalonia.Win32/DirectX/DirectXUnmanagedMethods.cs
+++ b/src/Windows/Avalonia.Win32/DirectX/DirectXUnmanagedMethods.cs
@@ -5,7 +5,7 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
-namespace Avalonia.Win32.DxgiSwapchain
+namespace Avalonia.Win32.DirectX
{
internal unsafe class DirectXUnmanagedMethods
{
@@ -23,7 +23,16 @@ namespace Avalonia.Win32.DxgiSwapchain
[DllImport("user32", ExactSpelling = true)]
internal static extern bool EnumDisplaySettingsW(ushort* lpszDeviceName, uint iModeNum, DEVMODEW* lpDevMode);
- [DllImport("user32", ExactSpelling = true, SetLastError = true)]
- internal static extern bool GetClientRect(IntPtr hWnd, Interop.UnmanagedMethods.RECT* lpRect);
+ [DllImport("d3d11", ExactSpelling = true, PreserveSig = false)]
+ public static extern void D3D11CreateDevice(
+ IntPtr adapter, D3D_DRIVER_TYPE DriverType,
+ IntPtr Software,
+ uint Flags,
+ D3D_FEATURE_LEVEL[] pFeatureLevels,
+ uint FeatureLevels,
+ uint SDKVersion,
+ out IntPtr ppDevice,
+ out D3D_FEATURE_LEVEL pFeatureLevel,
+ IntPtr* ppImmediateContext);
}
}
diff --git a/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs b/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs
index 8c13ecdcc1..e82f7633be 100644
--- a/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs
+++ b/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs
@@ -10,11 +10,12 @@ using Avalonia.Logging;
using Avalonia.OpenGL.Angle;
using Avalonia.OpenGL.Egl;
using Avalonia.Rendering;
+using Avalonia.Win32.OpenGl.Angle;
using static Avalonia.Win32.Interop.UnmanagedMethods;
-using static Avalonia.Win32.DxgiSwapchain.DirectXUnmanagedMethods;
+using static Avalonia.Win32.DirectX.DirectXUnmanagedMethods;
using MicroCom.Runtime;
-namespace Avalonia.Win32.DxgiSwapchain
+namespace Avalonia.Win32.DirectX
{
#pragma warning disable CA1416 // This should only be reachable on Windows
#nullable enable
@@ -25,34 +26,27 @@ namespace Avalonia.Win32.DxgiSwapchain
public bool RunsInBackground => true;
public event Action? Tick;
-
- private AngleWin32EglDisplay _angle;
- private EglPlatformOpenGlInterface _gl;
private object _syncLock;
private IDXGIOutput? _output = null;
private Stopwatch? _stopwatch = null;
+ private const string LogArea = "DXGI";
- public DxgiConnection(EglPlatformOpenGlInterface gl, object syncLock)
+ public DxgiConnection(object syncLock)
{
-
_syncLock = syncLock;
- _angle = (AngleWin32EglDisplay)gl.Display;
- _gl = gl;
}
-
- public EglPlatformOpenGlInterface Egl => _gl;
-
- public static void TryCreateAndRegister(EglPlatformOpenGlInterface angle)
+
+ public static void TryCreateAndRegister()
{
try
{
- TryCreateAndRegisterCore(angle);
+ TryCreateAndRegisterCore();
}
catch (Exception ex)
{
- Logger.TryGet(LogEventLevel.Error, nameof(DxgiSwapchain))
+ Logger.TryGet(LogEventLevel.Error, LogArea)
?.Log(null, "Unable to establish Dxgi: {0}", ex);
}
}
@@ -66,7 +60,7 @@ namespace Avalonia.Win32.DxgiSwapchain
}
catch (Exception ex)
{
- Logger.TryGet(LogEventLevel.Error, nameof(DxgiSwapchain))
+ Logger.TryGet(LogEventLevel.Error, LogArea)
?.Log(this, $"Failed to wait for vblank, Exception: {ex.Message}, HRESULT = {ex.HResult}");
}
@@ -84,7 +78,7 @@ namespace Avalonia.Win32.DxgiSwapchain
}
catch (Exception ex)
{
- Logger.TryGet(LogEventLevel.Error, nameof(DxgiSwapchain))
+ Logger.TryGet(LogEventLevel.Error, LogArea)
?.Log(this, $"Failed to wait for vblank, Exception: {ex.Message}, HRESULT = {ex.HResult}");
_output.Dispose();
_output = null;
@@ -103,7 +97,7 @@ namespace Avalonia.Win32.DxgiSwapchain
}
catch (Exception ex)
{
- Logger.TryGet(LogEventLevel.Error, nameof(DxgiSwapchain))
+ Logger.TryGet(LogEventLevel.Error, LogArea)
?.Log(this, $"Failed to wait for vblank, Exception: {ex.Message}, HRESULT = {ex.HResult}");
}
}
@@ -168,7 +162,7 @@ namespace Avalonia.Win32.DxgiSwapchain
}
// Used the windows composition as a blueprint for this startup/creation
- static private bool TryCreateAndRegisterCore(EglPlatformOpenGlInterface gl)
+ static private bool TryCreateAndRegisterCore()
{
var tcs = new TaskCompletionSource();
var pumpLock = new object();
@@ -178,7 +172,7 @@ namespace Avalonia.Win32.DxgiSwapchain
{
DxgiConnection connection;
- connection = new DxgiConnection(gl, pumpLock);
+ connection = new DxgiConnection(pumpLock);
AvaloniaLocator.CurrentMutable.BindToSelf(connection);
AvaloniaLocator.CurrentMutable.Bind().ToConstant(connection);
diff --git a/src/Windows/Avalonia.Win32/DirectX/DxgiRenderTarget.cs b/src/Windows/Avalonia.Win32/DirectX/DxgiRenderTarget.cs
index 2ab9b30d43..cb7826e185 100644
--- a/src/Windows/Avalonia.Win32/DirectX/DxgiRenderTarget.cs
+++ b/src/Windows/Avalonia.Win32/DirectX/DxgiRenderTarget.cs
@@ -8,11 +8,12 @@ using System.Threading.Tasks;
using Avalonia.OpenGL.Angle;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
+using Avalonia.Win32.OpenGl.Angle;
using MicroCom.Runtime;
using static Avalonia.OpenGL.Egl.EglGlPlatformSurfaceBase;
using static Avalonia.Win32.Interop.UnmanagedMethods;
-namespace Avalonia.Win32.DxgiSwapchain
+namespace Avalonia.Win32.DirectX
{
#pragma warning disable CA1416 // Validate platform compatibility, if you enter this not on windows you have messed up badly
#nullable enable
@@ -22,8 +23,7 @@ namespace Avalonia.Win32.DxgiSwapchain
public const uint DXGI_USAGE_RENDER_TARGET_OUTPUT = 0x00000020U;
- private IEglWindowGlPlatformSurfaceInfo _window;
- private EglPlatformOpenGlInterface _egl;
+ private EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _window;
private DxgiConnection _connection;
private IDXGIDevice? _dxgiDevice = null;
private IDXGIFactory2? _dxgiFactory = null;
@@ -36,15 +36,14 @@ namespace Avalonia.Win32.DxgiSwapchain
private Guid ID3D11Texture2DGuid = Guid.Parse("6F15AAF2-D208-4E89-9AB4-489535D34F9C");
- public DxgiRenderTarget(IEglWindowGlPlatformSurfaceInfo window, EglPlatformOpenGlInterface egl, DxgiConnection connection) : base(egl)
+ public DxgiRenderTarget(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo window, EglContext context, DxgiConnection connection) : base(context)
{
_window = window;
- _egl = egl;
_connection = connection;
// the D3D device is expected to at least be an ID3D11Device
// but how do I wrap an IntPtr as a managed IUnknown now? Like this.
- IUnknown pdevice = MicroComRuntime.CreateProxyFor(((AngleWin32EglDisplay)_egl.Display).GetDirect3DDevice(), false);
+ IUnknown pdevice = MicroComRuntime.CreateProxyFor(((AngleWin32EglDisplay)context.Display).GetDirect3DDevice(), false);
_dxgiDevice = pdevice.QueryInterface();
@@ -86,14 +85,14 @@ namespace Avalonia.Win32.DxgiSwapchain
_clientRect = pClientRect;
}
- public override IGlPlatformSurfaceRenderingSession BeginDraw()
+ public override IGlPlatformSurfaceRenderingSession BeginDrawCore()
{
if (_swapChain is null)
{
throw new InvalidOperationException("No chain to draw on");
}
- var contextLock = _egl.PrimaryContext.EnsureCurrent();
+ var contextLock = Context.EnsureCurrent();
EglSurface? surface = null;
IDisposable? transaction = null;
var success = false;
@@ -132,10 +131,10 @@ namespace Avalonia.Win32.DxgiSwapchain
_renderTexture = texture;
// I also have to get the pointer to this texture directly
- surface = ((AngleWin32EglDisplay)_egl.Display).WrapDirect3D11Texture(_egl, MicroComRuntime.GetNativeIntPtr(_renderTexture),
+ surface = ((AngleWin32EglDisplay)Context.Display).WrapDirect3D11Texture(MicroComRuntime.GetNativeIntPtr(_renderTexture),
0, 0, size.Width, size.Height);
- var res = base.BeginDraw(surface, _window, () =>
+ var res = base.BeginDraw(surface, _window.Size, _window.Scaling, () =>
{
_swapChain.Present((ushort)0U, (ushort)0U);
surface?.Dispose();
diff --git a/src/Windows/Avalonia.Win32/DirectX/DxgiSwapchainWindow.cs b/src/Windows/Avalonia.Win32/DirectX/DxgiSwapchainWindow.cs
index a40d8abf80..88226c5c89 100644
--- a/src/Windows/Avalonia.Win32/DirectX/DxgiSwapchainWindow.cs
+++ b/src/Windows/Avalonia.Win32/DirectX/DxgiSwapchainWindow.cs
@@ -3,29 +3,29 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using Avalonia.OpenGL;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
-namespace Avalonia.Win32.DxgiSwapchain
+namespace Avalonia.Win32.DirectX
{
public class DxgiSwapchainWindow : EglGlPlatformSurfaceBase
{
private DxgiConnection _connection;
- private EglPlatformOpenGlInterface _egl;
- private IEglWindowGlPlatformSurfaceInfo _window;
+ private EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _window;
- public DxgiSwapchainWindow(DxgiConnection connection, IEglWindowGlPlatformSurfaceInfo window)
+ public DxgiSwapchainWindow(DxgiConnection connection, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo window)
{
_connection = connection;
_window = window;
- _egl = connection.Egl;
}
- public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
+ public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context)
{
- using (_egl.PrimaryContext.EnsureCurrent())
+ var eglContext = (EglContext)context;
+ using (eglContext.EnsureCurrent())
{
- return new DxgiRenderTarget(_window, _egl, _connection);
+ return new DxgiRenderTarget(_window, eglContext, _connection);
}
}
}
diff --git a/src/Windows/Avalonia.Win32/DirectX/IDirect3D11TexturePlatformSurface.cs b/src/Windows/Avalonia.Win32/DirectX/IDirect3D11TexturePlatformSurface.cs
new file mode 100644
index 0000000000..d2a2b2f513
--- /dev/null
+++ b/src/Windows/Avalonia.Win32/DirectX/IDirect3D11TexturePlatformSurface.cs
@@ -0,0 +1,26 @@
+using System;
+using Avalonia.OpenGL;
+using Avalonia.Platform;
+
+namespace Avalonia.Win32.DirectX;
+
+public interface IDirect3D11TexturePlatformSurface
+{
+ public IDirect3D11TextureRenderTarget CreateRenderTarget(IPlatformGraphicsContext graphicsContext, IntPtr d3dDevice);
+}
+
+
+
+public interface IDirect3D11TextureRenderTarget : IDisposable
+{
+ bool IsCorrupted { get; }
+ IDirect3D11TextureRenderTargetRenderSession BeginDraw();
+}
+
+public interface IDirect3D11TextureRenderTargetRenderSession : IDisposable
+{
+ public IntPtr D3D11Texture2D { get; }
+ public PixelSize Size { get; }
+ public PixelPoint Offset { get; }
+ public double Scaling { get; }
+}
diff --git a/src/Windows/Avalonia.Win32/DirectX/directx.idl b/src/Windows/Avalonia.Win32/DirectX/directx.idl
index 09d1adf574..a4552eedea 100644
--- a/src/Windows/Avalonia.Win32/DirectX/directx.idl
+++ b/src/Windows/Avalonia.Win32/DirectX/directx.idl
@@ -1,4 +1,4 @@
-@clr-namespace Avalonia.Win32.DxgiSwapchain
+@clr-namespace Avalonia.Win32.DirectX
@clr-access internal
@clr-map FLOAT float
@clr-map HSTRING IntPtr
@@ -12,6 +12,7 @@
@clr-map HWND IntPtr
@clr-map BOOL int
@clr-map DWORD int
+@clr-map SIZE_T IntPtr
@clr-map boolean int
@clr-map BYTE byte
@clr-map INT16 short
@@ -242,9 +243,9 @@ interface IDXGIOutput : IDXGIObject
HRESULT WaitForVBlank();
HRESULT TakeOwnership([in, annotation("_In_")] IUnknown* pDevice, BOOL Exclusive);
void ReleaseOwnership();
- HRESULT GetGammaControlCapabilities([out, annotation("_Out_")] DXGI_GAMMA_CONTROL_CAPABILITIES* pGammaCaps);
- HRESULT SetGammaControl([in, annotation("_In_")] DXGI_GAMMA_CONTROL* pArray);
- HRESULT GetGammaControl([out, annotation("_Out_")] DXGI_GAMMA_CONTROL* pArray);
+ HRESULT GetGammaControlCapabilities(IntPtr pGammaCaps);
+ HRESULT SetGammaControl([in, annotation("_In_")] void* pArray);
+ HRESULT GetGammaControl(IntPtr pArray);
HRESULT SetDisplaySurface([in, annotation("_In_")] IDXGISurface* pScanoutSurface);
HRESULT GetDisplaySurfaceData([in, annotation("_In_")] IDXGISurface* pDestination);
HRESULT GetFrameStatistics([out, annotation("_Out_")] DXGI_FRAME_STATISTICS* pStats);
@@ -303,3 +304,180 @@ interface IDXGISwapChain1 : IDXGISwapChain
HRESULT GetRotation([out, annotation("_Out_")] DXGI_MODE_ROTATION* pRotation);
}
+enum D3D11_FEATURE
+{
+ D3D11_FEATURE_THREADING,
+ D3D11_FEATURE_DOUBLES,
+ D3D11_FEATURE_FORMAT_SUPPORT,
+ D3D11_FEATURE_FORMAT_SUPPORT2,
+ D3D11_FEATURE_D3D10_X_HARDWARE_OPTIONS,
+ D3D11_FEATURE_D3D11_OPTIONS,
+ D3D11_FEATURE_ARCHITECTURE_INFO,
+ D3D11_FEATURE_D3D9_OPTIONS,
+ D3D11_FEATURE_SHADER_MIN_PRECISION_SUPPORT,
+ D3D11_FEATURE_D3D9_SHADOW_SUPPORT,
+ D3D11_FEATURE_D3D11_OPTIONS1,
+ D3D11_FEATURE_D3D9_SIMPLE_INSTANCING_SUPPORT,
+ D3D11_FEATURE_MARKER_SUPPORT,
+ D3D11_FEATURE_D3D9_OPTIONS1,
+ D3D11_FEATURE_D3D11_OPTIONS2,
+ D3D11_FEATURE_D3D11_OPTIONS3,
+ D3D11_FEATURE_GPU_VIRTUAL_ADDRESS_SUPPORT,
+ D3D11_FEATURE_D3D11_OPTIONSS,
+ D3D11_FEATURE_SHADER_CACHE
+}
+
+[uuid(db6f6ddb-ac77-4e88-8253-819df9bbf140)]
+interface ID3D11Device : IUnknown
+{
+ HRESULT CreateBuffer(
+ IntPtr pDesc,
+ IntPtr pInitialData,
+ [out, retval] IUnknown** ppBuffer );
+ HRESULT CreateTexture1D(
+ IntPtr pDesc,
+ IntPtr pInitialData,
+ [out, retval] IUnknown** ppTexture1D );
+ HRESULT CreateTexture2D(
+ IntPtr pDesc,
+ IntPtr pInitialData,
+ [out, retval] IUnknown** ppTexture2D );
+ HRESULT CreateTexture3D(
+ IntPtr pDesc,
+ IntPtr pInitialData,
+ [out, retval] IUnknown** ppTexture3D );
+ HRESULT CreateShaderResourceView(
+ IntPtr pResource,
+ IntPtr pDesc,
+ [out, retval] IUnknown** ppSRView );
+ HRESULT CreateUnorderedAccessView(
+ IntPtr pResource,
+ IntPtr pDesc,
+ [out, retval] IUnknown** ppUAView );
+ HRESULT CreateRenderTargetView(
+ IntPtr pResource,
+ IntPtr pDesc,
+ [out, retval] IUnknown** ppRTView);
+ HRESULT CreateDepthStencilView(
+ IntPtr pResource,
+ IntPtr pDesc,
+ [out, retval] IUnknown** ppDepthStencilView );
+ HRESULT CreateInputLayout(
+ IntPtr pInputElementDescs,
+ UINT NumElements,
+ void* pShaderBytecodeWithInputSignature,
+ IntPtr BytecodeLength,
+ [out, retval] IUnknown** ppInputLayout );
+ HRESULT CreateVertexShader(
+ IntPtr pShaderBytecode,
+ [annotation("_In_")] SIZE_T BytecodeLength,
+ IntPtr pClassLinkage,
+ [out, retval] IUnknown** ppVertexShader );
+ HRESULT CreateGeometryShader(
+ IntPtr pShaderBytecode,
+ [annotation("_In_")] SIZE_T BytecodeLength,
+ IntPtr pClassLinkage,
+ [out, retval] IUnknown** ppGeometryShader );
+ HRESULT CreateGeometryShaderWithStreamOutput(
+ IntPtr pShaderBytecode,
+ [annotation("_In_")] SIZE_T BytecodeLength,
+ IntPtr pSODeclaration,
+ UINT NumEntries,
+ UINT* pBufferStrides,
+ UINT NumStrides,
+ UINT RasterizedStream,
+ IntPtr pClassLinkage,
+ [out, retval] IUnknown** ppGeometryShader );
+ HRESULT CreatePixelShader(
+ IntPtr pShaderBytecode,
+ [annotation("_In_")] SIZE_T BytecodeLength,
+ IntPtr pClassLinkage,
+ [out, retval] IUnknown** ppPixelShader );
+ HRESULT CreateHullShader(
+ IntPtr pShaderBytecode,
+ [annotation("_In_")] SIZE_T BytecodeLength,
+ IntPtr pClassLinkage,
+ [out, retval] IUnknown** ppHullShader );
+ HRESULT CreateDomainShader(
+ IntPtr pShaderBytecode,
+ [annotation("_In_")] SIZE_T BytecodeLength,
+ IntPtr pClassLinkage,
+ [out, retval] IUnknown** ppDomainShader );
+ HRESULT CreateComputeShader(
+ IntPtr pShaderBytecode,
+ [annotation("_In_")] SIZE_T BytecodeLength,
+ IntPtr pClassLinkage,
+ [out, retval] IUnknown** ppComputeShader );
+ HRESULT CreateClassLinkage([out, retval]IUnknown** ppLinkage);
+ HRESULT CreateBlendState(
+ IntPtr pBlendStateDesc,
+ [out, retval] IUnknown** ppBlendState );
+ HRESULT CreateDepthStencilState(
+ IntPtr pDepthStencilDesc,
+ [out, retval] IUnknown** ppDepthStencilState );
+ HRESULT CreateRasterizerState(
+ IntPtr pRasterizerDesc,
+ [out, retval] IUnknown** ppRasterizerState );
+ HRESULT CreateSamplerState(
+ IntPtr pSamplerDesc,
+ [out, retval] IUnknown** ppSamplerState );
+ HRESULT CreateQuery(
+ IntPtr pQueryDesc,
+ [out, retval] IUnknown** ppQuery );
+ HRESULT CreatePredicate(
+ IntPtr pPredicateDesc,
+ [out, retval] IUnknown** ppPredicate );
+ HRESULT CreateCounter(
+ IntPtr pCounterDesc,
+ [out, retval] IUnknown** ppCounter );
+ HRESULT CreateDeferredContext(
+ UINT ContextFlags, // Reserved parameter; must be 0
+ [out, retval] IUnknown** ppDeferredContext );
+ HRESULT OpenSharedResource(
+ IntPtr hResource,
+ [out] Guid* ReturnedInterface,
+ [out, retval] IUnknown** ppResource);
+
+ // Check*
+ HRESULT CheckFormatSupport(
+ [annotation("_In_")] DXGI_FORMAT Format,
+ [annotation("_Out_")] UINT* pFormatSupport );
+ HRESULT CheckMultisampleQualityLevels(
+ [annotation("_In_")] DXGI_FORMAT Format,
+ [annotation("_In_")] UINT SampleCount,
+ [annotation("_Out_")] UINT* pNumQualityLevels );
+ void CheckCounterInfo(IntPtr pCounterInfo );
+ HRESULT CheckCounter(
+ IntPtr pDesc,
+ IntPtr pType,
+ IntPtr pActiveCounters,
+ IntPtr szName,
+ UINT* pNameLength,
+ IntPtr szUnits,
+ UINT* pUnitsLength,
+ IntPtr szDescription,
+ UINT* pDescriptionLength );
+ HRESULT CheckFeatureSupport(
+ D3D11_FEATURE Feature,
+ void* pFeatureSupportData,
+ UINT FeatureSupportDataSize );
+
+ HRESULT GetPrivateData(
+ Guid* guid,
+ UINT* pDataSize,
+ void* pData );
+ HRESULT SetPrivateData(
+ Guid* guid,
+ UINT DataSize,
+ IntPtr* pData );
+ HRESULT SetPrivateDataInterface(
+ Guid* guid,
+ IUnknown* pData );
+
+ D3D_FEATURE_LEVEL GetFeatureLevel();
+ UINT GetCreationFlags();
+ int GetDeviceRemovedReason();
+ void GetImmediateContext(IntPtr* ppImmediateContext );
+ HRESULT SetExceptionMode( UINT RaiseFlags );
+ UINT GetExceptionMode();
+}
diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
index 9b86154043..a7cca5b0f3 100644
--- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
+++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
@@ -1627,7 +1627,7 @@ namespace Avalonia.Win32.Interop
public static extern bool wglDeleteContext(IntPtr context);
- [DllImport("opengl32.dll")]
+ [DllImport("opengl32.dll", SetLastError = true)]
public static extern bool wglMakeCurrent(IntPtr hdc, IntPtr context);
[DllImport("opengl32.dll")]
diff --git a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleD3DTextureFeature.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleD3DTextureFeature.cs
new file mode 100644
index 0000000000..6c951010c8
--- /dev/null
+++ b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleD3DTextureFeature.cs
@@ -0,0 +1,103 @@
+using System.Runtime.InteropServices;
+using Avalonia.OpenGL;
+using Avalonia.OpenGL.Egl;
+using Avalonia.OpenGL.Surfaces;
+using Avalonia.Win32.DirectX;
+
+namespace Avalonia.Win32.OpenGl.Angle;
+
+internal class AngleD3DTextureFeature : IGlPlatformSurfaceRenderTargetFactory
+{
+ public bool CanRenderToSurface(IGlContext context, object surface) =>
+ context is EglContext
+ {
+ Display: AngleWin32EglDisplay { PlatformApi: AngleOptions.PlatformApi.DirectX11 }
+ } && surface is IDirect3D11TexturePlatformSurface;
+
+ class RenderTargetWrapper : EglPlatformSurfaceRenderTargetBase
+ {
+ private readonly AngleWin32EglDisplay _angle;
+ private readonly IDirect3D11TextureRenderTarget _target;
+
+ public RenderTargetWrapper(EglContext context,
+ AngleWin32EglDisplay angle,
+ IDirect3D11TextureRenderTarget target) : base(context)
+ {
+ _angle = angle;
+ _target = target;
+ }
+
+ public override IGlPlatformSurfaceRenderingSession BeginDrawCore()
+ {
+ var success = false;
+ var contextLock = Context.EnsureCurrent();
+ IDirect3D11TextureRenderTargetRenderSession session = null;
+ EglSurface surface = null;
+ try
+ {
+ try
+ {
+ session = _target.BeginDraw();
+ }
+ catch (RenderTargetCorruptedException e)
+ {
+ if (e.InnerException is COMException com
+ && ((DXGI_ERROR)com.HResult).IsDeviceLostError())
+ Context.NotifyContextLost();
+
+ throw;
+ }
+
+ surface = _angle.WrapDirect3D11Texture(session.D3D11Texture2D, session.Offset.X, session.Offset.Y,
+ session.Size.Width, session.Size.Height);
+ var rv = BeginDraw(surface, session.Size, session.Scaling, () =>
+ {
+ using(contextLock)
+ using (session)
+ using (surface)
+ {
+ }
+ }, true);
+ success = true;
+ return rv;
+ }
+ finally
+ {
+ if (!success)
+ {
+ using(contextLock)
+ using (session)
+ using (surface)
+ {
+ }
+ }
+ }
+ }
+
+ public override void Dispose()
+ {
+ _target.Dispose();
+ base.Dispose();
+ }
+
+ public override bool IsCorrupted => _target.IsCorrupted || base.IsCorrupted;
+ }
+
+ public IGlPlatformSurfaceRenderTarget CreateRenderTarget(IGlContext context, object surface)
+ {
+ var ctx = (EglContext)context;
+ var angle = (AngleWin32EglDisplay)ctx.Display;
+ var textureSurface = (IDirect3D11TexturePlatformSurface)surface;
+ try
+ {
+ var target = textureSurface.CreateRenderTarget(context, angle.GetDirect3DDevice());
+ return new RenderTargetWrapper(ctx, angle, target);
+ }
+ catch (COMException com)
+ {
+ if (((DXGI_ERROR)com.HResult).IsDeviceLostError())
+ ctx.NotifyContextLost();
+ throw;
+ }
+ }
+}
diff --git a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleEglInterface.cs
similarity index 56%
rename from src/Avalonia.OpenGL/Angle/AngleEglInterface.cs
rename to src/Windows/Avalonia.Win32/OpenGl/Angle/AngleEglInterface.cs
index 3a332f37ad..4e00bc2c72 100644
--- a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs
+++ b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleEglInterface.cs
@@ -1,18 +1,31 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.OpenGL.Egl;
+using Avalonia.SourceGenerator;
namespace Avalonia.OpenGL.Angle
{
- public class AngleEglInterface : EglInterface
+ internal partial class Win32AngleEglInterface : EglInterface
{
[DllImport("av_libGLESv2.dll", CharSet = CharSet.Ansi)]
static extern IntPtr EGL_GetProcAddress(string proc);
- public AngleEglInterface() : base(LoadAngle())
- {
+ public Win32AngleEglInterface() : this(LoadAngle())
+ {
+
}
+
+ private Win32AngleEglInterface(Func getProcAddress) : base(getProcAddress)
+ {
+ Initialize(getProcAddress);
+ }
+
+ [GetProcAddress("eglCreateDeviceANGLE", true)]
+ public partial IntPtr CreateDeviceANGLE(int deviceType, IntPtr nativeDevice, int[] attribs);
+
+ [GetProcAddress("eglReleaseDeviceANGLE", true)]
+ public partial void ReleaseDeviceANGLE(IntPtr device);
static Func LoadAngle()
{
diff --git a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32EglDisplay.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32EglDisplay.cs
new file mode 100644
index 0000000000..b2d7b2014b
--- /dev/null
+++ b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32EglDisplay.cs
@@ -0,0 +1,140 @@
+using System;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+using Avalonia.OpenGL;
+using Avalonia.OpenGL.Angle;
+using Avalonia.OpenGL.Egl;
+using Avalonia.Win32.DirectX;
+using MicroCom.Runtime;
+using static Avalonia.OpenGL.Egl.EglConsts;
+
+namespace Avalonia.Win32.OpenGl.Angle
+{
+ internal class AngleWin32EglDisplay : EglDisplay
+ {
+ protected override bool DisplayLockIsSharedWithContexts => true;
+
+ public static AngleWin32EglDisplay CreateD3D9Display(EglInterface egl)
+ {
+ var display = egl.GetPlatformDisplayExt(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero,
+ new[] { EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE, EGL_NONE });
+
+ return new AngleWin32EglDisplay(display, new EglDisplayOptions()
+ {
+ Egl = egl,
+ ContextLossIsDisplayLoss = true,
+ GlVersions = AvaloniaLocator.Current.GetService()?.GlProfiles
+ }, AngleOptions.PlatformApi.DirectX9);
+ }
+
+ public static AngleWin32EglDisplay CreateSharedD3D11Display(EglInterface egl)
+ {
+ var display = egl.GetPlatformDisplayExt(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero,
+ new[] { EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, EGL_NONE });
+
+ return new AngleWin32EglDisplay(display, new EglDisplayOptions()
+ {
+ Egl = egl,
+ ContextLossIsDisplayLoss = true,
+ GlVersions = AvaloniaLocator.Current.GetService()?.GlProfiles
+ }, AngleOptions.PlatformApi.DirectX11);
+ }
+
+ public static AngleWin32EglDisplay CreateD3D11Display(Win32AngleEglInterface egl)
+ {
+ unsafe
+ {
+ var featureLevels = new[]
+ {
+ D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_1,
+ D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_0,
+ D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_10_1,
+ D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_10_0,
+ D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_9_3,
+ D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_9_2,
+ D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_9_1
+ };
+
+ DirectXUnmanagedMethods.D3D11CreateDevice(IntPtr.Zero, D3D_DRIVER_TYPE.D3D_DRIVER_TYPE_HARDWARE,
+ IntPtr.Zero, 0, featureLevels, (uint)featureLevels.Length,
+ 7, out var pD3dDevice, out var featureLevel, null);
+ if (pD3dDevice == IntPtr.Zero)
+ throw new Win32Exception("Unable to create D3D11 Device");
+
+ var d3dDevice = MicroComRuntime.CreateProxyFor(pD3dDevice, true);
+ var angleDevice = IntPtr.Zero;
+ var display = IntPtr.Zero;
+
+ void Cleanup()
+ {
+ if (angleDevice != IntPtr.Zero)
+ egl.ReleaseDeviceANGLE(angleDevice);
+ d3dDevice.Dispose();
+ }
+
+ bool success = false;
+ try
+ {
+ angleDevice = egl.CreateDeviceANGLE(EGL_D3D11_DEVICE_ANGLE, pD3dDevice, null);
+ if (angleDevice == IntPtr.Zero)
+ throw OpenGlException.GetFormattedException("eglCreateDeviceANGLE", egl);
+
+ display = egl.GetPlatformDisplayExt(EGL_PLATFORM_DEVICE_EXT, angleDevice, null);
+ if (display == IntPtr.Zero)
+ throw OpenGlException.GetFormattedException("eglGetPlatformDisplayEXT", egl);
+
+
+ var rv = new AngleWin32EglDisplay(display, new EglDisplayOptions
+ {
+ DisposeCallback = Cleanup,
+ Egl = egl,
+ ContextLossIsDisplayLoss = true,
+ DeviceLostCheckCallback = () => d3dDevice.DeviceRemovedReason != 0,
+ GlVersions = AvaloniaLocator.Current.GetService()?.GlProfiles
+ }, AngleOptions.PlatformApi.DirectX11);
+ success = true;
+ return rv;
+ }
+ finally
+ {
+ if (!success)
+ {
+ if (display != IntPtr.Zero)
+ egl.Terminate(display);
+ Cleanup();
+ }
+ }
+ }
+ }
+
+ private AngleWin32EglDisplay(IntPtr display, EglDisplayOptions options, AngleOptions.PlatformApi platformApi) : base(display, options)
+ {
+ PlatformApi = platformApi;
+ }
+
+ public AngleOptions.PlatformApi PlatformApi { get; }
+
+ public IntPtr GetDirect3DDevice()
+ {
+ if (!EglInterface.QueryDisplayAttribExt(Handle, EglConsts.EGL_DEVICE_EXT, out var eglDevice))
+ throw new OpenGlException("Unable to get EGL_DEVICE_EXT");
+ if (!EglInterface.QueryDeviceAttribExt(eglDevice, PlatformApi == AngleOptions.PlatformApi.DirectX9 ? EGL_D3D9_DEVICE_ANGLE : EGL_D3D11_DEVICE_ANGLE, out var d3dDeviceHandle))
+ throw new OpenGlException("Unable to get EGL_D3D9_DEVICE_ANGLE");
+ return d3dDeviceHandle;
+ }
+
+ public EglSurface WrapDirect3D11Texture( IntPtr handle)
+ {
+ if (PlatformApi != AngleOptions.PlatformApi.DirectX11)
+ throw new InvalidOperationException("Current platform API is " + PlatformApi);
+ return CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_NONE, EGL_NONE });
+ }
+
+ public EglSurface WrapDirect3D11Texture(IntPtr handle, int offsetX, int offsetY, int width, int height)
+ {
+ if (PlatformApi != AngleOptions.PlatformApi.DirectX11)
+ throw new InvalidOperationException("Current platform API is " + PlatformApi);
+ return CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_WIDTH, width, EGL_HEIGHT, height, EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE, EGL_TRUE, EGL_TEXTURE_OFFSET_X_ANGLE, offsetX, EGL_TEXTURE_OFFSET_Y_ANGLE, offsetY, EGL_NONE });
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32PlatformGraphics.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32PlatformGraphics.cs
new file mode 100644
index 0000000000..9b4eefd170
--- /dev/null
+++ b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32PlatformGraphics.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.ExceptionServices;
+using Avalonia.Logging;
+using Avalonia.OpenGL;
+using Avalonia.OpenGL.Angle;
+using Avalonia.OpenGL.Egl;
+using Avalonia.Platform;
+
+namespace Avalonia.Win32.OpenGl.Angle;
+
+internal class AngleWin32PlatformGraphics : IPlatformGraphics
+{
+ private readonly Win32AngleEglInterface _egl;
+ private AngleWin32EglDisplay _sharedDisplay;
+ private EglContext _sharedContext;
+ public bool UsesSharedContext => PlatformApi == AngleOptions.PlatformApi.DirectX9;
+
+ public AngleOptions.PlatformApi PlatformApi { get; } = AngleOptions.PlatformApi.DirectX11;
+ public IPlatformGraphicsContext CreateContext()
+ {
+ if (UsesSharedContext)
+ throw new InvalidOperationException();
+
+ var display = AngleWin32EglDisplay.CreateD3D11Display(_egl);
+ var success = false;
+ try
+ {
+ var rv = display.CreateContext(new EglContextOptions
+ {
+ DisposeCallback = display.Dispose,
+ ExtraFeatures = new Dictionary
+ {
+ [typeof(IGlPlatformSurfaceRenderTargetFactory)] = new AngleD3DTextureFeature()
+ }
+ });
+ success = true;
+ return rv;
+ }
+ finally
+ {
+ if (!success)
+ display.Dispose();
+ }
+ }
+
+ public IPlatformGraphicsContext GetSharedContext()
+ {
+ if (!UsesSharedContext)
+ throw new InvalidOperationException();
+ if (_sharedContext == null || _sharedContext.IsLost)
+ {
+ _sharedContext?.Dispose();
+ _sharedContext = null;
+ _sharedContext = _sharedDisplay.CreateContext(new EglContextOptions());
+ }
+
+ return _sharedContext;
+ }
+
+ public AngleWin32PlatformGraphics(Win32AngleEglInterface egl, AngleWin32EglDisplay display)
+ : this(egl, display.PlatformApi)
+ {
+ _sharedDisplay = display;
+ }
+
+ public AngleWin32PlatformGraphics(Win32AngleEglInterface egl, AngleOptions.PlatformApi api)
+ {
+ _egl = egl;
+ PlatformApi = api;
+ }
+
+
+ public static AngleWin32PlatformGraphics TryCreate(AngleOptions options)
+ {
+
+
+ Win32AngleEglInterface egl;
+ try
+ {
+ egl = new();
+ }
+ catch (Exception e)
+ {
+ Logger.TryGet(LogEventLevel.Error, "OpenGL")
+ ?.Log(null, "Unable to load ANGLE: {0}", e);
+ return null;
+ }
+
+ return new AngleWin32PlatformGraphics(egl, AngleWin32EglDisplay.CreateSharedD3D11Display(egl));
+
+ foreach (var api in (options?.AllowedPlatformApis ?? new []
+ {
+ AngleOptions.PlatformApi.DirectX11
+ }).Distinct())
+ if (api == AngleOptions.PlatformApi.DirectX11)
+ {
+ try
+ {
+ using var display = AngleWin32EglDisplay.CreateD3D11Display(egl);
+ using var ctx = display.CreateContext(new EglContextOptions());
+ ctx.MakeCurrent().Dispose();
+ }
+ catch (Exception e)
+ {
+ Logger.TryGet(LogEventLevel.Error, "OpenGL")
+ ?.Log(null, "Unable to initialize ANGLE-based rendering with DirectX11 : {0}", e);
+ continue;
+ }
+
+ return new AngleWin32PlatformGraphics(egl, AngleOptions.PlatformApi.DirectX11);
+ }
+ else
+ {
+ AngleWin32EglDisplay sharedDisplay = null;
+ try
+ {
+ sharedDisplay = AngleWin32EglDisplay.CreateD3D9Display(egl);
+ using (var ctx = sharedDisplay.CreateContext(new EglContextOptions()))
+ ctx.MakeCurrent().Dispose();
+
+ return new AngleWin32PlatformGraphics(egl, sharedDisplay);
+ }
+ catch (Exception e)
+ {
+ sharedDisplay?.Dispose();
+ Logger.TryGet(LogEventLevel.Error, "OpenGL")
+ ?.Log(null, "Unable to initialize ANGLE-based rendering with DirectX9 : {0}", e);
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs b/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs
index d6633ddb61..d535efc5b7 100644
--- a/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs
+++ b/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs
@@ -1,6 +1,10 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Reactive.Disposables;
+using System.Threading;
using Avalonia.OpenGL;
+using Avalonia.Platform;
using Avalonia.Win32.Interop;
using static Avalonia.Win32.Interop.UnmanagedMethods;
using static Avalonia.Win32.OpenGl.WglConsts;
@@ -43,8 +47,9 @@ namespace Avalonia.Win32.OpenGl
public void Dispose()
{
wglDeleteContext(_context);
- ReleaseDC(_hWnd, _dc);
- DestroyWindow(_hWnd);
+ WglGdiResourceManager.ReleaseDC(_hWnd, _dc);
+ WglGdiResourceManager.DestroyWindow(_hWnd);
+ IsLost = true;
}
public GlVersion Version { get; }
@@ -55,17 +60,26 @@ namespace Avalonia.Win32.OpenGl
private bool IsCurrent => wglGetCurrentContext() == _context && wglGetCurrentDC() == _dc;
public IDisposable MakeCurrent()
{
+ if (IsLost)
+ throw new PlatformGraphicsContextLostException();
if(IsCurrent)
return Disposable.Empty;
return new WglRestoreContext(_dc, _context, _lock);
}
+ public bool IsLost { get; private set; }
public IDisposable EnsureCurrent() => MakeCurrent();
+ internal IDisposable Lock()
+ {
+ Monitor.Enter(_lock);
+ return Disposable.Create(_lock, Monitor.Exit);
+ }
+
public IntPtr CreateConfiguredDeviceContext(IntPtr hWnd)
{
- var dc = GetDC(hWnd);
+ var dc = WglGdiResourceManager.GetDC(hWnd);
var fmt = _formatDescriptor;
SetPixelFormat(dc, _pixelFormat, ref fmt);
return dc;
@@ -81,5 +95,14 @@ namespace Avalonia.Win32.OpenGl
|| _sharedWith == context
|| _sharedWith != null && _sharedWith == c._sharedWith;
}
+
+ public bool CanCreateSharedContext => true;
+ public IGlContext CreateSharedContext(IEnumerable preferredVersions = null)
+ {
+ var versions = preferredVersions?.Append(Version).ToArray() ?? new[] { Version };
+ return WglDisplay.CreateContext(versions, _sharedWith ?? this);
+ }
+
+ public object TryGetFeature(Type featureType) => null;
}
}
diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs b/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs
index bc27589689..9a1963a97a 100644
--- a/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs
+++ b/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs
@@ -1,6 +1,7 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.OpenGL;
+using Avalonia.Threading;
using Avalonia.Win32.Interop;
using static Avalonia.Win32.Interop.UnmanagedMethods;
using static Avalonia.Win32.OpenGl.WglConsts;
@@ -9,8 +10,6 @@ namespace Avalonia.Win32.OpenGl
internal class WglDisplay
{
private static bool? _initialized;
- private static ushort _windowClass;
- private static readonly WndProc _wndProcDelegate = WndProc;
private static readonly DebugCallbackDelegate _debugCallback = DebugCallback;
private static IntPtr _bootstrapContext;
@@ -44,18 +43,9 @@ namespace Avalonia.Win32.OpenGl
}
static bool InitializeCore()
{
- var wndClassEx = new WNDCLASSEX
- {
- cbSize = Marshal.SizeOf(),
- hInstance = GetModuleHandle(null),
- lpfnWndProc = _wndProcDelegate,
- lpszClassName = "AvaloniaGlWindow-" + Guid.NewGuid(),
- style = (int)ClassStyles.CS_OWNDC
- };
-
- _windowClass = RegisterClassEx(ref wndClassEx);
- _bootstrapWindow = CreateOffscreenWindow();
- _bootstrapDc = GetDC(_bootstrapWindow);
+ Dispatcher.UIThread.VerifyAccess();
+ _bootstrapWindow = WglGdiResourceManager.CreateOffscreenWindow();
+ _bootstrapDc = WglGdiResourceManager.GetDC(_bootstrapWindow);
_defaultPfd = new PixelFormatDescriptor
{
Size = (ushort)Marshal.SizeOf(),
@@ -105,17 +95,11 @@ namespace Avalonia.Win32.OpenGl
DescribePixelFormat(_bootstrapDc, formats[0], Marshal.SizeOf(), ref _defaultPfd);
_defaultPixelFormat = formats[0];
}
-
-
+
wglMakeCurrent(IntPtr.Zero, IntPtr.Zero);
return true;
}
-
- static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
- {
- return DefWindowProc(hWnd, msg, wParam, lParam);
- }
-
+
private static void DebugCallback(int source, int type, int id, int severity, int len, IntPtr message, IntPtr userparam)
{
var err = Marshal.PtrToStringAnsi(message, len);
@@ -131,27 +115,32 @@ namespace Avalonia.Win32.OpenGl
using (new WglRestoreContext(_bootstrapDc, _bootstrapContext, null))
{
- var window = CreateOffscreenWindow();
- var dc = GetDC(window);
+ var window = WglGdiResourceManager.CreateOffscreenWindow();
+ var dc = WglGdiResourceManager.GetDC(window);
SetPixelFormat(dc, _defaultPixelFormat, ref _defaultPfd);
foreach (var version in versions)
{
if(version.Type != GlProfileType.OpenGL)
continue;
- var context = WglCreateContextAttribsArb(dc, shareContext?.Handle ?? IntPtr.Zero,
- new[]
- {
- // major
- WGL_CONTEXT_MAJOR_VERSION_ARB, version.Major,
- // minor
- WGL_CONTEXT_MINOR_VERSION_ARB, version.Minor,
- // core profile
- WGL_CONTEXT_PROFILE_MASK_ARB, 1,
- // debug
- // WGL_CONTEXT_FLAGS_ARB, 1,
- // end
- 0, 0
- });
+ IntPtr context;
+ using (shareContext?.Lock())
+ {
+ context = WglCreateContextAttribsArb(dc, shareContext?.Handle ?? IntPtr.Zero,
+ new[]
+ {
+ // major
+ WGL_CONTEXT_MAJOR_VERSION_ARB, version.Major,
+ // minor
+ WGL_CONTEXT_MINOR_VERSION_ARB, version.Minor,
+ // core profile
+ WGL_CONTEXT_PROFILE_MASK_ARB, 1,
+ // debug
+ // WGL_CONTEXT_FLAGS_ARB, 1,
+ // end
+ 0, 0
+ });
+ }
+
using(new WglRestoreContext(dc, context, null))
GlDebugMessageCallback(Marshal.GetFunctionPointerForDelegate(_debugCallback), IntPtr.Zero);
if (context != IntPtr.Zero)
@@ -159,26 +148,13 @@ namespace Avalonia.Win32.OpenGl
_defaultPixelFormat, _defaultPfd);
}
- ReleaseDC(window, dc);
- DestroyWindow(window);
+ WglGdiResourceManager.ReleaseDC(window, dc);
+ WglGdiResourceManager.DestroyWindow(window);
return null;
}
}
- static IntPtr CreateOffscreenWindow() =>
- CreateWindowEx(
- 0,
- _windowClass,
- null,
- (int)WindowStyles.WS_OVERLAPPEDWINDOW,
- 0,
- 0,
- 640,
- 480,
- IntPtr.Zero,
- IntPtr.Zero,
- IntPtr.Zero,
- IntPtr.Zero);
+
}
}
diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglGdiResourceManager.cs b/src/Windows/Avalonia.Win32/OpenGl/WglGdiResourceManager.cs
new file mode 100644
index 0000000000..ed40488c70
--- /dev/null
+++ b/src/Windows/Avalonia.Win32/OpenGl/WglGdiResourceManager.cs
@@ -0,0 +1,166 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia.Win32.Interop;
+
+namespace Avalonia.Win32.OpenGl;
+
+///
+/// - ReleaseDC can only happen from the same thread that has called GetDC
+/// - When thread exits all of its windows and HDCs are getting destroyed
+/// - We need to create OpenGL context (require a window and an HDC) and render targets (require an HDC) from thread pool threads
+///
+/// So this class hosts a dedicated thread for managing offscreen windows and HDCs for OpenGL
+///
+
+internal class WglGdiResourceManager
+{
+ class GetDCOp
+ {
+ public IntPtr Window;
+ public TaskCompletionSource Result;
+ }
+
+ class ReleaseDCOp
+ {
+ public IntPtr Window;
+ public IntPtr DC;
+ public TaskCompletionSource Result;
+ }
+
+ class CreateWindowOp
+ {
+ public TaskCompletionSource Result;
+ }
+
+ class DestroyWindowOp
+ {
+ public IntPtr Window;
+ public TaskCompletionSource Result;
+ }
+
+ private static readonly Queue s_Queue = new();
+ private static readonly AutoResetEvent s_Event = new(false);
+ private static readonly ushort s_WindowClass;
+ private static readonly UnmanagedMethods.WndProc s_wndProcDelegate = WndProc;
+
+ static void Worker()
+ {
+ while (true)
+ {
+ s_Event.WaitOne();
+ lock (s_Queue)
+ {
+ if(s_Queue.Count == 0)
+ continue;
+ var job = s_Queue.Dequeue();
+ if (job is GetDCOp getDc)
+ getDc.Result.TrySetResult(UnmanagedMethods.GetDC(getDc.Window));
+ else if (job is ReleaseDCOp releaseDc)
+ {
+ UnmanagedMethods.ReleaseDC(releaseDc.Window, releaseDc.DC);
+ releaseDc.Result.SetResult(null);
+ }
+ else if (job is CreateWindowOp createWindow)
+ createWindow.Result.TrySetResult(UnmanagedMethods.CreateWindowEx(
+ 0,
+ s_WindowClass,
+ null,
+ (int)UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW,
+ 0,
+ 0,
+ 640,
+ 480,
+ IntPtr.Zero,
+ IntPtr.Zero,
+ IntPtr.Zero,
+ IntPtr.Zero));
+ else if (job is DestroyWindowOp destroyWindow)
+ {
+ UnmanagedMethods.DestroyWindow(destroyWindow.Window);
+ destroyWindow.Result.TrySetResult(null);
+ }
+ }
+ }
+ }
+
+ static WglGdiResourceManager()
+ {
+ var wndClassEx = new UnmanagedMethods.WNDCLASSEX
+ {
+ cbSize = Marshal.SizeOf(),
+ hInstance = UnmanagedMethods.GetModuleHandle(null),
+ lpfnWndProc = s_wndProcDelegate,
+ lpszClassName = "AvaloniaGlWindow-" + Guid.NewGuid(),
+ style = (int)UnmanagedMethods.ClassStyles.CS_OWNDC
+ };
+
+ s_WindowClass = UnmanagedMethods.RegisterClassEx(ref wndClassEx);
+ var th = new Thread(Worker) { IsBackground = true, Name = "Win32 OpenGL HDC manager" };
+ // This makes CLR to automatically pump the event queue from WaitOne
+ th.SetApartmentState(ApartmentState.STA);
+ th.Start();
+ }
+
+
+
+ static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
+ {
+ return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam);
+ }
+
+ public static IntPtr CreateOffscreenWindow()
+ {
+ var tcs = new TaskCompletionSource();
+ lock(s_Queue)
+ s_Queue.Enqueue(new CreateWindowOp()
+ {
+ Result = tcs
+ });
+ s_Event.Set();
+ return tcs.Task.Result;
+ }
+
+ public static IntPtr GetDC(IntPtr hWnd)
+ {
+ var tcs = new TaskCompletionSource();
+ lock(s_Queue)
+ s_Queue.Enqueue(new GetDCOp
+ {
+ Window = hWnd,
+ Result = tcs
+ });
+ s_Event.Set();
+ return tcs.Task.Result;
+ }
+
+ public static void ReleaseDC(IntPtr hWnd, IntPtr hDC)
+ {
+ var tcs = new TaskCompletionSource();
+ lock(s_Queue)
+ s_Queue.Enqueue(new ReleaseDCOp()
+ {
+ Window = hWnd,
+ DC = hDC,
+ Result = tcs
+ });
+ s_Event.Set();
+ tcs.Task.Wait();
+ }
+
+ public static void DestroyWindow(IntPtr hWnd)
+ {
+ var tcs = new TaskCompletionSource();
+ lock(s_Queue)
+ s_Queue.Enqueue(new DestroyWindowOp()
+ {
+ Window = hWnd,
+ Result = tcs
+ });
+ s_Event.Set();
+ tcs.Task.Wait();
+
+ }
+}
diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs b/src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs
index 72bcffd447..6d3d2d28c4 100644
--- a/src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs
+++ b/src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs
@@ -11,18 +11,16 @@ namespace Avalonia.Win32.OpenGl
class WglGlPlatformSurface: IGlPlatformSurface
{
- private readonly WglContext _context;
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
- public WglGlPlatformSurface(WglContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info)
+ public WglGlPlatformSurface( EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info)
{
- _context = context;
_info = info;
}
- public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
+ public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context)
{
- return new RenderTarget(_context, _info);
+ return new RenderTarget((WglContext)context, _info);
}
class RenderTarget : IGlPlatformSurfaceRenderTarget
@@ -39,7 +37,7 @@ namespace Avalonia.Win32.OpenGl
public void Dispose()
{
- UnmanagedMethods.ReleaseDC(_hdc, _info.Handle);
+ WglGdiResourceManager.ReleaseDC(_info.Handle, _hdc);
}
public IGlPlatformSurfaceRenderingSession BeginDraw()
diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs b/src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs
index 1d0880a468..39dd330d52 100644
--- a/src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs
+++ b/src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs
@@ -6,15 +6,13 @@ using Avalonia.Platform;
namespace Avalonia.Win32.OpenGl
{
- class WglPlatformOpenGlInterface : IPlatformOpenGlInterface
+ class WglPlatformOpenGlInterface : IPlatformGraphics
{
public WglContext PrimaryContext { get; }
- IPlatformGpuContext IPlatformGpu.PrimaryContext => PrimaryContext;
- IGlContext IPlatformOpenGlInterface.PrimaryContext => PrimaryContext;
- public IGlContext CreateSharedContext() => WglDisplay.CreateContext(new[] { PrimaryContext.Version }, PrimaryContext);
-
- public bool CanShareContexts => true;
- public bool CanCreateContexts => true;
+ public bool UsesSharedContext => false;
+ IPlatformGraphicsContext IPlatformGraphics.CreateContext() => CreateContext();
+ public IPlatformGraphicsContext GetSharedContext() => throw new NotSupportedException();
+
public IGlContext CreateContext() => WglDisplay.CreateContext(new[] { PrimaryContext.Version }, null);
private WglPlatformOpenGlInterface(WglContext primary)
diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs b/src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs
index 265f078a5c..b145ffbb37 100644
--- a/src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs
+++ b/src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs
@@ -1,4 +1,5 @@
using System;
+using System.Runtime.InteropServices;
using System.Threading;
using Avalonia.OpenGL;
using static Avalonia.Win32.Interop.UnmanagedMethods;
@@ -22,9 +23,11 @@ namespace Avalonia.Win32.OpenGl
if (!wglMakeCurrent(gc, context))
{
+ var lastError = Marshal.GetLastWin32Error();
+ var caps = GetDeviceCaps(gc, (DEVICECAP)12);
if(monitor != null && takeMonitor)
Monitor.Exit(monitor);
- throw new OpenGlException("Unable to make the context current");
+ throw new OpenGlException($"Unable to make the context current: {lastError}, DC valid: {caps != 0}");
}
}
diff --git a/src/Windows/Avalonia.Win32/Win32GlManager.cs b/src/Windows/Avalonia.Win32/Win32GlManager.cs
index 204acc82c9..25ea060576 100644
--- a/src/Windows/Avalonia.Win32/Win32GlManager.cs
+++ b/src/Windows/Avalonia.Win32/Win32GlManager.cs
@@ -2,8 +2,9 @@ using Avalonia.OpenGL;
using Avalonia.OpenGL.Angle;
using Avalonia.OpenGL.Egl;
using Avalonia.Platform;
-using Avalonia.Win32.DxgiSwapchain;
+using Avalonia.Win32.DirectX;
using Avalonia.Win32.OpenGl;
+using Avalonia.Win32.OpenGl.Angle;
using Avalonia.Win32.WinRT.Composition;
namespace Avalonia.Win32
@@ -11,15 +12,15 @@ namespace Avalonia.Win32
static class Win32GlManager
{
- public static IPlatformOpenGlInterface Initialize()
+ public static IPlatformGraphics Initialize()
{
var gl = InitializeCore();
- AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl);
- AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl);
+ AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl);
+ AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl);
return gl;
}
- static IPlatformOpenGlInterface InitializeCore()
+ static IPlatformGraphics InitializeCore()
{
var opts = AvaloniaLocator.Current.GetService() ?? new Win32PlatformOptions();
@@ -31,28 +32,18 @@ namespace Avalonia.Win32
if (opts.AllowEglInitialization ?? Win32Platform.WindowsVersion > PlatformConstants.Windows7)
{
- var egl = EglPlatformOpenGlInterface.TryCreate(() => new AngleWin32EglDisplay());
+ var egl = AngleWin32PlatformGraphics.TryCreate(AvaloniaLocator.Current.GetService() ??
+ new());
- if (egl != null)
+ if (egl != null && egl.PlatformApi == AngleOptions.PlatformApi.DirectX11)
{
- if (opts.EglRendererBlacklist != null)
- {
- foreach (var item in opts.EglRendererBlacklist)
- {
- if (egl.PrimaryEglContext.GlInterface.Renderer.Contains(item))
- {
- return null;
- }
- }
- }
-
if (opts.UseWindowsUIComposition)
{
- WinUICompositorConnection.TryCreateAndRegister(egl, opts.CompositionBackdropCornerRadius);
+ WinUiCompositorConnection.TryCreateAndRegister();
}
else if (opts.UseLowLatencyDxgiSwapChain)
{
- DxgiConnection.TryCreateAndRegister(egl);
+ DxgiConnection.TryCreateAndRegister();
}
}
diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs
index 63cad9679a..3f220f0f09 100644
--- a/src/Windows/Avalonia.Win32/Win32Platform.cs
+++ b/src/Windows/Avalonia.Win32/Win32Platform.cs
@@ -19,6 +19,7 @@ using Avalonia.Threading;
using Avalonia.Utilities;
using Avalonia.Win32.Input;
using Avalonia.Win32.Interop;
+using JetBrains.Annotations;
using static Avalonia.Win32.Interop.UnmanagedMethods;
namespace Avalonia
@@ -59,11 +60,6 @@ namespace Avalonia
/// GPU rendering will not be enabled if this is set to false.
///
public bool? AllowEglInitialization { get; set; }
-
- public IList EglRendererBlacklist { get; set; } = new List
- {
- "Microsoft Basic Render"
- };
///
/// Embeds popups to the window when set to true. The default value is false.
@@ -106,6 +102,11 @@ namespace Avalonia
/// which if active will override this setting.
///
public bool UseLowLatencyDxgiSwapChain { get; set; } = false;
+
+ ///
+ /// Provides a way to use a custom-implemented graphics context such as a custom ISkiaGpu
+ ///
+ [CanBeNull] public IPlatformGraphics CustomPlatformGraphics { get; set; }
}
}
@@ -139,6 +140,7 @@ namespace Avalonia.Win32
public static Win32PlatformOptions Options { get; private set; }
internal static Compositor Compositor { get; private set; }
+ internal static PlatformRenderInterfaceContextManager RenderInterface { get; private set; }
public static void Initialize()
{
@@ -169,16 +171,19 @@ namespace Avalonia.Win32
.Bind().ToConstant(new NonPumpingSyncContext.HelperImpl())
.Bind().ToConstant(new WindowsMountedVolumeInfoProvider())
.Bind().ToConstant(s_instance);
-
- var gl = Win32GlManager.Initialize();
-
+
_uiThread = Thread.CurrentThread;
+ var platformGraphics = options?.CustomPlatformGraphics
+ ?? Win32GlManager.Initialize();
+
if (OleContext.Current != null)
AvaloniaLocator.CurrentMutable.Bind().ToSingleton();
if (Options.UseCompositor)
- Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), gl);
+ Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), platformGraphics);
+ else
+ RenderInterface = new PlatformRenderInterfaceContextManager(platformGraphics);
}
public bool HasMessages()
diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositedWindow.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositedWindow.cs
deleted file mode 100644
index ef3de9fbe1..0000000000
--- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositedWindow.cs
+++ /dev/null
@@ -1,109 +0,0 @@
-using System;
-using System.Numerics;
-using System.Reactive.Disposables;
-using System.Threading;
-using Avalonia.MicroCom;
-using Avalonia.OpenGL;
-using Avalonia.OpenGL.Egl;
-using Avalonia.Win32.Interop;
-using MicroCom.Runtime;
-
-namespace Avalonia.Win32.WinRT.Composition
-{
- public class WinUICompositedWindow : IDisposable
- {
- private EglContext _syncContext;
- private readonly object _pumpLock;
- private readonly IVisual _micaVisual;
- private readonly ICompositionRoundedRectangleGeometry _roundedRectangleGeometry;
- private readonly IVisual _blurVisual;
- private ICompositionTarget _compositionTarget;
- private IVisual _contentVisual;
- private ICompositionDrawingSurfaceInterop _surfaceInterop;
- private PixelSize _size;
-
- private static Guid IID_ID3D11Texture2D = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c");
- private ICompositor _compositor;
-
-
- internal WinUICompositedWindow(EglContext syncContext,
- ICompositor compositor,
- object pumpLock,
- ICompositionTarget compositionTarget,
- ICompositionDrawingSurfaceInterop surfaceInterop,
- IVisual contentVisual, IVisual blurVisual, IVisual micaVisual,
- ICompositionRoundedRectangleGeometry roundedRectangleGeometry)
- {
- _compositor = compositor.CloneReference();
- _syncContext = syncContext;
- _pumpLock = pumpLock;
- _micaVisual = micaVisual;
- _roundedRectangleGeometry = roundedRectangleGeometry;
- _blurVisual = blurVisual.CloneReference();
- _compositionTarget = compositionTarget.CloneReference();
- _contentVisual = contentVisual.CloneReference();
- _surfaceInterop = surfaceInterop.CloneReference();
- }
-
-
- public void ResizeIfNeeded(PixelSize size)
- {
- using (_syncContext.EnsureLocked())
- {
- if (_size != size)
- {
- _surfaceInterop.Resize(new UnmanagedMethods.POINT { X = size.Width, Y = size.Height });
- _contentVisual.SetSize(new Vector2(size.Width, size.Height));
- _roundedRectangleGeometry?.SetSize(new Vector2(size.Width, size.Height));
- _size = size;
- }
- }
- }
-
- public unsafe IUnknown BeginDrawToTexture(out PixelPoint offset)
- {
- if (!_syncContext.IsCurrent)
- throw new InvalidOperationException();
-
- var iid = IID_ID3D11Texture2D;
- void* pTexture;
- var off = _surfaceInterop.BeginDraw(null, &iid, &pTexture);
- offset = new PixelPoint(off.X, off.Y);
- return MicroComRuntime.CreateProxyFor(pTexture, true);
- }
-
- public void EndDraw()
- {
- if (!_syncContext.IsCurrent)
- throw new InvalidOperationException();
- _surfaceInterop.EndDraw();
- }
-
- public void SetBlur(BlurEffect blurEffect)
- {
- using (_syncContext.EnsureLocked())
- {
- _blurVisual.SetIsVisible(blurEffect == BlurEffect.Acrylic ? 1 : 0);
- _micaVisual?.SetIsVisible(blurEffect == BlurEffect.Mica ? 1 : 0);
- }
- }
-
- public IDisposable BeginTransaction()
- {
- Monitor.Enter(_pumpLock);
- return Disposable.Create(() => Monitor.Exit(_pumpLock));
- }
-
- public void Dispose()
- {
- if (_syncContext == null)
- {
- _compositor.Dispose();
- _blurVisual.Dispose();
- _contentVisual.Dispose();
- _surfaceInterop.Dispose();
- _compositionTarget.Dispose();
- }
- }
- }
-}
diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs
deleted file mode 100644
index 6da17b8ea5..0000000000
--- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs
+++ /dev/null
@@ -1,311 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Numerics;
-using System.Runtime.InteropServices;
-using System.Threading;
-using System.Threading.Tasks;
-using Avalonia.Logging;
-using Avalonia.MicroCom;
-using Avalonia.OpenGL;
-using Avalonia.OpenGL.Angle;
-using Avalonia.OpenGL.Egl;
-using Avalonia.Rendering;
-using Avalonia.Win32.Interop;
-using MicroCom.Runtime;
-
-namespace Avalonia.Win32.WinRT.Composition
-{
- class WinUICompositorConnection : IRenderTimer
- {
- public static readonly Version MinHostBackdropVersion = new Version(10, 0, 22000);
- private readonly float? _backdropCornerRadius;
- private readonly EglContext _syncContext;
- private readonly ICompositionBrush _micaBrush;
- private ICompositor _compositor;
- private ICompositor5 _compositor5;
- private ICompositorInterop _compositorInterop;
- private AngleWin32EglDisplay _angle;
- private ICompositionGraphicsDevice _device;
- private EglPlatformOpenGlInterface _gl;
- private ICompositorDesktopInterop _compositorDesktopInterop;
- private ICompositionBrush _blurBrush;
- private object _pumpLock = new object();
-
- public WinUICompositorConnection(EglPlatformOpenGlInterface gl, object pumpLock, float? backdropCornerRadius)
- {
- _gl = gl;
- _pumpLock = pumpLock;
- _backdropCornerRadius = backdropCornerRadius;
- _syncContext = _gl.PrimaryEglContext;
- _angle = (AngleWin32EglDisplay)_gl.Display;
- _compositor = NativeWinRTMethods.CreateInstance("Windows.UI.Composition.Compositor");
- _compositor5 = _compositor.QueryInterface();
- _compositorInterop = _compositor.QueryInterface();
- _compositorDesktopInterop = _compositor.QueryInterface();
- using var device = MicroComRuntime.CreateProxyFor(_angle.GetDirect3DDevice(), true);
-
- _device = _compositorInterop.CreateGraphicsDevice(device);
- _blurBrush = CreateAcrylicBlurBackdropBrush();
- _micaBrush = CreateMicaBackdropBrush();
- }
-
- public EglPlatformOpenGlInterface Egl => _gl;
-
- static bool TryCreateAndRegisterCore(EglPlatformOpenGlInterface angle, float? backdropCornerRadius)
- {
- var tcs = new TaskCompletionSource();
- var pumpLock = new object();
- var th = new Thread(() =>
- {
- WinUICompositorConnection connect;
- try
- {
- NativeWinRTMethods.CreateDispatcherQueueController(new NativeWinRTMethods.DispatcherQueueOptions
- {
- apartmentType = NativeWinRTMethods.DISPATCHERQUEUE_THREAD_APARTMENTTYPE.DQTAT_COM_NONE,
- dwSize = Marshal.SizeOf(),
- threadType = NativeWinRTMethods.DISPATCHERQUEUE_THREAD_TYPE.DQTYPE_THREAD_CURRENT
- });
- connect = new WinUICompositorConnection(angle, pumpLock, backdropCornerRadius);
- AvaloniaLocator.CurrentMutable.BindToSelf(connect);
- AvaloniaLocator.CurrentMutable.Bind().ToConstant(connect);
- tcs.SetResult(true);
-
- }
- catch (Exception e)
- {
- tcs.SetException(e);
- return;
- }
- connect.RunLoop();
- })
- {
- IsBackground = true
- };
- th.SetApartmentState(ApartmentState.STA);
- th.Start();
- return tcs.Task.Result;
- }
-
- class RunLoopHandler : IAsyncActionCompletedHandler, IMicroComShadowContainer
- {
- private readonly WinUICompositorConnection _parent;
- private Stopwatch _st = Stopwatch.StartNew();
-
- public RunLoopHandler(WinUICompositorConnection parent)
- {
- _parent = parent;
- }
- public void Dispose()
- {
-
- }
-
- public void Invoke(IAsyncAction asyncInfo, AsyncStatus asyncStatus)
- {
- _parent.Tick?.Invoke(_st.Elapsed);
- using var act = _parent._compositor5.RequestCommitAsync();
- act.SetCompleted(this);
- }
-
- public MicroComShadow Shadow { get; set; }
- public void OnReferencedFromNative()
- {
- }
-
- public void OnUnreferencedFromNative()
- {
- }
- }
-
- private void RunLoop()
- {
- var cts = new CancellationTokenSource();
- AppDomain.CurrentDomain.ProcessExit += (sender, args) =>
- cts.Cancel();
-
- using (var act = _compositor5.RequestCommitAsync())
- act.SetCompleted(new RunLoopHandler(this));
-
- while (!cts.IsCancellationRequested)
- {
- UnmanagedMethods.GetMessage(out var msg, IntPtr.Zero, 0, 0);
- lock (_pumpLock)
- UnmanagedMethods.DispatchMessage(ref msg);
- }
- }
-
- public static void TryCreateAndRegister(EglPlatformOpenGlInterface angle,
- float? backdropCornerRadius)
- {
- const int majorRequired = 10;
- const int buildRequired = 17134;
-
- var majorInstalled = Win32Platform.WindowsVersion.Major;
- var buildInstalled = Win32Platform.WindowsVersion.Build;
-
- if (majorInstalled >= majorRequired &&
- buildInstalled >= buildRequired)
- {
- try
- {
- TryCreateAndRegisterCore(angle, backdropCornerRadius);
- return;
- }
- catch (Exception e)
- {
- Logger.TryGet(LogEventLevel.Error, "WinUIComposition")
- ?.Log(null, "Unable to initialize WinUI compositor: {0}", e);
-
- }
- }
-
- var osVersionNotice =
- $"Windows {majorRequired} Build {buildRequired} is required. Your machine has Windows {majorInstalled} Build {buildInstalled} installed.";
-
- Logger.TryGet(LogEventLevel.Warning, "WinUIComposition")?.Log(null,
- $"Unable to initialize WinUI compositor: {osVersionNotice}");
- }
-
-
- public WinUICompositedWindow CreateWindow(IntPtr hWnd)
- {
- using var sc = _syncContext.EnsureLocked();
- using var desktopTarget = _compositorDesktopInterop.CreateDesktopWindowTarget(hWnd, 0);
- using var target = desktopTarget.QueryInterface();
- using var device2 = _device.QueryInterface();
-
- using var drawingSurface = device2.CreateDrawingSurface2(new UnmanagedMethods.SIZE(), DirectXPixelFormat.B8G8R8A8UIntNormalized,
- DirectXAlphaMode.Premultiplied);
- using var surface = drawingSurface.QueryInterface();
- using var surfaceInterop = drawingSurface.QueryInterface();
-
- using var surfaceBrush = _compositor.CreateSurfaceBrushWithSurface(surface);
- using var brush = surfaceBrush.QueryInterface();
-
- using var spriteVisual = _compositor.CreateSpriteVisual();
- spriteVisual.SetBrush(brush);
- using var visual = spriteVisual.QueryInterface();
- using var visual2 = spriteVisual.QueryInterface();
- using var container = _compositor.CreateContainerVisual();
- using var containerVisual = container.QueryInterface();
- using var containerVisual2 = container.QueryInterface();
- containerVisual2.SetRelativeSizeAdjustment(new Vector2(1, 1));
- using var containerChildren = container.Children;
-
- target.SetRoot(containerVisual);
-
- using var blur = CreateBlurVisual(_blurBrush);
- IVisual mica = null;
- if (_micaBrush != null)
- {
- mica = CreateBlurVisual(_micaBrush);
- containerChildren.InsertAtTop(mica);
- }
-
- var compositionRoundedRectangleGeometry = ClipVisual(blur, mica);
-
- containerChildren.InsertAtTop(blur);
- containerChildren.InsertAtTop(visual);
-
- return new WinUICompositedWindow(_syncContext, _compositor, _pumpLock, target, surfaceInterop, visual,
- blur, mica, compositionRoundedRectangleGeometry);
- }
-
- private ICompositionBrush CreateMicaBackdropBrush()
- {
- if (Win32Platform.WindowsVersion.Build < 22000)
- return null;
-
- using var compositorWithBlurredWallpaperBackdropBrush =
- _compositor.QueryInterface();
- using var blurredWallpaperBackdropBrush =
- compositorWithBlurredWallpaperBackdropBrush?.TryCreateBlurredWallpaperBackdropBrush();
- using var micaBackdropBrush = blurredWallpaperBackdropBrush?.QueryInterface();
- return micaBackdropBrush.CloneReference();
- }
-
- private unsafe ICompositionBrush CreateAcrylicBlurBackdropBrush()
- {
- using var backDropParameterFactory = NativeWinRTMethods.CreateActivationFactory(
- "Windows.UI.Composition.CompositionEffectSourceParameter");
- using var backdropString = new HStringInterop("backdrop");
- using var backDropParameter =
- backDropParameterFactory.Create(backdropString.Handle);
- using var backDropParameterAsSource = backDropParameter.QueryInterface();
- var blurEffect = new WinUIGaussianBlurEffect(backDropParameterAsSource);
- using var blurEffectFactory = _compositor.CreateEffectFactory(blurEffect);
- using var compositionEffectBrush = blurEffectFactory.CreateBrush();
- using var backdropBrush = CreateBackdropBrush();
-
- var saturateEffect = new SaturationEffect(blurEffect);
- using var satEffectFactory = _compositor.CreateEffectFactory(saturateEffect);
- using var sat = satEffectFactory.CreateBrush();
- compositionEffectBrush.SetSourceParameter(backdropString.Handle, backdropBrush);
- return compositionEffectBrush.QueryInterface();
- }
-
- private ICompositionRoundedRectangleGeometry ClipVisual(params IVisual[] containerVisuals)
- {
- if (!_backdropCornerRadius.HasValue)
- return null;
- using var roundedRectangleGeometry = _compositor5.CreateRoundedRectangleGeometry();
- roundedRectangleGeometry.SetCornerRadius(new Vector2(_backdropCornerRadius.Value, _backdropCornerRadius.Value));
-
- using var compositor6 = _compositor.QueryInterface();
- using var compositionGeometry = roundedRectangleGeometry
- .QueryInterface();
-
- using var geometricClipWithGeometry =
- compositor6.CreateGeometricClipWithGeometry(compositionGeometry);
- foreach (var visual in containerVisuals)
- {
- visual?.SetClip(geometricClipWithGeometry.QueryInterface());
- }
-
- return roundedRectangleGeometry.CloneReference();
- }
-
- private unsafe IVisual CreateBlurVisual(ICompositionBrush compositionBrush)
- {
- using var spriteVisual = _compositor.CreateSpriteVisual();
- using var visual = spriteVisual.QueryInterface();
- using var visual2 = spriteVisual.QueryInterface();
-
-
- spriteVisual.SetBrush(compositionBrush);
- visual.SetIsVisible(0);
- visual2.SetRelativeSizeAdjustment(new Vector2(1.0f, 1.0f));
-
- return visual.CloneReference();
- }
-
- private ICompositionBrush CreateBackdropBrush()
- {
- ICompositionBackdropBrush brush = null;
- try
- {
- if (Win32Platform.WindowsVersion >= MinHostBackdropVersion)
- {
- using var compositor3 = _compositor.QueryInterface();
- brush = compositor3.CreateHostBackdropBrush();
- }
- else
- {
- using var compositor2 = _compositor.QueryInterface();
- brush = compositor2.CreateBackdropBrush();
- }
-
- return brush.QueryInterface();
- }
- finally
- {
- brush?.Dispose();
- }
- }
-
- public event Action Tick;
- public bool RunsInBackground => true;
- }
-}
diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs
new file mode 100644
index 0000000000..32019f4c15
--- /dev/null
+++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Numerics;
+using System.Reactive.Disposables;
+using System.Threading;
+using Avalonia.OpenGL.Egl;
+using Avalonia.Win32.Interop;
+using MicroCom.Runtime;
+
+namespace Avalonia.Win32.WinRT.Composition;
+
+internal class WinUiCompositedWindow : IDisposable
+{
+ public EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo WindowInfo { get; }
+ private readonly WinUiCompositionShared _shared;
+ private readonly ICompositionRoundedRectangleGeometry _compositionRoundedRectangleGeometry;
+ private readonly IVisual _mica;
+ private readonly IVisual _blur;
+ private readonly IVisual _visual;
+ private PixelSize _size;
+ private readonly ICompositionSurfaceBrush _surfaceBrush;
+ private readonly ICompositionTarget _target;
+
+ public void Dispose()
+ {
+ lock (_shared.SyncRoot)
+ {
+ _compositionRoundedRectangleGeometry?.Dispose();
+ _blur?.Dispose();
+ _mica?.Dispose();
+ _visual.Dispose();
+ _surfaceBrush.Dispose();
+ _target.Dispose();
+ }
+ }
+
+ public WinUiCompositedWindow(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info,
+ WinUiCompositionShared shared, float? backdropCornerRadius)
+ {
+ WindowInfo = info;
+ _shared = shared;
+ using var desktopTarget = shared.DesktopInterop.CreateDesktopWindowTarget(WindowInfo.Handle, 0);
+ _target = desktopTarget.QueryInterface();
+
+
+ using var container = shared.Compositor.CreateContainerVisual();
+ using var containerVisual = container.QueryInterface();
+ using var containerVisual2 = container.QueryInterface();
+ containerVisual2.SetRelativeSizeAdjustment(new Vector2(1, 1));
+ using var containerChildren = container.Children;
+
+ _target.SetRoot(containerVisual);
+
+ _blur = WinUiCompositionUtils.CreateBlurVisual(shared.Compositor, shared.BlurBrush);
+ if (shared.MicaBrush != null)
+ {
+ _mica = WinUiCompositionUtils.CreateBlurVisual(shared.Compositor, shared.MicaBrush);
+ containerChildren.InsertAtTop(_mica);
+ }
+
+ _compositionRoundedRectangleGeometry =
+ WinUiCompositionUtils.ClipVisual(shared.Compositor, backdropCornerRadius, _blur, _mica);
+
+ containerChildren.InsertAtTop(_blur);
+ using var spriteVisual = shared.Compositor.CreateSpriteVisual();
+ _visual = spriteVisual.QueryInterface();
+ containerChildren.InsertAtTop(_visual);
+
+ _surfaceBrush = shared.Compositor.CreateSurfaceBrush();
+ using var compositionBrush = _surfaceBrush.QueryInterface();
+ spriteVisual.SetBrush(compositionBrush);
+ _target.SetRoot(containerVisual);
+
+
+
+ }
+
+ public void SetSurface(ICompositionSurface surface) => _surfaceBrush.SetSurface(surface);
+
+ public void SetBlur(BlurEffect blurEffect)
+ {
+ lock (_shared.SyncRoot)
+ {
+
+ _blur.SetIsVisible(blurEffect == BlurEffect.Acrylic
+ || blurEffect == BlurEffect.Mica && _mica == null ?
+ 1 :
+ 0);
+ _mica?.SetIsVisible(blurEffect == BlurEffect.Mica ? 1 : 0);
+ }
+ }
+
+ public IDisposable BeginTransaction()
+ {
+ Monitor.Enter(_shared.SyncRoot);
+ return Disposable.Create(() => Monitor.Exit(_shared.SyncRoot));
+ }
+
+ public void ResizeIfNeeded(PixelSize size)
+ {
+ lock (_shared.SyncRoot)
+ {
+ if (_size != size)
+ {
+ _visual.SetSize(new Vector2(size.Width, size.Height));
+ _compositionRoundedRectangleGeometry?.SetSize(new Vector2(size.Width, size.Height));
+ _size = size;
+ }
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs
index fc04dcda26..9ee024f9ad 100644
--- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs
+++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs
@@ -1,119 +1,203 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.MicroCom;
+using Avalonia.OpenGL;
using Avalonia.OpenGL.Angle;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
+using Avalonia.Platform;
using Avalonia.Utilities;
+using Avalonia.Win32.DirectX;
using Avalonia.Win32.Interop;
+using Avalonia.Win32.OpenGl.Angle;
using MicroCom.Runtime;
namespace Avalonia.Win32.WinRT.Composition
{
- internal class WinUiCompositedWindowSurface : EglGlPlatformSurfaceBase, IBlurHost, IDisposable
+ internal class WinUiCompositedWindowSurface : IDirect3D11TexturePlatformSurface, IDisposable, IBlurHost
{
- private readonly WinUICompositorConnection _connection;
- private EglPlatformOpenGlInterface _egl;
- private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info;
- private IRef _window;
+ private readonly WinUiCompositionShared _shared;
+ private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
+ private WinUiCompositedWindow _window;
private BlurEffect _blurEffect;
- public WinUiCompositedWindowSurface(WinUICompositorConnection connection, IEglWindowGlPlatformSurfaceInfo info) : base()
+ public WinUiCompositedWindowSurface(WinUiCompositionShared shared, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info)
{
- _connection = connection;
- _egl = connection.Egl;
+ _shared = shared;
_info = info;
}
- public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
+ public IDirect3D11TextureRenderTarget CreateRenderTarget(IPlatformGraphicsContext context, IntPtr d3dDevice)
{
- using (_egl.PrimaryContext.EnsureCurrent())
- {
- if (_window?.Item == null)
- {
- _window = RefCountable.Create(_connection.CreateWindow(_info.Handle));
- _window.Item.SetBlur(_blurEffect);
- }
+ var cornerRadius = AvaloniaLocator.Current.GetService()
+ ?.CompositionBackdropCornerRadius;
+ _window ??= new WinUiCompositedWindow(_info, _shared, cornerRadius);
+ _window.SetBlur(_blurEffect);
- return new CompositionRenderTarget(_egl, _window, _info);
- }
+ return new WinUiCompositedWindowRenderTarget(context, _window, d3dDevice, _shared.Compositor);
+ }
+
+ public void Dispose()
+ {
+ _window?.Dispose();
+ _window = null;
+ }
+
+ public void SetBlur(BlurEffect enable)
+ {
+ _blurEffect = enable;
+ _window?.SetBlur(enable);
}
+ }
- class CompositionRenderTarget : EglPlatformSurfaceRenderTargetBase
+ internal class WinUiCompositedWindowRenderTarget : IDirect3D11TextureRenderTarget
+ {
+ private readonly IPlatformGraphicsContext _context;
+ private readonly WinUiCompositedWindow _window;
+ private readonly IUnknown _d3dDevice;
+ private readonly ICompositor _compositor;
+ private readonly ICompositorInterop _interop;
+ private readonly ICompositionGraphicsDevice _compositionDevice;
+ private readonly ICompositionGraphicsDevice2 _compositionDevice2;
+ private readonly ICompositionSurface _surface;
+ private PixelSize _size;
+ private bool _lost;
+ private readonly ICompositionDrawingSurfaceInterop _surfaceInterop;
+
+ public WinUiCompositedWindowRenderTarget(IPlatformGraphicsContext context,
+ WinUiCompositedWindow window, IntPtr device,
+ ICompositor compositor)
{
- private readonly EglPlatformOpenGlInterface _egl;
- private readonly IRef _window;
- private readonly IEglWindowGlPlatformSurfaceInfo _info;
-
- public CompositionRenderTarget(EglPlatformOpenGlInterface egl,
- IRef window,
- IEglWindowGlPlatformSurfaceInfo info)
- : base(egl)
+ _context = context;
+ _window = window;
+
+ try
{
- _egl = egl;
- _window = window.Clone();
- _info = info;
- _window.Item.ResizeIfNeeded(_info.Size);
+ _d3dDevice = MicroComRuntime.CreateProxyFor(device, false).CloneReference();
+ _compositor = compositor.CloneReference();
+ _interop = compositor.QueryInterface();
+ _compositionDevice = _interop.CreateGraphicsDevice(_d3dDevice);
+ _compositionDevice2 = _compositionDevice.QueryInterface();
+ _drawingSurface = _compositionDevice2.CreateDrawingSurface2(new UnmanagedMethods.SIZE(),
+ DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied);
+ _surface = _drawingSurface.QueryInterface();
+ _surfaceInterop = _drawingSurface.QueryInterface();
}
+ finally
+ {
+ if (_surfaceInterop == null)
+ Dispose();
+ }
+ }
- public override IGlPlatformSurfaceRenderingSession BeginDraw()
+ public void Dispose()
+ {
+ _surface?.Dispose();
+ _surfaceInterop?.Dispose();
+ _drawingSurface?.Dispose();
+ _compositionDevice2?.Dispose();
+ _compositionDevice?.Dispose();
+ _interop?.Dispose();
+ _compositor?.Dispose();
+ _d3dDevice?.Dispose();
+ }
+
+ public bool IsCorrupted => _context.IsLost || _lost;
+ private static Guid IID_ID3D11Texture2D = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c");
+ private readonly ICompositionDrawingSurface _drawingSurface;
+
+ public unsafe IDirect3D11TextureRenderTargetRenderSession BeginDraw()
+ {
+ if (IsCorrupted)
+ throw new RenderTargetCorruptedException();
+ var transaction = _window.BeginTransaction();
+ bool needsEndDraw = false;
+ try
{
- var contextLock = _egl.PrimaryEglContext.EnsureCurrent();
- IUnknown texture = null;
- EglSurface surface = null;
- IDisposable transaction = null;
- var success = false;
+ var size = _window.WindowInfo.Size;
+ var scale = _window.WindowInfo.Scaling;
+ _window.ResizeIfNeeded(size);
+ _window.SetSurface(_surface);
+
+ void* pTexture;
+ UnmanagedMethods.POINT off;
try
{
- if (_window?.Item == null)
- throw new ObjectDisposedException(GetType().FullName);
-
- var size = _info.Size;
- transaction = _window.Item.BeginTransaction();
- _window.Item.ResizeIfNeeded(size);
- texture = _window.Item.BeginDrawToTexture(out var offset);
-
- surface = ((AngleWin32EglDisplay) _egl.Display).WrapDirect3D11Texture(_egl,
- texture.GetNativeIntPtr(),
- offset.X, offset.Y, size.Width, size.Height);
-
- var res = base.BeginDraw(surface, _info, () =>
+ if (_size != size)
{
- surface?.Dispose();
- texture?.Dispose();
- _window.Item.EndDraw();
- transaction?.Dispose();
- contextLock?.Dispose();
- }, true);
- success = true;
- return res;
+ _surfaceInterop.Resize(new UnmanagedMethods.POINT
+ {
+ X = size.Width,
+ Y = size.Height
+ });
+ _size = size;
+ }
+ var iid = IID_ID3D11Texture2D;
+ off = _surfaceInterop.BeginDraw(null, &iid, &pTexture);
}
- finally
+ catch (Exception e)
{
- if (!success)
- {
- surface?.Dispose();
- texture?.Dispose();
- transaction?.Dispose();
- contextLock.Dispose();
- }
+ _lost = true;
+ throw new RenderTargetCorruptedException(e);
+ }
+
+ needsEndDraw = true;
+ var offset = new PixelPoint(off.X, off.Y);
+ using var texture = MicroComRuntime.CreateProxyFor(pTexture, true);
+
+ var session = new Session(_surfaceInterop, texture, transaction, _size, offset, scale);
+ transaction = null;
+ return session;
+ }
+ finally
+ {
+ if (transaction != null)
+ {
+ if (needsEndDraw)
+ _surfaceInterop.EndDraw();
+ transaction?.Dispose();
}
}
}
- public void SetBlur(BlurEffect blurEffect)
+ class Session : IDirect3D11TextureRenderTargetRenderSession
{
- _blurEffect = blurEffect;
- _window?.Item?.SetBlur(blurEffect);
- }
+ private readonly IDisposable _transaction;
+ private readonly PixelSize _size;
+ private readonly PixelPoint _offset;
+ private readonly double _scaling;
+ private readonly ICompositionDrawingSurfaceInterop _surfaceInterop;
+ private readonly IUnknown _texture;
- public void Dispose()
- {
- using (_egl.PrimaryEglContext.EnsureLocked())
+ public Session(ICompositionDrawingSurfaceInterop surfaceInterop, IUnknown texture, IDisposable transaction,
+ PixelSize size, PixelPoint offset, double scaling)
{
- _window?.Dispose();
- _window = null;
+ _transaction = transaction;
+ _size = size;
+ _offset = offset;
+ _scaling = scaling;
+ _surfaceInterop = surfaceInterop.CloneReference();
+ _texture = texture.CloneReference();
}
+
+ public void Dispose()
+ {
+ try
+ {
+ _texture.Dispose();
+ _surfaceInterop.EndDraw();
+ _surfaceInterop.Dispose();
+ }
+ finally
+ {
+ _transaction.Dispose();
+ }
+ }
+
+ public IntPtr D3D11Texture2D => _texture.GetNativeIntPtr();
+ public PixelSize Size => _size;
+ public PixelPoint Offset => _offset;
+ public double Scaling => _scaling;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs
new file mode 100644
index 0000000000..301e605e7a
--- /dev/null
+++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs
@@ -0,0 +1,34 @@
+using System;
+using MicroCom.Runtime;
+
+namespace Avalonia.Win32.WinRT.Composition;
+
+internal class WinUiCompositionShared : IDisposable
+{
+ public ICompositor Compositor { get; }
+ public ICompositor5 Compositor5 { get; }
+ public ICompositorDesktopInterop DesktopInterop { get; }
+ public ICompositionBrush BlurBrush;
+ public ICompositionBrush MicaBrush;
+ public object SyncRoot { get; } = new();
+
+ public static readonly Version MinHostBackdropVersion = new Version(10, 0, 22000);
+
+ public WinUiCompositionShared(ICompositor compositor)
+ {
+ Compositor = compositor.CloneReference();
+ Compositor5 = compositor.QueryInterface();
+ BlurBrush = WinUiCompositionUtils.CreateAcrylicBlurBackdropBrush(compositor);
+ MicaBrush = WinUiCompositionUtils.CreateMicaBackdropBrush(compositor);
+ DesktopInterop = compositor.QueryInterface();
+ }
+
+ public void Dispose()
+ {
+ BlurBrush.Dispose();
+ MicaBrush.Dispose();
+ DesktopInterop.Dispose();
+ Compositor.Dispose();
+ Compositor5.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs
new file mode 100644
index 0000000000..e7af4046d6
--- /dev/null
+++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Numerics;
+using MicroCom.Runtime;
+
+namespace Avalonia.Win32.WinRT.Composition;
+
+internal static class WinUiCompositionUtils
+{
+ public static ICompositionBrush CreateMicaBackdropBrush(ICompositor compositor)
+ {
+ if (Win32Platform.WindowsVersion.Build < 22000)
+ return null;
+
+ using var compositorWithBlurredWallpaperBackdropBrush =
+ compositor.QueryInterface();
+ using var blurredWallpaperBackdropBrush =
+ compositorWithBlurredWallpaperBackdropBrush?.TryCreateBlurredWallpaperBackdropBrush();
+ return blurredWallpaperBackdropBrush?.QueryInterface();
+ }
+
+ public static unsafe ICompositionBrush CreateAcrylicBlurBackdropBrush(ICompositor compositor)
+ {
+ using var backDropParameterFactory =
+ NativeWinRTMethods.CreateActivationFactory(
+ "Windows.UI.Composition.CompositionEffectSourceParameter");
+ using var backdropString = new HStringInterop("backdrop");
+ using var backDropParameter =
+ backDropParameterFactory.Create(backdropString.Handle);
+ using var backDropParameterAsSource = backDropParameter.QueryInterface();
+ var blurEffect = new WinUIGaussianBlurEffect(backDropParameterAsSource);
+ using var blurEffectFactory = compositor.CreateEffectFactory(blurEffect);
+ using var compositionEffectBrush = blurEffectFactory.CreateBrush();
+ using var backdropBrush = CreateBackdropBrush(compositor);
+
+ var saturateEffect = new SaturationEffect(blurEffect);
+ using var satEffectFactory = compositor.CreateEffectFactory(saturateEffect);
+ using var sat = satEffectFactory.CreateBrush();
+ compositionEffectBrush.SetSourceParameter(backdropString.Handle, backdropBrush);
+ return compositionEffectBrush.QueryInterface();
+ }
+
+ public static ICompositionRoundedRectangleGeometry ClipVisual(ICompositor compositor, float? _backdropCornerRadius, params IVisual[] containerVisuals)
+ {
+ if (!_backdropCornerRadius.HasValue)
+ return null;
+ using var compositor5 = compositor.QueryInterface();
+ using var roundedRectangleGeometry = compositor5.CreateRoundedRectangleGeometry();
+ roundedRectangleGeometry.SetCornerRadius(new Vector2(_backdropCornerRadius.Value, _backdropCornerRadius.Value));
+
+ using var compositor6 = compositor.QueryInterface();
+ using var compositionGeometry = roundedRectangleGeometry
+ .QueryInterface();
+
+ using var geometricClipWithGeometry =
+ compositor6.CreateGeometricClipWithGeometry(compositionGeometry);
+ foreach (var visual in containerVisuals)
+ {
+ visual?.SetClip(geometricClipWithGeometry.QueryInterface());
+ }
+
+ return roundedRectangleGeometry.CloneReference();
+ }
+
+ public static IVisual CreateBlurVisual(ICompositor compositor, ICompositionBrush compositionBrush)
+ {
+ using var spriteVisual = compositor.CreateSpriteVisual();
+ using var visual = spriteVisual.QueryInterface();
+ using var visual2 = spriteVisual.QueryInterface();
+
+
+ spriteVisual.SetBrush(compositionBrush);
+ visual.SetIsVisible(0);
+ visual2.SetRelativeSizeAdjustment(new Vector2(1.0f, 1.0f));
+
+ return visual.CloneReference();
+ }
+
+ public static ICompositionBrush CreateBackdropBrush(ICompositor compositor)
+ {
+ ICompositionBackdropBrush brush = null;
+ try
+ {
+ if (Win32Platform.WindowsVersion >= WinUiCompositionShared.MinHostBackdropVersion)
+ {
+ using var compositor3 = compositor.QueryInterface();
+ brush = compositor3.CreateHostBackdropBrush();
+ }
+ else
+ {
+ using var compositor2 = compositor.QueryInterface();
+ brush = compositor2.CreateBackdropBrush();
+ }
+
+ return brush.QueryInterface();
+ }
+ finally
+ {
+ brush?.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs
new file mode 100644
index 0000000000..a1408baae0
--- /dev/null
+++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs
@@ -0,0 +1,151 @@
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia.Logging;
+using Avalonia.MicroCom;
+using Avalonia.OpenGL.Egl;
+using Avalonia.Rendering;
+using Avalonia.Win32.DirectX;
+using Avalonia.Win32.Interop;
+using Avalonia.Win32.OpenGl.Angle;
+using MicroCom.Runtime;
+
+namespace Avalonia.Win32.WinRT.Composition;
+
+internal class WinUiCompositorConnection : IRenderTimer
+{
+ private readonly WinUiCompositionShared _shared;
+ public event Action Tick;
+ public bool RunsInBackground => true;
+
+ public unsafe WinUiCompositorConnection()
+ {
+ using var compositor = NativeWinRTMethods.CreateInstance("Windows.UI.Composition.Compositor");
+ /*
+ var levels = new[] { D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_1 };
+ DirectXUnmanagedMethods.D3D11CreateDevice(IntPtr.Zero, D3D_DRIVER_TYPE.D3D_DRIVER_TYPE_HARDWARE,
+ IntPtr.Zero, 0, levels, (uint)levels.Length, 7, out var pD3dDevice, out var level, null);
+
+ var d3dDevice = MicroComRuntime.CreateProxyFor(pD3dDevice, true);
+
+ var compositionDevice = compositor.QueryInterface().CreateGraphicsDevice(d3dDevice);
+ var surf = compositionDevice.CreateDrawingSurface(new UnmanagedMethods.SIZE_F { X = 100, Y = 100 },
+ DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied);
+ var surfInterop = surf.QueryInterface();
+ var IID_ID3D11Texture2D = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c");
+ void* texture = null;
+ surfInterop.BeginDraw(null, &IID_ID3D11Texture2D, &texture);
+ */
+
+ _shared = new WinUiCompositionShared(compositor);
+ }
+
+ static bool TryCreateAndRegisterCore()
+ {
+ var tcs = new TaskCompletionSource();
+ var pumpLock = new object();
+ var th = new Thread(() =>
+ {
+ WinUiCompositorConnection connect;
+ try
+ {
+ NativeWinRTMethods.CreateDispatcherQueueController(new NativeWinRTMethods.DispatcherQueueOptions
+ {
+ apartmentType = NativeWinRTMethods.DISPATCHERQUEUE_THREAD_APARTMENTTYPE.DQTAT_COM_NONE,
+ dwSize = Marshal.SizeOf(),
+ threadType = NativeWinRTMethods.DISPATCHERQUEUE_THREAD_TYPE.DQTYPE_THREAD_CURRENT
+ });
+ connect = new WinUiCompositorConnection();
+ AvaloniaLocator.CurrentMutable.BindToSelf(connect);
+ AvaloniaLocator.CurrentMutable.Bind().ToConstant(connect);
+ tcs.SetResult(true);
+
+ }
+ catch (Exception e)
+ {
+ tcs.SetException(e);
+ return;
+ }
+
+ connect.RunLoop();
+ })
+ {
+ IsBackground = true,
+ Name = "DwmRenderTimerLoop"
+ };
+ th.SetApartmentState(ApartmentState.STA);
+ th.Start();
+ return tcs.Task.Result;
+ }
+
+ class RunLoopHandler : CallbackBase, IAsyncActionCompletedHandler
+ {
+ private readonly WinUiCompositorConnection _parent;
+ private Stopwatch _st = Stopwatch.StartNew();
+
+ public RunLoopHandler(WinUiCompositorConnection parent)
+ {
+ _parent = parent;
+ }
+
+ public void Invoke(IAsyncAction asyncInfo, AsyncStatus asyncStatus)
+ {
+ _parent.Tick?.Invoke(_st.Elapsed);
+ using var act = _parent._shared.Compositor5.RequestCommitAsync();
+ act.SetCompleted(this);
+ }
+ }
+
+ private void RunLoop()
+ {
+ var cts = new CancellationTokenSource();
+ AppDomain.CurrentDomain.ProcessExit += (sender, args) =>
+ cts.Cancel();
+
+ lock (_shared.SyncRoot)
+ using (var act = _shared.Compositor5.RequestCommitAsync())
+ act.SetCompleted(new RunLoopHandler(this));
+
+ while (!cts.IsCancellationRequested)
+ {
+ UnmanagedMethods.GetMessage(out var msg, IntPtr.Zero, 0, 0);
+ lock (_shared.SyncRoot)
+ UnmanagedMethods.DispatchMessage(ref msg);
+ }
+ }
+
+ public static void TryCreateAndRegister()
+ {
+ const int majorRequired = 10;
+ const int buildRequired = 17134;
+
+ var majorInstalled = Win32Platform.WindowsVersion.Major;
+ var buildInstalled = Win32Platform.WindowsVersion.Build;
+
+ if (majorInstalled >= majorRequired &&
+ buildInstalled >= buildRequired)
+ {
+ try
+ {
+ TryCreateAndRegisterCore();
+ return;
+ }
+ catch (Exception e)
+ {
+ Logger.TryGet(LogEventLevel.Error, "WinUIComposition")
+ ?.Log(null, "Unable to initialize WinUI compositor: {0}", e);
+
+ }
+ }
+
+ var osVersionNotice =
+ $"Windows {majorRequired} Build {buildRequired} is required. Your machine has Windows {majorInstalled} Build {buildInstalled} installed.";
+
+ Logger.TryGet(LogEventLevel.Warning, "WinUIComposition")?.Log(null,
+ $"Unable to initialize WinUI compositor: {osVersionNotice}");
+ }
+
+ public WinUiCompositedWindowSurface CreateSurface(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) => new(_shared, info);
+}
diff --git a/src/Windows/Avalonia.Win32/WinRT/WinRTColor.cs b/src/Windows/Avalonia.Win32/WinRT/WinRTColor.cs
index 786d698daa..35f0737d5f 100644
--- a/src/Windows/Avalonia.Win32/WinRT/WinRTColor.cs
+++ b/src/Windows/Avalonia.Win32/WinRT/WinRTColor.cs
@@ -3,7 +3,7 @@
namespace Avalonia.Win32.WinRT
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
- public struct WinRTColor
+ public record struct WinRTColor
{
public byte A;
public byte R;
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index 5f813eec1d..596b60a8cb 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -26,7 +26,8 @@ using static Avalonia.Win32.Interop.UnmanagedMethods;
using Avalonia.Collections.Pooled;
using Avalonia.Metadata;
using Avalonia.Platform.Storage;
-using Avalonia.Win32.DxgiSwapchain;
+using Avalonia.Win32.DirectX;
+using Avalonia.Win32.OpenGl.Angle;
namespace Avalonia.Win32
{
@@ -64,7 +65,6 @@ namespace Avalonia.Win32
private Thickness _offScreenMargin;
private double _extendTitleBarHint = -1;
private bool _isUsingComposition;
- private bool _isUsingDxgiSwapchain;
private IBlurHost _blurHost;
private PlatformResizeReason _resizeReason;
private MOUSEMOVEPOINT _lastWmMousePoint;
@@ -79,7 +79,7 @@ namespace Avalonia.Win32
private readonly PenDevice _penDevice;
private readonly ManagedDeferredRendererLock _rendererLock;
private readonly FramebufferManager _framebuffer;
- private readonly IGlPlatformSurface _gl;
+ private readonly object _gl;
private readonly bool _wmPointerEnabled;
private Win32NativeControlHost _nativeControlHost;
@@ -136,23 +136,20 @@ namespace Avalonia.Win32
};
_rendererLock = new ManagedDeferredRendererLock();
- var glPlatform = AvaloniaLocator.Current.GetService();
+ var glPlatform = AvaloniaLocator.Current.GetService();
- var compositionConnector = AvaloniaLocator.Current.GetService();
+ var compositionConnector = AvaloniaLocator.Current.GetService();
- _isUsingComposition = compositionConnector is { } &&
- glPlatform is EglPlatformOpenGlInterface egl &&
- egl.Display is AngleWin32EglDisplay angleDisplay &&
- angleDisplay.PlatformApi == AngleOptions.PlatformApi.DirectX11;
+ var isUsingAngleDX11 = glPlatform is AngleWin32PlatformGraphics angle &&
+ angle.PlatformApi == AngleOptions.PlatformApi.DirectX11;
+ _isUsingComposition = compositionConnector is { } && isUsingAngleDX11;
DxgiConnection dxgiConnection = null;
+ var isUsingDxgiSwapchain = false;
if (!_isUsingComposition)
{
dxgiConnection = AvaloniaLocator.Current.GetService();
- _isUsingDxgiSwapchain = dxgiConnection is { } &&
- glPlatform is EglPlatformOpenGlInterface eglDxgi &&
- eglDxgi.Display is AngleWin32EglDisplay angleDisplayDxgi &&
- angleDisplayDxgi.PlatformApi == AngleOptions.PlatformApi.DirectX11;
+ isUsingDxgiSwapchain = dxgiConnection is { } && isUsingAngleDX11;
}
_wmPointerEnabled = Win32Platform.WindowsVersion >= PlatformConstants.Windows8;
@@ -164,24 +161,24 @@ namespace Avalonia.Win32
{
if (_isUsingComposition)
{
- var cgl = new WinUiCompositedWindowSurface(compositionConnector, this);
+ var cgl = compositionConnector.CreateSurface(this);
_blurHost = cgl;
_gl = cgl;
_isUsingComposition = true;
}
- else if (_isUsingDxgiSwapchain)
+ else if (isUsingDxgiSwapchain)
{
var dxgigl = new DxgiSwapchainWindow(dxgiConnection, this);
_gl = dxgigl;
}
else
{
- if (glPlatform is EglPlatformOpenGlInterface egl2)
- _gl = new EglGlPlatformSurface(egl2, this);
+ if (glPlatform is AngleWin32PlatformGraphics egl2)
+ _gl = new EglGlPlatformSurface(this);
else if (glPlatform is WglPlatformOpenGlInterface wgl)
- _gl = new WglGlPlatformSurface(wgl.PrimaryContext, this);
+ _gl = new WglGlPlatformSurface(this);
}
}
@@ -434,7 +431,7 @@ namespace Avalonia.Win32
_ => BlurEffect.None
};
- if (Win32Platform.WindowsVersion >= WinUICompositorConnection.MinHostBackdropVersion)
+ if (Win32Platform.WindowsVersion >= WinUiCompositionShared.MinHostBackdropVersion)
{
unsafe
{
@@ -443,7 +440,7 @@ namespace Avalonia.Win32
}
}
- if (Win32Platform.WindowsVersion < WinUICompositorConnection.MinHostBackdropVersion && effect == BlurEffect.Mica)
+ if (Win32Platform.WindowsVersion < WinUiCompositionShared.MinHostBackdropVersion && effect == BlurEffect.Mica)
{
effect = BlurEffect.Acrylic;
}
@@ -570,16 +567,22 @@ namespace Avalonia.Win32
return customRendererFactory.Create(root, loop);
if (Win32Platform.Compositor != null)
- return new CompositingRenderer(root, Win32Platform.Compositor);
-
+ return new CompositingRenderer(root, Win32Platform.Compositor, () => Surfaces);
+
return Win32Platform.UseDeferredRendering
? _isUsingComposition
- ? new DeferredRenderer(root, loop)
+ ? new DeferredRenderer(root, loop,
+ () => Win32Platform.RenderInterface.CreateRenderTarget(Surfaces),
+ Win32Platform.RenderInterface)
{
RenderOnlyOnRenderThread = true
}
- : (IRenderer)new DeferredRenderer(root, loop, rendererLock: _rendererLock)
- : new ImmediateRenderer((Visual)root);
+ : (IRenderer)new DeferredRenderer(root, loop, rendererLock: _rendererLock,
+ renderTargetFactory: () => Win32Platform.RenderInterface.CreateRenderTarget(Surfaces),
+ renderInterface: Win32Platform.RenderInterface)
+ : new ImmediateRenderer((Visual)root,
+ () => Win32Platform.RenderInterface.CreateRenderTarget(Surfaces),
+ Win32Platform.RenderInterface);
}
public void Resize(Size value, PlatformResizeReason reason)
diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs
index 2080352020..48358c745f 100644
--- a/src/iOS/Avalonia.iOS/AvaloniaView.cs
+++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs
@@ -70,7 +70,8 @@ namespace Avalonia.iOS
// No-op
}
- public IRenderer CreateRenderer(IRenderRoot root) => new CompositingRenderer(root, Platform.Compositor);
+ public IRenderer CreateRenderer(IRenderRoot root) =>
+ new CompositingRenderer(root, Platform.Compositor, () => Surfaces);
public void Invalidate(Rect rect)
diff --git a/src/iOS/Avalonia.iOS/EaglDisplay.cs b/src/iOS/Avalonia.iOS/EaglDisplay.cs
index 906bbc29e7..7a5e1a496d 100644
--- a/src/iOS/Avalonia.iOS/EaglDisplay.cs
+++ b/src/iOS/Avalonia.iOS/EaglDisplay.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Reactive.Disposables;
using Avalonia.OpenGL;
using Avalonia.Platform;
@@ -6,29 +7,37 @@ using OpenGLES;
namespace Avalonia.iOS
{
- class EaglFeature : IPlatformOpenGlInterface
+ class EaglPlatformGraphics : IPlatformGraphics
{
- public IGlContext PrimaryContext => Context;
- public IGlContext CreateSharedContext() => throw new NotSupportedException();
- IPlatformGpuContext IPlatformGpu.PrimaryContext => PrimaryContext;
- public bool CanShareContexts => false;
- public bool CanCreateContexts => false;
- public IGlContext CreateContext() => throw new System.NotSupportedException();
- public GlContext Context { get; } = new GlContext();
+ public IPlatformGraphicsContext GetSharedContext() => Context;
+
+ public bool UsesSharedContext => true;
+ public IPlatformGraphicsContext CreateContext() => throw new System.NotSupportedException();
+ public GlContext Context { get; }
+ public static GlVersion GlVersion { get; } = new(GlProfileType.OpenGLES, 3, 0);
+
+ public EaglPlatformGraphics()
+ {
+
+ const string path = "/System/Library/Frameworks/OpenGLES.framework/OpenGLES";
+ var libGl = ObjCRuntime.Dlfcn.dlopen(path, 1);
+ if (libGl == IntPtr.Zero)
+ throw new OpenGlException("Unable to load " + path);
+ var iface = new GlInterface(GlVersion, proc => ObjCRuntime.Dlfcn.dlsym(libGl, proc));
+ Context = new(iface, null);
+ }
}
class GlContext : IGlContext
{
public EAGLContext Context { get; private set; }
- public GlContext()
+ public GlContext(GlInterface glInterface, EAGLSharegroup sharegroup)
{
- const string path = "/System/Library/Frameworks/OpenGLES.framework/OpenGLES";
- var libGl = ObjCRuntime.Dlfcn.dlopen(path, 1);
- if (libGl == IntPtr.Zero)
- throw new OpenGlException("Unable to load " + path);
- GlInterface = new GlInterface(Version, proc => ObjCRuntime.Dlfcn.dlsym(libGl, proc));
- Context = new EAGLContext(EAGLRenderingAPI.OpenGLES3);
+ GlInterface = glInterface;
+ Context = sharegroup == null ?
+ new EAGLContext(EAGLRenderingAPI.OpenGLES3) :
+ new(EAGLRenderingAPI.OpenGLES3, sharegroup);
}
public void Dispose()
@@ -59,22 +68,34 @@ namespace Avalonia.iOS
public IDisposable MakeCurrent()
{
+ if (Context == null)
+ throw new PlatformGraphicsContextLostException();
var old = EAGLContext.CurrentContext;
if (!EAGLContext.SetCurrentContext(Context))
throw new OpenGlException("Unable to make context current");
return new ResetContext(old);
}
+ public bool IsLost => Context == null;
+
public IDisposable EnsureCurrent()
{
+ if (Context == null)
+ throw new PlatformGraphicsContextLostException();
if(EAGLContext.CurrentContext == Context)
return Disposable.Empty;
return MakeCurrent();
}
- public bool IsSharedWith(IGlContext context) => false;
+ public bool IsSharedWith(IGlContext context) => context is GlContext other
+ && ReferenceEquals(other.Context?.ShareGroup, Context?.ShareGroup);
+ public bool CanCreateSharedContext => true;
+ public IGlContext CreateSharedContext(IEnumerable preferredVersions = null)
+ {
+ return new GlContext(GlInterface, Context.ShareGroup);
+ }
- public GlVersion Version { get; } = new GlVersion(GlProfileType.OpenGLES, 3, 0);
+ public GlVersion Version => EaglPlatformGraphics.GlVersion;
public GlInterface GlInterface { get; }
public int SampleCount
{
@@ -92,5 +113,7 @@ namespace Avalonia.iOS
return stencil;
}
}
+
+ public object TryGetFeature(Type featureType) => null;
}
}
diff --git a/src/iOS/Avalonia.iOS/EaglLayerSurface.cs b/src/iOS/Avalonia.iOS/EaglLayerSurface.cs
index 0e8945d921..88812653d7 100644
--- a/src/iOS/Avalonia.iOS/EaglLayerSurface.cs
+++ b/src/iOS/Avalonia.iOS/EaglLayerSurface.cs
@@ -78,10 +78,12 @@ namespace Avalonia.iOS
throw new InvalidOperationException("Invalid thread, go away");
}
- public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
+ public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context)
{
CheckThread();
var ctx = Platform.GlFeature.Context;
+ if (ctx != context)
+ throw new InvalidOperationException("Platform surface is only usable with tha main context");
using (ctx.MakeCurrent())
{
var fbo = new SizeSynchronizedLayerFbo(ctx.Context, ctx.GlInterface, _layer);
diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs
index c2cf8c6f6c..eb0a55734a 100644
--- a/src/iOS/Avalonia.iOS/Platform.cs
+++ b/src/iOS/Avalonia.iOS/Platform.cs
@@ -25,18 +25,18 @@ namespace Avalonia.iOS
{
static class Platform
{
- public static EaglFeature GlFeature;
+ public static EaglPlatformGraphics GlFeature;
public static DisplayLinkTimer Timer;
internal static Compositor Compositor { get; private set; }
public static void Register()
{
- GlFeature ??= new EaglFeature();
+ GlFeature ??= new EaglPlatformGraphics();
Timer ??= new DisplayLinkTimer();
var keyboard = new KeyboardDevice();
AvaloniaLocator.CurrentMutable
- .Bind().ToConstant(GlFeature)
+ .Bind().ToConstant(GlFeature)
.Bind().ToConstant(new CursorFactoryStub())
.Bind().ToConstant(new WindowingPlatformStub())
.Bind().ToConstant(new ClipboardImpl())
@@ -50,7 +50,7 @@ namespace Avalonia.iOS
Compositor = new Compositor(
AvaloniaLocator.Current.GetRequiredService(),
- AvaloniaLocator.Current.GetService());
+ AvaloniaLocator.Current.GetService());
}
diff --git a/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs b/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs
index 2d8ee62ef2..e36ce21009 100644
--- a/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs
+++ b/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs
@@ -1,5 +1,7 @@
+using System;
using Avalonia.Controls;
using Avalonia.Input;
+using Avalonia.Interactivity;
using Avalonia.UnitTests;
using Xunit;
@@ -25,6 +27,33 @@ namespace Avalonia.Base.UnitTests.Input
}
}
+ [Fact]
+ public void Focus_Should_Not_Get_Restored_To_Enabled_Control()
+ {
+ using (UnitTestApplication.Start(TestServices.RealFocus))
+ {
+ var sp = new StackPanel();
+ Button target = new Button();
+ Button target1 = new Button();
+ target.Click += (s, e) => target.IsEnabled = false;
+ target1.Click += (s, e) => target.IsEnabled = true;
+ sp.Children.Add(target);
+ sp.Children.Add(target1);
+ var root = new TestRoot
+ {
+ Child = sp
+ };
+
+ target.Focus();
+ target.RaiseEvent(new RoutedEventArgs(AccessKeyHandler.AccessKeyPressedEvent));
+ Assert.False(target.IsEnabled);
+ Assert.False(target.IsFocused);
+ target1.RaiseEvent(new RoutedEventArgs(AccessKeyHandler.AccessKeyPressedEvent));
+ Assert.True(target.IsEnabled);
+ Assert.False(target.IsFocused);
+ }
+ }
+
[Fact]
public void Focus_Should_Be_Cleared_When_Control_Is_Removed_From_VisualTree()
{
diff --git a/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs b/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs
index df16c1b34f..363fb3f5b3 100644
--- a/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs
@@ -181,9 +181,7 @@ namespace Avalonia.Base.UnitTests.Media
var count = glyphAdvances.Length;
var glyphIndices = new ushort[count];
- var start = bidiLevel == 0 ? glyphClusters[0] : glyphClusters[^1];
-
- var characters = new ReadOnlySlice(Enumerable.Repeat('a', count).ToArray(), start, count);
+ var characters = Enumerable.Repeat('a', count).ToArray();
return new GlyphRun(new MockGlyphTypeface(), 10, characters, glyphIndices, glyphAdvances,
glyphClusters: glyphClusters, biDiLevel: bidiLevel);
diff --git a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/BiDiClassTests.cs b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/BiDiClassTests.cs
index d2cea45ce1..b2c40f4ff1 100644
--- a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/BiDiClassTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/BiDiClassTests.cs
@@ -2,6 +2,7 @@
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
+using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Xunit;
using Xunit.Abstractions;
@@ -36,7 +37,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
var text = Encoding.UTF32.GetString(MemoryMarshal.Cast(t.CodePoints).ToArray());
// Append
- bidiData.Append(text.AsMemory());
+ bidiData.Append(new CharacterBufferRange(text));
// Act
for (int i = 0; i < 10; i++)
diff --git a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs
index c9f869cea9..4e0207a85d 100644
--- a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs
@@ -1,6 +1,7 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
+using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Visuals.UnitTests.Media.TextFormatting;
using Xunit;
@@ -37,11 +38,11 @@ namespace Avalonia.Base.UnitTests.Media.TextFormatting
var text = Encoding.UTF32.GetString(MemoryMarshal.Cast(t.Codepoints).ToArray());
var grapheme = Encoding.UTF32.GetString(MemoryMarshal.Cast(t.Grapheme).ToArray()).AsSpan();
- var enumerator = new GraphemeEnumerator(text.AsMemory());
+ var enumerator = new GraphemeEnumerator(new CharacterBufferRange(text));
enumerator.MoveNext();
- var actual = enumerator.Current.Text.Span;
+ var actual = enumerator.Current.Text;
var pass = true;
@@ -86,9 +87,7 @@ namespace Avalonia.Base.UnitTests.Media.TextFormatting
{
const string text = "ABCDEFGHIJ";
- var textMemory = text.AsMemory();
-
- var enumerator = new GraphemeEnumerator(textMemory);
+ var enumerator = new GraphemeEnumerator(new CharacterBufferRange(text));
var count = 0;
diff --git a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/LineBreakEnumuratorTests.cs b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/LineBreakEnumuratorTests.cs
index 15be1200c8..b2648bf348 100644
--- a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/LineBreakEnumuratorTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/LineBreakEnumuratorTests.cs
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
+using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Xunit;
using Xunit.Abstractions;
@@ -22,7 +23,7 @@ namespace Avalonia.Base.UnitTests.Media.TextFormatting
[Fact]
public void BasicLatinTest()
{
- var lineBreaker = new LineBreakEnumerator("Hello World\r\nThis is a test.".AsMemory());
+ var lineBreaker = new LineBreakEnumerator(new CharacterBufferRange("Hello World\r\nThis is a test."));
Assert.True(lineBreaker.MoveNext());
Assert.Equal(6, lineBreaker.Current.PositionWrap);
@@ -55,7 +56,7 @@ namespace Avalonia.Base.UnitTests.Media.TextFormatting
[Fact]
public void ForwardTextWithOuterWhitespace()
{
- var lineBreaker = new LineBreakEnumerator(" Apples Pears Bananas ".AsMemory());
+ var lineBreaker = new LineBreakEnumerator(new CharacterBufferRange(" Apples Pears Bananas "));
var positionsF = GetBreaks(lineBreaker);
Assert.Equal(1, positionsF[0].PositionWrap);
Assert.Equal(0, positionsF[0].PositionMeasure);
@@ -82,7 +83,7 @@ namespace Avalonia.Base.UnitTests.Media.TextFormatting
[Fact]
public void ForwardTest()
{
- var lineBreaker = new LineBreakEnumerator("Apples Pears Bananas".AsMemory());
+ var lineBreaker = new LineBreakEnumerator(new CharacterBufferRange("Apples Pears Bananas"));
var positionsF = GetBreaks(lineBreaker);
Assert.Equal(7, positionsF[0].PositionWrap);
@@ -99,7 +100,7 @@ namespace Avalonia.Base.UnitTests.Media.TextFormatting
{
var text = string.Join(null, codePoints.Select(char.ConvertFromUtf32));
- var lineBreaker = new LineBreakEnumerator(text.AsMemory());
+ var lineBreaker = new LineBreakEnumerator(new CharacterBufferRange(text));
var foundBreaks = new List();
diff --git a/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs b/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs
index 7b987d8e68..d407a09b06 100644
--- a/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs
+++ b/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs
@@ -76,7 +76,7 @@ public class CompositorTestsBase
public IRenderer CreateRenderer(IRenderRoot root)
{
- return Renderer = new CompositingRenderer(root, _compositor);
+ return Renderer = new CompositingRenderer(root, _compositor, () => Surfaces);
}
public void Invalidate(Rect rect)
diff --git a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests.cs
index 08e5955ec5..f0c5a24cc4 100644
--- a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests.cs
@@ -46,6 +46,7 @@ namespace Avalonia.Base.UnitTests.Rendering
var target = new DeferredRenderer(
root,
loop.Object,
+ renderTargetFactory: root.CreateRenderTarget,
sceneBuilder: sceneBuilder.Object);
target.Start();
@@ -83,6 +84,7 @@ namespace Avalonia.Base.UnitTests.Rendering
root,
loop.Object,
sceneBuilder: sceneBuilder.Object,
+ renderTargetFactory: root.CreateRenderTarget,
dispatcher: dispatcher);
target.Start();
@@ -133,6 +135,7 @@ namespace Avalonia.Base.UnitTests.Rendering
root,
loop.Object,
sceneBuilder: sceneBuilder,
+ renderTargetFactory: root.CreateRenderTarget,
dispatcher: dispatcher);
root.Renderer = target;
@@ -178,6 +181,7 @@ namespace Avalonia.Base.UnitTests.Rendering
root,
loop.Object,
sceneBuilder: sceneBuilder,
+ renderTargetFactory: root.CreateRenderTarget,
dispatcher: dispatcher);
root.Renderer = target;
@@ -223,6 +227,7 @@ namespace Avalonia.Base.UnitTests.Rendering
root,
loop.Object,
sceneBuilder: sceneBuilder,
+ renderTargetFactory: root.CreateRenderTarget,
dispatcher: dispatcher);
root.Renderer = target;
@@ -270,6 +275,7 @@ namespace Avalonia.Base.UnitTests.Rendering
root,
loop.Object,
sceneBuilder: sceneBuilder,
+ renderTargetFactory: root.CreateRenderTarget,
dispatcher: dispatcher);
root.Renderer = target;
@@ -317,6 +323,7 @@ namespace Avalonia.Base.UnitTests.Rendering
root,
loop.Object,
sceneBuilder: sceneBuilder,
+ renderTargetFactory: root.CreateRenderTarget,
dispatcher: dispatcher);
root.Renderer = target;
@@ -366,6 +373,7 @@ namespace Avalonia.Base.UnitTests.Rendering
root,
loop.Object,
sceneBuilder: sceneBuilder,
+ renderTargetFactory: root.CreateRenderTarget,
dispatcher: dispatcher);
root.Renderer = target;
@@ -415,6 +423,7 @@ namespace Avalonia.Base.UnitTests.Rendering
root,
loop.Object,
sceneBuilder: sceneBuilder,
+ renderTargetFactory: root.CreateRenderTarget,
dispatcher: dispatcher);
var otherSceneBuilder = new SceneBuilder();
@@ -423,6 +432,7 @@ namespace Avalonia.Base.UnitTests.Rendering
otherRoot,
loop.Object,
sceneBuilder: otherSceneBuilder,
+ renderTargetFactory: root.CreateRenderTarget,
dispatcher: dispatcher);
root.Renderer = target;
@@ -738,6 +748,7 @@ namespace Avalonia.Base.UnitTests.Rendering
root,
new RenderLoop(timer.Object, dispatcher),
sceneBuilder: sceneBuilder,
+ renderTargetFactory: root.CreateRenderTarget,
dispatcher: dispatcher);
root.Renderer = target;
target.Start();
diff --git a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs
index 2cf42d9604..52a9f58006 100644
--- a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs
+++ b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs
@@ -35,7 +35,7 @@ namespace Avalonia.Base.UnitTests.Rendering
}
};
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null);
+ root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
@@ -63,7 +63,7 @@ namespace Avalonia.Base.UnitTests.Rendering
}
};
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null);
+ root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
@@ -100,7 +100,7 @@ namespace Avalonia.Base.UnitTests.Rendering
}
};
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null);
+ root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
@@ -129,7 +129,7 @@ namespace Avalonia.Base.UnitTests.Rendering
}
};
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null);
+ root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
@@ -173,7 +173,7 @@ namespace Avalonia.Base.UnitTests.Rendering
}
};
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null);
+ root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize));
@@ -227,7 +227,7 @@ namespace Avalonia.Base.UnitTests.Rendering
}
};
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null);
+ root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize));
@@ -276,7 +276,7 @@ namespace Avalonia.Base.UnitTests.Rendering
}
};
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null);
+ root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
@@ -324,7 +324,7 @@ namespace Avalonia.Base.UnitTests.Rendering
}
};
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null);
+ root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize));
@@ -399,7 +399,7 @@ namespace Avalonia.Base.UnitTests.Rendering
scroll.UpdateChild();
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null);
+ root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize));
@@ -447,7 +447,7 @@ namespace Avalonia.Base.UnitTests.Rendering
}
};
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null);
+ root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
@@ -486,7 +486,7 @@ namespace Avalonia.Base.UnitTests.Rendering
}
};
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null);
+ root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
Assert.Equal(new Rect(100, 100, 200, 200), border.Bounds);
@@ -522,7 +522,7 @@ namespace Avalonia.Base.UnitTests.Rendering
}
};
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null);
+ root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
@@ -560,7 +560,7 @@ namespace Avalonia.Base.UnitTests.Rendering
}
};
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null);
+ root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
diff --git a/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests.cs
index 26a155d203..a07191f464 100644
--- a/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests.cs
@@ -36,7 +36,7 @@ namespace Avalonia.Base.UnitTests.Rendering
root.LayoutManager.ExecuteInitialLayoutPass();
- var target = new ImmediateRenderer(root);
+ var target = new ImmediateRenderer(root, root.CreateRenderTarget);
target.AddDirty(child);
@@ -69,7 +69,7 @@ namespace Avalonia.Base.UnitTests.Rendering
child.RenderTransform = new ScaleTransform() { ScaleX = 2, ScaleY = 2 };
- var target = new ImmediateRenderer(root);
+ var target = new ImmediateRenderer(root, root.CreateRenderTarget);
target.AddDirty(child);
@@ -97,7 +97,7 @@ namespace Avalonia.Base.UnitTests.Rendering
Height = 400,
};
- var target = new ImmediateRenderer(root);
+ var target = new ImmediateRenderer(root, root.CreateRenderTarget);
root.LayoutManager.ExecuteInitialLayoutPass();
target.AddDirty(child);
@@ -165,7 +165,7 @@ namespace Avalonia.Base.UnitTests.Rendering
stackPanel.Children.Add(control3);
var root = new TestRoot(rootGrid);
- root.Renderer = new ImmediateRenderer(root);
+ root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
root.LayoutManager.ExecuteInitialLayoutPass();
var rootSize = new Size(RootWidth, RootHeight);
@@ -223,7 +223,7 @@ namespace Avalonia.Base.UnitTests.Rendering
stackPanel.Children.Add(control3);
var root = new TestRoot(rootGrid);
- root.Renderer = new ImmediateRenderer(root);
+ root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
root.LayoutManager.ExecuteInitialLayoutPass();
var rootSize = new Size(RootWidth, RootHeight);
diff --git a/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs b/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs
index 70e4acba1e..9ce8c42e33 100644
--- a/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs
+++ b/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs
@@ -32,7 +32,7 @@ namespace Avalonia.Base.UnitTests.Rendering
}
};
- root.Renderer = new ImmediateRenderer(root);
+ root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
@@ -70,7 +70,7 @@ namespace Avalonia.Base.UnitTests.Rendering
}
};
- root.Renderer = new ImmediateRenderer(root);
+ root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
@@ -100,7 +100,7 @@ namespace Avalonia.Base.UnitTests.Rendering
}
};
- root.Renderer = new ImmediateRenderer(root);
+ root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
@@ -145,7 +145,7 @@ namespace Avalonia.Base.UnitTests.Rendering
}
};
- root.Renderer = new ImmediateRenderer(root);
+ root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
@@ -200,7 +200,7 @@ namespace Avalonia.Base.UnitTests.Rendering
}
};
- root.Renderer = new ImmediateRenderer(root);
+ root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
@@ -259,7 +259,7 @@ namespace Avalonia.Base.UnitTests.Rendering
}
};
- root.Renderer = new ImmediateRenderer(root);
+ root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
@@ -308,7 +308,7 @@ namespace Avalonia.Base.UnitTests.Rendering
}
};
- root.Renderer = new ImmediateRenderer(root);
+ root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
@@ -384,7 +384,7 @@ namespace Avalonia.Base.UnitTests.Rendering
scroll.UpdateChild();
- root.Renderer = new ImmediateRenderer(root);
+ root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
@@ -434,7 +434,7 @@ namespace Avalonia.Base.UnitTests.Rendering
}
};
- root.Renderer = new ImmediateRenderer(root);
+ root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
diff --git a/tests/Avalonia.Base.UnitTests/Utilities/ReadOnlySpanTests.cs b/tests/Avalonia.Base.UnitTests/Utilities/ReadOnlySpanTests.cs
deleted file mode 100644
index da30ee9d02..0000000000
--- a/tests/Avalonia.Base.UnitTests/Utilities/ReadOnlySpanTests.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using System.Linq;
-using Avalonia.Utilities;
-using Xunit;
-
-namespace Avalonia.Base.UnitTests.Utilities
-{
- public class ReadOnlySpanTests
- {
- [Fact]
- public void Should_Skip()
- {
- var buffer = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
-
- var slice = new ReadOnlySlice(buffer);
-
- var skipped = slice.Skip(2);
-
- var expected = buffer.Skip(2);
-
- Assert.Equal(expected, skipped);
- }
-
- [Fact]
- public void Should_Take()
- {
- var buffer = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
-
- var slice = new ReadOnlySlice(buffer);
-
- var taken = slice.Take(8);
-
- var expected = buffer.Take(8);
-
- Assert.Equal(expected, taken);
- }
- }
-}
diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
index 10db08f302..42e33729ac 100644
--- a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
+++ b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
@@ -8,13 +8,15 @@ using Avalonia.Media.Imaging;
namespace Avalonia.Base.UnitTests.VisualTree
{
- class MockRenderInterface : IPlatformRenderInterface
+ class MockRenderInterface : IPlatformRenderInterface, IPlatformRenderInterfaceContext
{
public IRenderTarget CreateRenderTarget(IEnumerable surfaces)
{
throw new NotImplementedException();
}
+ public object TryGetFeature(Type featureType) => null;
+
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)
{
throw new NotImplementedException();
@@ -77,6 +79,11 @@ namespace Avalonia.Base.UnitTests.VisualTree
throw new NotImplementedException();
}
+ public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext)
+ {
+ return this;
+ }
+
public bool SupportsIndividualRoundRects { get; set; }
public AlphaFormat DefaultAlphaFormat { get; }
public PixelFormat DefaultPixelFormat { get; }
@@ -263,6 +270,11 @@ namespace Avalonia.Base.UnitTests.VisualTree
}
}
}
+
+ public void Dispose()
+ {
+
+ }
}
}
diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs b/tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs
index b40ea40985..0c516a0481 100644
--- a/tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs
+++ b/tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs
@@ -44,7 +44,7 @@ namespace Avalonia.Base.UnitTests.VisualTree
}
};
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null);
+ root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
@@ -86,7 +86,7 @@ namespace Avalonia.Base.UnitTests.VisualTree
}
};
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null);
+ root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
diff --git a/tests/Avalonia.Benchmarks/NullRenderer.cs b/tests/Avalonia.Benchmarks/NullRenderer.cs
index 16f8998038..feb325f630 100644
--- a/tests/Avalonia.Benchmarks/NullRenderer.cs
+++ b/tests/Avalonia.Benchmarks/NullRenderer.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Threading.Tasks;
using Avalonia.Rendering;
using Avalonia.VisualTree;
@@ -43,5 +44,7 @@ namespace Avalonia.Benchmarks
public void Stop()
{
}
+
+ public ValueTask TryGetRenderInterfaceFeature(Type featureType) => new(0);
}
}
diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
index 4170de71e6..a272d89b8a 100644
--- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
+++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
@@ -9,7 +9,7 @@ using Microsoft.Diagnostics.Runtime;
namespace Avalonia.Benchmarks
{
- internal class NullRenderingPlatform : IPlatformRenderInterface
+ internal class NullRenderingPlatform : IPlatformRenderInterface, IPlatformRenderInterfaceContext
{
public IGeometryImpl CreateEllipseGeometry(Rect rect)
{
@@ -46,6 +46,8 @@ namespace Avalonia.Benchmarks
throw new NotImplementedException();
}
+ public object TryGetFeature(Type featureType) => null;
+
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)
{
throw new NotImplementedException();
@@ -123,10 +125,19 @@ namespace Avalonia.Benchmarks
return new MockGlyphRun();
}
+ public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext)
+ {
+ return this;
+ }
+
public bool SupportsIndividualRoundRects => true;
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
public PixelFormat DefaultPixelFormat => PixelFormat.Rgba8888;
+ public void Dispose()
+ {
+
+ }
}
}
diff --git a/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs
index e82576c7d9..70636d1fe6 100644
--- a/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs
+++ b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs
@@ -16,6 +16,8 @@ namespace Avalonia.Benchmarks.Themes
public class ThemeBenchmark : IDisposable
{
private IDisposable _app;
+ private readonly FluentTheme _reusableFluentTheme = new FluentTheme();
+ private readonly SimpleTheme _reusableSimpleTheme = new SimpleTheme();
public ThemeBenchmark()
{
@@ -49,6 +51,26 @@ namespace Avalonia.Benchmarks.Themes
};
return ((IResourceHost)UnitTestApplication.Current).TryGetResource("ThemeAccentColor", out _);
}
+
+ [Benchmark]
+ [Arguments(typeof(Button))]
+ [Arguments(typeof(TextBox))]
+ [Arguments(typeof(DatePicker))]
+ public object FindFluentControlTheme(Type type)
+ {
+ _reusableFluentTheme.TryGetResource(type, out var theme);
+ return theme;
+ }
+
+ [Benchmark]
+ [Arguments(typeof(Button))]
+ [Arguments(typeof(TextBox))]
+ [Arguments(typeof(DatePicker))]
+ public object FindSimpleControlTheme(Type type)
+ {
+ _reusableSimpleTheme.TryGetResource(type, out var theme);
+ return theme;
+ }
public void Dispose()
{
diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/TextPresenter_Tests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/TextPresenter_Tests.cs
index 8cc8e4c16f..8e06fbd831 100644
--- a/tests/Avalonia.Controls.UnitTests/Presenters/TextPresenter_Tests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Presenters/TextPresenter_Tests.cs
@@ -46,7 +46,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.NotNull(target.TextLayout);
var actual = string.Join(null,
- target.TextLayout.TextLines.SelectMany(x => x.TextRuns).Select(x => x.Text.Span.ToString()));
+ target.TextLayout.TextLines.SelectMany(x => x.TextRuns).Select(x => x.CharacterBufferReference.CharacterBuffer.Span.ToString()));
Assert.Equal("****", actual);
}
diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
index 6b9105ccb5..bd6d5d55e2 100644
--- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
@@ -59,6 +59,28 @@ namespace Avalonia.Controls.UnitTests
}
}
+ [Fact]
+ public void TextBox_Should_Lose_Focus_When_Disabled()
+ {
+ using (UnitTestApplication.Start(FocusServices))
+ {
+ var target = new TextBox
+ {
+ Template = CreateTemplate()
+ };
+
+ target.ApplyTemplate();
+
+ var root = new TestRoot() { Child = target };
+
+ target.Focus();
+ Assert.True(target.IsFocused);
+ target.IsEnabled = false;
+ Assert.False(target.IsFocused);
+ Assert.False(target.IsEnabled);
+ }
+ }
+
[Fact]
public void Opening_Context_Flyout_Does_not_Lose_Selection()
{
diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
index bcf719d2bb..214eefa8e7 100644
--- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
@@ -421,6 +421,587 @@ namespace Avalonia.Controls.UnitTests
}
}
+ [Fact]
+ public void Double_Clicking_Item_Header_Should_Expand_It()
+ {
+ using (Application())
+ {
+ var tree = CreateTestTreeData();
+ var target = new TreeView
+ {
+ Template = CreateTreeViewTemplate(),
+ Items = tree,
+ };
+
+ var visualRoot = new TestRoot();
+ visualRoot.Child = target;
+
+ CreateNodeDataTemplate(target);
+ ApplyTemplates(target);
+ CollapseAll(target);
+
+ var item = tree[0].Children[1];
+ var container = (TreeViewItem)target.TreeContainerFromItem(item);
+
+ Assert.NotNull(container);
+ Assert.False(container.IsExpanded);
+ var header = container.HeaderPresenter.Child;
+ Assert.NotNull(header);
+
+ _mouse.DoubleClick(header);
+
+ Assert.True(container.IsExpanded);
+ }
+ }
+
+ [Fact]
+ public void Double_Clicking_Item_Header_With_No_Children_Does_Not_Expand_It()
+ {
+ using (Application())
+ {
+ var tree = CreateTestTreeData();
+ var target = new TreeView
+ {
+ Template = CreateTreeViewTemplate(),
+ Items = tree,
+ };
+
+ var visualRoot = new TestRoot();
+ visualRoot.Child = target;
+
+ CreateNodeDataTemplate(target);
+ ApplyTemplates(target);
+ CollapseAll(target);
+
+ var item = tree[0].Children[1].Children[0];
+ var container = (TreeViewItem)target.TreeContainerFromItem(item);
+
+ Assert.NotNull(container);
+ Assert.False(container.IsExpanded);
+ var header = container.HeaderPresenter.Child;
+ Assert.NotNull(header);
+
+ _mouse.DoubleClick(header);
+
+ Assert.False(container.IsExpanded);
+ }
+ }
+
+ [Fact]
+ public void Double_Clicking_Item_Header_Should_Collapse_It()
+ {
+ using (Application())
+ {
+ var tree = CreateTestTreeData();
+ var target = new TreeView
+ {
+ Template = CreateTreeViewTemplate(),
+ Items = tree,
+ };
+
+ var visualRoot = new TestRoot();
+ visualRoot.Child = target;
+
+ CreateNodeDataTemplate(target);
+ ApplyTemplates(target);
+ ExpandAll(target);
+
+ var item = tree[0].Children[1];
+ var container = (TreeViewItem)target.TreeContainerFromItem(item);
+
+ Assert.NotNull(container);
+ Assert.True(container.IsExpanded);
+ var header = container.HeaderPresenter.Child;
+ Assert.NotNull(header);
+
+ _mouse.DoubleClick(header);
+
+ Assert.False(container.IsExpanded);
+ }
+ }
+
+ [Fact]
+ public void Enter_Key_Should_Collapse_TreeViewItem()
+ {
+ using (Application())
+ {
+ var tree = CreateTestTreeData();
+ var target = new TreeView
+ {
+ Template = CreateTreeViewTemplate(),
+ Items = tree,
+ };
+
+ var visualRoot = new TestRoot();
+ visualRoot.Child = target;
+
+ CreateNodeDataTemplate(target);
+ ApplyTemplates(target);
+ ExpandAll(target); // NOTE this line
+
+ var item = tree[0];
+ var container = (TreeViewItem)target.TreeContainerFromItem(item);
+
+ Assert.NotNull(container);
+ Assert.True(container.IsExpanded);
+ var header = container.HeaderPresenter.Child;
+ Assert.NotNull(header);
+
+ container.RaiseEvent(new KeyEventArgs
+ {
+ RoutedEvent = InputElement.KeyDownEvent,
+ Key = Key.Enter,
+ });
+
+ Assert.False(container.IsExpanded);
+ }
+ }
+
+ [Fact]
+ public void Enter_plus_Ctrl_Key_Should_Collapse_TreeViewItem_Recursively()
+ {
+ using (Application())
+ {
+ var tree = CreateTestTreeData();
+ var target = new TreeView
+ {
+ Template = CreateTreeViewTemplate(),
+ Items = tree,
+ };
+
+ var visualRoot = new TestRoot();
+ visualRoot.Child = target;
+
+ CreateNodeDataTemplate(target);
+ ApplyTemplates(target);
+ ExpandAll(target); // NOTE this line
+
+ var item = tree[0];
+ var container = (TreeViewItem)target.TreeContainerFromItem(item);
+
+ Assert.NotNull(container);
+ Assert.True(container.IsExpanded);
+ var header = container.HeaderPresenter.Child;
+ Assert.NotNull(header);
+
+ container.RaiseEvent(new KeyEventArgs
+ {
+ RoutedEvent = InputElement.KeyDownEvent,
+ Key = Key.Enter,
+ KeyModifiers = KeyModifiers.Control,
+ });
+
+ Assert.False(container.IsExpanded);
+
+ AssertEachItemWithChildrenIsCollapsed(item);
+
+ void AssertEachItemWithChildrenIsCollapsed(Node node)
+ {
+ var container = (TreeViewItem)target.TreeContainerFromItem(node);
+ Assert.NotNull(container);
+ if (node.Children?.Count > 0)
+ {
+ Assert.False(container.IsExpanded);
+ foreach (var c in node.Children)
+ {
+ AssertEachItemWithChildrenIsCollapsed(c);
+ }
+ }
+ else
+ {
+ Assert.True(container.IsExpanded);
+ }
+ }
+ }
+ }
+
+ [Fact]
+ public void Enter_Key_Should_Expand_TreeViewItem()
+ {
+ using (Application())
+ {
+ var tree = CreateTestTreeData();
+ var target = new TreeView
+ {
+ Template = CreateTreeViewTemplate(),
+ Items = tree,
+ };
+
+ var visualRoot = new TestRoot();
+ visualRoot.Child = target;
+
+ CreateNodeDataTemplate(target);
+ ApplyTemplates(target);
+ CollapseAll(target); // NOTE this line
+
+ var item = tree[0];
+ var container = (TreeViewItem)target.TreeContainerFromItem(item);
+
+ Assert.NotNull(container);
+ Assert.False(container.IsExpanded);
+ var header = container.HeaderPresenter.Child;
+ Assert.NotNull(header);
+
+ container.RaiseEvent(new KeyEventArgs
+ {
+ RoutedEvent = InputElement.KeyDownEvent,
+ Key = Key.Enter,
+ });
+
+ Assert.True(container.IsExpanded);
+ }
+ }
+
+ [Fact]
+ public void Enter_plus_Ctrl_Key_Should_Expand_TreeViewItem_Recursively()
+ {
+ using (Application())
+ {
+ var tree = CreateTestTreeData();
+ var target = new TreeView
+ {
+ Template = CreateTreeViewTemplate(),
+ Items = tree,
+ };
+
+ var visualRoot = new TestRoot();
+ visualRoot.Child = target;
+
+ CreateNodeDataTemplate(target);
+ ApplyTemplates(target);
+ CollapseAll(target); // NOTE this line
+
+ var item = tree[0];
+ var container = (TreeViewItem)target.TreeContainerFromItem(item);
+
+ Assert.NotNull(container);
+ Assert.False(container.IsExpanded);
+ var header = container.HeaderPresenter.Child;
+ Assert.NotNull(header);
+
+ container.RaiseEvent(new KeyEventArgs
+ {
+ RoutedEvent = InputElement.KeyDownEvent,
+ Key = Key.Enter,
+ KeyModifiers = KeyModifiers.Control,
+ });
+
+ Assert.True(container.IsExpanded);
+
+ AssertEachItemWithChildrenIsExpanded(item);
+
+ void AssertEachItemWithChildrenIsExpanded(Node node)
+ {
+ var container = (TreeViewItem)target.TreeContainerFromItem(node);
+ Assert.NotNull(container);
+ if (node.Children?.Count > 0)
+ {
+ Assert.True(container.IsExpanded);
+ foreach (var c in node.Children)
+ {
+ AssertEachItemWithChildrenIsExpanded(c);
+ }
+ }
+ else
+ {
+ Assert.False(container.IsExpanded);
+ }
+ }
+ }
+ }
+
+ [Fact]
+ public void Space_Key_Should_Collapse_TreeViewItem()
+ {
+ using (Application())
+ {
+ var tree = CreateTestTreeData();
+ var target = new TreeView
+ {
+ Template = CreateTreeViewTemplate(),
+ Items = tree,
+ };
+
+ var visualRoot = new TestRoot();
+ visualRoot.Child = target;
+
+ CreateNodeDataTemplate(target);
+ ApplyTemplates(target);
+ ExpandAll(target); // NOTE this line
+
+ var item = tree[0];
+ var container = (TreeViewItem)target.TreeContainerFromItem(item);
+
+ Assert.NotNull(container);
+ Assert.True(container.IsExpanded);
+ var header = container.HeaderPresenter.Child;
+ Assert.NotNull(header);
+
+ container.RaiseEvent(new KeyEventArgs
+ {
+ RoutedEvent = InputElement.KeyDownEvent,
+ Key = Key.Enter,
+ });
+
+ Assert.False(container.IsExpanded);
+ }
+ }
+
+ [Fact]
+ public void Space_plus_Ctrl_Key_Should_Collapse_TreeViewItem_Recursively()
+ {
+ using (Application())
+ {
+ var tree = CreateTestTreeData();
+ var target = new TreeView
+ {
+ Template = CreateTreeViewTemplate(),
+ Items = tree,
+ };
+
+ var visualRoot = new TestRoot();
+ visualRoot.Child = target;
+
+ CreateNodeDataTemplate(target);
+ ApplyTemplates(target);
+ ExpandAll(target); // NOTE this line
+
+ var item = tree[0];
+ var container = (TreeViewItem)target.TreeContainerFromItem(item);
+
+ Assert.NotNull(container);
+ Assert.True(container.IsExpanded);
+ var header = container.HeaderPresenter.Child;
+ Assert.NotNull(header);
+
+ container.RaiseEvent(new KeyEventArgs
+ {
+ RoutedEvent = InputElement.KeyDownEvent,
+ Key = Key.Enter,
+ KeyModifiers = KeyModifiers.Control,
+ });
+
+ Assert.False(container.IsExpanded);
+
+ AssertEachItemWithChildrenIsCollapsed(item);
+
+ void AssertEachItemWithChildrenIsCollapsed(Node node)
+ {
+ var container = (TreeViewItem)target.TreeContainerFromItem(node);
+ Assert.NotNull(container);
+ if (node.Children?.Count > 0)
+ {
+ Assert.False(container.IsExpanded);
+ foreach (var c in node.Children)
+ {
+ AssertEachItemWithChildrenIsCollapsed(c);
+ }
+ }
+ else
+ {
+ Assert.True(container.IsExpanded);
+ }
+ }
+ }
+ }
+
+ [Fact]
+ public void Space_Key_Should_Expand_TreeViewItem()
+ {
+ using (Application())
+ {
+ var tree = CreateTestTreeData();
+ var target = new TreeView
+ {
+ Template = CreateTreeViewTemplate(),
+ Items = tree,
+ };
+
+ var visualRoot = new TestRoot();
+ visualRoot.Child = target;
+
+ CreateNodeDataTemplate(target);
+ ApplyTemplates(target);
+ CollapseAll(target); // NOTE this line
+
+ var item = tree[0];
+ var container = (TreeViewItem)target.TreeContainerFromItem(item);
+
+ Assert.NotNull(container);
+ Assert.False(container.IsExpanded);
+ var header = container.HeaderPresenter.Child;
+ Assert.NotNull(header);
+
+ container.RaiseEvent(new KeyEventArgs
+ {
+ RoutedEvent = InputElement.KeyDownEvent,
+ Key = Key.Enter,
+ });
+
+ Assert.True(container.IsExpanded);
+ }
+ }
+
+ [Fact]
+ public void Space_plus_Ctrl_Key_Should_Expand_TreeViewItem_Recursively()
+ {
+ using (Application())
+ {
+ var tree = CreateTestTreeData();
+ var target = new TreeView
+ {
+ Template = CreateTreeViewTemplate(),
+ Items = tree,
+ };
+
+ var visualRoot = new TestRoot();
+ visualRoot.Child = target;
+
+ CreateNodeDataTemplate(target);
+ ApplyTemplates(target);
+ CollapseAll(target); // NOTE this line
+
+ var item = tree[0];
+ var container = (TreeViewItem)target.TreeContainerFromItem(item);
+
+ Assert.NotNull(container);
+ Assert.False(container.IsExpanded);
+ var header = container.HeaderPresenter.Child;
+ Assert.NotNull(header);
+
+ container.RaiseEvent(new KeyEventArgs
+ {
+ RoutedEvent = InputElement.KeyDownEvent,
+ Key = Key.Enter,
+ KeyModifiers = KeyModifiers.Control,
+ });
+
+ Assert.True(container.IsExpanded);
+
+ AssertEachItemWithChildrenIsExpanded(item);
+
+ void AssertEachItemWithChildrenIsExpanded(Node node)
+ {
+ var container = (TreeViewItem)target.TreeContainerFromItem(node);
+ Assert.NotNull(container);
+ if (node.Children?.Count > 0)
+ {
+ Assert.True(container.IsExpanded);
+ foreach (var c in node.Children)
+ {
+ AssertEachItemWithChildrenIsExpanded(c);
+ }
+ }
+ else
+ {
+ Assert.False(container.IsExpanded);
+ }
+ }
+ }
+ }
+
+ [Fact]
+ public void Numpad_Star_Should_Expand_All_Children_Recursively()
+ {
+ using (Application())
+ {
+ var tree = CreateTestTreeData();
+ var target = new TreeView
+ {
+ Template = CreateTreeViewTemplate(),
+ Items = tree,
+ };
+
+ var visualRoot = new TestRoot();
+ visualRoot.Child = target;
+
+ CreateNodeDataTemplate(target);
+ ApplyTemplates(target);
+ CollapseAll(target);
+
+ var item = tree[0];
+ var container = (TreeViewItem)target.TreeContainerFromItem(item);
+
+ Assert.NotNull(container);
+ container.RaiseEvent(new KeyEventArgs
+ {
+ RoutedEvent = InputElement.KeyDownEvent,
+ Key = Key.Multiply,
+ });
+
+ AssertEachItemWithChildrenIsExpanded(item);
+
+ void AssertEachItemWithChildrenIsExpanded(Node node)
+ {
+ var container = (TreeViewItem)target.TreeContainerFromItem(node);
+ Assert.NotNull(container);
+ if (node.Children?.Count > 0)
+ {
+ Assert.True(container.IsExpanded);
+ foreach (var c in node.Children)
+ {
+ AssertEachItemWithChildrenIsExpanded(c);
+ }
+ }
+ else
+ {
+ Assert.False(container.IsExpanded);
+ }
+ }
+ }
+ }
+
+ [Fact]
+ public void Numpad_Slash_Should_Collapse_All_Children_Recursively()
+ {
+ using (Application())
+ {
+ var tree = CreateTestTreeData();
+ var target = new TreeView
+ {
+ Template = CreateTreeViewTemplate(),
+ Items = tree,
+ };
+
+ var visualRoot = new TestRoot();
+ visualRoot.Child = target;
+
+ CreateNodeDataTemplate(target);
+ ApplyTemplates(target);
+ ExpandAll(target);
+
+ var item = tree[0];
+ var container = (TreeViewItem)target.TreeContainerFromItem(item);
+
+ Assert.NotNull(container);
+ container.RaiseEvent(new KeyEventArgs
+ {
+ RoutedEvent = InputElement.KeyDownEvent,
+ Key = Key.Divide,
+ });
+
+ AssertEachItemWithChildrenIsCollapsed(item);
+
+ void AssertEachItemWithChildrenIsCollapsed(Node node)
+ {
+ var container = (TreeViewItem)target.TreeContainerFromItem(node);
+ Assert.NotNull(container);
+ if (node.Children?.Count > 0)
+ {
+ Assert.False(container.IsExpanded);
+ foreach (var c in node.Children)
+ {
+ AssertEachItemWithChildrenIsCollapsed(c);
+ }
+ }
+ else
+ {
+ Assert.True(container.IsExpanded);
+ }
+ }
+ }
+ }
+
[Fact]
public void Setting_SelectedItem_Should_Set_Container_Selected()
{
@@ -1279,11 +1860,15 @@ namespace Avalonia.Controls.UnitTests
{
Children =
{
- new ContentPresenter
+ new Border
{
- Name = "PART_HeaderPresenter",
- [~ContentPresenter.ContentProperty] = parent[~TreeViewItem.HeaderProperty],
- [~ContentPresenter.ContentTemplateProperty] = parent[~TreeViewItem.HeaderTemplateProperty],
+ Name = "PART_Header",
+ Child = new ContentPresenter
+ {
+ Name = "PART_HeaderPresenter",
+ [~ContentPresenter.ContentProperty] = parent[~TreeViewItem.HeaderProperty],
+ [~ContentPresenter.ContentTemplateProperty] = parent[~TreeViewItem.HeaderTemplateProperty],
+ }.RegisterInNameScope(scope)
}.RegisterInNameScope(scope),
new ItemsPresenter
{
@@ -1301,6 +1886,14 @@ namespace Avalonia.Controls.UnitTests
}
}
+ private void CollapseAll(TreeView tree)
+ {
+ foreach (var i in tree.GetRealizedContainers())
+ {
+ tree.CollapseSubTree((TreeViewItem)i);
+ }
+ }
+
private List ExtractItemHeader(TreeView tree, int level)
{
return ExtractItemContent(tree.Presenter.Panel, 0, level)
diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
index bb8a683a60..435d0d92ce 100644
--- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
@@ -403,7 +403,7 @@ namespace Avalonia.Controls.UnitTests
parent.Close();
var ex = Assert.Throws(() => target.Show(parent));
- Assert.Equal("Cannot show a window with a closed parent.", ex.Message);
+ Assert.Equal("Cannot show a window with a closed owner.", ex.Message);
}
}
@@ -431,7 +431,7 @@ namespace Avalonia.Controls.UnitTests
var target = new Window();
var ex = Assert.Throws(() => target.Show(parent));
- Assert.Equal("Cannot show window with non-visible parent.", ex.Message);
+ Assert.Equal("Cannot show window with non-visible owner.", ex.Message);
}
}
@@ -444,7 +444,7 @@ namespace Avalonia.Controls.UnitTests
var target = new Window();
var ex = await Assert.ThrowsAsync(() => target.ShowDialog(parent));
- Assert.Equal("Cannot show window with non-visible parent.", ex.Message);
+ Assert.Equal("Cannot show window with non-visible owner.", ex.Message);
}
}
@@ -456,7 +456,7 @@ namespace Avalonia.Controls.UnitTests
var target = new Window();
var ex = Assert.Throws(() => target.Show(target));
- Assert.Equal("A Window cannot be its own parent.", ex.Message);
+ Assert.Equal("A Window cannot be its own owner.", ex.Message);
}
}
@@ -986,7 +986,46 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(SizeToContent.WidthAndHeight, target.SizeToContent);
}
}
+
+ [Fact]
+ public void IsVisible_Should_Open_Window()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var target = new Window();
+ var raised = false;
+
+ target.Opened += (s, e) => raised = true;
+ target.IsVisible = true;
+ Assert.True(raised);
+ }
+ }
+
+ [Fact]
+ public void IsVisible_Should_Close_DialogWindow()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var parent = new Window();
+ parent.Show();
+
+ var target = new Window();
+
+ var raised = false;
+
+ var task = target.ShowDialog(parent);
+
+ target.Closed += (sender, args) => raised = true;
+
+ target.IsVisible = false;
+
+ Assert.True(raised);
+
+ Assert.False(task.Result);
+ }
+ }
+
protected virtual void Show(Window window)
{
window.Show();
diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs
index 59cc023509..ca08f3bcb2 100644
--- a/tests/Avalonia.LeakTests/ControlTests.cs
+++ b/tests/Avalonia.LeakTests/ControlTests.cs
@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Disposables;
-
+using System.Threading.Tasks;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
@@ -1066,6 +1066,8 @@ namespace Avalonia.LeakTests
public void Stop()
{
}
+
+ public ValueTask TryGetRenderInterfaceFeature(Type featureType) => new(null);
}
}
}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs
new file mode 100644
index 0000000000..520abee59a
--- /dev/null
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Xml;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Styling;
+using Xunit;
+
+namespace Avalonia.Markup.Xaml.UnitTests.Xaml;
+
+public class MergeResourceIncludeTests
+{
+ [Fact]
+ public void MergeResourceInclude_Works_With_Single_Resource()
+ {
+ var documents = new[]
+ {
+ new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources.xaml"), @"
+
+ Red
+"),
+ new RuntimeXamlLoaderDocument(@"
+
+
+
+ Blue
+
+
+
+
+
+")
+ };
+
+ var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
+ var contentControl = Assert.IsType(objects[1]);
+
+ var resources = Assert.IsType(contentControl.Resources);
+ Assert.Empty(resources.MergedDictionaries);
+
+ var initialResource = (ISolidColorBrush)resources["brush1"]!;
+ Assert.Equal(Colors.Blue, initialResource.Color);
+
+ var mergedResource = (ISolidColorBrush)resources["brush2"]!;
+ Assert.Equal(Colors.Red, mergedResource.Color);
+ }
+
+ [Fact]
+ public void Mixing_MergeResourceInclude_And_ResourceInclude_Is_Not_Allowed()
+ {
+ var documents = new[]
+ {
+ new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources1.xaml"), @"
+
+ Red
+"),
+ new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources2.xaml"), @"
+
+ Blue
+"),
+ new RuntimeXamlLoaderDocument(@"
+
+
+
+
+
+")
+ };
+
+ Assert.ThrowsAny(() => AvaloniaRuntimeXamlLoader.LoadGroup(documents));
+ }
+
+ [Fact]
+ public void MergeResourceInclude_Works_With_Multiple_Resources()
+ {
+ var documents = new[]
+ {
+ new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources1.xaml"), @"
+
+ Red
+ Blue
+"),
+ new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources2.xaml"), @"
+
+ Yellow
+
+
+
+"),
+ new RuntimeXamlLoaderDocument(@"
+
+
+
+
+
+ Black
+ White
+"),
+ new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources1_2.xaml"), @"
+
+ Green
+"),
+ };
+
+ var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
+ var resources = Assert.IsType(objects[2]);
+ Assert.Empty(resources.MergedDictionaries);
+
+ Assert.Equal(Colors.Red, ((ISolidColorBrush)resources["brush1"]!).Color);
+ Assert.Equal(Colors.Blue, ((ISolidColorBrush)resources["brush2"]!).Color);
+ Assert.Equal(Colors.Green, ((ISolidColorBrush)resources["brush3"]!).Color);
+ Assert.Equal(Colors.Yellow, ((ISolidColorBrush)resources["brush4"]!).Color);
+ Assert.Equal(Colors.Black, ((ISolidColorBrush)resources["brush5"]!).Color);
+ Assert.Equal(Colors.White, ((ISolidColorBrush)resources["brush6"]!).Color);
+ }
+}
diff --git a/tests/Avalonia.RenderTests/Media/BitmapTests.cs b/tests/Avalonia.RenderTests/Media/BitmapTests.cs
index d52539c371..2d83f5ce0f 100644
--- a/tests/Avalonia.RenderTests/Media/BitmapTests.cs
+++ b/tests/Avalonia.RenderTests/Media/BitmapTests.cs
@@ -69,8 +69,9 @@ namespace Avalonia.Direct2D1.RenderTests.Media
{
var testName = nameof(FramebufferRenderResultsShouldBeUsableAsBitmap) + "_" + fmt;
var fb = new Framebuffer(fmt, new PixelSize(80, 80));
- var r = Avalonia.AvaloniaLocator.Current.GetService();
- using (var target = r.CreateRenderTarget(new object[] { fb }))
+ var r = Avalonia.AvaloniaLocator.Current.GetRequiredService();
+ using(var cpuContext = r.CreateBackendContext(null))
+ using (var target = cpuContext.CreateRenderTarget(new object[] { fb }))
using (var ctx = target.CreateDrawingContext(null))
{
ctx.Clear(Colors.Transparent);
diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs
index 3f918e2a73..8a127897d7 100644
--- a/tests/Avalonia.RenderTests/TestBase.cs
+++ b/tests/Avalonia.RenderTests/TestBase.cs
@@ -13,6 +13,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Threading;
+using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Media;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
@@ -122,10 +123,13 @@ namespace Avalonia.Direct2D1.RenderTests
var timer = new ManualRenderTimer();
var compositor = new Compositor(new RenderLoop(timer, Dispatcher.UIThread), null);
- using (var rtb = factory.CreateRenderTargetBitmap(pixelSize, dpiVector))
+ using (var writableBitmap = factory.CreateWriteableBitmap(pixelSize, dpiVector, factory.DefaultPixelFormat, factory.DefaultAlphaFormat))
{
- var root = new TestRenderRoot(dpiVector.X / 96, rtb);
- using (var renderer = new CompositingRenderer(root, compositor) { RenderOnlyOnRenderThread = false})
+ var root = new TestRenderRoot(dpiVector.X / 96, null!);
+ using (var renderer = new CompositingRenderer(root, compositor, () => new[]
+ {
+ new BitmapFramebufferSurface(writableBitmap)
+ }) { RenderOnlyOnRenderThread = false })
{
root.Initialize(renderer, target);
renderer.Start();
@@ -136,8 +140,20 @@ namespace Avalonia.Direct2D1.RenderTests
// Free pools
for (var c = 0; c < 11; c++)
TestThreadingInterface.RunTimers();
- rtb.Save(compositedPath);
+ writableBitmap.Save(compositedPath);
+ }
+ }
+
+ class BitmapFramebufferSurface : IFramebufferPlatformSurface
+ {
+ private readonly IWriteableBitmapImpl _bitmap;
+
+ public BitmapFramebufferSurface(IWriteableBitmapImpl bitmap)
+ {
+ _bitmap = bitmap;
}
+
+ public ILockedFramebuffer Lock() => _bitmap.Lock();
}
protected void CompareImages([CallerMemberName] string testName = "")
diff --git a/tests/Avalonia.Skia.UnitTests/HitTesting.cs b/tests/Avalonia.Skia.UnitTests/HitTesting.cs
index dceb0cdb9b..df267ee136 100644
--- a/tests/Avalonia.Skia.UnitTests/HitTesting.cs
+++ b/tests/Avalonia.Skia.UnitTests/HitTesting.cs
@@ -30,7 +30,7 @@ namespace Avalonia.Skia.UnitTests
}
};
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null);
+ root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
@@ -64,7 +64,7 @@ namespace Avalonia.Skia.UnitTests
}
};
- root.Renderer = new DeferredRenderer((IRenderRoot)root, null);
+ root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget);
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
diff --git a/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs b/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs
index 3b9caa393e..4083a67b5e 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/GlyphRunTests.cs
@@ -19,7 +19,7 @@ namespace Avalonia.Skia.UnitTests.Media
{
var options = new TextShaperOptions(Typeface.Default.GlyphTypeface, 10, direction, CultureInfo.CurrentCulture);
var shapedBuffer =
- TextShaper.Current.ShapeText(text.AsMemory(), options);
+ TextShaper.Current.ShapeText(new CharacterBufferReference(text), text.Length, options);
var glyphRun = CreateGlyphRun(shapedBuffer);
@@ -39,8 +39,6 @@ namespace Avalonia.Skia.UnitTests.Media
}
else
{
- shapedBuffer.GlyphInfos.Span.Reverse();
-
foreach (var rect in rects)
{
characterHit = glyphRun.GetNextCaretCharacterHit(characterHit);
@@ -62,7 +60,7 @@ namespace Avalonia.Skia.UnitTests.Media
{
var options = new TextShaperOptions(Typeface.Default.GlyphTypeface, 10, direction, CultureInfo.CurrentCulture);
var shapedBuffer =
- TextShaper.Current.ShapeText(text.AsMemory(), options);
+ TextShaper.Current.ShapeText(new CharacterBufferReference(text), text.Length, options);
var glyphRun = CreateGlyphRun(shapedBuffer);
@@ -84,8 +82,6 @@ namespace Avalonia.Skia.UnitTests.Media
}
else
{
- shapedBuffer.GlyphInfos.Span.Reverse();
-
foreach (var rect in rects)
{
characterHit = glyphRun.GetPreviousCaretCharacterHit(characterHit);
@@ -107,7 +103,7 @@ namespace Avalonia.Skia.UnitTests.Media
{
var options = new TextShaperOptions(Typeface.Default.GlyphTypeface, 10, direction, CultureInfo.CurrentCulture);
var shapedBuffer =
- TextShaper.Current.ShapeText(text.AsMemory(), options);
+ TextShaper.Current.ShapeText(new CharacterBufferReference(text), text.Length, options);
var glyphRun = CreateGlyphRun(shapedBuffer);
@@ -116,16 +112,14 @@ namespace Avalonia.Skia.UnitTests.Media
var characterHit =
glyphRun.GetCharacterHitFromDistance(glyphRun.Metrics.WidthIncludingTrailingWhitespace, out _);
- Assert.Equal(glyphRun.Characters.Length, characterHit.FirstCharacterIndex + characterHit.TrailingLength);
+ Assert.Equal(glyphRun.Characters.Count, characterHit.FirstCharacterIndex + characterHit.TrailingLength);
}
else
{
- shapedBuffer.GlyphInfos.Span.Reverse();
-
- var characterHit =
+ var characterHit =
glyphRun.GetCharacterHitFromDistance(0, out _);
- Assert.Equal(glyphRun.Characters.Length, characterHit.FirstCharacterIndex + characterHit.TrailingLength);
+ Assert.Equal(glyphRun.Characters.Count, characterHit.FirstCharacterIndex + characterHit.TrailingLength);
}
var rects = BuildRects(glyphRun);
@@ -218,15 +212,22 @@ namespace Avalonia.Skia.UnitTests.Media
private static GlyphRun CreateGlyphRun(ShapedBuffer shapedBuffer)
{
- return new GlyphRun(
+ var glyphRun = new GlyphRun(
shapedBuffer.GlyphTypeface,
shapedBuffer.FontRenderingEmSize,
- shapedBuffer.Text,
+ shapedBuffer.CharacterBufferRange,
shapedBuffer.GlyphIndices,
shapedBuffer.GlyphAdvances,
shapedBuffer.GlyphOffsets,
shapedBuffer.GlyphClusters,
shapedBuffer.BidiLevel);
+
+ if(shapedBuffer.BidiLevel == 1)
+ {
+ shapedBuffer.GlyphInfos.Span.Reverse();
+ }
+
+ return glyphRun;
}
private static IDisposable Start()
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/MultiBufferTextSource.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/MultiBufferTextSource.cs
index 005bcdf70e..aa499bb135 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/MultiBufferTextSource.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/MultiBufferTextSource.cs
@@ -29,8 +29,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var runText = _runTexts[index];
- return new TextCharacters(
- new ReadOnlySlice(runText.AsMemory(), textSourceIndex, runText.Length), _defaultStyle);
+ return new TextCharacters(runText, _defaultStyle);
}
}
}
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/SingleBufferTextSource.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/SingleBufferTextSource.cs
index dee4fe7f77..f12f42bd5e 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/SingleBufferTextSource.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/SingleBufferTextSource.cs
@@ -1,30 +1,33 @@
-using System;
-using Avalonia.Media.TextFormatting;
-using Avalonia.Utilities;
+using Avalonia.Media.TextFormatting;
namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
internal class SingleBufferTextSource : ITextSource
{
- private readonly ReadOnlySlice _text;
+ private readonly CharacterBufferRange _text;
private readonly GenericTextRunProperties _defaultGenericPropertiesRunProperties;
public SingleBufferTextSource(string text, GenericTextRunProperties defaultProperties)
{
- _text = text.AsMemory();
+ _text = new CharacterBufferRange(text);
_defaultGenericPropertiesRunProperties = defaultProperties;
}
public TextRun GetTextRun(int textSourceIndex)
{
- if (textSourceIndex > _text.Length)
+ if (textSourceIndex >= _text.Length)
{
return null;
}
-
+
var runText = _text.Skip(textSourceIndex);
- return runText.IsEmpty ? null : new TextCharacters(runText, _defaultGenericPropertiesRunProperties);
+ if (runText.IsEmpty)
+ {
+ return null;
+ }
+
+ return new TextCharacters(runText.CharacterBufferReference, runText.Length, _defaultGenericPropertiesRunProperties);
}
}
}
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
index 316926b00c..33d4fba5f1 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
@@ -37,7 +37,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(defaultProperties.ForegroundBrush, textRun.Properties.ForegroundBrush);
- Assert.Equal(text.Length, textRun.Text.Length);
+ Assert.Equal(text.Length, textRun.Length);
}
}
@@ -82,7 +82,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
new ValueSpan(9, 1, defaultProperties)
};
- var textSource = new FormattedTextSource(text.AsMemory(), defaultProperties, GenericTextRunPropertiesRuns);
+ var textSource = new FormattedTextSource(text, defaultProperties, GenericTextRunPropertiesRuns);
var formatter = new TextFormatterImpl();
@@ -97,7 +97,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textRun = textLine.TextRuns[i];
- Assert.Equal(GenericTextRunPropertiesRun.Length, textRun.Text.Length);
+ Assert.Equal(GenericTextRunPropertiesRun.Length, textRun.Length);
}
}
}
@@ -166,7 +166,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var firstRun = textLine.TextRuns[0];
- Assert.Equal(4, firstRun.Text.Length);
+ Assert.Equal(4, firstRun.Length);
}
}
@@ -216,7 +216,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
using (Start())
{
- var lineBreaker = new LineBreakEnumerator(text.AsMemory());
+ var lineBreaker = new LineBreakEnumerator(new CharacterBufferRange(text));
var expected = new List();
@@ -369,7 +369,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
new GenericTextRunProperties(new Typeface("Verdana", FontStyle.Italic),32))
};
- var textSource = new FormattedTextSource(text.AsMemory(), defaultProperties, styleSpans);
+ var textSource = new FormattedTextSource(text, defaultProperties, styleSpans);
var formatter = new TextFormatterImpl();
@@ -389,7 +389,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
if (textLine.Width > 300 || currentHeight + textLine.Height > 240)
{
- textLine = textLine.Collapse(new TextTrailingWordEllipsis(new ReadOnlySlice(new[] { TextTrimming.DefaultEllipsisChar }), 300, defaultProperties));
+ textLine = textLine.Collapse(new TextTrailingWordEllipsis(TextTrimming.DefaultEllipsisChar, 300, defaultProperties));
}
currentHeight += textLine.Height;
@@ -472,7 +472,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine =
formatter.FormatLine(textSource, textPosition, 50, paragraphProperties, lastBreak);
- Assert.Equal(textLine.Length, textLine.TextRuns.Sum(x => x.TextSourceLength));
+ Assert.Equal(textLine.Length, textLine.TextRuns.Sum(x => x.Length));
textPosition += textLine.Length;
@@ -534,7 +534,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
new GenericTextRunProperties(Typeface.Default, 12, foregroundBrush: foreground))
};
- var textSource = new FormattedTextSource(text.AsMemory(), defaultProperties, spans);
+ var textSource = new FormattedTextSource(text, defaultProperties, spans);
var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity, paragraphProperties);
@@ -614,8 +614,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
return new RectangleRun(new Rect(0, 0, 50, 50), Brushes.Green);
}
- return new TextCharacters(_text.AsMemory(),
- new GenericTextRunProperties(Typeface.Default, foregroundBrush: Brushes.Black));
+ return new TextCharacters(_text, 0, _text.Length, new GenericTextRunProperties(Typeface.Default, foregroundBrush: Brushes.Black));
}
}
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
index d6da2c77c4..a407b38eb1 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
@@ -60,9 +60,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textRun = textLine.TextRuns[1];
- Assert.Equal(2, textRun.Text.Length);
+ Assert.Equal(2, textRun.Length);
- var actual = textRun.Text.Span.ToString();
+ var actual = new CharacterBufferRange(textRun).Span.ToString();
Assert.Equal("1 ", actual);
@@ -144,8 +144,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var expectedGlyphs = expected.TextLines.Select(x => string.Join('|', x.TextRuns.Cast()
.SelectMany(x => x.ShapedBuffer.GlyphIndices))).ToList();
- var outer = new GraphemeEnumerator(text.AsMemory());
- var inner = new GraphemeEnumerator(text.AsMemory());
+ var outer = new GraphemeEnumerator(new CharacterBufferRange(text));
+ var inner = new GraphemeEnumerator(new CharacterBufferRange(text));
var i = 0;
var j = 0;
@@ -190,7 +190,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
break;
}
- inner = new GraphemeEnumerator(text.AsMemory());
+ inner = new GraphemeEnumerator(new CharacterBufferRange(text));
i += outer.Current.Text.Length;
}
@@ -223,10 +223,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textRun = textLine.TextRuns[0];
- Assert.Equal(2, textRun.Text.Length);
+ Assert.Equal(2, textRun.Length);
- var actual = SingleLineText.Substring(textRun.Text.Start,
- textRun.Text.Length);
+ var actual = SingleLineText[..textRun.Length];
Assert.Equal("01", actual);
@@ -260,9 +259,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textRun = textLine.TextRuns[1];
- Assert.Equal(2, textRun.Text.Length);
+ Assert.Equal(2, textRun.Length);
- var actual = textRun.Text.Span.ToString();
+ var actual = new CharacterBufferRange(textRun).Span.ToString();
Assert.Equal("89", actual);
@@ -296,7 +295,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textRun = textLine.TextRuns[0];
- Assert.Equal(1, textRun.Text.Length);
+ Assert.Equal(1, textRun.Length);
Assert.Equal(foreground, textRun.Properties.ForegroundBrush);
}
@@ -330,9 +329,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textRun = textLine.TextRuns[1];
- Assert.Equal(2, textRun.Text.Length);
+ Assert.Equal(2, textRun.Length);
- var actual = textRun.Text.Span.ToString();
+ var actual = new CharacterBufferRange(textRun).Span.ToString();
Assert.Equal("😄", actual);
@@ -369,7 +368,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(
MultiLineText.Length,
layout.TextLines.Select(textLine =>
- textLine.TextRuns.Sum(textRun => textRun.Text.Length))
+ textLine.TextRuns.Sum(textRun => textRun.Length))
.Sum());
}
}
@@ -402,7 +401,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(
text.Length,
layout.TextLines.Select(textLine =>
- textLine.TextRuns.Sum(textRun => textRun.Text.Length))
+ textLine.TextRuns.Sum(textRun => textRun.Length))
.Sum());
}
}
@@ -558,7 +557,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textRun = (ShapedTextCharacters)textLine.TextRuns[0];
- Assert.Equal(7, textRun.Text.Length);
+ Assert.Equal(7, textRun.Length);
var replacementGlyph = Typeface.Default.GlyphTypeface.GetGlyph(Codepoint.ReplacementCodepoint);
@@ -668,10 +667,10 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(5, layout.TextLines.Count);
- Assert.Equal("123\r\n", layout.TextLines[0].TextRuns[0].Text);
- Assert.Equal("\r\n", layout.TextLines[1].TextRuns[0].Text);
- Assert.Equal("456\r\n", layout.TextLines[2].TextRuns[0].Text);
- Assert.Equal("\r\n", layout.TextLines[3].TextRuns[0].Text);
+ Assert.Equal("123\r\n", new CharacterBufferRange(layout.TextLines[0].TextRuns[0]));
+ Assert.Equal("\r\n", new CharacterBufferRange(layout.TextLines[1].TextRuns[0]));
+ Assert.Equal("456\r\n", new CharacterBufferRange(layout.TextLines[2].TextRuns[0]));
+ Assert.Equal("\r\n", new CharacterBufferRange(layout.TextLines[3].TextRuns[0]));
}
}
@@ -815,7 +814,10 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
Assert.True(textLine.Width <= maxWidth);
- var actual = new string(textLine.TextRuns.Cast().OrderBy(x => x.Text.Start).SelectMany(x => x.Text).ToArray());
+ var actual = new string(textLine.TextRuns.Cast()
+ .OrderBy(x => x.CharacterBufferReference.OffsetToFirstChar)
+ .SelectMany(x => new CharacterBufferRange(x.CharacterBufferReference, x.Length)).ToArray());
+
var expected = text.Substring(textLine.FirstTextSourceIndex, textLine.Length);
Assert.Equal(expected, actual);
@@ -966,7 +968,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var i = 0;
- var graphemeEnumerator = new GraphemeEnumerator(text.AsMemory());
+ var graphemeEnumerator = new GraphemeEnumerator(new CharacterBufferRange(text));
while (graphemeEnumerator.MoveNext())
{
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
index 87de9ed11f..d6257a0de8 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
@@ -90,7 +90,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var clusters = new List();
- foreach (var textRun in textLine.TextRuns.OrderBy(x => x.Text.Start))
+ foreach (var textRun in textLine.TextRuns.OrderBy(x => x.CharacterBufferReference.OffsetToFirstChar))
{
var shapedRun = (ShapedTextCharacters)textRun;
@@ -137,7 +137,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var clusters = new List();
- foreach (var textRun in textLine.TextRuns.OrderBy(x => x.Text.Start))
+ foreach (var textRun in textLine.TextRuns.OrderBy(x => x.CharacterBufferReference.OffsetToFirstChar))
{
var shapedRun = (ShapedTextCharacters)textRun;
@@ -187,14 +187,16 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
- var clusters = textLine.TextRuns.Cast().SelectMany(x => x.ShapedBuffer.GlyphClusters)
- .ToArray();
+ var clusters = BuildGlyphClusters(textLine);
var nextCharacterHit = new CharacterHit(0);
- for (var i = 0; i < clusters.Length; i++)
+ for (var i = 0; i < clusters.Count; i++)
{
- Assert.Equal(clusters[i], nextCharacterHit.FirstCharacterIndex);
+ var expectedCluster = clusters[i];
+ var actualCluster = nextCharacterHit.FirstCharacterIndex;
+
+ Assert.Equal(expectedCluster, actualCluster);
nextCharacterHit = textLine.GetNextCaretCharacterHit(nextCharacterHit);
}
@@ -406,7 +408,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.True(collapsedLine.HasCollapsed);
- var trimmedText = collapsedLine.TextRuns.SelectMany(x => x.Text).ToArray();
+ var trimmedText = collapsedLine.TextRuns.SelectMany(x => new CharacterBufferRange(x)).ToArray();
Assert.Equal(expected.Length, trimmedText.Length);
@@ -450,8 +452,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
currentHit = textLine.GetNextCaretCharacterHit(currentHit);
- Assert.Equal(3, currentHit.FirstCharacterIndex);
- Assert.Equal(1, currentHit.TrailingLength);
+ Assert.Equal(4, currentHit.FirstCharacterIndex);
+ Assert.Equal(0, currentHit.TrailingLength);
}
}
@@ -473,18 +475,18 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var currentHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(3, 1));
- Assert.Equal(3, currentHit.FirstCharacterIndex);
- Assert.Equal(0, currentHit.TrailingLength);
+ Assert.Equal(2, currentHit.FirstCharacterIndex);
+ Assert.Equal(1, currentHit.TrailingLength);
currentHit = textLine.GetPreviousCaretCharacterHit(currentHit);
- Assert.Equal(2, currentHit.FirstCharacterIndex);
- Assert.Equal(0, currentHit.TrailingLength);
+ Assert.Equal(1, currentHit.FirstCharacterIndex);
+ Assert.Equal(1, currentHit.TrailingLength);
currentHit = textLine.GetPreviousCaretCharacterHit(currentHit);
- Assert.Equal(1, currentHit.FirstCharacterIndex);
- Assert.Equal(0, currentHit.TrailingLength);
+ Assert.Equal(0, currentHit.FirstCharacterIndex);
+ Assert.Equal(1, currentHit.TrailingLength);
currentHit = textLine.GetPreviousCaretCharacterHit(currentHit);
@@ -509,13 +511,13 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var characterHit = textLine.GetCharacterHitFromDistance(50);
- Assert.Equal(3, characterHit.FirstCharacterIndex);
+ Assert.Equal(5, characterHit.FirstCharacterIndex);
Assert.Equal(1, characterHit.TrailingLength);
characterHit = textLine.GetCharacterHitFromDistance(32);
- Assert.Equal(2, characterHit.FirstCharacterIndex);
- Assert.Equal(1, characterHit.TrailingLength);
+ Assert.Equal(3, characterHit.FirstCharacterIndex);
+ Assert.Equal(0, characterHit.TrailingLength);
}
}
@@ -649,7 +651,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var run = textRuns[i];
var bounds = runBounds[i];
- Assert.Equal(run.Text.Start, bounds.TextSourceCharacterIndex);
+ Assert.Equal(run.CharacterBufferReference.OffsetToFirstChar, bounds.TextSourceCharacterIndex);
Assert.Equal(run, bounds.TextRun);
Assert.Equal(run.Size.Width, bounds.Rectangle.Width);
}
@@ -683,13 +685,13 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
switch (textSourceIndex)
{
case 0:
- return new TextCharacters(new ReadOnlySlice("aaaaaaaaaa".AsMemory()), new GenericTextRunProperties(Typeface.Default));
+ return new TextCharacters("aaaaaaaaaa", new GenericTextRunProperties(Typeface.Default));
case 10:
- return new TextCharacters(new ReadOnlySlice