Skip to content

Instantly share code, notes, and snippets.

@mattleibow
Created April 30, 2026 15:38
Show Gist options
  • Select an option

  • Save mattleibow/ff2fc822907c6bc21a07e1ef977015be to your computer and use it in GitHub Desktop.

Select an option

Save mattleibow/ff2fc822907c6bc21a07e1ef977015be to your computer and use it in GitHub Desktop.
Deep Research: Decoupling SkiaSharp's Public API From Skia — agreements, disagreements, and creative alternatives

Deep Research: Decoupling SkiaSharp's Public API From Skia

Context: This is a research-driven response to a thought experiment proposing that SkiaSharp should evolve from a 1:1 Skia mirror into a "Skia-inspired but independently versioned" API. Five parallel research agents investigated the actual codebase, tooling, consumer impact, and creative alternatives.


✅ What I AGREE With

1. "Churn is concentrated, not pervasive" — Verified, mostly.

The codebase confirms: SKPath (2.6% of API surface), SKFont/SKPaint split (4.5%), and GPU backends (4.5%) account for ~11.5% of total binding code but caused the majority of the 1,090 lines of 3.0.0 breaking changes. The 80/20 rule holds — but the analysis understates secondary churn in SKBitmap (11 methods removed), SKCodec (9), SKImage (8), and color management APIs.

2. "Don't do Option A" — Agree strongly.

The analysis correctly identifies that a full redesign is a multi-quarter effort against a massive surface (42,833 LOC of bindings, 102+ public types). The cost/benefit is terrible given the concentration of churn.

3. "Microsoft.Maui.Graphics already occupies the abstraction slot" — Agree, but weaker than claimed.

Research found zero bridge code between SkiaSharp and Maui.Graphics — no ICanvas adapters, no shared abstractions. They're complementary layers, not competing ones. SkiaSharp exposes 9 major advanced feature APIs (runtime effects, image filters, color spaces, shader compilation) that Maui.Graphics fundamentally cannot express. The "competition" argument is a red herring — nobody choosing SkiaSharp would be satisfied with Maui.Graphics.

4. "Option C+D is the right move" — Agree on direction, disagree on scope (see below).


❌ What I DISAGREE With

1. "AI changes the cost calculus" — Overstated.

This is the analysis's weakest argument. Auditing the actual update-skia skill reveals a 10-phase workflow where:

  • Only 2 phases are automated (version bumping + binding regeneration = ~5 minutes)
  • 7 phases require expert judgment (breaking change analysis, C API shim fixes, merge conflict resolution)
  • Phase 5 (C API shim adaptation) alone is 2-8 hours of expert work per milestone
Phase Name Automated? Cost
1 Discovery & Current State Docs only 15 min
2 Breaking Change Analysis ❌ Manual 2-3 hrs
3 Validation AI-assisted (Opus agent) 30 min
4 Upstream Merge ❌ Manual 1-2 hrs
5 Fix C API Shim ❌ Manual 2-8 hrs
6 Update Version Files ✅ Automated 2 min
7 Regenerate Bindings ✅ Automated 3 min
8 Fix C# Wrappers ❌ Manual 1-4 hrs
9 Build & Test Partially 30 min - 2 hrs
10 Create PRs ❌ Manual 30 min

The binding generation IS cheap. But that was never the expensive part. The expensive part is the C++ → C shim layer adaptation and breaking change analysis — and those remain manual. AI assists but doesn't automate the hard phases. The analysis conflates "binding generation" with "keeping in sync" — they're different problems.

2. "Skia milestones every 4 instead of every 1" — Risky advice.

Batching milestone updates sounds efficient but means absorbing 4× the breaking changes per update. The known-gotchas.md documents 22 hard problems that compound across milestones. The opposite may be better: update more frequently with smaller deltas, leveraging AI-assisted analysis to make each update predictable.

3. "The Roslyn analyzer handles consumer migration" — Insufficient.

The analysis treats this as a silver bullet. Reality: there are 203 [Obsolete] attributes in the current codebase, 41 active NuGet packages, and 13 platform-specific view packages. Consumers must upgrade all packages simultaneously or face version conflicts. A Roslyn analyzer helps with code changes but doesn't solve the coordination problem. The 3.0 migration had no step-by-step guide for hard cases like SK3dView removal or SKMatrix44 class→struct conversion.

