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