Skip to content

Add SkiaSharp.Vulkan.Silk.NET package and run the Vulkan tests on Android + Linux - #4527

Merged
mattleibow merged 17 commits into
mainfrom
mattleibow-research-dotnet-vulkan-bindings
Jul 24, 2026
Merged

Add SkiaSharp.Vulkan.Silk.NET package and run the Vulkan tests on Android + Linux#4527
mattleibow merged 17 commits into
mainfrom
mattleibow-research-dotnet-vulkan-bindings

Conversation

@mattleibow

@mattleibow mattleibow commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

SkiaSharp's only managed Vulkan helper today is SkiaSharp.Vulkan.SharpVk, pinned to SharpVk 0.4.2 (a 2018 release). SharpVk is effectively unmaintained and — critically — its native loader throws NotSupportedException on Android (it only handles Windows and Linux), which is why the Vulkan tests have historically been desktop/Windows-only.

This PR:

  1. Adds a new convenience package, SkiaSharp.Vulkan.Silk.NET, built on the actively maintained, cross-platform Silk.NET.Vulkan, mirroring the shape of the existing SharpVk package.
  2. Makes GRVkExtensions directly constructible so a Vulkan backend can be brought up without a bespoke factory.
  3. Restructures the Vulkan tests so they actually run on Linux (software Lavapipe) and Android (real libvulkan.so) in CI — the platform SharpVk could never reach — while remaining correct on Apple, WASM, and .NET Framework.

SkiaSharp.Vulkan.SharpVk is intentionally left in place; nothing is removed or broken.

Stacked follow-up: #4531 builds on this PR to enable the Windows Vulkan CI leg (a software SwiftShader ICD + Windows goldens). It targets this branch and merges after this PR.


1. New package: SkiaSharp.Vulkan.Silk.NET

A thin convenience layer (namespace SkiaSharp, multi-targets netstandard2.0net10.0) that maps Silk.NET's Vulkan handles onto SkiaSharp's raw-IntPtr GRVkBackendContext:

  • GRSilkNetBackendContext : GRVkBackendContext — takes Silk.NET's Instance / PhysicalDevice / Device / Queue (nint handles) plus a pinned PhysicalDeviceFeatures, and bridges the GetProcedureAddress delegate that Skia calls to resolve Vulkan entry points.
  • GRSilkNetGetProcedureAddressDelegate(string name, Instance, Device) — the proc-address shape Silk.NET consumers already have.
  • GRVkExtensionsSilkNetExtensions.Initialize(...) — a typed extension on GRVkExtensions (plus an overload accepting instance/device extension name arrays), matching the SharpVk helper's ergonomics.

Idiomatic usage:

using var ext = new GRVkExtensions();
ext.Initialize(getProc, instance, physicalDevice);

using var backend = new GRSilkNetBackendContext { /* handles + getProc */ };
using var context = GRContext.CreateVulkan(backend);

2. Core API change: public GRVkExtensions constructor

GRVkExtensions() (the parameterless constructor) is changed from private to public. This mirrors native skgpu::VulkanExtensions (default-construct, then init) and SkiaSharp's own allocating SKPaint/SKPath constructors, and lets the Silk.NET helper expose a plain Initialize(...) extension instead of a one-off Create(...) factory. GRVkExtensions.Create(...) is unchanged.

This is the only change to a shipping package. It is purely additive (a widened accessibility), so it is ABI/API-safe.

3. Test restructuring (why the test project layout changed)

The Vulkan tests bring up Vulkan through a headless Silk.NET context (SilkVkContext) that uses the OS-provided loader, so they run anywhere a Vulkan ICD is present. The SharpVk-specific tests are kept but stay Windows-only.

The unsigned Silk.NET.Vulkan / SharpVk dependencies cannot be added to the shared SkiaSharp.Tests base library, because that library flows into every test host, and two hosts reject those dependencies:

Host Problem if Vulkan deps are in the base library
iOS / Mac Catalyst The Apple AOT/ILC linker must resolve every native symbol at link time. There is no libvulkan on Apple, so the link fails with clang++ ... Undefined symbols: _vkBindImageMemory (and friends).
Windows .NET Framework (net48) The full framework enforces strong-name references at load time. The strong-named test host cannot load the unsigned Silk.NET.Vulkan, throwing FileLoadException "A strongly-named assembly is required" (0x80131044). (.NET Core does not enforce this, so the failure only appears at runtime on net48.)

So the Vulkan tests live in two dedicated, non-strong-named projects, and the base library stays Vulkan-free:

  • tests/SkiaSharp.Vulkan.Tests.Console — a standalone Microsoft.Testing.Platform executable targeting net10.0 + net48, SignAssembly=false. It references the signed SkiaSharp.Tests.Console for shared infrastructure (an unsigned assembly may reference a signed one) plus Silk.NET.Vulkan / SharpVk. This is the desktop runner (Linux/Lavapipe and Windows).
  • tests/SkiaSharp.Vulkan.Tests — a library targeting only net10.0-android / -windows, SignAssembly=false, referenced by the MAUI Devices host and registered in MauiProgram. Vulkan therefore never reaches the iOS/Mac Catalyst heads.

Supporting changes so the separate assemblies integrate cleanly with the shared visual-regression harness:

  • CatalogReflection discovers renderers across the catalog assembly, the entry assembly, and all loaded SkiaSharp.* assemblies. This lets the ganesh-vulkan renderer (in the separate Vulkan library) join the shared VisualMatrixTests inside the single-process MAUI Devices host. It also sets up the identical pattern for a future Direct3D test library: reference the library + register its assembly.
  • VisualMatrixTests matches only its own assembly's renderers (NamesIn(own assembly) instead of AllNames), so when both the base matrix and a satellite are loaded in one process, no visual cell is rendered twice.
  • GPU rendering is serialized with an xUnit [Collection(DisableParallelization = true)] instead of the previous process-wide GpuRenderGate static lock — the idiomatic xUnit equivalent, matching the existing HandleDictionaryThreadingCollection pattern (the GL and Metal renderers drop the manual lock accordingly). xUnit collections are scoped to a single assembly, so each assembly that drives GPU work carries its own definition: GpuRenderingCollection (base library, serializing the GL/Metal classes) and VulkanGpuRenderingCollection (Vulkan projects, serializing the Vulkan classes). No cross-assembly lock is needed, because the base and Vulkan tests never run concurrently: the desktop Console hosts run them as separate processes, and the MAUI Devices runner (DeviceRunners) executes its registered assemblies sequentially (assembly-level parallelism is off by default).
  • tests/Dockerfile.linux installs libvulkan1 (the loader) + mesa-vulkan-drivers (Mesa Lavapipe software ICD) so the Vulkan GPU tests render headlessly in the container.