4. Option C's scope is too narrow.

The analysis says "wrap ~6 surfaces." But the 3.0 breaking changes show churn across 34 types, not 6. SKBitmap pixel access, SKCodec options, SKImage factory methods, SKSurface color management — all broke too. A shim covering only Path/Font/GPU would miss half the actual breakage.


💡 Creative Alternatives the Analysis Missed

F) Interface Extraction (Zero-ABI-Break Stability Layer)

The codebase already uses internal interfaces (ISKReferenceCounted, ISKNonVirtualReferenceCounted). Making public interfaces for stable API surfaces is additive and non-breaking:

// Add without breaking anything
public interface ISkiaCanvas : IDisposable {
    void DrawRect(SKRect rect, SKPaint paint);
    void DrawPath(SKPath path, SKPaint paint);
    int Save(); void Restore();
}
public class SKCanvas : SKObject, ISkiaCanvas { /* unchanged */ }

Users who type against ISkiaCanvas get a stability contract. Users on SKCanvas see no change. Versioned interfaces (ISkiaCanvasV1, V2) handle evolution. This is much cheaper than Option C's shim and achieves similar goals.

G) Polyfill Extension Methods Package

SkiaExtensions partial classes with 50+ extension methods already exist in Definitions.cs, EnumMappings.cs, etc. A separate SkiaSharp.Polyfill.M140 package could re-implement removed APIs as extension methods over new ones — exactly how System.Memory backports Span<T> to older .NET. Opt-in, zero overhead, per-milestone versioned.

H) Multi-Backend Native Loading

LibraryLoader.cs already implements platform-aware library loading with fallback chains. It's technically feasible to ship both libSkiaSharp_stable (pinned milestone) and libSkiaSharp_latest simultaneously. Conservative users pin to stable; bleeding-edge users get latest. The C# wrapper stays the same for both.

I) Layered Package Hierarchy

Formalize what already exists organically:

Layer 1: SkiaSharp.Native.*     (just binaries)
Layer 2: SkiaSharp              (1:1 mirror, accepts churn)
Layer 3: SkiaSharp.Interfaces   (stable contracts, additive)
Layer 4: SkiaSharp.Polyfill.*   (migration helpers per milestone)

Users pick their stability level. All layers coexist.


🎯 Revised Recommendation

Do D + F + G instead of C + D.

Action Effort Impact
D: Stability tiers Low (documentation) Sets expectations, enables informed decisions
F: Public interfaces Low (additive, no ABI break) Gives consumers a stability contract without a parallel API
G: Polyfill packages Medium (per-milestone) Absorbs breaking changes for consumers who can't migrate immediately
Roslyn analyzer Medium (one-time) Automates the mechanical parts of migration

Why not C (the shim)?

Because a shim is a second implementation to maintain forever. Interfaces + polyfills achieve the same consumer-facing stability with far less ongoing maintenance. The shim creates a new bug surface; interfaces and extension methods don't.

Why not "update every 4 milestones"?

Update frequently (every 1-2), but use the polyfill package to give consumers a 6-12 month migration window. The maintainer absorbs churn continuously; the consumer migrates at their pace.

When to revisit Option A

The analysis's three triggers are correct (Impeller migration, second backend requirement, AI cost regression). I'd add a fourth: if the interface extraction (F) grows beyond ~30 interfaces, that's a signal the abstraction wants to become a real API layer.


Supporting Data

API Surface Scale

  • 42,833 LOC of C# binding code
  • 102+ public types
  • 203 [Obsolete] attributes in current codebase
  • 41 active NuGet packages, 13 platform-specific view packages
  • 1,090 lines of breaking changes documented for 3.0.0

Churn Concentration (v3.0.0)

  • 34 types had breaking changes
  • Top 3 areas (Path, Font/Paint, GPU) = 11.5% of LOC, ~50% of breaks
  • Secondary churn (Bitmap, Codec, Image, Surface) = ~20% of breaks
  • Remaining = scattered across 28+ types

AI Automation Reality

  • Automated: 5 minutes (version files + binding regeneration)
  • Expert-required: 6-18 hours per milestone update
  • Automation ratio: ~2% of total effort is fully automated
  • 5 of 8 workflow gates require human judgment/approval
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment