diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs index 0d93e9d8ed..c469034f9b 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs @@ -19,13 +19,16 @@ namespace Avalonia.PropertyStore private T? _baseValue; private UncommonFields? _uncommon; - public EffectiveValue(AvaloniaObject owner, StyledProperty property) + public EffectiveValue( + AvaloniaObject owner, + StyledProperty property, + EffectiveValue? inherited) { Priority = BindingPriority.Unset; BasePriority = BindingPriority.Unset; _metadata = property.GetMetadata(owner.GetType()); - var value = _metadata.DefaultValue; + var value = inherited is null ? _metadata.DefaultValue : inherited.Value; if (property.HasCoercion && _metadata.CoerceValue is { } coerce) { diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index 8b702665f8..ec6ed392c1 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -184,7 +184,7 @@ namespace Avalonia.PropertyStore } else { - var effectiveValue = new EffectiveValue(Owner, property); + var effectiveValue = CreateEffectiveValue(property); AddEffectiveValue(property, effectiveValue); effectiveValue.SetAndRaise(this, result, priority); } @@ -200,7 +200,7 @@ namespace Avalonia.PropertyStore } else { - var effectiveValue = new EffectiveValue(Owner, property); + var effectiveValue = CreateEffectiveValue(property); AddEffectiveValue(property, effectiveValue); effectiveValue.SetLocalValueAndRaise(this, property, value); } @@ -217,7 +217,7 @@ namespace Avalonia.PropertyStore } else { - var effectiveValue = new EffectiveValue(Owner, property); + var effectiveValue = CreateEffectiveValue(property); AddEffectiveValue(property, effectiveValue); effectiveValue.SetCurrentValueAndRaise(this, property, value); } @@ -287,6 +287,16 @@ namespace Avalonia.PropertyStore return false; } + public EffectiveValue CreateEffectiveValue(StyledProperty property) + { + EffectiveValue? inherited = null; + + if (property.Inherits && TryGetInheritedValue(property, out var v)) + inherited = (EffectiveValue)v; + + return new EffectiveValue(Owner, property, inherited); + } + public void SetInheritanceParent(AvaloniaObject? newParent) { var values = AvaloniaPropertyDictionaryPool.Get(); @@ -799,7 +809,7 @@ namespace Avalonia.PropertyStore // - The value is a non-animation value and its priority is higher than the current // effective value's base priority var isRelevantPriority = current is null || - priority < current.Priority || + (priority < current.Priority && priority < current.BasePriority) || (priority > BindingPriority.Animation && priority < current.BasePriority); if (foundEntry && isRelevantPriority && entry!.HasValue) diff --git a/src/Avalonia.Base/StyledProperty.cs b/src/Avalonia.Base/StyledProperty.cs index ad1f09066e..8695918c18 100644 --- a/src/Avalonia.Base/StyledProperty.cs +++ b/src/Avalonia.Base/StyledProperty.cs @@ -171,7 +171,7 @@ namespace Avalonia internal override EffectiveValue CreateEffectiveValue(AvaloniaObject o) { - return new EffectiveValue(o, this); + return o.GetValueStore().CreateEffectiveValue(this); } /// diff --git a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs b/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs index 9834969898..b0b1d731d2 100644 --- a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs +++ b/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs @@ -68,8 +68,6 @@ namespace Avalonia.Headless .Bind().ToConstant(new KeyboardDevice()) .Bind().ToConstant(new RenderLoop()) .Bind().ToConstant(new RenderTimer(60)) - .Bind().ToSingleton() - .Bind().ToSingleton() .Bind().ToConstant(new HeadlessWindowingPlatform()) .Bind().ToSingleton(); Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), null); diff --git a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj index 73493c69c0..4dd7432082 100644 --- a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj +++ b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj @@ -13,6 +13,7 @@ + diff --git a/src/Avalonia.OpenGL/Controls/CompositionOpenGlSwapchain.cs b/src/Avalonia.OpenGL/Controls/CompositionOpenGlSwapchain.cs index 3db0aafd7b..42b1af7b25 100644 --- a/src/Avalonia.OpenGL/Controls/CompositionOpenGlSwapchain.cs +++ b/src/Avalonia.OpenGL/Controls/CompositionOpenGlSwapchain.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; using Avalonia.Platform; using Avalonia.Rendering; @@ -22,7 +21,7 @@ internal class CompositionOpenGlSwapchain : SwapchainBase } public CompositionOpenGlSwapchain(IGlContext context, ICompositionGpuInterop interop, CompositionDrawingSurface target, - IGlContextExternalObjectsFeature externalObjectsFeature) : base(interop, target) + IGlContextExternalObjectsFeature? externalObjectsFeature) : base(interop, target) { _context = context; _externalObjectsFeature = externalObjectsFeature; @@ -95,7 +94,7 @@ internal class DxgiMutexOpenGlSwapChainImage : IGlSwapchainImage public int TextureId => _texture.TextureId; public int InternalFormat => _texture.InternalFormat; public PixelSize Size => new(_texture.Properties.Width, _texture.Properties.Height); - public Task LastPresent => _lastPresent; + public Task? LastPresent => _lastPresent; public void BeginDraw() => _texture.AcquireKeyedMutex(0); public void Present() diff --git a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs index 4d0663a12b..7e73397743 100644 --- a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs +++ b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Threading.Tasks; using Avalonia.Controls; @@ -11,10 +12,10 @@ namespace Avalonia.OpenGL.Controls { public abstract class OpenGlControlBase : Control { - private CompositionSurfaceVisual _visual; - private Action _update; + private CompositionSurfaceVisual? _visual; + private readonly Action _update; private bool _updateQueued; - private Task _initialization; + private Task? _initialization; private OpenGlControlBaseResources? _resources; private Compositor? _compositor; protected GlVersion GlVersion => _resources?.Context.Version ?? default; @@ -24,7 +25,7 @@ namespace Avalonia.OpenGL.Controls _update = Update; } - void DoCleanup() + private void DoCleanup() { if (_initialization is { Status: TaskStatus.RanToCompletion } && _resources != null) { @@ -63,14 +64,14 @@ namespace Avalonia.OpenGL.Controls RequestNextFrameRendering(); } + [MemberNotNullWhen(true, nameof(_resources))] private bool EnsureInitializedCore( ICompositionGpuInterop interop, - IOpenGlTextureSharingRenderInterfaceContextFeature contextSharingFeature) + IOpenGlTextureSharingRenderInterfaceContextFeature? contextSharingFeature) { - var surface = _compositor.CreateDrawingSurface(); + var surface = _compositor!.CreateDrawingSurface(); - IGlContext ctx = null; - var contextFactory = AvaloniaLocator.Current.GetService(); + IGlContext? ctx = null; try { if (contextSharingFeature?.CanCreateSharedContext == true) @@ -78,6 +79,7 @@ namespace Avalonia.OpenGL.Controls if(_resources == null) { + var contextFactory = AvaloniaLocator.Current.GetRequiredService(); ctx = contextFactory.CreateContext(null); if (ctx.TryGetFeature(out var externalObjects)) _resources = OpenGlControlBaseResources.TryCreate(ctx, surface, interop, externalObjects); @@ -121,13 +123,14 @@ namespace Avalonia.OpenGL.Controls base.OnPropertyChanged(change); } - void ContextLost() + private void ContextLost() { _initialization = null; _resources?.DisposeAsync(); OnOpenGlLost(); } + [MemberNotNullWhen(true, nameof(_resources))] private bool EnsureInitialized() { if (_initialization != null) @@ -170,11 +173,11 @@ namespace Avalonia.OpenGL.Controls private void Update() { _updateQueued = false; - if (VisualRoot == null) + if (VisualRoot is not { } visualRoot) return; if(!EnsureInitialized()) return; - using (_resources.BeginDraw(GetPixelSize())) + using (_resources.BeginDraw(GetPixelSize(visualRoot))) OnOpenGlRender(_resources.Context.GlInterface, _resources.Fbo); } @@ -190,7 +193,7 @@ namespace Avalonia.OpenGL.Controls var gpuInteropTask = _compositor.TryGetCompositionGpuInterop(); var contextSharingFeature = - (IOpenGlTextureSharingRenderInterfaceContextFeature) + (IOpenGlTextureSharingRenderInterfaceContextFeature?) await _compositor.TryGetRenderInterfaceFeature( typeof(IOpenGlTextureSharingRenderInterfaceContextFeature)); var interop = await gpuInteropTask; @@ -208,7 +211,7 @@ namespace Avalonia.OpenGL.Controls return false; } - using (_resources!.Context.MakeCurrent()) + using (_resources.Context.MakeCurrent()) OnOpenGlInit(_resources.Context.GlInterface); return true; @@ -228,9 +231,9 @@ namespace Avalonia.OpenGL.Controls } } - private PixelSize GetPixelSize() + private PixelSize GetPixelSize(IRenderRoot visualRoot) { - var scaling = VisualRoot!.RenderScaling; + var scaling = visualRoot.RenderScaling; return new PixelSize(Math.Max(1, (int)(Bounds.Width * scaling)), Math.Max(1, (int)(Bounds.Height * scaling))); } diff --git a/src/Avalonia.OpenGL/Controls/OpenGlControlResources.cs b/src/Avalonia.OpenGL/Controls/OpenGlControlResources.cs index bda2bd1568..3b0b3bd028 100644 --- a/src/Avalonia.OpenGL/Controls/OpenGlControlResources.cs +++ b/src/Avalonia.OpenGL/Controls/OpenGlControlResources.cs @@ -14,14 +14,14 @@ internal class OpenGlControlBaseResources : IAsyncDisposable public int Fbo { get; private set; } private PixelSize _depthBufferSize; public CompositionDrawingSurface Surface { get; } - public CompositionOpenGlSwapchain _swapchain; + private readonly CompositionOpenGlSwapchain _swapchain; public IGlContext Context { get; private set; } public static OpenGlControlBaseResources? TryCreate(CompositionDrawingSurface surface, ICompositionGpuInterop interop, IOpenGlTextureSharingRenderInterfaceContextFeature feature) { - IGlContext context; + IGlContext? context; try { context = feature.CreateSharedContext(); @@ -71,7 +71,7 @@ internal class OpenGlControlBaseResources : IAsyncDisposable new CompositionOpenGlSwapchain(context, interop, Surface, externalObjects); } - void UpdateDepthRenderbuffer(PixelSize size) + private void UpdateDepthRenderbuffer(PixelSize size) { if (size == _depthBufferSize && _depthBuffer != 0) return; diff --git a/src/Avalonia.OpenGL/Egl/EglContext.cs b/src/Avalonia.OpenGL/Egl/EglContext.cs index 48dfc6c4c0..936ec06c1d 100644 --- a/src/Avalonia.OpenGL/Egl/EglContext.cs +++ b/src/Avalonia.OpenGL/Egl/EglContext.cs @@ -12,15 +12,15 @@ namespace Avalonia.OpenGL.Egl { private readonly EglDisplay _disp; private readonly EglInterface _egl; - private readonly EglContext _sharedWith; + private readonly EglContext? _sharedWith; private bool _isLost; private IntPtr _context; - private readonly Action _disposeCallback; + private readonly Action? _disposeCallback; private readonly Dictionary _features; private readonly object _lock; - internal EglContext(EglDisplay display, EglInterface egl, EglContext sharedWith, IntPtr ctx, EglSurface offscreenSurface, - GlVersion version, int sampleCount, int stencilSize, Action disposeCallback, + internal EglContext(EglDisplay display, EglInterface egl, EglContext? sharedWith, IntPtr ctx, EglSurface? offscreenSurface, + GlVersion version, int sampleCount, int stencilSize, Action? disposeCallback, Dictionary> features) { _disp = display; @@ -42,19 +42,19 @@ namespace Avalonia.OpenGL.Egl public IntPtr Context => _context == IntPtr.Zero ? throw new ObjectDisposedException(nameof(EglContext)) : _context; - public EglSurface OffscreenSurface { get; } + public EglSurface? OffscreenSurface { get; } public GlVersion Version { get; } public GlInterface GlInterface { get; } public int SampleCount { get; } public int StencilSize { get; } public EglDisplay Display => _disp; - class RestoreContext : IDisposable + private class RestoreContext : IDisposable { private readonly EglInterface _egl; private readonly object _l; private readonly IntPtr _display; - public IntPtr _context, _read, _draw; + private readonly IntPtr _context, _read, _draw; public RestoreContext(EglInterface egl, IntPtr defDisplay, object l) { @@ -78,7 +78,7 @@ namespace Avalonia.OpenGL.Egl public IDisposable MakeCurrent() => MakeCurrent(OffscreenSurface); - public IDisposable MakeCurrent(EglSurface surface) + public IDisposable MakeCurrent(EglSurface? surface) { if (IsLost) throw new PlatformGraphicsContextLostException(); @@ -147,7 +147,7 @@ namespace Avalonia.OpenGL.Egl public bool CanCreateSharedContext => _disp.SupportsSharing; - public IGlContext CreateSharedContext(IEnumerable preferredVersions = null) => + public IGlContext CreateSharedContext(IEnumerable? preferredVersions = null) => _disp.CreateContext(new EglContextOptions { ShareWith = _sharedWith ?? this @@ -174,9 +174,9 @@ namespace Avalonia.OpenGL.Egl _disposeCallback?.Invoke(); } - public object TryGetFeature(Type featureType) + public object? TryGetFeature(Type featureType) { - if (_features?.TryGetValue(featureType, out var feature) == true) + if (_features.TryGetValue(featureType, out var feature)) return feature; return null; } diff --git a/src/Avalonia.OpenGL/Egl/EglDisplay.cs b/src/Avalonia.OpenGL/Egl/EglDisplay.cs index 60cad2fa1e..db1fe56139 100644 --- a/src/Avalonia.OpenGL/Egl/EglDisplay.cs +++ b/src/Avalonia.OpenGL/Egl/EglDisplay.cs @@ -14,14 +14,14 @@ namespace Avalonia.OpenGL.Egl private readonly EglDisplayOptions _options; private EglConfigInfo _config; private bool _isLost; - private object _lock = new(); + private readonly object _lock = new(); public bool SupportsSharing { get; } public IntPtr Handle => _display; public IntPtr Config => _config.Config; internal bool SingleContext => !_options.SupportsMultipleContexts; - private List _contexts = new(); + private readonly List _contexts = new(); public EglDisplay() : this(new EglDisplayCreationOptions { @@ -38,7 +38,7 @@ namespace Avalonia.OpenGL.Egl public EglDisplay(IntPtr display, EglDisplayOptions options) { - _egl = options.Egl; + _egl = options.Egl ?? new EglInterface(); SupportsSharing = options.SupportsContextSharing; _display = display; _options = options; @@ -49,7 +49,7 @@ namespace Avalonia.OpenGL.Egl } public EglInterface EglInterface => _egl; - public EglContext CreateContext(EglContextOptions options) + public EglContext CreateContext(EglContextOptions? options) { if (SingleContext && _contexts.Any()) throw new OpenGlException("This EGLDisplay can only have one active context"); @@ -129,7 +129,7 @@ namespace Avalonia.OpenGL.Egl protected virtual bool DisplayLockIsSharedWithContexts => false; - internal object ContextSharedSyncRoot => DisplayLockIsSharedWithContexts ? _lock : null; + internal object? ContextSharedSyncRoot => DisplayLockIsSharedWithContexts ? _lock : null; internal void OnContextLost(EglContext context) { @@ -171,7 +171,6 @@ namespace Avalonia.OpenGL.Egl 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 index 906a533e2d..a2cbeb5b3d 100644 --- a/src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs +++ b/src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs @@ -5,26 +5,26 @@ namespace Avalonia.OpenGL.Egl; public class EglDisplayOptions { - public EglInterface Egl { get; set; } + 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 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 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; } + public int[]? PlatformDisplayAttrs { get; set; } } diff --git a/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs b/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs index 5573eb39fa..0adf114b54 100644 --- a/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs +++ b/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs @@ -1,15 +1,14 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using static Avalonia.OpenGL.Egl.EglConsts; namespace Avalonia.OpenGL.Egl; -static class EglDisplayUtils +internal static class EglDisplayUtils { public static IntPtr CreateDisplay(EglDisplayCreationOptions options) { - var egl = options.Egl; + var egl = options.Egl ?? new EglInterface(); var display = IntPtr.Zero; if (options.PlatformType == null) { @@ -30,9 +29,9 @@ static class EglDisplayUtils return display; } - public static EglConfigInfo InitializeAndGetConfig(EglInterface egl, IntPtr display, IEnumerable versions) + public static EglConfigInfo InitializeAndGetConfig(EglInterface egl, IntPtr display, IEnumerable? versions) { - if (!egl.Initialize(display, out var major, out var minor)) + if (!egl.Initialize(display, out _, out _)) throw OpenGlException.GetFormattedException("eglInitialize", egl); // TODO: AvaloniaLocator.Current.GetService()?.GlProfiles @@ -112,7 +111,7 @@ static class EglDisplayUtils } -class EglConfigInfo +internal class EglConfigInfo { public IntPtr Config { get; } public GlVersion Version { get; } diff --git a/src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs index 348028bd08..59ef25b1a4 100644 --- a/src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs +++ b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs @@ -27,9 +27,9 @@ namespace Avalonia.OpenGL.Egl return new RenderTarget(glSurface, eglContext, _info); } - class RenderTarget : EglPlatformSurfaceRenderTargetBase + private class RenderTarget : EglPlatformSurfaceRenderTargetBase { - private EglSurface _glSurface; + private EglSurface? _glSurface; private readonly IEglWindowGlPlatformSurfaceInfo _info; private PixelSize _currentSize; private readonly IntPtr _handle; @@ -42,12 +42,12 @@ namespace Avalonia.OpenGL.Egl _handle = _info.Handle; } - public override void Dispose() => _glSurface.Dispose(); + public override void Dispose() => _glSurface?.Dispose(); public override IGlPlatformSurfaceRenderingSession BeginDrawCore() { if (_info.Size != _currentSize - || _handle != _info.Handle + || _handle != _info.Handle || _glSurface == null) { _glSurface?.Dispose(); diff --git a/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs index f66f630556..927e1253fc 100644 --- a/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs +++ b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs @@ -33,7 +33,7 @@ namespace Avalonia.OpenGL.Egl public abstract IGlPlatformSurfaceRenderingSession BeginDrawCore(); protected IGlPlatformSurfaceRenderingSession BeginDraw(EglSurface surface, - PixelSize size, double scaling, Action onFinish = null, bool isYFlipped = false) + PixelSize size, double scaling, Action? onFinish = null, bool isYFlipped = false) { var restoreContext = Context.MakeCurrent(surface); @@ -56,19 +56,18 @@ namespace Avalonia.OpenGL.Egl restoreContext.Dispose(); } } - - class Session : IGlPlatformSurfaceRenderingSession + + private class Session : IGlPlatformSurfaceRenderingSession { private readonly EglContext _context; private readonly EglSurface _glSurface; private readonly EglDisplay _display; private readonly IDisposable _restoreContext; - private readonly Action _onFinish; - + private readonly Action? _onFinish; public Session(EglDisplay display, EglContext context, EglSurface glSurface, PixelSize size, double scaling, - IDisposable restoreContext, Action onFinish, bool isYFlipped) + IDisposable restoreContext, Action? onFinish, bool isYFlipped) { Size = size; Scaling = scaling; diff --git a/src/Avalonia.OpenGL/Egl/EglInterface.cs b/src/Avalonia.OpenGL/Egl/EglInterface.cs index d2d41f4def..2235d9087c 100644 --- a/src/Avalonia.OpenGL/Egl/EglInterface.cs +++ b/src/Avalonia.OpenGL/Egl/EglInterface.cs @@ -49,7 +49,7 @@ namespace Avalonia.OpenGL.Egl public partial IntPtr GetDisplay(IntPtr nativeDisplay); [GetProcAddress("eglGetPlatformDisplayEXT", true)] - public partial IntPtr GetPlatformDisplayExt(int platform, IntPtr nativeDisplay, int[] attrs); + public partial IntPtr GetPlatformDisplayExt(int platform, IntPtr nativeDisplay, int[]? attrs); [GetProcAddress("eglInitialize")] public partial bool Initialize(IntPtr display, out int major, out int minor); @@ -75,7 +75,7 @@ namespace Avalonia.OpenGL.Egl public partial bool DestroyContext(IntPtr display, IntPtr context); [GetProcAddress("eglCreatePbufferSurface")] - public partial IntPtr CreatePBufferSurface(IntPtr display, IntPtr config, int[] attrs); + public partial IntPtr CreatePBufferSurface(IntPtr display, IntPtr config, int[]? attrs); [GetProcAddress("eglMakeCurrent")] public partial bool MakeCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context); @@ -96,7 +96,7 @@ namespace Avalonia.OpenGL.Egl public partial void SwapBuffers(IntPtr display, IntPtr surface); [GetProcAddress("eglCreateWindowSurface")] - public partial IntPtr CreateWindowSurface(IntPtr display, IntPtr config, IntPtr window, int[] attrs); + public partial IntPtr CreateWindowSurface(IntPtr display, IntPtr config, IntPtr window, int[]? attrs); [GetProcAddress("eglBindTexImage")] public partial int BindTexImage(IntPtr display, IntPtr surface, int buffer); @@ -116,7 +116,7 @@ namespace Avalonia.OpenGL.Egl [GetProcAddress("eglQueryString")] public partial IntPtr QueryStringNative(IntPtr display, int i); - public string QueryString(IntPtr display, int i) + public string? QueryString(IntPtr display, int i) { var rv = QueryStringNative(display, i); if (rv == IntPtr.Zero) @@ -125,7 +125,7 @@ namespace Avalonia.OpenGL.Egl } [GetProcAddress("eglCreatePbufferFromClientBuffer")] - public partial IntPtr CreatePbufferFromClientBuffer(IntPtr display, int buftype, IntPtr buffer, IntPtr config, int[] attrib_list); + public partial IntPtr CreatePbufferFromClientBuffer(IntPtr display, int buftype, IntPtr buffer, IntPtr config, int[]? attrib_list); [GetProcAddress("eglQueryDisplayAttribEXT", true)] public partial bool QueryDisplayAttribExt(IntPtr display, int attr, out IntPtr res); diff --git a/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs b/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs index faa9f279a6..07e304febe 100644 --- a/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs +++ b/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs @@ -1,7 +1,6 @@ using System; using Avalonia.Logging; using Avalonia.Platform; -using static Avalonia.OpenGL.Egl.EglConsts; namespace Avalonia.OpenGL.Egl { @@ -24,7 +23,7 @@ namespace Avalonia.OpenGL.Egl AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature); } - public static EglPlatformGraphics TryCreate() => TryCreate(() => new EglDisplay(new EglDisplayCreationOptions + public static EglPlatformGraphics? TryCreate() => TryCreate(() => new EglDisplay(new EglDisplayCreationOptions { Egl = new EglInterface(), // Those are expected to be supported by most EGL implementations @@ -32,7 +31,7 @@ namespace Avalonia.OpenGL.Egl SupportsContextSharing = true })); - public static EglPlatformGraphics TryCreate(Func displayFactory) + public static EglPlatformGraphics? TryCreate(Func displayFactory) { try { diff --git a/src/Avalonia.OpenGL/Features/ExternalObjectsOpenGlExtensionFeature.cs b/src/Avalonia.OpenGL/Features/ExternalObjectsOpenGlExtensionFeature.cs index 02d152ff61..6778726f84 100644 --- a/src/Avalonia.OpenGL/Features/ExternalObjectsOpenGlExtensionFeature.cs +++ b/src/Avalonia.OpenGL/Features/ExternalObjectsOpenGlExtensionFeature.cs @@ -63,10 +63,10 @@ public class ExternalObjectsOpenGlExtensionFeature : IGlContextExternalObjectsFe { private readonly IGlContext _context; private readonly ExternalObjectsInterface _ext; - private List _imageTypes = new(); - private List _semaphoreTypes = new(); + private readonly List _imageTypes = new(); + private readonly List _semaphoreTypes = new(); - public static ExternalObjectsOpenGlExtensionFeature TryCreate(IGlContext context) + public static ExternalObjectsOpenGlExtensionFeature? TryCreate(IGlContext context) { var extensions = context.GlInterface.GetExtensions(); if (extensions.Contains("GL_EXT_memory_object") && extensions.Contains("GL_EXT_semaphore")) @@ -142,10 +142,15 @@ public class ExternalObjectsOpenGlExtensionFeature : IGlContextExternalObjectsFe public IGlExternalImageTexture ImportImage(IPlatformHandle handle, PlatformGraphicsExternalImageProperties properties) { - if(!_imageTypes.Contains(handle.HandleDescriptor)) - throw new ArgumentException(handle.HandleDescriptor + " is not supported"); - - if (handle.HandleDescriptor == KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor) + var handleDescriptor = handle.HandleDescriptor; + + if (string.IsNullOrEmpty(handleDescriptor)) + throw new ArgumentException("The handle must have a descriptor", nameof(handle)); + + if (!_imageTypes.Contains(handleDescriptor)) + throw new ArgumentException(handleDescriptor + " is not supported", nameof(handle)); + + if (handleDescriptor == KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor) { while (_context.GlInterface.GetError() != 0) { @@ -174,23 +179,27 @@ public class ExternalObjectsOpenGlExtensionFeature : IGlContextExternalObjectsFe return new ExternalImageTexture(_context, properties, _ext, memoryObject, texture); } - throw new ArgumentException(handle.HandleDescriptor + " is not supported"); + throw new ArgumentException(handleDescriptor + " is not supported", nameof(handle)); } public IGlExternalSemaphore ImportSemaphore(IPlatformHandle handle) { - if(!_semaphoreTypes.Contains(handle.HandleDescriptor)) - throw new ArgumentException(handle.HandleDescriptor + " is not supported"); + var handleDescriptor = handle.HandleDescriptor; + + if (string.IsNullOrEmpty(handleDescriptor)) + throw new ArgumentException("The handle must have a descriptor", nameof(handle)); + + if (!_semaphoreTypes.Contains(handleDescriptor)) + throw new ArgumentException(handleDescriptor + " is not supported"); - if (handle.HandleDescriptor == - KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor) + if (handleDescriptor == KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor) { _ext.GenSemaphoresEXT(1, out var semaphore); _ext.ImportSemaphoreFdEXT(semaphore, GL_HANDLE_TYPE_OPAQUE_FD_EXT, handle.Handle.ToInt32()); return new ExternalSemaphore(_context, _ext, semaphore); } - throw new ArgumentException(handle.HandleDescriptor + " is not supported"); + throw new ArgumentException(handleDescriptor + " is not supported", nameof(handle)); } public CompositionGpuImportedImageSynchronizationCapabilities GetSynchronizationCapabilities(string imageHandleType) @@ -200,10 +209,10 @@ public class ExternalObjectsOpenGlExtensionFeature : IGlContextExternalObjectsFe return default; } - public byte[] DeviceLuid { get; } - public byte[] DeviceUuid { get; } + public byte[]? DeviceLuid { get; } + public byte[]? DeviceUuid { get; } - unsafe class ExternalSemaphore : IGlExternalSemaphore + private unsafe class ExternalSemaphore : IGlExternalSemaphore { private readonly IGlContext _context; private readonly ExternalObjectsInterface _ext; @@ -242,7 +251,7 @@ public class ExternalObjectsOpenGlExtensionFeature : IGlContextExternalObjectsFe } } - class ExternalImageTexture : IGlExternalImageTexture + private class ExternalImageTexture : IGlExternalImageTexture { private readonly IGlContext _context; private readonly ExternalObjectsInterface _ext; diff --git a/src/Avalonia.OpenGL/GlBasicInfoInterface.cs b/src/Avalonia.OpenGL/GlBasicInfoInterface.cs index 7a7110f15b..38969cc966 100644 --- a/src/Avalonia.OpenGL/GlBasicInfoInterface.cs +++ b/src/Avalonia.OpenGL/GlBasicInfoInterface.cs @@ -2,14 +2,14 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; -using Avalonia.Platform.Interop; using Avalonia.SourceGenerator; namespace Avalonia.OpenGL { public unsafe partial class GlBasicInfoInterface { - public GlBasicInfoInterface(Func getProcAddress){ + public GlBasicInfoInterface(Func getProcAddress) + { Initialize(getProcAddress); } @@ -22,7 +22,7 @@ namespace Avalonia.OpenGL [GetProcAddress("glGetStringi")] public partial IntPtr GetStringiNative(int v, int v1); - public string GetString(int v) + public string? GetString(int v) { var ptr = GetStringNative(v); if (ptr != IntPtr.Zero) @@ -30,7 +30,7 @@ namespace Avalonia.OpenGL return null; } - public string GetString(int v, int index) + public string? GetString(int v, int index) { var ptr = GetStringiNative(v, index); if (ptr != IntPtr.Zero) @@ -46,7 +46,13 @@ namespace Avalonia.OpenGL GetIntegerv(GlConsts.GL_NUM_EXTENSIONS, out int count); var rv = new List(count); for (var c = 0; c < count; c++) - rv.Add(GetString(GlConsts.GL_EXTENSIONS, c)); + { + if (GetString(GlConsts.GL_EXTENSIONS, c) is { } extension) + { + rv.Add(extension); + } + } + return rv; } } diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs index a9dd882e3b..a6dadcd087 100644 --- a/src/Avalonia.OpenGL/GlInterface.cs +++ b/src/Avalonia.OpenGL/GlInterface.cs @@ -11,9 +11,9 @@ namespace Avalonia.OpenGL public unsafe partial class GlInterface : GlBasicInfoInterface { private readonly Func _getProcAddress; - public string Version { get; } - public string Vendor { get; } - public string Renderer { get; } + public string? Version { get; } + public string? Vendor { get; } + public string? Renderer { get; } public GlContextInfo ContextInfo { get; } public class GlContextInfo @@ -39,9 +39,9 @@ namespace Avalonia.OpenGL { _getProcAddress = getProcAddress; ContextInfo = info; - Version = GetString(GlConsts.GL_VERSION); - Renderer = GetString(GlConsts.GL_RENDERER); - Vendor = GetString(GlConsts.GL_VENDOR); + Version = GetString(GL_VERSION); + Renderer = GetString(GL_RENDERER); + Vendor = GetString(GL_VENDOR); Initialize(getProcAddress, ContextInfo); } @@ -203,7 +203,7 @@ namespace Avalonia.OpenGL [GetProcAddress("glGetShaderInfoLog")] public partial void GetShaderInfoLog(int shader, int maxLength, out int length, void* infoLog); - public unsafe string CompileShaderAndGetError(int shader, string source) + public unsafe string? CompileShaderAndGetError(int shader, string source) { ShaderSourceString(shader, source); CompileShader(shader); @@ -238,7 +238,7 @@ namespace Avalonia.OpenGL [GetProcAddress("glGetProgramInfoLog")] public partial void GetProgramInfoLog(int program, int maxLength, out int len, void* infoLog); - public unsafe string LinkProgramAndGetError(int program) + public unsafe string? LinkProgramAndGetError(int program) { LinkProgram(program); int compiled; @@ -374,4 +374,4 @@ namespace Avalonia.OpenGL }); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.OpenGL/IGlContext.cs b/src/Avalonia.OpenGL/IGlContext.cs index d45ea931d0..fb4554d37f 100644 --- a/src/Avalonia.OpenGL/IGlContext.cs +++ b/src/Avalonia.OpenGL/IGlContext.cs @@ -15,7 +15,7 @@ namespace Avalonia.OpenGL IDisposable EnsureCurrent(); bool IsSharedWith(IGlContext context); bool CanCreateSharedContext { get; } - IGlContext CreateSharedContext(IEnumerable preferredVersions = null); + IGlContext? CreateSharedContext(IEnumerable? preferredVersions = null); } public interface IGlPlatformSurfaceRenderTargetFactory diff --git a/src/Avalonia.OpenGL/IOpenGlTextureSharingRenderInterfaceContextFeature.cs b/src/Avalonia.OpenGL/IOpenGlTextureSharingRenderInterfaceContextFeature.cs index 2043e944d1..19127fba40 100644 --- a/src/Avalonia.OpenGL/IOpenGlTextureSharingRenderInterfaceContextFeature.cs +++ b/src/Avalonia.OpenGL/IOpenGlTextureSharingRenderInterfaceContextFeature.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using Avalonia.Rendering.Composition; @@ -7,7 +6,7 @@ namespace Avalonia.OpenGL public interface IOpenGlTextureSharingRenderInterfaceContextFeature { bool CanCreateSharedContext { get; } - IGlContext CreateSharedContext(IEnumerable preferredVersions = null); + IGlContext? CreateSharedContext(IEnumerable? preferredVersions = null); ICompositionImportableOpenGlSharedTexture CreateSharedTextureForComposition(IGlContext context, PixelSize size); } diff --git a/src/Avalonia.OpenGL/OpenGlException.cs b/src/Avalonia.OpenGL/OpenGlException.cs index c498ed7833..d7a42c4400 100644 --- a/src/Avalonia.OpenGL/OpenGlException.cs +++ b/src/Avalonia.OpenGL/OpenGlException.cs @@ -5,13 +5,13 @@ namespace Avalonia.OpenGL { public class OpenGlException : Exception { - public int? ErrorCode { get; private set; } + public int? ErrorCode { get; } - public OpenGlException(string message) : base(message) + public OpenGlException(string? message) : base(message) { } - private OpenGlException(string message, int errorCode) : base(message) + private OpenGlException(string? message, int errorCode) : base(message) { ErrorCode = errorCode; } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index ba646c64ee..eededb2836 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -34,9 +34,9 @@ namespace Avalonia.Skia private GRContext _grContext; public GRContext GrContext => _grContext; private ISkiaGpu _gpu; - private readonly SKPaint _strokePaint = SKPaintCache.Get(); - private readonly SKPaint _fillPaint = SKPaintCache.Get(); - private readonly SKPaint _boxShadowPaint = SKPaintCache.Get(); + private readonly SKPaint _strokePaint = SKPaintCache.Shared.Get(); + private readonly SKPaint _fillPaint = SKPaintCache.Shared.Get(); + private readonly SKPaint _boxShadowPaint = SKPaintCache.Shared.Get(); private static SKShader s_acrylicNoiseShader; private readonly ISkiaGpuRenderSession _session; private bool _leased = false; @@ -186,13 +186,13 @@ namespace Avalonia.Skia var s = sourceRect.ToSKRect(); var d = destRect.ToSKRect(); - var paint = SKPaintCache.Get(); + var paint = SKPaintCache.Shared.Get(); paint.Color = new SKColor(255, 255, 255, (byte)(255 * opacity * _currentOpacity)); paint.FilterQuality = bitmapInterpolationMode.ToSKFilterQuality(); paint.BlendMode = _currentBlendingMode.ToSKBlendMode(); drawableImage.Draw(this, s, d, paint); - SKPaintCache.ReturnReset(paint); + SKPaintCache.Shared.ReturnReset(paint); } /// @@ -315,15 +315,20 @@ namespace Avalonia.Skia var rc = rect.Rect.ToSKRect(); var isRounded = rect.IsRounded; var needRoundRect = rect.IsRounded; - using var skRoundRect = needRoundRect ? new SKRoundRect() : null; + SKRoundRect skRoundRect = null; if (needRoundRect) + { + skRoundRect = SKRoundRectCache.Shared.Get(); skRoundRect.SetRectRadii(rc, new[] { - rect.RadiiTopLeft.ToSKPoint(), rect.RadiiTopRight.ToSKPoint(), - rect.RadiiBottomRight.ToSKPoint(), rect.RadiiBottomLeft.ToSKPoint(), + rect.RadiiTopLeft.ToSKPoint(), + rect.RadiiTopRight.ToSKPoint(), + rect.RadiiBottomRight.ToSKPoint(), + rect.RadiiBottomLeft.ToSKPoint(), }); + } if (material != null) { @@ -332,6 +337,7 @@ namespace Avalonia.Skia if (isRounded) { Canvas.DrawRoundRect(skRoundRect, paint.Paint); + SKRoundRectCache.Shared.Return(skRoundRect); } else { @@ -356,14 +362,11 @@ namespace Avalonia.Skia var rc = rect.Rect.ToSKRect(); var isRounded = rect.IsRounded; var needRoundRect = rect.IsRounded || (boxShadows.HasInsetShadows); - using var skRoundRect = needRoundRect ? new SKRoundRect() : null; + SKRoundRect skRoundRect = null; if (needRoundRect) - skRoundRect.SetRectRadii(rc, - new[] - { - rect.RadiiTopLeft.ToSKPoint(), rect.RadiiTopRight.ToSKPoint(), - rect.RadiiBottomRight.ToSKPoint(), rect.RadiiBottomLeft.ToSKPoint(), - }); + { + skRoundRect = SKRoundRectCache.Shared.GetAndSetRadii(rc, rect); + } foreach (var boxShadow in boxShadows) { @@ -378,7 +381,7 @@ namespace Avalonia.Skia Canvas.Save(); if (isRounded) { - using var shadowRect = new SKRoundRect(skRoundRect); + var shadowRect = SKRoundRectCache.Shared.GetAndSetRadii(skRoundRect!.Rect, skRoundRect.Radii); if (spread != 0) shadowRect.Inflate(spread, spread); Canvas.ClipRoundRect(skRoundRect, @@ -388,6 +391,7 @@ namespace Avalonia.Skia Transform = oldTransform * Matrix.CreateTranslation(boxShadow.OffsetX, boxShadow.OffsetY); Canvas.DrawRoundRect(shadowRect, shadow.Paint); Transform = oldTransform; + SKRoundRectCache.Shared.Return(shadowRect); } else { @@ -433,7 +437,7 @@ namespace Avalonia.Skia var outerRect = AreaCastingShadowInHole(rc, (float)boxShadow.Blur, spread, offsetX, offsetY); Canvas.Save(); - using var shadowRect = new SKRoundRect(skRoundRect); + var shadowRect = SKRoundRectCache.Shared.GetAndSetRadii(skRoundRect!.Rect, skRoundRect.Radii); if (spread != 0) shadowRect.Deflate(spread, spread); Canvas.ClipRoundRect(skRoundRect, @@ -445,6 +449,7 @@ namespace Avalonia.Skia Canvas.DrawRoundRectDifference(outerRRect, shadowRect, shadow.Paint); Transform = oldTransform; Canvas.Restore(); + SKRoundRectCache.Shared.Return(shadowRect); } } } @@ -466,6 +471,9 @@ namespace Avalonia.Skia } } } + + if(isRounded) + SKRoundRectCache.Shared.Return(skRoundRect); } /// @@ -535,7 +543,24 @@ namespace Avalonia.Skia { CheckLease(); Canvas.Save(); - Canvas.ClipRoundRect(clip.ToSKRoundRect(), antialias:true); + + // Get the rounded rectangle + var rc = clip.Rect.ToSKRect(); + + // Get a round rect from the cache. + var roundRect = SKRoundRectCache.Shared.Get(); + + roundRect.SetRectRadii(rc, + new[] + { + clip.RadiiTopLeft.ToSKPoint(), clip.RadiiTopRight.ToSKPoint(), + clip.RadiiBottomRight.ToSKPoint(), clip.RadiiBottomLeft.ToSKPoint(), + }); + + Canvas.ClipRoundRect(roundRect, antialias:true); + + // Should not need to reset as SetRectRadii overrides the values. + SKRoundRectCache.Shared.Return(roundRect); } /// @@ -569,9 +594,9 @@ namespace Avalonia.Skia try { // Return leased paints. - SKPaintCache.ReturnReset(_strokePaint); - SKPaintCache.ReturnReset(_fillPaint); - SKPaintCache.ReturnReset(_boxShadowPaint); + SKPaintCache.Shared.ReturnReset(_strokePaint); + SKPaintCache.Shared.ReturnReset(_fillPaint); + SKPaintCache.Shared.ReturnReset(_boxShadowPaint); if (_grContext != null) { @@ -633,7 +658,7 @@ namespace Avalonia.Skia { CheckLease(); - var paint = SKPaintCache.Get(); + var paint = SKPaintCache.Shared.Get(); Canvas.SaveLayer(paint); _maskStack.Push(CreatePaint(paint, mask, bounds.Size)); @@ -644,11 +669,11 @@ namespace Avalonia.Skia { CheckLease(); - var paint = SKPaintCache.Get(); + var paint = SKPaintCache.Shared.Get(); paint.BlendMode = SKBlendMode.DstIn; Canvas.SaveLayer(paint); - SKPaintCache.ReturnReset(paint); + SKPaintCache.Shared.ReturnReset(paint); PaintWrapper paintWrapper; using (paintWrapper = _maskStack.Pop()) @@ -656,7 +681,7 @@ namespace Avalonia.Skia Canvas.DrawPaint(paintWrapper.Paint); } // Return the paint wrapper's paint less the reset since the paint is already reset in the Dispose method above. - SKPaintCache.Return(paintWrapper.Paint); + SKPaintCache.Shared.Return(paintWrapper.Paint); Canvas.Restore(); diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs index 15a3ebff40..51386d2a45 100644 --- a/src/Skia/Avalonia.Skia/GeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs @@ -81,12 +81,12 @@ namespace Avalonia.Skia } else { - var paint = SKPaintCache.Get(); + var paint = SKPaintCache.Shared.Get(); paint.IsStroke = true; paint.StrokeWidth = strokeWidth; paint.GetFillPath(EffectivePath, strokePath); - SKPaintCache.ReturnReset(paint); + SKPaintCache.Shared.ReturnReset(paint); _pathCache.Cache(strokePath, strokeWidth, strokePath.TightBounds.ToAvaloniaRect()); } diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs index 4b3c7a016d..25e004f4ef 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs @@ -13,7 +13,7 @@ namespace Avalonia.Skia { private readonly GRContext _grContext; private IGlPlatformSurfaceRenderTarget _surface; - + private static readonly SKSurfaceProperties _surfaceProperties = new SKSurfaceProperties(SKPixelGeometry.RgbHorizontal); public GlRenderTarget(GRContext grContext, IGlContext glContext, IGlPlatformSurface glSurface) { _grContext = grContext; @@ -92,7 +92,7 @@ namespace Avalonia.Skia var renderTarget = new GRBackendRenderTarget(size.Width, size.Height, samples, disp.StencilSize, glInfo); var surface = SKSurface.Create(_grContext, renderTarget, glSession.IsYFlipped ? GRSurfaceOrigin.TopLeft : GRSurfaceOrigin.BottomLeft, - colorType, new SKSurfaceProperties(SKPixelGeometry.RgbHorizontal)); + colorType, _surfaceProperties); success = true; diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index e795f3d304..8e9a19239b 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -76,13 +76,14 @@ namespace Avalonia.Skia } var fontRenderingEmSize = (float)glyphRun.FontRenderingEmSize; - var skFont = new SKFont(glyphTypeface.Typeface, fontRenderingEmSize) - { - Size = fontRenderingEmSize, - Edging = SKFontEdging.Alias, - Hinting = SKFontHinting.None, - LinearMetrics = true - }; + + var skFont = SKFontCache.Shared.Get(); + + skFont.Typeface = glyphTypeface.Typeface; + skFont.Size = fontRenderingEmSize; + skFont.Edging = SKFontEdging.Alias; + skFont.Hinting = SKFontHinting.None; + skFont.LinearMetrics = true; SKPath path = new SKPath(); @@ -101,6 +102,8 @@ namespace Avalonia.Skia currentX += glyphRun.GlyphInfos[i].GlyphAdvance; } + SKFontCache.Shared.Return(skFont); + return new StreamGeometryImpl(path); } @@ -224,20 +227,19 @@ namespace Avalonia.Skia var glyphTypefaceImpl = glyphTypeface as GlyphTypefaceImpl; - var font = new SKFont - { - LinearMetrics = true, - Subpixel = true, - Edging = SKFontEdging.SubpixelAntialias, - Hinting = SKFontHinting.Full, - Size = (float)fontRenderingEmSize, - Typeface = glyphTypefaceImpl.Typeface, - Embolden = (glyphTypefaceImpl.FontSimulations & FontSimulations.Bold) != 0, - SkewX = (glyphTypefaceImpl.FontSimulations & FontSimulations.Oblique) != 0 ? -0.2f : 0 - }; + var font = SKFontCache.Shared.Get(); + + font.LinearMetrics = true; + font.Subpixel = true; + font.Edging = SKFontEdging.SubpixelAntialias; + font.Hinting = SKFontHinting.Full; + font.Size = (float)fontRenderingEmSize; + font.Typeface = glyphTypefaceImpl.Typeface; + font.Embolden = (glyphTypefaceImpl.FontSimulations & FontSimulations.Bold) != 0; + font.SkewX = (glyphTypefaceImpl.FontSimulations & FontSimulations.Oblique) != 0 ? -0.2f : 0; - var builder = new SKTextBlobBuilder(); + var builder = SKTextBlobBuilderCache.Shared.Get(); var count = glyphInfos.Count; var runBuffer = builder.AllocatePositionedRun(font, count); @@ -245,6 +247,8 @@ namespace Avalonia.Skia var glyphSpan = runBuffer.GetGlyphSpan(); var positionSpan = runBuffer.GetPositionSpan(); + SKFontCache.Shared.Return(font); + var width = 0.0; for (int i = 0; i < count; i++) @@ -261,8 +265,11 @@ namespace Avalonia.Skia var scale = fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight; var height = glyphTypeface.Metrics.LineSpacing * scale; + var skTextBlob = builder.Build(); + + SKTextBlobBuilderCache.Shared.Return(builder); - return new GlyphRunImpl(builder.Build(), new Size(width, height), baselineOrigin); + return new GlyphRunImpl(skTextBlob, new Size(width, height), baselineOrigin); } } } diff --git a/src/Skia/Avalonia.Skia/SKCacheBase.cs b/src/Skia/Avalonia.Skia/SKCacheBase.cs new file mode 100644 index 0000000000..e1e78cd081 --- /dev/null +++ b/src/Skia/Avalonia.Skia/SKCacheBase.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Concurrent; +using SkiaSharp; + +namespace Avalonia.Skia +{ + /// + /// Cache base for Skia objects. + /// + internal abstract class SKCacheBase + where TCachedItem : IDisposable, new() + where TCache : new() + { + /// + /// Bag to hold the cached items. + /// + protected readonly ConcurrentBag Cache; + + /// + /// Shared cache. + /// + public static readonly TCache Shared = new TCache(); + + protected SKCacheBase() + { + Cache = new ConcurrentBag(); + } + + /// + /// Gets a cached item for usage. + /// + /// + /// If there is a available item in the cache, the cached item will be returned.. + /// Otherwise a new cached item will be created. + /// + /// + public TCachedItem Get() + { + if (!Cache.TryTake(out var item)) + { + item = new TCachedItem(); + } + + return item; + } + + /// + /// Returns the item for reuse later. + /// + /// + /// Do not use the item further. + /// Do not return the same item multiple times as that will break the cache. + /// + /// + public void Return(TCachedItem item) + { + Cache.Add(item); + } + + /// + /// Clears and disposes all cached items. + /// + public void Clear() + { + while (Cache.TryTake(out var item)) + { + item.Dispose(); + } + } + + } +} diff --git a/src/Skia/Avalonia.Skia/SKFontCache.cs b/src/Skia/Avalonia.Skia/SKFontCache.cs new file mode 100644 index 0000000000..348e085253 --- /dev/null +++ b/src/Skia/Avalonia.Skia/SKFontCache.cs @@ -0,0 +1,13 @@ +using System.Collections.Concurrent; +using SkiaSharp; + +namespace Avalonia.Skia +{ + /// + /// Cache for SKFonts. + /// + internal class SKFontCache : SKCacheBase + { + + } +} diff --git a/src/Skia/Avalonia.Skia/SKPaintCache.cs b/src/Skia/Avalonia.Skia/SKPaintCache.cs index 6588ab8da8..82c4dd23c7 100644 --- a/src/Skia/Avalonia.Skia/SKPaintCache.cs +++ b/src/Skia/Avalonia.Skia/SKPaintCache.cs @@ -6,46 +6,8 @@ namespace Avalonia.Skia /// /// Cache for SKPaints. /// - internal static class SKPaintCache + internal class SKPaintCache : SKCacheBase { - private static ConcurrentBag s_cachedPaints; - - static SKPaintCache() - { - s_cachedPaints = new ConcurrentBag(); - } - - /// - /// Gets a SKPaint for usage. - /// - /// - /// If a SKPaint is in the cache, that existing SKPaint will be returned. - /// Otherwise a new SKPaint will be created. - /// - /// - public static SKPaint Get() - { - if (!s_cachedPaints.TryTake(out var paint)) - { - paint = new SKPaint(); - } - - return paint; - } - - /// - /// Returns a SKPaint for reuse later. - /// - /// - /// Do not use the paint further. - /// Do not return the same paint multiple times as that will break the cache. - /// - /// - public static void Return(SKPaint paint) - { - s_cachedPaints.Add(paint); - } - /// /// Returns a SKPaint and resets it for reuse later. /// @@ -54,23 +16,11 @@ namespace Avalonia.Skia /// Do not return the same paint multiple times as that will break the cache. /// Uses SKPaint.Reset() for reuse later. /// - /// - public static void ReturnReset(SKPaint paint) + /// Paint to reset. + public void ReturnReset(SKPaint paint) { paint.Reset(); - s_cachedPaints.Add(paint); + Cache.Add(paint); } - - /// - /// Clears and disposes all cached paints. - /// - public static void Clear() - { - while (s_cachedPaints.TryTake(out var paint)) - { - paint.Dispose(); - } - } - } } diff --git a/src/Skia/Avalonia.Skia/SKRoundRectCache.cs b/src/Skia/Avalonia.Skia/SKRoundRectCache.cs new file mode 100644 index 0000000000..4be6171a93 --- /dev/null +++ b/src/Skia/Avalonia.Skia/SKRoundRectCache.cs @@ -0,0 +1,99 @@ +using System.Buffers; +using System.Collections.Concurrent; +using System.Collections.Generic; +using SkiaSharp; + +namespace Avalonia.Skia +{ + /// + /// Cache for SKRoundRectCache. + /// + internal class SKRoundRectCache : SKCacheBase + { + /// + /// Cache for points to use for setting the radii. + /// + private readonly ConcurrentBag _radiiCache = new(); + + /// + /// Gets a cached SKRoundRect and sets it with the passed rectangle and Radii. + /// + /// Rectangle size to set the cached rectangle to. + /// Rounded rectangle to copy the radii from. + /// Configured rounded rectangle + public SKRoundRect GetAndSetRadii(in SKRect rectangle, in RoundedRect roundedRect) + { + if (!Cache.TryTake(out var item)) + { + item = new SKRoundRect(); + } + + // Try and acquire a cached point array. + if (!_radiiCache.TryTake(out var skArray)) + { + skArray = new SKPoint[4]; + } + + skArray[0].X = (float)roundedRect.RadiiTopLeft.X; + skArray[0].Y = (float)roundedRect.RadiiTopLeft.Y; + skArray[1].X = (float)roundedRect.RadiiTopRight.X; + skArray[1].Y = (float)roundedRect.RadiiTopRight.Y; + skArray[2].X = (float)roundedRect.RadiiBottomRight.X; + skArray[2].Y = (float)roundedRect.RadiiBottomRight.Y; + skArray[3].X = (float)roundedRect.RadiiBottomLeft.X; + skArray[3].Y = (float)roundedRect.RadiiBottomLeft.Y; + + item.SetRectRadii(rectangle, skArray); + + // Add the array back to the cache. + _radiiCache.Add(skArray); + + return item; + } + + /// + /// Gets a cached SKRoundRect and sets it with the passed rectangle and Radii. + /// + /// Rectangle size to set the cached rectangle to. + /// point array of radii. + /// Configured rounded rectangle + public SKRoundRect GetAndSetRadii(in SKRect rectangle, in SKPoint[] radii) + { + if (!Cache.TryTake(out var item)) + { + item = new SKRoundRect(); + } + + item.SetRectRadii(rectangle, radii); + + return item; + } + /// + /// Returns a SKPaint and resets it for reuse later. + /// + /// + /// Do not use the rect further. + /// Do not return the same rect multiple times as that will break the cache. + /// Uses SKRoundRect.SetEmpty(); for reuse later. + /// + /// Rectangle to reset + public void ReturnReset(SKRoundRect rect) + { + rect.SetEmpty(); + Cache.Add(rect); + } + + /// + /// Clears and disposes all cached items. + /// + public new void Clear() + { + base.Clear(); + + // Clear out the cache of SKPoint arrays. + while (_radiiCache.TryTake(out var item)) + { + } + } + } +} diff --git a/src/Skia/Avalonia.Skia/SKTextBlobBuilderCache.cs b/src/Skia/Avalonia.Skia/SKTextBlobBuilderCache.cs new file mode 100644 index 0000000000..8c010ecb05 --- /dev/null +++ b/src/Skia/Avalonia.Skia/SKTextBlobBuilderCache.cs @@ -0,0 +1,13 @@ +using System.Collections.Concurrent; +using SkiaSharp; + +namespace Avalonia.Skia +{ + /// + /// Cache for SKTextBlobBuilder. + /// + internal class SKTextBlobBuilderCache : SKCacheBase + { + + } +} diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs index e1a6b93692..a21038839c 100644 --- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs +++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs @@ -1,5 +1,6 @@ using System; using System.Buffers; +using System.Collections.Concurrent; using System.Globalization; using System.Runtime.InteropServices; using Avalonia.Media.TextFormatting; @@ -13,6 +14,7 @@ namespace Avalonia.Skia { internal class TextShaperImpl : ITextShaperImpl { + private static readonly ConcurrentDictionary s_cachedLanguage = new(); public ShapedBuffer ShapeText(ReadOnlyMemory text, TextShaperOptions options) { var textSpan = text.Span; @@ -33,7 +35,9 @@ namespace Avalonia.Skia buffer.Direction = (bidiLevel & 1) == 0 ? Direction.LeftToRight : Direction.RightToLeft; - buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); + var usedCulture = culture ?? CultureInfo.CurrentCulture; + + buffer.Language = s_cachedLanguage.GetOrAdd(usedCulture.LCID, i => new Language(usedCulture)); var font = ((GlyphTypefaceImpl)typeface).Font; diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 57379d1878..287db92b4d 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; -using Avalonia.Controls; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media.Imaging; @@ -55,15 +55,18 @@ namespace Avalonia.Direct2D1 return; } #if DEBUG - try + if (Debugger.IsAttached) { - Direct2D1Factory = new SharpDX.Direct2D1.Factory1( - SharpDX.Direct2D1.FactoryType.MultiThreaded, + try + { + Direct2D1Factory = new SharpDX.Direct2D1.Factory1( + SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.Error); - } - catch - { - // + } + catch + { + // ignore, retry below without the debug layer + } } #endif if (Direct2D1Factory == null) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index baaed5104d..b6036bba8f 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -167,6 +167,22 @@ namespace Avalonia.Base.UnitTests Assert.Equal("foo", target.GetValue(property)); } + [Fact] + public void Completing_Animation_Binding_Reverts_To_Set_LocalValue_With_Style_Value() + { + var target = new Class1(); + var source = new Subject>(); + var property = Class1.FooProperty; + + target.SetValue(property, "style", BindingPriority.Style); + target.SetValue(property, "foo"); + target.Bind(property, source, BindingPriority.Animation); + source.OnNext("bar"); + source.OnCompleted(); + + Assert.Equal("foo", target.GetValue(property)); + } + [Fact] public void Completing_LocalValue_Binding_Raises_PropertyChanged() { diff --git a/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Inheritance.cs b/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Inheritance.cs index ed122e4ddc..91b0f9cdee 100644 --- a/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Inheritance.cs +++ b/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Inheritance.cs @@ -97,6 +97,27 @@ namespace Avalonia.Base.UnitTests.PropertyStore Assert.Same(parentStore, grandchild.GetValueStore().InheritanceAncestor); } + [Fact] + public void Child_Notifies_About_Setting_Back_To_Default_Value() + { + var parent = new Class1(); + var child = new Class1(); + + parent.Foo = "changed"; + child.Parent = parent; + + bool raised = false; + child.PropertyChanged += (_, args) => + { + raised = args.Property == Class1.FooProperty && args.GetNewValue() == "foodefault"; + }; + + Assert.Equal("changed", child.Foo); // inherited from parent. + + child.Foo = "foodefault"; // reset back to default. + Assert.True(raised); // expect event to be raised, as actual value was changed. + } + [Fact] public void Adding_Child_Sets_InheritanceAncestor() {