Add SkiaSharp.Vulkan.Silk.NET package and run the Vulkan tests on Android + Linux - #4527
Conversation
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).
📦 Try the packages from this PRWarning 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 -- 4527PowerShell / 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-4527More options
Or download manually from Azure Pipelines — look for the 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.
📊 SkiaSharp benchmarks — PR #4527⭐ this PR (full source build) vs 🌙 nightly · Linux · Windows · macOS
Highlights⏱️ Time — 🔴 60 slower · 🟢 15 faster
Full per-OS benchmark deltasLinux⏱️ Time (vs 🌙 nightly
Windows⏱️ Time (vs 🌙 nightly
macOS⏱️ Time (vs 🌙 nightly
|
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
There was a problem hiding this comment.
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.NETwithGRSilkNetBackendContextandGRVkExtensionshelpers 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.Consoleproject. - Integrates
ganesh-vulkaninto 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. |
|
📖 Documentation Preview The documentation for this PR has been deployed and is available at: 🔗 View Staging Site This preview will be updated automatically when you push new commits to this PR. This comment is automatically updated by the documentation staging workflow. |
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
📦 Artifact size reportPackages from this PR (build Total Packages
+38 package(s) unchanged (< 50.0 KB). Per-file changes
|
| 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.
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
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
…#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>
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 loaderthrowsNotSupportedExceptionon Android (it only handles Windows and Linux), which is why the Vulkan tests have historically been desktop/Windows-only.This PR:
SkiaSharp.Vulkan.Silk.NET, built on the actively maintained, cross-platform Silk.NET.Vulkan, mirroring the shape of the existing SharpVk package.GRVkExtensionsdirectly constructible so a Vulkan backend can be brought up without a bespoke factory.libvulkan.so) in CI — the platform SharpVk could never reach — while remaining correct on Apple, WASM, and .NET Framework.SkiaSharp.Vulkan.SharpVkis 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.NETA thin convenience layer (namespace
SkiaSharp, multi-targetsnetstandard2.0→net10.0) that maps Silk.NET's Vulkan handles onto SkiaSharp's raw-IntPtrGRVkBackendContext:GRSilkNetBackendContext : GRVkBackendContext— takes Silk.NET'sInstance/PhysicalDevice/Device/Queue(ninthandles) plus a pinnedPhysicalDeviceFeatures, and bridges theGetProcedureAddressdelegate 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 onGRVkExtensions(plus an overload accepting instance/device extension name arrays), matching the SharpVk helper's ergonomics.Idiomatic usage:
2. Core API change: public
GRVkExtensionsconstructorGRVkExtensions()(the parameterless constructor) is changed fromprivatetopublic. This mirrors nativeskgpu::VulkanExtensions(default-construct, theninit) and SkiaSharp's own allocatingSKPaint/SKPathconstructors, and lets the Silk.NET helper expose a plainInitialize(...)extension instead of a one-offCreate(...)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/SharpVkdependencies cannot be added to the sharedSkiaSharp.Testsbase library, because that library flows into every test host, and two hosts reject those dependencies:libvulkanon Apple, so the link fails withclang++ ... Undefined symbols: _vkBindImageMemory(and friends).Silk.NET.Vulkan, throwingFileLoadException"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 targetingnet10.0+net48,SignAssembly=false. It references the signedSkiaSharp.Tests.Consolefor shared infrastructure (an unsigned assembly may reference a signed one) plusSilk.NET.Vulkan/SharpVk. This is the desktop runner (Linux/Lavapipe and Windows).tests/SkiaSharp.Vulkan.Tests— a library targeting onlynet10.0-android/-windows,SignAssembly=false, referenced by the MAUI Devices host and registered inMauiProgram. Vulkan therefore never reaches the iOS/Mac Catalyst heads.Supporting changes so the separate assemblies integrate cleanly with the shared visual-regression harness:
CatalogReflectiondiscovers renderers across the catalog assembly, the entry assembly, and all loadedSkiaSharp.*assemblies. This lets theganesh-vulkanrenderer (in the separate Vulkan library) join the sharedVisualMatrixTestsinside 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.VisualMatrixTestsmatches only its own assembly's renderers (NamesIn(own assembly)instead ofAllNames), so when both the base matrix and a satellite are loaded in one process, no visual cell is rendered twice.[Collection(DisableParallelization = true)]instead of the previous process-wideGpuRenderGatestatic lock — the idiomatic xUnit equivalent, matching the existingHandleDictionaryThreadingCollectionpattern (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) andVulkanGpuRenderingCollection(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.linuxinstallslibvulkan1(the loader) +mesa-vulkan-drivers(Mesa Lavapipe software ICD) so the Vulkan GPU tests render headlessly in the container.Platform behaviour
libvulkan1+mesa-vulkan-drivers)libvulkan.so(real ICD) — the platform SharpVk cannot reachValidation
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 seededganesh-vulkan.linuxgoldens.ranchuICD): the Devices head builds and deploys, Vulkan comes up throughlibvulkan.so, and both the baseVisualMatrixTestsand the VulkanVulkanVisualTestsrender and pass — the fiveganesh-vulkan.androidcells match seeded goldens, and each renderer runs exactly once (no double-run).clang++ ... Undefined symbols: _vk*link failures are gone because Vulkan is no longer linked into the Apple heads.API changes
Core
SkiaSharppackage (additive only):GRVkExtensions()— parameterless constructor is now public (wasprivate).GRVkExtensions.Create(...)unchanged.New package
SkiaSharp.Vulkan.Silk.NET(namespaceSkiaSharp):class GRSilkNetBackendContext : GRVkBackendContextdelegate 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.SharpVkis untouched.Notes for reviewers / follow-ups
ganesh-vulkan.linux(Lavapipe) andganesh-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 + theganesh-vulkan.windowsgoldens) 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.GRVkExtensionsconstructor still need generated mdoc XML via the normal docs flow.Bugs Fixed: N/A
Required skia PR: None.
PR Checklist
GRVkExtensionsctor)