Skip to content

Add Graphite native and MAUI views - #4728

Closed
mattleibow wants to merge 1 commit into
mainfrom
mattleibow-add-graphite-maui-views
Closed

mattleibow wants to merge 1 commit into
mainfrom
mattleibow-add-graphite-maui-views

Conversation

@mattleibow

@mattleibow mattleibow commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Description

Adds production-oriented Graphite presentation controls for Apple and Android, then layers a backend-neutral SKGraphiteView over them for .NET MAUI.

Apple uses MTKView + Metal. Android uses TextureView + Vulkan with explicit acquire/render/present synchronization, swapchain recreation, device-loss containment, and a bounded reusable Graphite/Vulkan core. A new SkiaSharp.Views.Graphite package keeps the unsigned Silk.NET dependency isolated from the existing signed views assembly.

The MAUI sample demonstrates Graphite's actual record/snap/insert/submit model: a cached background Recording is replayed while two thread-affine recorders build animated layers concurrently. It exposes the backend, FPS, resource budget, worker mode, and workload controls without claiming unmeasured Ganesh performance wins.

Related issues

Fixes #4721
Related to #3962

Required skia PR

Requires mono/skia#345

Areas affected

  • Managed API (binding/)
  • Native / C API (externals/skia/src/c, include/c)
  • Generated P/Invoke bindings
  • Native dependency or Skia update (libpng, HarfBuzz, FreeType, zlib, milestone bump, …)
  • Views & integrations (MAUI, Uno, WPF, WinUI, Blazor, …)
  • Rendering output / visual behavior
  • Performance
  • Tests
  • Build, packaging, or CI
  • Documentation or samples

Changes

Public API

public delegate void SKGraphiteFinishedDelegate(bool success);

public sealed class SKGraphiteBackendSemaphore : SKObject
{
    public static SKGraphiteBackendSemaphore CreateVulkan(ulong vkSemaphore);
}

public sealed class SKGraphiteMutableTextureState : SKObject
{
    public static SKGraphiteMutableTextureState CreateVulkan(int imageLayout, uint queueFamilyIndex);
}

public ref struct SKGraphiteInsertRecordingOptions { /* typed target, sync spans, completion */ }

public static SKGraphiteBackendTexture CreateVulkan(
    int width, int height, SKGraphiteVkTextureInfo info,
    int imageLayout, uint queueFamilyIndex, ulong vkImage);
public SKGraphiteTextureInfo SKGraphiteBackendTexture.TextureInfo { get; }
public SKCanvas SKGraphiteRecorder.CreateDeferredCanvas(
    SKImageInfo info, SKGraphiteTextureInfo textureInfo);
public SKGraphiteInsertStatus SKGraphiteContext.InsertRecording(
    SKGraphiteRecording recording, SKGraphiteInsertRecordingOptions options);

public class SKGraphiteMetalView : MTKView;       // iOS, Mac Catalyst, macOS, tvOS
public class SKGraphiteVulkanView : TextureView;  // Android 7+
public class SKGraphiteView : Microsoft.Maui.Controls.View;
public interface ISKGraphiteView : IView;
public class SKPaintGraphiteSurfaceEventArgs : EventArgs;
public sealed class SKGraphiteRenderFailedEventArgs : EventArgs;

Behavior

  • Graphite-named views never silently fall back to Ganesh.
  • Android requires Vulkan 1.1/API 24+, preserves correct swapchain semaphore/layout ownership, and excludes faulted cores from reuse.
  • Windows/Tizen handlers report unsupported; the sample and tests omit Windows Graphite presentation.
  • Vulkan's fallback allocator is now internally synchronized for concurrent recorders.

Testing

  • Built native libSkiaSharp from source: Android (all ABIs), iOS, Mac Catalyst, macOS, and tvOS.
  • Built and packed SkiaSharp.Views.Graphite, MAUI Core, and MAUI Controls across current/previous Apple + Android TFMs.
  • Android API 36 arm64 emulator: Vulkan rendering, parallel/serial toggle, touch, detach/reattach, full context reuse, and repeated Shell navigation.
  • iOS Simulator: Metal rendering at 60 FPS with parallel recorders.
  • Mac Catalyst: app launched and rendered without Metal/Graphite errors.
  • MAUI device-test projects build for Android and iOS Simulator.
  • API validation tests pass; Vulkan presentation tests build and are exercised by the Android sample.
  • Full console suite: 6,123 passed, 23 skipped. The managed-stream GC flake passed on focused rerun. One unrelated existing ganesh-gl/DiagonalLines macOS golden mismatch remains reproducible with no Graphite code path involved.
