Browse Source

Merge branch 'master' into storage-provider-fixes

pull/10343/head
Max Katz 4 years ago
committed by GitHub
parent
commit
8e5f0f823b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
  2. 18
      src/Avalonia.Base/PropertyStore/ValueStore.cs
  3. 2
      src/Avalonia.Base/StyledProperty.cs
  4. 2
      src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
  5. 1
      src/Avalonia.OpenGL/Avalonia.OpenGL.csproj
  6. 5
      src/Avalonia.OpenGL/Controls/CompositionOpenGlSwapchain.cs
  7. 33
      src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
  8. 6
      src/Avalonia.OpenGL/Controls/OpenGlControlResources.cs
  9. 22
      src/Avalonia.OpenGL/Egl/EglContext.cs
  10. 11
      src/Avalonia.OpenGL/Egl/EglDisplay.cs
  11. 18
      src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs
  12. 11
      src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs
  13. 8
      src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs
  14. 11
      src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs
  15. 10
      src/Avalonia.OpenGL/Egl/EglInterface.cs
  16. 5
      src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs
  17. 43
      src/Avalonia.OpenGL/Features/ExternalObjectsOpenGlExtensionFeature.cs
  18. 16
      src/Avalonia.OpenGL/GlBasicInfoInterface.cs
  19. 18
      src/Avalonia.OpenGL/GlInterface.cs
  20. 2
      src/Avalonia.OpenGL/IGlContext.cs
  21. 3
      src/Avalonia.OpenGL/IOpenGlTextureSharingRenderInterfaceContextFeature.cs
  22. 6
      src/Avalonia.OpenGL/OpenGlException.cs
  23. 75
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  24. 4
      src/Skia/Avalonia.Skia/GeometryImpl.cs
  25. 4
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs
  26. 47
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  27. 72
      src/Skia/Avalonia.Skia/SKCacheBase.cs
  28. 13
      src/Skia/Avalonia.Skia/SKFontCache.cs
  29. 58
      src/Skia/Avalonia.Skia/SKPaintCache.cs
  30. 99
      src/Skia/Avalonia.Skia/SKRoundRectCache.cs
  31. 13
      src/Skia/Avalonia.Skia/SKTextBlobBuilderCache.cs
  32. 6
      src/Skia/Avalonia.Skia/TextShaperImpl.cs
  33. 19
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  34. 16
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
  35. 21
      tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Inheritance.cs

7
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<T> property)
public EffectiveValue(
AvaloniaObject owner,
StyledProperty<T> property,
EffectiveValue<T>? 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)
{

18
src/Avalonia.Base/PropertyStore/ValueStore.cs

@ -184,7 +184,7 @@ namespace Avalonia.PropertyStore
}
else
{
var effectiveValue = new EffectiveValue<T>(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<T>(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<T>(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<T> CreateEffectiveValue<T>(StyledProperty<T> property)
{
EffectiveValue<T>? inherited = null;
if (property.Inherits && TryGetInheritedValue(property, out var v))
inherited = (EffectiveValue<T>)v;
return new EffectiveValue<T>(Owner, property, inherited);
}
public void SetInheritanceParent(AvaloniaObject? newParent)
{
var values = AvaloniaPropertyDictionaryPool<OldNewValue>.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)

2
src/Avalonia.Base/StyledProperty.cs

@ -171,7 +171,7 @@ namespace Avalonia
internal override EffectiveValue CreateEffectiveValue(AvaloniaObject o)
{
return new EffectiveValue<TValue>(o, this);
return o.GetValueStore().CreateEffectiveValue(this);
}
/// <inheritdoc/>

2
src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs

@ -68,8 +68,6 @@ namespace Avalonia.Headless
.Bind<IKeyboardDevice>().ToConstant(new KeyboardDevice())
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(new RenderTimer(60))
.Bind<IFontManagerImpl>().ToSingleton<HeadlessFontManagerStub>()
.Bind<ITextShaperImpl>().ToSingleton<HeadlessTextShaperStub>()
.Bind<IWindowingPlatform>().ToConstant(new HeadlessWindowingPlatform())
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService<IRenderLoop>(), null);

1
src/Avalonia.OpenGL/Avalonia.OpenGL.csproj

@ -13,6 +13,7 @@
<Import Project="..\..\build\DevAnalyzers.props" />
<Import Project="..\..\build\SourceGenerators.props" />
<Import Project="..\..\build\TrimmingEnable.props" />
<Import Project="..\..\build\NullableEnable.props" />
<ItemGroup>
<Compile Remove="..\Shared\SourceGeneratorAttributes.cs" />

5
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<IGlSwapchainImage>
}
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()

33
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<bool> _initialization;
private Task<bool>? _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<IPlatformGraphicsOpenGlContextFactory>();
IGlContext? ctx = null;
try
{
if (contextSharingFeature?.CanCreateSharedContext == true)
@ -78,6 +79,7 @@ namespace Avalonia.OpenGL.Controls
if(_resources == null)
{
var contextFactory = AvaloniaLocator.Current.GetRequiredService<IPlatformGraphicsOpenGlContextFactory>();
ctx = contextFactory.CreateContext(null);
if (ctx.TryGetFeature<IGlContextExternalObjectsFeature>(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)));
}

6
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;

22
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<Type, object> _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<Type, Func<EglContext, object>> 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<GlVersion> preferredVersions = null) =>
public IGlContext CreateSharedContext(IEnumerable<GlVersion>? 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;
}

11
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<EglContext> _contexts = new();
private readonly List<EglContext> _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();
}
}

