Browse Source

Fix vector geometry anti-aliasing regression by making stencil buffers opt-in (#21899)

* Add failing test for stencil buffer default, single-source the policy

Stencil buffer selection was duplicated as `useStencilBuffers == false` across the
GL, Metal and Vulkan backends, so there was no single place to review or test the
default. Move it to SkiaOptions.ShouldAvoidStencilBuffers and add a test asserting
the contract the default should honour. The test fails at this commit.

* Avoid stencil buffers unless explicitly enabled

Enabling stencil buffers lets Skia select multisample-based path rendering, which
quantizes edge coverage instead of computing analytic coverage. Vector geometry
therefore rasterizes with visibly softer, rougher edges on GPU backends, most
noticeably on small icons. Text is unaffected because glyphs are drawn from the
mask atlas rather than the path renderer.

Make the null default avoid stencil buffers again, restoring 12.0.5 rendering.
Applications that want the faster clipping can still opt in explicitly with
SkiaOptions.UseStencilBuffers = true.

Fixes #21760
pull/22069/head
Nathan Nguyen 2 months ago
committed by GitHub
parent
commit
6074f9cce2
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs
  2. 2
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs
  3. 2
      src/Skia/Avalonia.Skia/Gpu/Vulkan/VulkanSkiaGpu.cs
  4. 12
      src/Skia/Avalonia.Skia/SkiaOptions.cs
  5. 19
      tests/Avalonia.Skia.UnitTests/SkiaOptionsTests.cs

2
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 })

2
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)
{

2
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");

12
src/Skia/Avalonia.Skia/SkiaOptions.cs

@ -26,9 +26,17 @@ namespace Avalonia
public bool UseOpacitySaveLayer { get; set; } = false;
/// <summary>
/// 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.
/// </summary>
public bool? UseStencilBuffers { get; set; }
/// <summary>
/// Maps <see cref="UseStencilBuffers"/> onto Skia's inverted <c>GRContextOptions.AvoidStencilBuffers</c>.
/// Only an explicit <c>true</c> opts in: stencil buffers trade geometry anti-aliasing quality for
/// clipping speed (see issue #21760), so they must never be enabled implicitly.
/// </summary>
internal static bool ShouldAvoidStencilBuffers(bool? useStencilBuffers) => useStencilBuffers != true;
}
}

19
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));
}
}
}
Loading…
Cancel
Save