Graphite sample screenshots
Android / Vulkan iOS / Metal
Graphite sample on Android Graphite sample on iOS

Checklist

  • Tests added or updated (if omitted, explain why above)
  • Changes above lists all public API and behavioral changes (or "None.")
  • New/changed public API? Filed Document Graphite native and MAUI views SkiaSharp-API-docs#200 so reference docs can be written later
  • Native change? Companion mono/skia PR linked above and bindings regenerated

Add Metal and Vulkan native controls, a cross-platform MAUI handler, presentation interop, tests, packaging, and a multi-recorder sample.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>\nCopilot-Session: 00cda8ff-9603-406f-ae2e-45c70e39b3e7
@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 -- 4728

PowerShell / Windows:

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

Step 2 — Add the local NuGet source

dotnet nuget add source ~/.skiasharp/hives/pr-4728/packages --name skiasharp-pr-4728
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-4728

@github-actions

Copy link
Copy Markdown
Contributor

📊 SkiaSharp benchmarks — PR #4728

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 — 🔴 33 slower · 🟢 54 faster

  • 🔴 RuntimeEffectShaderBenchmark.DrawFrame · Linux · 2.85 ms → 5.13 ms (+80%)
  • 🔴 SceneRenderBenchmark.RenderFrame(Complexity: 4) · Linux · 18.07 ms → 27.84 ms (+54%)
  • 🔴 SceneRenderBenchmark.RenderFrame(Complexity: 1) · Linux · 3.94 ms → 6.02 ms (+53%)
  • 🔴 BitmapDrawBenchmark.DrawScaledTiles(Tiles: 64) · macOS · 195.71 µs → 275.08 µs (+41%)
  • 🟢 RuntimeEffectShaderBenchmark.DrawFrame · Windows · 1.22 ms → 732.09 µs (-40%)
  • …and 82 more (see details below)

📦 Allocations — 🔴 1 more alloc · 🟢 0 less alloc

  • 🔴 RuntimeEffectShaderBenchmark.DrawFrame · Linux · 683 B → 730 B (+7%)
Full per-OS benchmark deltas

Linux

⏱️ Time (vs 🌙 nightly 4.152.0-nightly.10)

Benchmark baseline this PR Δ
RuntimeEffectShaderBenchmark.DrawFrame 2.85 ms 5.13 ms 🔴 +80%
SceneRenderBenchmark.RenderFrame(Complexity: 4) 18.07 ms 27.84 ms 🔴 +54%
SceneRenderBenchmark.RenderFrame(Complexity: 1) 3.94 ms 6.02 ms 🔴 +53%
ColorMathBenchmark.ToColor(Colors: 4096) 14.99 µs 9.62 µs 🟢 -36%
ColorMathBenchmark.PreMultiply(Colors: 4096) 12.87 µs 9.35 µs 🟢 -27%
ColorMathBenchmark.UnPreMultiply(Colors: 4096) 11.44 µs 8.62 µs 🟢 -25%
MatrixMapPointsBenchmark.MapPoint(Points: 256) 4.51 µs 3.68 µs 🟢 -18%
CanvasDrawBenchmark.Draw(Shapes: 64) 4.15 ms 4.90 ms 🔴 +18%
LargeImageScaleBenchmark.UpscaleOpaque(Size: 2048) 2.92 ms 2.41 ms 🟢 -17%
MatrixOpsBenchmark.Invert(Count: 4096) 130.80 µs 108.22 µs 🟢 -17%
ColorParseBenchmark.Parse(Iterations: 1000) 74.14 µs 62.43 µs 🟢 -16%
LargeImageScaleBenchmark.UpscaleOpaque(Size: 1024) 757.90 µs 640.20 µs 🟢 -16%
CanvasDrawBenchmark.Draw(Shapes: 512) 33.17 ms 38.31 ms 🔴 +15%
MatrixMapPointsBenchmark.MapPoint(Points: 4096) 71.97 µs 61.84 µs 🟢 -14%
PathBoundsBenchmark.TightBounds(Points: 1024) 10.15 µs 8.89 µs 🟢 -12%
MatrixOpsBenchmark.Concat(Count: 4096) 218.29 µs 192.20 µs 🟢 -12%
MatrixMapPointsBenchmark.MapRadius(Points: 256) 13.72 µs 12.59 µs 🟢 -8%
MatrixMapPointsBenchmark.MapVector(Points: 256) 10.84 µs 9.95 µs 🟢 -8%
MatrixMapPointsBenchmark.MapRadius(Points: 4096) 216.94 µs 199.35 µs 🟢 -8%
MatrixMapPointsBenchmark.MapVector(Points: 4096) 174.32 µs 160.25 µs 🟢 -8%
RasterImageLifecycleBenchmark.CreateRasterImage(Count: 256) 257.65 µs 237.38 µs 🟢 -8%
MatrixMapPointsBenchmark.MapPoints(Points: 256) 209.2 ns 225.3 ns 🔴 +8%
PathBoundsBenchmark.TightBounds(Points: 64) 977.7 ns 905.1 ns 🟢 -7%
LargeImageScaleBenchmark.UpscaleCrossfade(Size: 2048) 7.96 ms 7.42 ms 🟢 -7%
LargeImageScaleBenchmark.UpscaleCrossfade(Size: 1024) 2.06 ms 1.94 ms 🟢 -6%

📦 Allocations (vs 🌙 nightly 4.152.0-nightly.10)

Benchmark baseline this PR Δ
RuntimeEffectShaderBenchmark.DrawFrame 683 B 730 B 🔴 +7%

Windows

⏱️ Time (vs 🌙 nightly 4.152.0-nightly.10)

Benchmark baseline this PR Δ
RuntimeEffectShaderBenchmark.DrawFrame 1.22 ms 732.09 µs 🟢 -40%
ColorMathBenchmark.UnPreMultiply(Colors: 4096) 12.79 µs 8.25 µs 🟢 -35%
ColorMathBenchmark.PreMultiply(Colors: 4096) 14.74 µs 9.69 µs 🟢 -34%
RasterImageLifecycleBenchmark.CreateDataWithReleaseProc(Count: 256) 96.45 µs 121.32 µs 🔴 +26%
BitmapDrawBenchmark.DrawUnscaledTiles(Tiles: 256) 969.18 µs 720.03 µs 🟢 -26%
BitmapDrawBenchmark.DrawScaledTiles(Tiles: 64) 361.89 µs 269.51 µs 🟢 -26%
BitmapDrawBenchmark.DrawScaledTiles(Tiles: 256) 1.34 ms 1.00 ms 🟢 -25%
MatrixMapPointsBenchmark.MapPoint(Points: 4096) 86.67 µs 65.42 µs 🟢 -25%
MatrixOpsBenchmark.Concat(Count: 4096) 256.01 µs 193.46 µs 🟢 -24%
LargeImageScaleBenchmark.UpscaleCrossfade(Size: 1024) 2.84 ms 2.15 ms 🟢 -24%
LargeImageScaleBenchmark.UpscaleCrossfade(Size: 2048) 11.24 ms 8.54 ms 🟢 -24%
BitmapDrawBenchmark.DrawUnscaledTiles(Tiles: 64) 257.66 µs 196.98 µs 🟢 -24%
MatrixOpsBenchmark.Invert(Count: 4096) 148.24 µs 113.64 µs 🟢 -23%
MatrixMapPointsBenchmark.MapPoint(Points: 256) 5.42 µs 4.21 µs 🟢 -22%
CanvasDrawBenchmark.Draw(Shapes: 64) 2.65 ms 2.18 ms 🟢 -18%
MatrixMapPointsBenchmark.MapRect(Points: 256) 8.53 µs 7.15 µs 🟢 -16%
CanvasDrawBenchmark.Draw(Shapes: 512) 20.57 ms 17.26 ms 🟢 -16%
MatrixMapPointsBenchmark.MapVector(Points: 256) 8.88 µs 7.49 µs 🟢 -16%
LargeImageScaleBenchmark.UpscaleOpaque(Size: 1024) 822.95 µs 696.27 µs 🟢 -15%
LargeImageScaleBenchmark.UpscaleOpaque(Size: 2048) 3.25 ms 2.75 ms 🟢 -15%
MatrixMapPointsBenchmark.MapRadius(Points: 256) 9.81 µs 8.49 µs 🟢 -13%
ColorMathBenchmark.ToColor(Colors: 4096) 11.93 µs 10.40 µs 🟢 -13%
PathBoundsBenchmark.TightBounds(Points: 1024) 16.70 µs 14.60 µs 🟢 -13%
SceneRenderBenchmark.RenderFrame(Complexity: 4) 10.38 ms 9.08 ms 🟢 -13%
PathBoundsBenchmark.TightBounds(Points: 64) 1.61 µs 1.41 µs 🟢 -12%
…and 7 more