18
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<bool> DeviceLostCheckCallback { get; set; }
public Action DisposeCallback { get; set; }
public IEnumerable<GlVersion> GlVersions { get; set; }
public Func<bool>? DeviceLostCheckCallback { get; set; }
public Action? DisposeCallback { get; set; }
public IEnumerable<GlVersion>? GlVersions { get; set; }
}
public class EglContextOptions
{
public EglContext ShareWith { get; set; }
public EglSurface OffscreenSurface { get; set; }
public Action DisposeCallback { get; set; }
public Dictionary<Type, Func<EglContext, object>> ExtraFeatures { get; set; }
public EglContext? ShareWith { get; set; }
public EglSurface? OffscreenSurface { get; set; }
public Action? DisposeCallback { get; set; }
public Dictionary<Type, Func<EglContext, object>>? 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; }
}

11
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<GlVersion> versions)
public static EglConfigInfo InitializeAndGetConfig(EglInterface egl, IntPtr display, IEnumerable<GlVersion>? 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<AngleOptions>()?.GlProfiles
@ -112,7 +111,7 @@ static class EglDisplayUtils
}
class EglConfigInfo
internal class EglConfigInfo
{
public IntPtr Config { get; }
public GlVersion Version { get; }

8
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();

11
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;

10
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);

5
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<IPlatformGraphics>().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<EglDisplay> displayFactory)
public static EglPlatformGraphics? TryCreate(Func<EglDisplay> displayFactory)
{
try
{

43
src/Avalonia.OpenGL/Features/ExternalObjectsOpenGlExtensionFeature.cs

@ -63,10 +63,10 @@ public class ExternalObjectsOpenGlExtensionFeature : IGlContextExternalObjectsFe
{
private readonly IGlContext _context;
private readonly ExternalObjectsInterface _ext;
private List<string> _imageTypes = new();
private List<string> _semaphoreTypes = new();
private readonly List<string> _imageTypes = new();
private readonly List<string> _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;

16
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<string, IntPtr> getProcAddress){
public GlBasicInfoInterface(Func<string, IntPtr> 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<string>(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;
}
}

18
src/Avalonia.OpenGL/GlInterface.cs

@ -11,9 +11,9 @@ namespace Avalonia.OpenGL
public unsafe partial class GlInterface : GlBasicInfoInterface
{
private readonly Func<string, IntPtr> _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
});
}
}
}
}

2
src/Avalonia.OpenGL/IGlContext.cs

@ -15,7 +15,7 @@ namespace Avalonia.OpenGL
IDisposable EnsureCurrent();
bool IsSharedWith(IGlContext context);
bool CanCreateSharedContext { get; }
IGlContext CreateSharedContext(IEnumerable<GlVersion> preferredVersions = null);
IGlContext? CreateSharedContext(IEnumerable<GlVersion>? preferredVersions = null);
}
public interface IGlPlatformSurfaceRenderTargetFactory

3
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<GlVersion> preferredVersions = null);
IGlContext? CreateSharedContext(IEnumerable<GlVersion>? preferredVersions = null);
ICompositionImportableOpenGlSharedTexture CreateSharedTextureForComposition(IGlContext context, PixelSize size);
}

6
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;
}

75
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);
}
/// <inheritdoc />
@ -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);
}
/// <inheritdoc />
@ -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);
}
/// <inheritdoc />
@ -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();

4
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());
}

4
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;

47
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);
}
}
}

72
src/Skia/Avalonia.Skia/SKCacheBase.cs

@ -0,0 +1,72 @@
using System;
using System.Collections.Concurrent;
using SkiaSharp;
namespace Avalonia.Skia
{
/// <summary>
/// Cache base for Skia objects.
/// </summary>
internal abstract class SKCacheBase<TCachedItem, TCache>
where TCachedItem : IDisposable, new()
where TCache : new()
{
/// <summary>
/// Bag to hold the cached items.
/// </summary>
protected readonly ConcurrentBag<TCachedItem> Cache;
/// <summary>
/// Shared cache.
/// </summary>
public static readonly TCache Shared = new TCache();
protected SKCacheBase()
{
Cache = new ConcurrentBag<TCachedItem>();
}
/// <summary>
/// Gets a cached item for usage.
/// </summary>
/// <remarks>
/// If there is a available item in the cache, the cached item will be returned..
/// Otherwise a new cached item will be created.
/// </remarks>
/// <returns></returns>
public TCachedItem Get()
{
if (!Cache.TryTake(out var item))
{
item = new TCachedItem();
}
return item;
}
/// <summary>
/// Returns the item for reuse later.
/// </summary>
/// <remarks>
/// Do not use the item further.
/// Do not return the same item multiple times as that will break the cache.
/// </remarks>
/// <param name="item"></param>
public void Return(TCachedItem item)
{
Cache.Add(item);
}
/// <summary>
/// Clears and disposes all cached items.
/// </summary>
public void Clear()
{
while (Cache.TryTake(out var item))
{
item.Dispose();
}
}
}
}

13
src/Skia/Avalonia.Skia/SKFontCache.cs

@ -0,0 +1,13 @@
using System.Collections.Concurrent;
using SkiaSharp;
namespace Avalonia.Skia
{
/// <summary>
/// Cache for SKFonts.
/// </summary>
internal class SKFontCache : SKCacheBase<SKFont, SKFontCache>
{
}
}

58
src/Skia/Avalonia.Skia/SKPaintCache.cs

@ -6,46 +6,8 @@ namespace Avalonia.Skia
/// <summary>
/// Cache for SKPaints.
/// </summary>
internal static class SKPaintCache
internal class SKPaintCache : SKCacheBase<SKPaint, SKPaintCache>
{
private static ConcurrentBag<SKPaint> s_cachedPaints;
static SKPaintCache()
{
s_cachedPaints = new ConcurrentBag<SKPaint>();
}
/// <summary>
/// Gets a SKPaint for usage.
/// </summary>
/// <remarks>
/// If a SKPaint is in the cache, that existing SKPaint will be returned.
/// Otherwise a new SKPaint will be created.
/// </remarks>
/// <returns></returns>
public static SKPaint Get()
{
if (!s_cachedPaints.TryTake(out var paint))
{
paint = new SKPaint();
}
return paint;
}
/// <summary>
/// Returns a SKPaint for reuse later.
/// </summary>
/// <remarks>
/// Do not use the paint further.
/// Do not return the same paint multiple times as that will break the cache.
/// </remarks>
/// <param name="paint"></param>
public static void Return(SKPaint paint)
{
s_cachedPaints.Add(paint);
}
/// <summary>
/// Returns a SKPaint and resets it for reuse later.
/// </summary>
@ -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.
/// </remarks>
/// <param name="paint"></param>
public static void ReturnReset(SKPaint paint)
/// <param name="paint">Paint to reset.</param>
public void ReturnReset(SKPaint paint)
{
paint.Reset();
s_cachedPaints.Add(paint);
Cache.Add(paint);
}
/// <summary>
/// Clears and disposes all cached paints.
/// </summary>
public static void Clear()
{
while (s_cachedPaints.TryTake(out var paint))
{
paint.Dispose();
}
}
}
}

99
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
{
/// <summary>
/// Cache for SKRoundRectCache.
/// </summary>
internal class SKRoundRectCache : SKCacheBase<SKRoundRect, SKRoundRectCache>
{
/// <summary>
/// Cache for points to use for setting the radii.
/// </summary>
private readonly ConcurrentBag<SKPoint[]> _radiiCache = new();
/// <summary>
/// Gets a cached SKRoundRect and sets it with the passed rectangle and Radii.
/// </summary>
/// <param name="rectangle">Rectangle size to set the cached rectangle to.</param>
/// <param name="roundedRect">Rounded rectangle to copy the radii from.</param>
/// <returns>Configured rounded rectangle</returns>
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;
}
/// <summary>
/// Gets a cached SKRoundRect and sets it with the passed rectangle and Radii.
/// </summary>
/// <param name="rectangle">Rectangle size to set the cached rectangle to.</param>
/// <param name="radii">point array of radii.</param>
/// <returns>Configured rounded rectangle</returns>
public SKRoundRect GetAndSetRadii(in SKRect rectangle, in SKPoint[] radii)
{
if (!Cache.TryTake(out var item))
{
item = new SKRoundRect();
}
item.SetRectRadii(rectangle, radii);
return item;
}
/// <summary>
/// Returns a SKPaint and resets it for reuse later.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="rect">Rectangle to reset</param>
public void ReturnReset(SKRoundRect rect)
{
rect.SetEmpty();
Cache.Add(rect);
}
/// <summary>
/// Clears and disposes all cached items.
/// </summary>
public new void Clear()
{
base.Clear();
// Clear out the cache of SKPoint arrays.
while (_radiiCache.TryTake(out var item))
{
}
}
}
}

13
src/Skia/Avalonia.Skia/SKTextBlobBuilderCache.cs

@ -0,0 +1,13 @@
using System.Collections.Concurrent;
using SkiaSharp;
namespace Avalonia.Skia
{
/// <summary>
/// Cache for SKTextBlobBuilder.
/// </summary>
internal class SKTextBlobBuilderCache : SKCacheBase<SKTextBlobBuilder, SKTextBlobBuilderCache>
{
}
}

6
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<int, Language> s_cachedLanguage = new();
public ShapedBuffer ShapeText(ReadOnlyMemory<char> 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;

19
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)

16
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<BindingValue<string>>();
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()
{

21
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<string>() == "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()
{

Loading…
Cancel
Save