Dev/graphite backend#3968
Draft
ramezgerges wants to merge 69 commits into
Draft
Conversation
Contributor
📦 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 -- 3968PowerShell / Windows: iex "& { $(irm https://raw.githubusercontent.com/mono/SkiaSharp/main/scripts/get-skiasharp-pr.ps1) } 3968"Step 2 — Add the local NuGet source dotnet nuget add source ~/.skiasharp/hives/pr-3968/packages --name skiasharp-pr-3968More options
Or download manually from Azure Pipelines — look for the Remove the source when you're done: dotnet nuget remove source skiasharp-pr-3968 |
3 tasks
Contributor
|
/azp run |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
0468742 to
1731d7e
Compare
|
No pipelines are associated with this pull request. |
5 similar comments
|
No pipelines are associated with this pull request. |
|
No pipelines are associated with this pull request. |
|
No pipelines are associated with this pull request. |
|
No pipelines are associated with this pull request. |
|
No pipelines are associated with this pull request. |
14a22ec to
597e082
Compare
Exposes Skia's Graphite GPU backend to .NET through SkiaSharp by adding
a new sk_graphite_* C-API shim (committed alongside in externals/skia)
plus matching SKGraphite* C# wrappers that P/Invoke that shim.
Scope:
* C/C++ shim: externals/skia bump to dev/graphite-c-api-shim, which
adds include/c/sk_graphite{,_vulkan,_metal,_dawn}.h and the matching
src/c/sk_graphite*.cpp implementations under SK_GRAPHITE / SK_VULKAN
/ SK_METAL / SK_DAWN guards.
* C# wrappers: binding/SkiaSharp/Gpu/Graphite/SKGraphite{Context,
Recorder,Recording,BackendTexture,TextureInfo,VkBackendContext,
MtlBackendContext,DawnBackendContext,ImageProvider}.cs. Surface and
SKImage gain Graphite-flavored Create / ToTextureImage overloads.
* Native build: SUPPORT_GRAPHITE plumbed through native/{linux,macos,
windows,android}/build.cake. Linux+Android cake widened to allow
Linux as a build host (NDK r19+ ships an official linux-x86_64
toolchain, only CI policy gated it).
* SkiaApi.generated.cs is regenerated to include the new entry points.
* libSkiaSharp.json gains the typedef/proxy descriptors the generator
needs for the Vulkan get-proc callback and the Graphite backend
context init struct.
Validation strategy (see specs/002-graphite-backend-support/plan.md):
three-layer smoke proves the stack end-to-end --
1. tests/native/Graphite/cpp_smoke -- C++ against libSkiaSharp.so
2. tests/native/Graphite/c_smoke -- C against the new shim
3. tests/Tests/SkiaSharp/Graphite/ -- xUnit against the C# wrappers
Plus tests/Tests/SkiaSharp/Visual/ for golden-image comparison.
Headless validation in WSL2 uses Mesa Lavapipe (Vulkan software ICD).
documentation/dev/graphite-headless.md walks through the setup.
ABI: additive only. No existing GR*/SK* signature changes. Stubs in
the per-backend C files keep the symbol set consistent regardless of
which backends were enabled at build time (FR-013, FR-018).
Specs, contracts, and research live under specs/002-graphite-backend-support/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pulls in mono/skia commit that adds sk_graphite_* anchor references to SkiaKeeper.c. Required for the macOS build, which links libSkiaSharp.dylib against libskia.a via -lskia and relies on the keeper to keep C-API object files from being discarded by the linker. Without this bump, libSkiaSharp.dylib on macOS lacks sk_graphite_image_provider_new / sk_graphite_context_make_metal / etc., even though they exist in libskia.a, and managed P/Invoke throws EntryPointNotFoundException at runtime.
Adds a managed wrapper for the new sk_graphite_dawn_backend_texture_new
C entry point (skia submodule sibling commit). Used by the WASM
WebGPU renderer to wrap a canvas swap-chain texture as a Graphite
BackendTexture every frame:
var textureHandle = jsBridge.AcquireFrameTexture(canvas);
using var bt = SKGraphiteBackendTexture.CreateDawn(textureHandle);
using var s = SKSurface.Create(recorder, bt, SKColorType.Rgba8888);
The wrapper does not retain or release the WGPUTexture -- callers
keep it alive for the BackendTexture's lifetime. Surfaces/Images
that wrap the BackendTexture retain it for their own lifetime.
The HarfBuzzApi.generated.cs / Resources / SceneGraph / Skottie diffs
are pure regeneration side effects (pwsh utils/generate.ps1 picked
up some new HarfBuzz typedefs hb_draw_funcs_t / hb_paint_funcs_t
and a const correctness fix on hb_buffer_append that had landed
upstream since the last regen).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plumbs an opt-in Graphite-on-WebGPU path through the WASM build:
consumers set <SkiaSharpEnableWebGpu>True</SkiaSharpEnableWebGpu>
on their WASM head csproj and get -sUSE_WEBGPU=1 + an in-runtime
JS bridge at globalThis.skiaSharpWebGpu wired into the final emcc
link. Default is False so existing GL consumers see no change.
Native build (native/wasm/build.cake):
- New SUPPORT_GRAPHITE / SUPPORT_DAWN / SUPPORT_WEBGPU env-driven
flags. When SUPPORT_GRAPHITE=true, the GN invocation flips on
is_canvaskit=true (so :graphite's Dawn-backed sources resolve
<webgpu/webgpu_cpp.h> via Emscripten's bundled headers rather
than the native-Dawn-generated webgpu_cpp.h that doesn't exist
on Emscripten) and skia_enable_graphite / skia_use_dawn /
skia_use_webgpu.
- The cake archive-merge step previously globbed only *.a.wasm;
is_canvaskit=true makes Skia's GN toolchain switch static-lib
output extensions to plain *.a, so the merge silently produced
empty archives. Glob widened to both patterns, with libSkiaSharp.a
(self) and libHarfBuzzSharp.* (separate consumer-facing archive)
excluded.
Consumer-side plumbing (SkiaSharp.NativeAssets.WebAssembly.targets):
- Adds -s USE_WEBGPU=1 to EmccExtraLDFlags when SkiaSharpEnableWebGpu
is True.
- Adds --js-library skia_wgpu_bridge.js (the new JS file packed via
PackageFile) which installs globalThis.skiaSharpWebGpu at module
init. The bridge exposes initAsync / acquireFrameTexture /
releaseTexture / setSize and gives consumers access to the
Emscripten $WebGPU mgr* tables without needing to reach the live
Module instance (Blazor / .NET 10 WASM encapsulates it inside an
IIFE).
- Adds -s DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=['$SkiaSharpWebGpuBridge']
to force-include the bridge anchor (the $-prefixed library symbol
is otherwise dead-stripped because no C code references it). The
%24 escape is needed because MSBuild does not understand $$ as a
literal $ in this position.
- Adds WebGPU to EmccExportedRuntimeMethod for diagnostic / devtools
inspection (not required by skia_wgpu_bridge.js itself, which
uses the local $WebGPU binding directly).
- POSIX path separators on the NativeFileReference glob:
$(SkiaSharpStaticLibraryPath)/3.1.56/$(_SkiaSharpNativeBinaryType)
/*.a. MSBuild does NOT normalize backslashes on Linux, so the
previous Windows-style \\ separators silently produced empty
NativeFileReference items on Linux consumers (Uno SamplesApp on
Linux dev boxes). HarfBuzzSharp.targets gets the same fix.
skia_wgpu_bridge.js (new):
- Async initAsync(canvasId) -> {instanceId, deviceId, queueId,
textureId, format}: requestAdapter + requestDevice, configures
canvas's webgpu context, registers device/queue with $WebGPU's
mgrDevice/mgrQueue tables, returns numeric handles that Skia's
Graphite-Dawn shim treats as raw WGPU* on Emscripten.
- Emscripten 3.1.56's $WebGPU has no mgrInstance (newer versions
added it); the bridge falls back to wgpuCreateInstance(0) or a
sentinel value 1 (Skia's DawnBackendContext.fInstance is stored
opaquely; any non-null value works).
- acquireFrameTexture(canvasId, prevTextureId) is the per-frame
hot path -- releases the previous frame's texture handle then
grabs the current swap-chain texture.
externals/skia bump pulls in the sibling sk_graphite_dawn_backend_texture_new
shim + Emscripten guards.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds documentation/dev/graphite-wasm-webgpu.md covering the SkiaSharp-side path that the earlier three commits delivered: * Consumer setup -- one-line opt-in via SkiaSharpEnableWebGpu, example Blazor + Uno flows. * What globalThis.skiaSharpWebGpu exposes (initAsync / acquireFrameTexture / releaseTexture / setSize) and why we ship it as a JS library rather than expecting consumers to reach Module.WebGPU directly (.NET 10 encapsulates Module inside an IIFE). * Non-yielding mode trade-offs and the Context-destruction warning that follows from disposing under it. * SKGraphiteImageProvider plumbing -- without it, every DrawImage of a raster SkImage is silently dropped. * Build internals for maintainers: is_canvaskit GN switch, archive-merge glob, emscripten version pinning. * Headless test recipe with Chromium + SwiftShader Vulkan. * Troubleshooting table cross-referencing every gotcha we hit during bring-up (wasm-ld undefined symbols, MSBuild backslash globs on Linux, $-escape for DEFAULT_LIBRARY_FUNCS_TO_INCLUDE, canvas-context contention). Indexed under a new "Graphite Backend" section in documentation/dev/README.md alongside the existing graphite.md and graphite-headless.md (which were not previously linked). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EmccExportedRuntimeMethod=WebGPU was added speculatively for devtools inspection but the skia_wgpu_bridge.js library uses the local \$WebGPU binding directly. Dropping it shrinks the build's exports table.
Commit 608a3bc regenerated HarfBuzz bindings as a side effect of the SKGraphiteBackendTexture.CreateDawn work. The regen emitted 14 new partial-method declarations for hb_paint_*_func_t proxies whose implementation halves were never authored, leaving the build broken (CS8795). Wiring full hb_paint_funcs_t support — including delegate types and a PaintFunctions class — is unrelated to the Graphite work that commit was actually doing, and was never intended to land. Revert HarfBuzzApi.generated.cs to main's state; a future generator run + proper paint_funcs implementation can land independently.
Splits the test logic from the transport: scenes describe WHAT to
draw (deterministic Action<SKCanvas> identified by name), renderers
describe HOW to reach a backend (raster, Vulkan, Metal, …). One
xUnit theory now fans across every compatible (renderer × scene)
combination at discovery time, scaling with neither N×M boilerplate
nor per-platform divergence.
Shape:
tests/Tests/SkiaSharp/Visual/
Scenes/ ISkiaScene + SceneCatalog + 3 sample scenes
Renderers/ IRenderer + RendererCatalog + Raster/GaneshVk/
GraphiteVk/GraphiteMetal renderers
Tests/ VisualMatrixTests (the one matrix theory)
Goldens/
_shared/ canonical golden per scene (used by default)
raster/ per-renderer override (raster diverges from GPU)
Golden lookup: {renderer.Name}/{scene}.png → _shared/{scene}.png
fallback. Update mode (SKIASHARP_UPDATE_GOLDENS=1) writes to _shared/
by default; SKIASHARP_GOLDEN_SCOPE=renderer pins a per-renderer
divergence. Three new shared goldens + two raster overrides replace
the previous nine per-setup PNGs.
GraphiteDisposalTests and GraphiteCoexistenceTests switch from the
deleted Setup classes to VulkanLoader.Shared / the new renderers
directly. Behavior unchanged.
Mirror of the Vulkan path for GL: dlopen libEGL.so.1, enumerate EGL devices via EGL_EXT_platform_device, prefer the Mesa software device (EGL_MESA_device_software), bring up a surfaceless GL context via EGL_KHR_surfaceless_context. No display server contacted; pixels land deterministically through Mesa llvmpipe. GL contexts are thread-affine, so the renderer owns a dedicated worker thread that calls eglMakeCurrent once and pumps a work queue from RenderAsync. Per-render GRContext + SKSurface allocation matches the Vulkan/Metal lifetime model and keeps the assembly-end GarbageCleanupFixture clean. Matrix coverage on Linux is now: raster, ganesh-vulkan, graphite-vulkan, and ganesh-gl — 11/11 cells green against _shared/ goldens within the MaxChannelDelta=2 tolerance.
Two new cells on Linux CI: wasm-raster (CPU SkiaSharp inside the WASM runtime) and wasm-graphite-dawn (Skia Graphite on WebGPU via Emscripten Dawn). Both go through the same browser session — one Chromium tab, three in-page renderers dispatched by name. Pieces: * native/wasm/skia_wgpu_bridge.js — extended with three offscreen helpers (createOffscreenTexture, readTextureRgbaAsync, releaseOffscreenTexture) and a canvas-less initOffscreenAsync. WebGPU device + queue are acquired without a canvas; renders go into a GPUTexture we own; readback is JS-side copyTextureToBuffer + mapAsync, NOT Skia's ctx.ReadPixels. That last bit is critical: in Skia's non-yielding mode (we don't link -sASYNCIFY) ctx.ReadPixels deadlocks waiting on a mapAsync that needs the JS event loop to tick, which can't happen while C# is on a synchronous P/Invoke stack. * tests/Hosts/RenderHost.Wasm/ — barebones Microsoft.NET.Sdk. WebAssembly project (no Blazor, no Uno). Program.Main blocks forever so the runtime stays alive servicing the [JSExport] RenderSceneAsync entry point. Scene .cs files are Compile-Included from tests/Tests/SkiaSharp/Visual/Scenes/, so a scene change updates the in-process AND browser-side cells in one edit. SKGraphiteContext + Recorder are kept alive for the WASM process lifetime — disposing them in non-yielding mode fatally asserts "all GPU work must be finished". * tests/Tests/SkiaSharp/Visual/Renderers/WasmHostSession.cs + WasmRenderers.cs — Microsoft.Playwright-driven transport with a process-singleton browser + page. WasmRasterRenderer and WasmGraphiteDawnRenderer are IRenderer proxies; the renderer name is the dispatch key on the JS side. WasmGaneshGlEsRenderer is sketched but not wired (Ganesh-on-WebGL2 needs a separate JS-library shim for emscripten_webgl_create_context, deferred). * Goldens: wasm-raster's CPU output diverges from the GPU _shared baseline the same way desktop raster does — recorded as per-renderer overrides (5-44 channel delta vs MaxChannelDelta=2). wasm-graphite-dawn matches _shared bit-exactly on RedRoundedRectOnWhite; DiagonalLines and GpuOnly_FilledCircle drift by 5 channels (104-1764 pixels) under SwiftShader-WebGPU vs Lavapipe-Vulkan — recorded as overrides until the tolerance policy is revisited. Matrix on Linux is now 16/16 active cells green, 3 graphite-metal cells skip with a clean message.
Sibling JS-library shim skia_gl_bridge.js bootstraps an OffscreenCanvas + WebGL2 context and registers it with emscripten's $GL runtime. Living in tests/Hosts/RenderHost.Wasm/ because offscreen GL is test-only — production Uno/Blazor consumers target a real canvas through the regular emscripten WebGL plumbing. WebGL is synchronous on the readback path (glReadPixels blocks until queued work completes), so unlike the WebGPU cell we don't need the bridge to do an async copy + mapAsync. Per-render GRContext matches the Linux ganesh-gl model and keeps things tidy. Matrix on Linux is now 19/19 active cells green: raster ×2, ganesh-gl ×3, ganesh-vulkan ×3, graphite-vulkan ×3, wasm-raster ×2, wasm-ganesh-gles ×3, wasm-graphite-dawn ×3 Plus 3 graphite-metal cells that skip cleanly (no Apple host). Two scenes through wasm-ganesh-gles match the _shared/ GPU golden exactly within MaxChannelDelta=2; DiagonalLines drifts by 6 channels (28 pixels) under SwiftShader vs Lavapipe and gets a per-renderer override.
Three things:
1. WglLoader.cs — Windows equivalent of EglLoader. Bootstraps a
HWND_MESSAGE window with the built-in STATIC class, gets a DC,
sets a minimal RGBA8/D24/S8 pixel format, creates a dummy
wglCreateContext, queries wglCreateContextAttribsARB through it,
then creates a real GL 3.3 core context. The window is never
shown; Skia renders into its own FBO. GetProc falls back from
wglGetProcAddress (returns sentinels for legacy entry points) to
GetProcAddress on opengl32.dll so glClear / glBegin / etc.
resolve correctly.
2. GaneshGlRenderer dispatches the platform loader: EglLoader on
Linux, WglLoader on Windows. One renderer name (`ganesh-gl`);
exactly one loader is available per host, so the catalog stays
non-duplicate and the cell joins the matrix on both platforms
without forking the goldens.
3. VulkanLoader resolver — `[DllImport("vulkan")]` on Windows would
look for vulkan.dll which doesn't exist; the real Vulkan loader
ships as vulkan-1.dll. Install a SetDllImportResolver in the
static cctor that maps the name (Windows-only — Linux's loader
handles libvulkan.so → libvulkan.so.1 natively).
Plus tests/Tests/SkiaSharp/Visual/README.md documenting the matrix,
how to run, prerequisites per platform, the SKIASHARP_UPDATE_GOLDENS
+ SKIASHARP_GOLDEN_SCOPE conventions, and the rationale for each
per-renderer override that exists today.
Tested on Linux (19/19 active cells stay green after the refactor;
3 graphite-metal cells skip cleanly). Windows-specific paths need
real-machine verification.
Flips the default from "false" to "true" in the per-platform cake
files that have the switch (linux, windows, macos, wasm, android),
and adds skia_enable_graphite=true unconditionally to the two that
hardcode their GN args (ios, tvos — maccatalyst delegates to ios).
The env-var / cake-argument escape hatch stays in place: set
SUPPORT_GRAPHITE=false to opt out of the new default.
Vulkan was already on-by-default on the targets that support it; Dawn
stays off everywhere except WASM (where it follows SUPPORT_GRAPHITE
because it's the only Graphite backend the WebAssembly build ships).
Metal-platform builds (ios/tvos/maccatalyst, and macos via Metal)
pick up Graphite-on-Metal automatically.
Documentation: the three graphite-*.md files dropped their
'SUPPORT_GRAPHITE=true \\' lines from the bring-up snippets — those
are now no-ops — and graphite.md's index table notes the new defaults.
The actual libSkiaSharp binaries don't change shape from this commit;
the next time someone runs dotnet cake --target=externals-{platform}
they'll get Graphite + Vulkan built in without needing the env var.
Windows-only side effect of the test project's multi-target: net48 also tries to compile WasmHostSession.cs + WasmRenderers.cs, but Microsoft.Playwright + ValueTask + IAsyncDisposable aren't available there. Exclude the two files from the net48 build — the matrix theory still runs against the same renderer/scene set on net48, just without the wasm-* cells.
Replace OperatingSystem.IsX() (.NET 5+) with RuntimeInformation.IsOSPlatform(OSPlatform.X) — same intent, available on every TFM the test assembly targets including net48. VulkanLoader's SetDllImportResolver path is gated behind #if NET5_0_OR_GREATER. The resolver is purely a Windows-vulkan-1.dll mapping; net48-on-Windows users hitting that cell would see an unresolved-DLL failure, which matches the pre-matrix baseline. WglLoader's opengl32.dll-fallback path swaps NativeLibrary for plain kernel32 LoadLibraryW + GetProcAddress P/Invoke. Works on every TFM, no functional change on net10.0+. Linux net10.0 matrix re-verified — 19/19 cells still green.
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
13 similar comments
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
10 similar comments
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description of Change
Bugs Fixed
API Changes
Graphite backend support
Wraps Skia's new Graphite GPU backend (Vulkan / Metal / Dawn-WebGPU) in SkiaSharp. Depends on the C-shim PR in the skia submodule: mono/skia#236.
Graphite is Skia's next-generation GPU backend. Unlike Ganesh's auto-flushing draw stream, it uses an explicit
record → snap → submitmodel and a per-threadRecorder. The public surface mirrors that shape.What's new on the managed side
9 new types under
binding/SkiaSharp/Gpu/Graphite/:SKGraphiteContextskgpu::graphite::Context(+ staticCreateVulkan/Metal/Dawnfactories,IsBackendAvailable(...))SKGraphiteRecorderskgpu::graphite::RecorderSKGraphiteRecordingskgpu::graphite::RecordingSKGraphiteBackendTextureskgpu::graphite::BackendTextureSKGraphiteTextureInfoskgpu::graphite::TextureInfo(+ per-backend factories)SKGraphiteImageProviderContextOptions::fImageProviderSKGraphiteVkBackendContextVulkanBackendContext(instance / device / queue / extensions /vkGetProcAddr)SKGraphiteMtlBackendContextMtlBackendContext(device + queue)SKGraphiteDawnBackendContextDawnBackendContext(instance / device / queue)6 enums via codegen:
SKGraphiteBackend,SKGraphiteInsertStatus,SKGraphiteMarkFrameBoundary,SKGraphiteRescaleGamma,SKGraphiteRescaleMode,SKGraphiteSyncToCpu.Factory overloads on existing types (additive only; no signature changes — ABI is preserved):
SKImage.FromTexture(SKGraphiteRecorder, SKGraphiteBackendTexture, …)SKImage.ToTextureImage(SKGraphiteRecorder, …)SKSurface.Create(SKGraphiteRecorder, SKImageInfo, …)SKSurface.Create(SKGraphiteRecorder, SKGraphiteBackendTexture, …)SkiaApi.generated.csis regenerated against the new C headers — no hand-edits to generated files.Canonical render loop
Build-flag plumbing
SUPPORT_GRAPHITE=trueis now the default on every platform:native/{linux,windows,macos,ios,tvos,android,wasm}/build.cake. Each cake file passes the appropriate flags into the GN invocation (skia_enable_graphite, plus per-backendskia_use_dawn/skia_use_vulkan/skia_use_metal). The WASM build additionally flipsis_canvaskit=trueandskia_use_webgpu=true.After this PR merges,
dotnet cake --target=externals-{platform}produces alibSkiaSharpwith Graphite linked in. Pre-built natives downloaded viaexternals-downloadwill include Graphite once the next CI run publishes them.Tests
Three layers, all run in CI:
tests/native/Graphite/):cpp_smoke/— drives upstream Skia directly. Proves Graphite builds + runs on Lavapipe.c_smoke/— drives the newsk_graphite_*shim only. Proves the C ABI's ownership/error contracts.tests/Tests/SkiaSharp/Graphite/):GraphiteAbiSmokeTests— every new P/Invoke resolves (catchesEntryPointNotFoundExceptionregressions).GraphiteSmokeTests— full managed render-and-readback.GraphiteDisposalTests— disposal ordering (Context before Recorder/Recording).GraphiteCoexistenceTests— Ganesh vs. Graphite produce visibly equivalent pixels for the same scene (mean per-channel Δ < 1%).GraphiteBackendTextureTests— wrap a caller-allocatedVkImageas a Graphite surface.tests/Tests/SkiaSharp/Visual/) — opt-in viaSKIASHARP_VISUAL_TESTS=1, runs every renderer × scene combination across raster / Ganesh / Graphite × GL / Vulkan / Metal / WebGPU on Linux / macOS / WASM / Android / iOS. Wired into Azure Pipelines as a newtests_visualstage with two jobs (Linux + macOS); failure artifacts (actual.png+diff.png) are published per cell. Seedocumentation/dev/visual-tests.md.Headless development
Skia's Graphite always requires a real GPU API context — there's no software fallback at the Graphite layer. For headless CI / WSL2 / GPU-less dev machines we install Mesa Lavapipe as a software Vulkan ICD:
apt install mesa-vulkan-drivers vulkan-tools. WASM development uses Chromium's WebGPU (--headless=new) via Playwright. Full runbook indocumentation/dev/graphite-headless.md.Docs
documentation/dev/graphite.md— public-surface walkthrough + canonical render loop.documentation/dev/graphite-headless.md— Lavapipe setup.documentation/dev/graphite-wasm-webgpu.md— Dawn-on-WebGPU bring-up.documentation/dev/visual-tests.md— visual matrix workflow.Out of scope (follow-up PRs)
These are layered features that nothing in this PR blocks; tracking them for v2:
PrecompileContext/precompile/*— pipeline-cache warming.PersistentPipelineStorage— client-supplied cache blob so the GPU pipeline cache survives process restarts.BackendSemaphore— cross-queue / cross-context sync primitives.YUVABackendTextures— multi-plane Y'UV image wrapping.asyncRescaleAndReadPixelsonSKImage(onlySKSurfaceis wired in this PR).InsertRecordingInfo.TargetSurfaceretargeting.Coexistence and compatibility
GR*(Ganesh) types are unchanged. Apps can mix Ganesh and Graphite contexts in the same process; the coexistence regression test asserts they produce visually equivalent output.tests-visualcake target is separate fromtests-netcore; non-Graphite legs of CI are untouched.Behavioral Changes
None.
Required skia PR
mono/skia#236
PR Checklist