Platform behaviour

Platform Vulkan tests How Vulkan is provided
Linux (desktop / CI container) Run Mesa Lavapipe software ICD (libvulkan1 + mesa-vulkan-drivers)
Android (emulator / device) Run OS-provided libvulkan.so (real ICD) — the platform SharpVk cannot reach
Windows (net48 + net10) Run OS Vulkan loader; skips at runtime when no ICD is present. This PR's Windows CI has no ICD (so the cells skip); the stacked #4531 provisions a software SwiftShader ICD so the Windows leg actually executes.
iOS / Mac Catalyst Not present Vulkan library is not referenced on Apple heads
macOS (desktop) Skip at runtime Mac native is not built with Vulkan (see #3202)
WASM (browser) Not present Vulkan library is not referenced by the shared library

Validation

  • Linux (repo test container + Mesa Lavapipe): the Silk.NET Vulkan tests run and pass (GRContext.CreateVulkan → render) — locally 13 passed / 2 skipped / 0 failed (the 2 skips are the Windows-only SharpVk tests); the visual matrix is green against the seeded ganesh-vulkan.linux goldens.
  • Android (API 36 arm64 emulator, Vulkan 1.x via the ranchu ICD): the Devices head builds and deploys, Vulkan comes up through libvulkan.so, and both the base VisualMatrixTests and the Vulkan VulkanVisualTests render and pass — the five ganesh-vulkan.android cells match seeded goldens, and each renderer runs exactly once (no double-run).
  • iOS / Mac Catalyst: the Devices heads build cleanly — the earlier clang++ ... Undefined symbols: _vk* link failures are gone because Vulkan is no longer linked into the Apple heads.
  • macOS (desktop): the Vulkan tests skip cleanly (no macOS Vulkan).
  • CI: green on the xamarin "SkiaSharp (Public)" pipeline for the final commit; the previously-red iOS, Mac Catalyst, and Windows .NET Framework legs all report 0 failed tests.

API changes

Core SkiaSharp package (additive only):

  • GRVkExtensions() — parameterless constructor is now public (was private). GRVkExtensions.Create(...) unchanged.

New package SkiaSharp.Vulkan.Silk.NET (namespace SkiaSharp):

  • class GRSilkNetBackendContext : GRVkBackendContext
  • delegate IntPtr GRSilkNetGetProcedureAddressDelegate(string name, Silk.NET.Vulkan.Instance instance, Silk.NET.Vulkan.Device device)
  • static void GRVkExtensionsSilkNetExtensions.Initialize(this GRVkExtensions, GRSilkNetGetProcedureAddressDelegate, Instance, PhysicalDevice) (+ overload taking instance/device extension arrays)

No existing public APIs are removed. SkiaSharp.Vulkan.SharpVk is untouched.

Notes for reviewers / follow-ups

  • Goldens: ganesh-vulkan.linux (Lavapipe) and ganesh-vulkan.android (emulator) goldens are seeded in this PR. The CI Android emulator uses a different GPU path (swiftshader_indirect) than a local emulator, so those goldens may need reseeding from the CI leg. Windows Vulkan enablement (a software SwiftShader ICD + the ganesh-vulkan.windows goldens) is delivered by the stacked follow-up #4531, which has already produced real Windows renders and a green Windows Vulkan leg; it lands on top of this PR.
  • Docs: the new public types and the now-public GRVkExtensions constructor still need generated mdoc XML via the normal docs flow.

Bugs Fixed: N/A

Required skia PR: None.

PR Checklist

  • Has tests (Silk.NET Vulkan context + backend-context tests; visual goldens; validated on Linux/Lavapipe and an Android emulator)
  • Rebased on top of main at time of PR
  • Merged related skia PRs (N/A)
  • Changes adhere to coding standard
  • Updated documentation (mdoc XML for the new types + the now-public GRVkExtensions ctor)

Copilot AI added 6 commits July 23, 2026 17:06
Adds a new convenience package that brings up Vulkan for SkiaSharp via the
actively-maintained, cross-platform Silk.NET.Vulkan binding, alongside the
existing (2018, Android-incapable) SharpVk package which is left untouched.

- source/SkiaSharp.Vulkan/SkiaSharp.Vulkan.Silk.NET: GRSilkNetBackendContext
  (maps Silk.NET Instance/PhysicalDevice/Device/Queue nint handles + pinned
  PhysicalDeviceFeatures + GetProcedureAddress to the raw-IntPtr
  GRVkBackendContext), GRSilkNetGetProcedureAddressDelegate, and the
  GRVkExtensionsSilkNetExtensions.Initialize bridges.
- Wires the package into SkiaSharpSource.sln and scripts/VERSIONS.txt.
- Adds device-free GRSilkNetBackendContext tests and references the package
  from the Vulkan test console.

Builds on all BasicTargetFrameworks (netstandard2.0 -> net10.0); the two
device-free tests pass without a Vulkan ICD.

Note: Silk.NET.Vulkan must be promoted to the dotnet-public feed for CI restore.
Proves the SkiaSharp.Vulkan.Silk.NET package works with a real GPU Vulkan
context end-to-end: Silk.NET brings up instance/device/queue headlessly and
GRContext.CreateVulkan renders an offscreen SKSurface. Verified in a Linux
container against the repo's milestone-151 native using Mesa Lavapipe
(software Vulkan) on Apple Silicon.

- tests/VulkanTests/VkContexts/SilkVkContext.cs: headless, cross-platform
  Silk.NET bring-up (OS-provided Vulkan loader; no window/surface).
- tests/VulkanTests/GRSilkNetBackendContext.cs: VkGpuSurfaceIsCreatedSilkNetTypes
  drives GRContext.CreateVulkan through GRSilkNetBackendContext; skips cleanly
  when no ICD is present.
- GRVkExtensionsSilkNetExtensions.Create: build a GRVkExtensions straight from
  Silk.NET handles (GRVkExtensions has no public constructor otherwise). Skia
  needs MaxAPIVersion + GRVkExtensions on software ICDs like Lavapipe.
- nuget.config: add nuget.org so Silk.NET.Vulkan restores until it is mirrored
  to the dotnet-public feed.

Device-free tests pass and the GPU test skips on macOS (no Vulkan backend in
the mac native, #3202); the GPU test renders green on Linux+Lavapipe.
All backend-agnostic Vulkan tests now bring Vulkan up via the cross-platform
Silk.NET binding (SilkVkContext) instead of the desktop-only SharpVk
Win32VkContext. SharpVk types and their specific tests are kept but isolated:

- VKTest: CreateSilkVkContext() (default, cross-platform, skips without an ICD)
  and CreateSharpVkContext() (Windows-only, for SharpVk-specific tests only).
- GRContextTest, SKSurfaceTest.VkGpuSurfaceIsCreated, GaneshVulkanRenderer:
  now use SilkVkContext + GRVkBackendContext/GRSilkNetBackendContext with the
  MaxAPIVersion + GRVkExtensions that Skia needs on software ICDs.
- SharpVk-specific tests (GRSharpVkBackendContext, SKSurfaceTest SharpVk case)
  use CreateSharpVkContext and skip off-Windows.
- SilkVkContext gains BaseGetProc (raw GRVkBackendContext) + ApiVersion.

Verified on Mesa Lavapipe (Linux container, Apple Silicon): 8 passed,
0 failed, 2 skipped (the SharpVk GPU cases skip off-Windows).
…te project

Deletes the separate SkiaSharp.Vulkan.Tests.Console project and compiles the
tests/VulkanTests folder directly into the real hosts, only where Vulkan is
supported; it no-ops (skips) elsewhere:

- Console host (desktop net10/net48): includes all of VulkanTests + Silk.NET
  and SharpVk package/project refs. Runs on Linux/Windows; skips on macOS
  (no Vulkan in the mac native).
- Devices (MAUI) host: includes VulkanTests only for -android and -windows
  TFMs; iOS/MacCatalyst/tvOS never take the Vulkan dependency.
- GaneshVulkanRenderer is now available on Android too and joins the shared
  VisualMatrixTests automatically (via RendererCatalog.AllNames), so the
  separate VulkanVisualTests class is deleted to avoid double-running.
- Removes SkiaSharp.Vulkan.Tests.Console from the solution and the cake test
  lists; drops the unused SharpVk.Glfw reference.

Verified on macOS: Vulkan tests skip cleanly (no ICD), ganesh-vulkan visual
cells skip (unavailable); 0 Vulkan failures. (The one ganesh-gl visual
mismatch is a pre-existing GL golden difference, unrelated.)
Installs libvulkan1 (the Vulkan loader) and mesa-vulkan-drivers (the Lavapipe
software ICD) in tests/Dockerfile.linux so the merged Vulkan GPU tests run
headlessly in the container without a physical GPU. When no ICD is present the
tests skip; with Lavapipe they execute and render.
Harvests the 5 ganesh-vulkan visual-matrix goldens on Linux from the Mesa
Lavapipe (deterministic software Vulkan) render in the repo test container, so
the ganesh-vulkan cells compare strictly and pass instead of failing unseeded.

Verified: with these goldens the Visual matrix on Linux/Lavapipe is
0 failed / 10 succeeded / 10 skipped (the 5 ganesh-vulkan cells now pass; gl
and metal skip in the headless container). raster.linux goldens already matched
this container render and are left untouched.

Windows/Android ganesh-vulkan goldens are seeded separately from their own CI
legs (per-platform golden folders).
@github-actions

Copy link
Copy Markdown
Contributor

📦 Try the packages from this PR

Warning

Do not run these scripts without first reviewing the code in this PR.

Step 1 — Download the packages

bash / macOS / Linux:

curl -fsSL https://raw.githubusercontent.com/mono/SkiaSharp/main/scripts/get-skiasharp-pr.sh | bash -s -- 4527

PowerShell / Windows:

iex "& { $(irm https://raw.githubusercontent.com/mono/SkiaSharp/main/scripts/get-skiasharp-pr.ps1) } 4527"

Step 2 — Add the local NuGet source

dotnet nuget add source ~/.skiasharp/hives/pr-4527/packages --name skiasharp-pr-4527
More options
Option Description
--successful-only / -SuccessfulOnly Only use successful builds
--force / -Force Overwrite previously downloaded packages
--list / -List List available artifacts without downloading
--build-id ID / -BuildId ID Download from a specific build

Or download manually from Azure Pipelines — look for the nuget artifact on the build for this PR.

Remove the source when you're done:

dotnet nuget remove source skiasharp-pr-4527

- nuget.config: revert to base now that Silk.NET.Vulkan (and Silk.NET.Core)
  are mirrored to the dotnet-public feed; restores work without nuget.org.
- SkiaSharpSource.sln: replace the 357-line 'dotnet sln add' churn (which
  added x64/x86 configs to every project) with the minimal 7 lines that
  register SkiaSharp.Vulkan.Silk.NET under the existing SkiaSharp.Vulkan
  solution folder, mirroring SkiaSharp.Vulkan.SharpVk.
- TestConfig: add IsAndroid alongside IsLinux/IsMac/IsWindows and use it from
  GaneshVulkanRenderer instead of a local helper.
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

📊 SkiaSharp benchmarks — PR #4527

this PR (full source build) vs 🌙 nightly · Linux · Windows · macOS

Informational only — this never blocks the PR. 🟢 faster / less allocation · 🔴 slower / more allocation; moves under 5% are hidden as noise.

⏱️ Times are raw BenchmarkDotNet means, and the ⭐ PR and baseline legs run on separate CI runners, so microbenchmarks can swing run-to-run — treat small time deltas as noise. Allocations are deterministic and the reliable signal. The interactive perf-dashboard (linked below) applies smoothing for the trend view.

Highlights

⏱️ Time — 🔴 60 slower · 🟢 15 faster

  • 🔴 ColorMathBenchmark.ToColor(Colors: 4096) · Windows · 8.60 µs → 16.93 µs (+97%)
  • 🔴 PathBoundsBenchmark.TightBounds(Points: 1024) · Windows · 11.91 µs → 20.01 µs (+68%)
  • 🔴 RuntimeEffectShaderBenchmark.DrawFrame · Windows · 705.71 µs → 1.15 ms (+63%)
  • 🔴 PathBoundsBenchmark.TightBounds(Points: 64) · Windows · 1.22 µs → 1.89 µs (+55%)
  • 🔴 CanvasDrawBenchmark.Draw(Shapes: 64) · Windows · 1.98 ms → 2.87 ms (+45%)
  • …and 70 more (see details below)
Full per-OS benchmark deltas

Linux

⏱️ Time (vs 🌙 nightly 4.151.0-nightly.93)

Benchmark baseline this PR Δ
ColorMathBenchmark.ToColor(Colors: 4096) 16.25 µs 11.71 µs 🟢 -28%
MatrixMapPointsBenchmark.MapRect(Points: 256) 11.12 µs 12.86 µs 🔴 +16%
MatrixOpsBenchmark.Invert(Count: 4096) 129.71 µs 149.80 µs 🔴 +15%
MatrixMapPointsBenchmark.MapRect(Points: 4096) 178.82 µs 205.78 µs 🔴 +15%
MatrixMapPointsBenchmark.MapPoint(Points: 4096) 72.26 µs 82.24 µs 🔴 +14%
MatrixMapPointsBenchmark.MapPoint(Points: 256) 4.51 µs 5.13 µs 🔴 +14%
ColorMathBenchmark.PreMultiply(Colors: 4096) 12.85 µs 14.52 µs 🔴 +13%
MatrixOpsBenchmark.Concat(Count: 4096) 220.36 µs 247.79 µs 🔴 +12%
RasterImageLifecycleBenchmark.CreateDataWithReleaseProc(Count: 256) 98.42 µs 109.76 µs 🔴 +12%
ColorMathBenchmark.UnPreMultiply(Colors: 4096) 11.44 µs 12.68 µs 🔴 +11%
MatrixMapPointsBenchmark.MapRadius(Points: 256) 13.97 µs 15.34 µs 🔴 +10%
MatrixMapPointsBenchmark.MapRadius(Points: 4096) 223.33 µs 245.24 µs 🔴 +10%
MatrixMapPointsBenchmark.MapVector(Points: 4096) 175.84 µs 192.79 µs 🔴 +10%
PathBoundsBenchmark.TightBounds(Points: 1024) 10.45 µs 11.34 µs 🔴 +9%
MatrixMapPointsBenchmark.MapVector(Points: 256) 11.10 µs 12.05 µs 🔴 +8%
MatrixMapPointsBenchmark.MapPoints(Points: 256) 211.9 ns 227.5 ns 🔴 +7%
RuntimeEffectShaderBenchmark.DrawFrame 2.83 ms 3.03 ms 🔴 +7%
ColorMathBenchmark.ToColorF(Colors: 4096) 13.12 µs 14.05 µs 🔴 +7%
BitmapDrawBenchmark.DrawUnscaledTiles(Tiles: 64) 252.89 µs 266.88 µs 🔴 +6%

Windows

⏱️ Time (vs 🌙 nightly 4.151.0-nightly.93)

Benchmark baseline this PR Δ
ColorMathBenchmark.ToColor(Colors: 4096) 8.60 µs 16.93 µs 🔴 +97%
PathBoundsBenchmark.TightBounds(Points: 1024) 11.91 µs 20.01 µs 🔴 +68%
RuntimeEffectShaderBenchmark.DrawFrame 705.71 µs 1.15 ms 🔴 +63%
PathBoundsBenchmark.TightBounds(Points: 64) 1.22 µs 1.89 µs 🔴 +55%
CanvasDrawBenchmark.Draw(Shapes: 64) 1.98 ms 2.87 ms 🔴 +45%
CanvasDrawBenchmark.Draw(Shapes: 512) 15.80 ms 22.81 ms 🔴 +44%
ColorMathBenchmark.UnPreMultiply(Colors: 4096) 7.97 µs 11.48 µs 🔴 +44%
ColorMathBenchmark.PreMultiply(Colors: 4096) 9.15 µs 12.95 µs 🔴 +42%
BitmapDrawBenchmark.DrawUnscaledTiles(Tiles: 256) 664.96 µs 933.91 µs 🔴 +40%
BitmapDrawBenchmark.DrawScaledTiles(Tiles: 256) 904.43 µs 1.26 ms 🔴 +40%
ColorParseBenchmark.Parse(Iterations: 1000) 60.50 µs 84.06 µs 🔴 +39%
BitmapDrawBenchmark.DrawUnscaledTiles(Tiles: 64) 179.70 µs 249.47 µs 🔴 +39%
MatrixOpsBenchmark.Concat(Count: 4096) 180.77 µs 250.43 µs 🔴 +39%
BitmapDrawBenchmark.DrawScaledTiles(Tiles: 64) 247.63 µs 339.56 µs 🔴 +37%
MatrixMapPointsBenchmark.MapRadius(Points: 256) 7.67 µs 10.39 µs 🔴 +35%
MatrixMapPointsBenchmark.MapRadius(Points: 4096) 123.51 µs 166.05 µs 🔴 +34%
MatrixMapPointsBenchmark.MapPoint(Points: 4096) 58.72 µs 77.40 µs 🔴 +32%
MatrixMapPointsBenchmark.MapVector(Points: 256) 6.74 µs 8.87 µs 🔴 +32%
MatrixMapPointsBenchmark.MapPoint(Points: 256) 3.84 µs 4.94 µs 🔴 +29%
LargeImageScaleBenchmark.UpscaleCrossfade(Size: 2048) 7.96 ms 10.20 ms 🔴 +28%
MatrixMapPointsBenchmark.MapVector(Points: 4096) 110.01 µs 140.05 µs 🔴 +27%
RasterImageLifecycleBenchmark.CreateRasterImage(Count: 256) 225.45 µs 284.72 µs 🔴 +26%
MatrixOpsBenchmark.Invert(Count: 4096) 107.35 µs 135.30 µs 🔴 +26%
LargeImageScaleBenchmark.UpscaleCrossfade(Size: 1024) 2.05 ms 2.57 ms 🔴 +26%
RasterImageLifecycleBenchmark.CreateDataWithReleaseProc(Count: 256) 103.44 µs 129.09 µs 🔴 +25%
…and 6 more

macOS

⏱️ Time (vs 🌙 nightly 4.151.0-nightly.93)

Benchmark baseline this PR Δ
MatrixMapPointsBenchmark.MapPoints(Points: 256) 138.4 ns 184.1 ns 🔴 +33%
LargeImageScaleBenchmark.UpscaleOpaque(Size: 2048) 2.79 ms 2.15 ms 🟢 -23%
MatrixOpsBenchmark.Invert(Count: 4096) 74.00 µs 61.43 µs 🟢 -17%
ColorMathBenchmark.ToColor(Colors: 4096) 21.24 µs 17.66 µs 🟢 -17%
LargeImageScaleBenchmark.UpscaleOpaque(Size: 1024) 689.75 µs 587.77 µs 🟢 -15%
LargeImageScaleBenchmark.UpscaleCrossfade(Size: 2048) 6.17 ms 5.28 ms 🟢 -14%
BitmapDrawBenchmark.DrawScaledTiles(Tiles: 256) 1.00 ms 1.12 ms 🔴 +12%
MatrixMapPointsBenchmark.MapPoints(Points: 4096) 1.95 µs 2.15 µs 🔴 +10%
LargeImageScaleBenchmark.UpscaleCrossfade(Size: 1024) 1.53 ms 1.38 ms 🟢 -10%
ColorMathBenchmark.UnPreMultiply(Colors: 4096) 7.75 µs 8.50 µs 🔴 +10%
MatrixMapPointsBenchmark.MapRect(Points: 256) 3.62 µs 3.29 µs 🟢 -9%
PathBoundsBenchmark.TightBounds(Points: 1024) 10.06 µs 9.19 µs 🟢 -9%
MatrixOpsBenchmark.Concat(Count: 4096) 124.27 µs 134.93 µs 🔴 +9%
SceneRenderBenchmark.RenderFrame(Complexity: 4) 6.49 ms 5.93 ms 🟢 -9%
CanvasDrawBenchmark.Draw(Shapes: 64) 1.62 ms 1.75 ms 🔴 +8%
ColorMathBenchmark.ToColorF(Colors: 4096) 5.93 µs 5.48 µs 🟢 -8%
SceneRenderBenchmark.RenderFrame(Complexity: 1) 1.41 ms 1.31 ms 🟢 -7%
BitmapDrawBenchmark.DrawUnscaledTiles(Tiles: 256) 686.37 µs 732.60 µs 🔴 +7%
MatrixMapPointsBenchmark.MapRadius(Points: 256) 7.50 µs 7.00 µs 🟢 -7%
MatrixMapPointsBenchmark.MapRect(Points: 4096) 57.11 µs 53.63 µs 🟢 -6%
MatrixMapPointsBenchmark.MapPoint(Points: 256) 1.18 µs 1.25 µs 🔴 +6%
RasterImageLifecycleBenchmark.CreateDataWithReleaseProc(Count: 256) 80.51 µs 85.01 µs 🔴 +6%
ColorParseBenchmark.Parse(Iterations: 1000) 68.21 µs 72.00 µs 🔴 +6%
BitmapDrawBenchmark.DrawUnscaledTiles(Tiles: 64) 238.09 µs 224.90 µs 🟢 -6%
RuntimeEffectShaderBenchmark.DrawFrame 589.28 µs 618.86 µs 🔴 +5%

📈 Full interactive perf-dashboard & run details →

Copilot AI added 2 commits July 23, 2026 21:46
The gate is still needed (it serializes GPU rendering across xUnit's parallel
theory cells) and still cross-assembly: on the Devices host the Vulkan renderers
are compiled into the app assembly while the gate lives in the referenced
SkiaSharp.Tests assembly, and Direct3D remains a separate satellite. Only the
comment's 'Vulkan satellite' wording was stale; no behavior change.
Replace the process-wide GpuRenderGate lock with a DisableParallelization
xUnit collection. All GPU rendering is driven by the single VisualMatrixTests
class, so pinning that class to a serialized collection is an equivalent, more
idiomatic guarantee (it matches the existing HandleDictionaryThreadingCollection
pattern) and removes the per-renderer lock boilerplate.

- Add GpuRenderingCollection ([CollectionDefinition(DisableParallelization=true)])
- Apply [Collection] to VisualMatrixTests
- Drop the lock (GpuRenderGate.Sync) wrapper from the Ganesh GL/Metal/Vulkan
  renderers and delete GpuRenderGate

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 51747b15-4eb0-467b-8bc8-7029e750e7e1

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Vulkan helper package (SkiaSharp.Vulkan.Silk.NET) based on Silk.NET and reworks the Vulkan test integration so Vulkan coverage runs in the primary test hosts (Console + Devices) rather than a separate satellite test project, including enabling headless Vulkan in the Linux test container.

Changes:

  • Introduces SkiaSharp.Vulkan.Silk.NET with GRSilkNetBackendContext and GRVkExtensions helpers to bridge Silk.NET handles/proc lookup into Skia’s Vulkan backend context.
  • Replaces backend-agnostic Vulkan tests to bring up Vulkan via a headless Silk.NET context and removes the standalone SkiaSharp.Vulkan.Tests.Console project.
  • Integrates ganesh-vulkan into the shared visual matrix and serializes GPU visual rendering via an xUnit collection, plus adds Lavapipe Vulkan packages to the Linux test container.

Reviewed changes

Copilot reviewed 27 out of 32 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/VulkanTests/VKTest.cs Splits context creation helpers into Silk.NET (default) vs SharpVk (legacy) for tests.
tests/VulkanTests/VkContexts/SilkVkContext.cs Adds a headless Silk.NET Vulkan bring-up helper (instance/device/queue + proc lookup).
tests/VulkanTests/Visual/VulkanVisualTests.cs Removes now-redundant Vulkan-only visual matrix driver class.
tests/VulkanTests/Visual/GaneshVulkanRenderer.cs Switches the Vulkan visual renderer from SharpVk to Silk.NET and uses extensions + backend context.
tests/VulkanTests/SKSurfaceTest.cs Updates Vulkan surface tests to use Silk.NET context for backend-agnostic coverage, keeps SharpVk-specific test.
tests/VulkanTests/GRSilkNetBackendContext.cs Adds tests for GRSilkNetBackendContext mapping/pinning behavior and a Vulkan smoke render.
tests/VulkanTests/GRSharpVkBackendContext.cs Renames test context helper usage to the SharpVk-specific factory.
tests/VulkanTests/GRContextTest.cs Updates Vulkan context-creation tests to use Silk.NET and provide extensions + base proc lookup.
tests/Tests/TestConfig.cs Adds IsAndroid helper on TestConfig for host capability gating.
tests/Tests/SkiaSharp/Visual/Tests/VisualMatrixTests.cs Pins visual matrix execution to a serialized GPU xUnit collection.
tests/Tests/SkiaSharp/Visual/Renderers/GaneshMetalRenderer.cs Removes the explicit global GPU lock and relies on collection-based serialization.
tests/Tests/SkiaSharp/Visual/Renderers/Desktop/GaneshGlRenderer.cs Removes the explicit global GPU lock and relies on collection-based serialization.
tests/Tests/SkiaSharp/Visual/GpuRenderingCollection.cs Adds an xUnit DisableParallelization collection definition for GPU visual rendering.
tests/Tests/SkiaSharp/Visual/GpuRenderGate.cs Deletes the prior global lock gate used by GPU renderers.
tests/SkiaSharp.Vulkan.Tests.Console/SkiaSharp.Vulkan.Tests.Console.csproj Removes the separate Vulkan console test project.
tests/SkiaSharp.Tests.Devices/SkiaSharp.Tests.Devices.csproj Compiles Vulkan tests into Devices host (Android/Windows) and adds Silk.NET + project refs.
tests/SkiaSharp.Tests.Console/SkiaSharp.Tests.Console.csproj Compiles Vulkan tests into Console host and adds Silk.NET + Vulkan project refs.
tests/SkiaSharp.Tests.Console.sln Removes the Vulkan satellite console test project from the solution.
tests/Dockerfile.linux Installs Vulkan loader + Mesa Lavapipe ICD for headless Vulkan testing in the Linux container.
source/SkiaSharpSource.sln Adds the new SkiaSharp.Vulkan.Silk.NET project to the source solution.
source/SkiaSharp.Vulkan/SkiaSharp.Vulkan.Silk.NET/SkiaSharp.Vulkan.Silk.NET.csproj Adds new multi-targeted Vulkan helper package project referencing Silk.NET.Vulkan.
source/SkiaSharp.Vulkan/SkiaSharp.Vulkan.Silk.NET/Properties/AssemblyInfo.cs Adds assembly metadata for the new package.
source/SkiaSharp.Vulkan/SkiaSharp.Vulkan.Silk.NET/GRVkExtensionsSilkNetExtensions.cs Adds Silk.NET-friendly GRVkExtensions factory/initializer helpers.
source/SkiaSharp.Vulkan/SkiaSharp.Vulkan.Silk.NET/GRSilkNetBackendContext.cs Implements the Silk.NET backend context wrapper (handle mapping + pinned features + proc delegate bridge).
scripts/VERSIONS.txt Registers Silk.NET.Vulkan and the new NuGet package in the version inventory.
scripts/infra/tests/tests-netfx.cake Removes the deleted Vulkan satellite test assembly from the .NET Framework test list.
scripts/infra/tests/tests-netcore.cake Removes the deleted Vulkan satellite test assembly from the .NET (Core/modern) test list.

Comment thread tests/VulkanTests/VKTest.cs
@github-actions

Copy link
Copy Markdown
Contributor

📖 Documentation Preview

The documentation for this PR has been deployed and is available at:

🔗 View Staging Site
🔗 View Staging Docs
🔗 View Staging Gallery (Blazor)
🔗 View Staging Gallery (Uno Platform)
🔗 View Staging SkiaFiddle

This preview will be updated automatically when you push new commits to this PR.


This comment is automatically updated by the documentation staging workflow.

Copilot AI added 6 commits July 23, 2026 22:40
CatalogReflection found host-contributed renderers (Vulkan, Direct3D) via
Assembly.GetEntryAssembly(), which is null on Android/MAUI and WASM. As a result
the merged GaneshVulkanRenderer -- compiled into the device app assembly -- was
invisible on the Devices host, so ganesh-vulkan never joined the visual matrix
there and only raster ran.

Also scan loaded "SkiaSharp.Tests*" host assemblies by name so host renderers are
discovered regardless of whether GetEntryAssembly() is populated. On desktop the
entry assembly is unchanged; on device/wasm the app assembly is now included.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 51747b15-4eb0-467b-8bc8-7029e750e7e1
Captured from the Silk.NET Vulkan renderer running on an Android API-36 arm64
emulator (ranchu ICD, Vulkan 1.3). Confirms GRSilkNetBackendContext brings up
Vulkan and renders through Skia on Android, not just desktop/Lavapipe.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 51747b15-4eb0-467b-8bc8-7029e750e7e1
Move the Vulkan test dependencies (Silk.NET.Vulkan, SharpVk, and the two
SkiaSharp.Vulkan.* packages) and the ..\VulkanTests\** compile onto the shared
SkiaSharp.Tests library instead of Compile-Including them into each MAUI/WASM host.
The Devices and WASM app heads now pick up the Vulkan tests through their existing
ProjectReference to SkiaSharp.Tests, and the Console host keeps compiling the same
source directly (it does not reference the library).

Because GaneshVulkanRenderer now lives in the catalog assembly, the visual matrix
discovers it on every host with the original catalog+entry-assembly scan, so the
Assembly.GetEntryAssembly() Android/WASM workaround is no longer needed and is
reverted. Verified on the Android API-36 emulator: all five ganesh-vulkan.android
cells render and pass.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 51747b15-4eb0-467b-8bc8-7029e750e7e1
…ojects)

The two SkiaSharp.Vulkan.* project references already bring Silk.NET.Vulkan and
SharpVk in transitively (they are public API of those packages, so never private),
so the explicit PackageReferences on the base test library and the Console host
were redundant. Verified both still build.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 51747b15-4eb0-467b-8bc8-7029e750e7e1
GRVkExtensions had a private constructor, so the only way to obtain one was the
static GRVkExtensions.Create (raw IntPtr + delegate). That forced the Silk.NET
helper to expose its own Create factory, and left the SharpVk helper's Initialize
extension effectively unusable (there was no typed way to get an instance to
initialize).

Make the parameterless constructor public — mirroring native
skgpu::VulkanExtensions (default-construct, then init) and SkiaSharp's own
SKPaint/SKPath allocating ctors — so the idiomatic usage is:

    using var ext = new GRVkExtensions();
    ext.Initialize(getProc, instance, physicalDevice);

The Silk.NET helper drops its Create overloads and keeps only the typed
Initialize extension methods, matching the SharpVk helper (which is now usable).
GRVkExtensions.Create stays for source compatibility. Verified on the Android
emulator: all five ganesh-vulkan.android cells still render and pass.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 51747b15-4eb0-467b-8bc8-7029e750e7e1
…otnet-vulkan-bindings

# Conflicts:
#	source/SkiaSharpSource.sln
#	tests/SkiaSharp.Tests.Console.sln
@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

📦 Artifact size report

Packages from this PR (build 1524861) vs the latest nightly baseline 4.151.0-nightly.93 (observed 2026-07-24).

Total .nupkg size: 537.7 MB → 535.4 MB (−2.3 MB, -0.4%)

Packages

⚠️ marks growth over 500.0 KB or 2%. Changes under 50.0 KB are treated as noise.

Package baseline this PR Δ Δ%
SkiaSharp.NativeAssets.WinUI 109.2 MB 107.1 MB 🟢 −2.0 MB -1.9%
HarfBuzzSharp.NativeAssets.Win32 20.8 MB 20.5 MB 🟢 −252.9 KB -1.2%

+38 package(s) unchanged (< 50.0 KB).

Per-file changes

HarfBuzzSharp.NativeAssets.Win32

File Size
runtimes/win-x86/native/libHarfBuzzSharp.pdb 22.2 MB → 21.9 MB (🟢 −272.0 KB)
runtimes/win-x64/native/libHarfBuzzSharp.pdb 22.0 MB → 21.8 MB (🟢 −240.0 KB)
runtimes/win-arm64/native/libHarfBuzzSharp.pdb 22.3 MB → 22.3 MB (🟢 −8.0 KB)

SkiaSharp.NativeAssets.WinUI

File Size
runtimes/win-x86/native/libGLESv2.pdb 67.3 MB → 67.0 MB (🟢 −256.0 KB)
runtimes/win-arm64/native/libGLESv2.pdb 64.5 MB → 64.3 MB (🟢 −224.0 KB)
runtimes/win-x64/native/libGLESv2.pdb 66.6 MB → 66.4 MB (🟢 −224.0 KB)
runtimes/win-x64/native/libEGL.pdb 6.4 MB → 6.3 MB (🟢 −32.0 KB)
runtimes/win-x86/native/libEGL.pdb 6.5 MB → 6.5 MB (🟢 −16.0 KB)
runtimes/win-arm64/native/SkiaSharp.Views.WinUI.Native.pdb 67.7 MB → 67.7 MB (🟢 −8.0 KB)
runtimes/win-x64/native/SkiaSharp.Views.WinUI.Native.pdb 68.0 MB → 68.0 MB (🟢 −8.0 KB)
🧬 win-x64 (runtimes/win-x64/native/libGLESv2.dll) 4.9 MB → 4.9 MB (🔴 +2.0 KB)
🧬 win-arm64 (runtimes/win-arm64/native/libGLESv2.dll) 4.4 MB → 4.4 MB (🔴 +1.0 KB)

Informational only — this never blocks the PR. Native binaries are labelled by os/arch.

Copilot AI added 2 commits July 24, 2026 12:02
Context: #4527
Context: ADO build 159014 (SkiaSharp Public) — iOS, Mac Catalyst, and
Windows .NET Framework legs red.

Folding the Vulkan tests + their unsigned Silk.NET.Vulkan/SharpVk
dependencies into the shared, strong-named SkiaSharp.Tests base library
and Console host (the earlier "Option A") broke three CI legs:

  * iOS / Mac Catalyst — the Apple AOT/ILC linker requires every native
    symbol to resolve at link time. Pulling Silk.NET into the base lib
    flowed it into the Apple device heads, which have no libvulkan, so
    the link failed with "undefined symbol _vkBindImageMemory" (and
    friends).
  * Windows .NET Framework (net48) — the full framework enforces
    strong-name references at load time. The strong-named SkiaSharp.Tests
    Console could not load the unsigned Silk.NET.Vulkan assembly and
    threw FileLoadException "A strongly-named assembly is required"
    (0x80131044). net-core does not enforce this, which is why the
    net10 build was clean and the failure only surfaced at runtime on
    net48.

Keep Vulkan (and its unsigned deps) out of the base library and out of
every strong-named / Apple-linked assembly by moving the tests into two
dedicated, non-strong-named projects:

  * tests/SkiaSharp.Vulkan.Tests.Console — a standalone
    Microsoft.Testing.Platform exe (net10 + net48), SignAssembly=false.
    It ProjectReferences the signed SkiaSharp.Tests.Console for shared
    infra (unsigned -> signed is allowed) plus Silk.NET/SharpVk. This is
    the net48 fix: an unsigned host can load the unsigned Vulkan deps.
  * tests/SkiaSharp.Vulkan.Tests — a library targeting only
    net10-android / -windows, SignAssembly=false, referenced by the
    Devices host and registered in MauiProgram. This is the Apple fix:
    Vulkan never reaches the iOS/MacCatalyst heads.

To make the Vulkan renderer discoverable from these separate assemblies
in the single-process device host, CatalogReflection now scans the
catalog + entry + all loaded SkiaSharp.* assemblies (this also preps the
identical pattern for a future D3D test lib: reference the lib + register
it in MauiProgram). VisualMatrixTests switches from RendererCatalog.AllNames
to NamesIn(own assembly) so the base matrix and each satellite only run
their own renderers — no cell is executed twice when both are loaded.
A per-assembly VulkanGpuRenderingCollection serializes the Vulkan GPU
classes within the satellite/lib the way GpuRenderingCollection does for
GL/Metal in the main process.

Validated locally: base/Console/satellite-Console build (net10); the iOS
and Mac Catalyst Devices heads now build (the _vk link error is gone);
the Android head builds and the emulator renders all 5 ganesh-vulkan
cells green with no double-run; the satellite Console runs on macOS and
skips Vulkan cleanly (0 failures). net48 + Windows Vulkan are validated
on CI.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 51747b15-4eb0-467b-8bc8-7029e750e7e1
SkiaSharp.Vulkan.Tests.Console was already present in the netcore and
netfx test-assembly lists on main; the Option A rework had removed it
and the follow-up restore re-added it in a different position, leaving a
pure line-reordering with no functional effect. Restore the original
ordering so the diff carries only real changes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 51747b15-4eb0-467b-8bc8-7029e750e7e1
@mattleibow mattleibow changed the title Add SkiaSharp.Vulkan.Silk.NET package and modernize Vulkan tests Add SkiaSharp.Vulkan.Silk.NET package and run the Vulkan tests on Android + Linux Jul 24, 2026
@mattleibow
mattleibow merged commit 49db8d2 into main Jul 24, 2026
112 of 115 checks passed
@mattleibow
mattleibow deleted the mattleibow-research-dotnet-vulkan-bindings branch July 24, 2026 16:21
mattleibow pushed a commit to ramezgerges/SkiaSharp that referenced this pull request Jul 24, 2026
main (mono#4527) switched the Vulkan test infra from SharpVk to Silk.NET
(new SilkVkContext headless bring-up that uses the OS Vulkan loader, so
it runs on Linux, Windows and Android) and replaced the GpuRenderGate
lock with xUnit DisableParallelization collections. Bring the Graphite
tests in line:

- GraphiteVulkanRenderer + SKGraphiteReleaseVulkanTests + the backend
  context smoke test now bring Vulkan up through SilkVkContext and feed
  the raw handles (.Handle + BaseGetProc) to the binding-neutral
  SKGraphiteVkBackendContext -- no SharpVk. graphite-vulkan is now
  available on Linux, Windows and Android (was Linux/Windows-only).
- Drop the GpuRenderGate lock from SKGraphiteReleaseTestsBase and both
  Graphite renderers; the Metal/Dawn release tests join
  GpuRenderingCollection and the Vulkan ones join
  VulkanGpuRenderingCollection so GPU work stays serialized per assembly.
- Resolve the GaneshMetalRenderer merge to keep the PR's CI-hang guards
  (Azure x64 short-circuit + Metal family probe) on top of main's
  lock-free structure.

graphite-vulkan visual goldens still need seeding from a CI Linux +
Android run (the cells emit their PNGs for harvest, same flow as the
graphite-metal.macos goldens).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 6deeff10-31d8-42fe-a033-e5fc9f47b339
mattleibow added a commit that referenced this pull request Jul 24, 2026
…#4531)

Execute Vulkan visual tests on the Windows CI legs via a software ICD (#4531)

Context: #4527

Follow-up to #4527, which added the Silk.NET Vulkan test path and enabled the
ganesh-vulkan visual cells on Linux (Mesa lavapipe) and Android (emulator
SwiftShader). Windows already compiled the tests and built libSkiaSharp with
Vulkan, but the headless CI agent ships no Vulkan driver, so every ganesh-vulkan
cell skipped — the backend was built and never exercised.

Provision a software Vulkan ICD on the Windows test agent so those cells render
and golden-compare instead of skipping, mirroring what the Linux leg does with
lavapipe. A pwsh preBuildStep on both Windows test legs (.NET Core and .NET
Framework) runs scripts/infra/native/windows/install-vulkan-icd.ps1, which
downloads and SHA-256-verifies the Khronos loader (vulkan-1.dll) and Google
SwiftShader (vk_swiftshader.dll + vk_swiftshader_icd.json) from pinned, prebuilt
Silk.NET native packages, extracts them under externals/vulkan-icd, registers the
ICD, and puts the loader on PATH.

Register the ICD in HKLM\SOFTWARE\Khronos\Vulkan\Drivers rather than via
VK_ICD_FILENAMES / VK_DRIVER_FILES: the agent runs elevated, and the loader
ignores those environment variables under elevation, reading the driver list only
from the registry. The script fails the leg on any download, checksum, or registry
error instead of silently degrading back to a skip — provisioning can only add
coverage, never hide a regression.

Seed five ganesh-vulkan.windows goldens harvested from the agent's own SwiftShader
renders; cross-implementation pixel differences mean Windows goldens must come from
the Windows ICD, not from Linux/Android. A future driver-driven mismatch reseeds
via scripts/infra/tests/extract-visual-goldens.py, the same flow as the other legs.

CI-only change: no product, binding, or test-project code is touched (+88/-0), so
apps running against real Vulkan drivers are unaffected. Verified on CI — the x64
Windows .NET Core and .NET Framework legs now run SkiaSharp.Vulkan.Tests.Console
with 15 passed / 0 skipped, including all five VulkanVisualTests.RenderMatchesGolden
("ganesh-vulkan") cells, and golden extraction reports 0 mismatch / 0 unseeded. The
32-bit netfx (x86) slice still skips Vulkan (NotExecuted, never failed) because the
provisioned SwiftShader ICD is 64-bit only. Linux and Android provisioning is
unchanged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants