diff --git a/src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs b/src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs index 05bcd9fa42..c60b3b3dba 100644 --- a/src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs @@ -17,7 +17,7 @@ internal class SkiaMetalGpu : ISkiaGpu public SkiaMetalGpu(IMetalDevice device, long? maxResourceBytes, bool? useStencilBuffers = null) { - var avoidStencilBuffers = useStencilBuffers == false; + var avoidStencilBuffers = SkiaOptions.ShouldAvoidStencilBuffers(useStencilBuffers); _context = GRContext.CreateMetal( new GRMtlBackendContext { DeviceHandle = device.Device, QueueHandle = device.CommandQueue, }, new GRContextOptions { AvoidStencilBuffers = avoidStencilBuffers }) diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs index af957551e3..107ab79ac7 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs @@ -40,7 +40,7 @@ namespace Avalonia.Skia using(iface) { - var avoidStencilBuffers = useStencilBuffers == false; + var avoidStencilBuffers = SkiaOptions.ShouldAvoidStencilBuffers(useStencilBuffers); _grContext = GRContext.CreateGl(iface, new GRContextOptions { AvoidStencilBuffers = avoidStencilBuffers }); if (maxResourceBytes.HasValue) { diff --git a/src/Skia/Avalonia.Skia/Gpu/Vulkan/VulkanSkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/Vulkan/VulkanSkiaGpu.cs index b66054b340..9f8a746044 100644 --- a/src/Skia/Avalonia.Skia/Gpu/Vulkan/VulkanSkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/Vulkan/VulkanSkiaGpu.cs @@ -48,7 +48,7 @@ internal class VulkanSkiaGpu : ISkiaGpu GetProcedureAddress = GetProcAddressWrapper }; - var avoidStencilBuffers = useStencilBuffers == false; + var avoidStencilBuffers = SkiaOptions.ShouldAvoidStencilBuffers(useStencilBuffers); GrContext = GRContext.CreateVulkan(ctx, new GRContextOptions { AvoidStencilBuffers = avoidStencilBuffers }) ?? throw new VulkanException("Unable to create GrContext from IVulkanDevice"); diff --git a/src/Skia/Avalonia.Skia/SkiaOptions.cs b/src/Skia/Avalonia.Skia/SkiaOptions.cs index 1a95919300..9afaf6b307 100644 --- a/src/Skia/Avalonia.Skia/SkiaOptions.cs +++ b/src/Skia/Avalonia.Skia/SkiaOptions.cs @@ -26,9 +26,17 @@ namespace Avalonia public bool UseOpacitySaveLayer { get; set; } = false; /// - /// Gets whether stencil buffers can be used for various draw operations, improving performance. - /// If null (the default), Avalonia chooses whether to enable stencil buffers depending on the platform. + /// Gets whether stencil buffers can be used for various draw operations, improving clipping performance. + /// Enabling them lets Skia pick multisample-based path rendering, which quantizes edge coverage and + /// visibly degrades anti-aliasing of vector geometry, so they are disabled unless explicitly requested. /// public bool? UseStencilBuffers { get; set; } + + /// + /// Maps onto Skia's inverted GRContextOptions.AvoidStencilBuffers. + /// Only an explicit true opts in: stencil buffers trade geometry anti-aliasing quality for + /// clipping speed (see issue #21760), so they must never be enabled implicitly. + /// + internal static bool ShouldAvoidStencilBuffers(bool? useStencilBuffers) => useStencilBuffers != true; } } diff --git a/tests/Avalonia.Skia.UnitTests/SkiaOptionsTests.cs b/tests/Avalonia.Skia.UnitTests/SkiaOptionsTests.cs new file mode 100644 index 0000000000..660d89b929 --- /dev/null +++ b/tests/Avalonia.Skia.UnitTests/SkiaOptionsTests.cs @@ -0,0 +1,19 @@ +using Xunit; + +namespace Avalonia.Skia.UnitTests +{ + public class SkiaOptionsTests + { + // Stencil buffers let Skia choose multisample-based path rendering, which quantizes edge + // coverage and visibly degraded vector geometry anti-aliasing on GPU backends in 12.1.0. + // They are a performance opt-in, so anything other than an explicit `true` must avoid them. + [Theory] + [InlineData(null, true)] + [InlineData(false, true)] + [InlineData(true, false)] + public void Stencil_Buffers_Are_Avoided_Unless_Explicitly_Enabled(bool? useStencilBuffers, bool expected) + { + Assert.Equal(expected, SkiaOptions.ShouldAvoidStencilBuffers(useStencilBuffers)); + } + } +}