macOS

⏱️ Time (vs 🌙 nightly 4.152.0-nightly.10)

Benchmark baseline this PR Δ
BitmapDrawBenchmark.DrawScaledTiles(Tiles: 64) 195.71 µs 275.08 µs 🔴 +41%
MatrixMapPointsBenchmark.MapPoints(Points: 4096) 1.52 µs 1.85 µs 🔴 +22%
BitmapDrawBenchmark.DrawUnscaledTiles(Tiles: 64) 148.63 µs 180.51 µs 🔴 +21%
RasterImageLifecycleBenchmark.CreateDataWithReleaseProc(Count: 256) 66.42 µs 80.59 µs 🔴 +21%
BitmapDrawBenchmark.DrawScaledTiles(Tiles: 256) 664.73 µs 805.96 µs 🔴 +21%
LargeImageScaleBenchmark.UpscaleOpaque(Size: 2048) 2.69 ms 2.14 ms 🟢 -21%
RuntimeEffectShaderBenchmark.DrawFrame 507.38 µs 590.28 µs 🔴 +16%
MatrixMapPointsBenchmark.MapPoints(Points: 256) 135.9 ns 156.8 ns 🔴 +15%
ColorParseBenchmark.Parse(Iterations: 1000) 63.10 µs 72.07 µs 🔴 +14%
LargeImageScaleBenchmark.UpscaleOpaque(Size: 1024) 671.67 µs 576.91 µs 🟢 -14%
RasterImageLifecycleBenchmark.CreateRasterImage(Count: 256) 141.56 µs 160.06 µs 🔴 +13%
PathBoundsBenchmark.TightBounds(Points: 64) 850.3 ns 952.7 ns 🔴 +12%
PathBoundsBenchmark.TightBounds(Points: 1024) 8.07 µs 8.86 µs 🔴 +10%
MatrixOpsBenchmark.Invert(Count: 4096) 65.06 µs 59.19 µs 🟢 -9%
MatrixMapPointsBenchmark.MapVector(Points: 256) 2.43 µs 2.64 µs 🔴 +9%
BitmapDrawBenchmark.DrawUnscaledTiles(Tiles: 256) 543.43 µs 587.65 µs 🔴 +8%
ColorMathBenchmark.ToColor(Colors: 4096) 18.93 µs 17.40 µs 🟢 -8%
MatrixMapPointsBenchmark.MapPoint(Points: 4096) 18.14 µs 19.52 µs 🔴 +8%
ColorMathBenchmark.ToColorF(Colors: 4096) 4.92 µs 5.29 µs 🔴 +8%
MatrixMapPointsBenchmark.MapPoint(Points: 256) 1.14 µs 1.22 µs 🔴 +7%
MatrixOpsBenchmark.Concat(Count: 4096) 111.66 µs 119.13 µs 🔴 +7%
MatrixMapPointsBenchmark.MapRadius(Points: 4096) 97.96 µs 104.44 µs 🔴 +7%
LargeImageScaleBenchmark.UpscaleCrossfade(Size: 2048) 5.71 ms 5.34 ms 🟢 -7%
SceneRenderBenchmark.RenderFrame(Complexity: 4) 5.40 ms 5.75 ms 🔴 +6%
CanvasDrawBenchmark.Draw(Shapes: 64) 1.39 ms 1.47 ms 🔴 +6%
…and 5 more

📈 Full interactive perf-dashboard & run details →

@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.

@mattleibow

Copy link
Copy Markdown
Contributor Author

Superseded by the requested three-PR split:

All three target main for independent CI and are commit-stacked in that order.

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.

[FEATURE] Add native Graphite views and a .NET MAUI Graphite view

2 participants