Skip to content

Fix Graphite/Metal GradientBlend on the iOS simulator + shader-error diagnostic API (#4555) - #4586

Open
ramezgerges wants to merge 2 commits into
mono:mainfrom
ramezgerges:dev/issue-4555-graphite-shader-error-handler
Open

ramezgerges wants to merge 2 commits into
mono:mainfrom
ramezgerges:dev/issue-4555-graphite-shader-error-handler

Conversation

@ramezgerges

@ramezgerges ramezgerges commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes #4555 (Graphite/Metal GradientBlend cell returning null Recorder.Snap() only on the iOS simulator) and adds the C API surface used to diagnose it.

Two commits, two roles:

  1. SKGraphiteContext shader-error-handler overloads — wraps the new sk_graphite_context_options.fShaderErrorHandler C API. New backwards-compatible CreateDawn/CreateMetal/CreateVulkan overloads take an SKGraphiteShaderErrorHandlerDelegate; delegate is pinned for the Context's lifetime and freed in DisposeNative after the native context tears down its handler pointer. The proxy never throws across the FFI boundary — a bad handler cannot crash Skia's shader-compile path.

  2. Fix Graphite/Metal GradientBlend on the iOS simulator — bumps the submodule to pick up the two Skia-side fixes: PSO-creation failures now routed through ShaderErrorHandler, and framebuffer fetch disabled under TARGET_OS_SIMULATOR. Drops the simulator-only null-Snap() skip seam from GraphiteMetalRenderer (a null Snap is again a hard failure everywhere, so regressions can't hide behind the skip) and adds the newly-passing graphite-metal.ios/GradientBlend.png golden.

Related issues

Fixes #4555

Required skia PR

Requires mono/skia#308

Areas affected

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

Changes

Public API

ABI-stable additive changes only.

// new delegate
public delegate void SKGraphiteShaderErrorHandlerDelegate(string shader, string errors, bool shaderWasCached);

// new overloads on SKGraphiteContext
public static SKGraphiteContext CreateDawn(
    SKGraphiteDawnBackendContext backendContext,
    SKGraphiteContextOptions options,
    SKGraphiteShaderErrorHandlerDelegate shaderErrorHandler);

public static SKGraphiteContext CreateMetal(
    SKGraphiteMtlBackendContext backendContext,
    SKGraphiteContextOptions options,
    SKGraphiteShaderErrorHandlerDelegate shaderErrorHandler);

public static SKGraphiteContext CreateVulkan(
    SKGraphiteVkBackendContext backendContext,
    SKGraphiteContextOptions options,
    SKGraphiteShaderErrorHandlerDelegate shaderErrorHandler);

Behavior

  • Graphite/Metal on iOS/tvOS simulator: GradientBlend (and any other draw needing a dst read with a non-coefficient blend — Multiply, Screen, Overlay, …) now renders correctly instead of returning null from Recorder.Snap(). Real Apple devices and macOS unchanged.
  • The simulator-only null-Snap() skip in GraphiteMetalRenderer is removed — regressions to this class of bug now fail loudly.
  • Callers who install a shader-error handler now also receive Metal PSO-creation failures (previously invisible outside SkDebugf).

Testing

Full SkiaSharp.Tests.Devices run on iPhone 16 / iOS 26.1 simulator (arm64): 1219 total, 0 failed, 1138 succeeded, 81 skipped. GradientBlend cell rendering and matching its committed graphite-metal.ios golden.

GraphiteVkContextAcceptsShaderErrorHandler smoke test on Linux/Lavapipe (Vulkan): passes — proves no double-free / no EntryPointNotFoundException on Context teardown after installing a handler. The Metal simulator path itself needs an Apple host to reproduce, which is what the Devices-tests run above covered.

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 a docs issue in mono/SkiaSharp-API-docs so reference docs can be written later
  • Native change? Companion mono/skia PR linked above and bindings regenerated

@github-actions

github-actions Bot commented Jul 30, 2026

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 -- 4586

PowerShell / Windows:

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

Step 2 — Add the local NuGet source

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

mattleibow added a commit that referenced this pull request Jul 30, 2026
#4555 was broadened to cover all three Metal failures because they share a root
cause: no CI host has a real Metal device. That was wrong for tracking. #4586
carries 'Fixes #4555' and addresses only the iOS simulator's gradient pipeline,
so merging it would have silently closed the macOS shutdown hang and the Mac
Catalyst GPU-family gap along with it.

#4555 is back to its original scope. The other two are now #4598 (macOS
virtualized Metal hangs the test host on shutdown, killing the whole leg) and
#4599 (Mac Catalyst reports no MTLGPUFamily that Ganesh or Graphite accepts).
Each has its own cause and its own likely fix -- an Apple Silicon pool for the
first, possibly a probe change for the second -- so each closes on its own.

Repointed the opt-out comments and the policy doc at whichever issue explains
that leg, and updated the #4591 umbrella table. The iOS leg cites both #4598 and
#4555, because the simulator has the shutdown hang *and* the gradient bug.
@mattleibow

mattleibow commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines

Copy link
Copy Markdown
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.

1 similar comment
@azure-pipelines

Copy link
Copy Markdown
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.

@ramezgerges
ramezgerges force-pushed the dev/issue-4555-graphite-shader-error-handler branch from 0b92317 to 9ab7523 Compare August 3, 2026 17:18
@ramezgerges

Copy link
Copy Markdown
Contributor Author

Rebased onto the current main and resolved the GraphiteMetalRenderer.cs conflict (kept the version that removes the null-Snap workaround, which is exactly what this PR fixes). The two failed /azp run attempts happened while I was force-pushing; the branch is stable now.

@ramezgerges
ramezgerges force-pushed the dev/issue-4555-graphite-shader-error-handler branch from 9ab7523 to a12b2b3 Compare August 3, 2026 17:41
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

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

This PR fixes a Graphite/Metal iOS-simulator rendering failure (null Recorder.Snap() in the GradientBlend scene) and introduces a managed shader-error diagnostic hook by wiring a new native fShaderErrorHandler option through to SKGraphiteContext creation.

Changes:

  • Adds SKGraphiteShaderErrorHandlerDelegate and new CreateDawn/CreateMetal/CreateVulkan overloads that accept a shader-error handler, with lifetime management via pinned delegates and a native bridge handle.
  • Removes the simulator-specific null-Snap() special-casing in the Graphite/Metal visual renderer so regressions fail consistently across hosts.
  • Adds a Vulkan smoke test ensuring installing a shader-error handler does not break context teardown and basic submission.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/VulkanTests/GraphiteVkBackendContextTest.cs Adds a Vulkan smoke test for the new shader-error handler overload and lifetime/teardown behavior.
tests/Tests/SkiaSharp/Visual/Renderers/GraphiteMetalRenderer.cs Makes Recorder.Snap() null a hard failure on all hosts (including simulator) now that the underlying issue is fixed.
binding/SkiaSharp/SkiaApi.generated.cs Adds new P/Invoke surface and struct field for sk_graphite_shader_error_handler_t and SKGraphiteContextOptions.fShaderErrorHandler.
binding/SkiaSharp/Gpu/Graphite/SKGraphiteContext.cs Adds handler-overload factories and pins/tears down managed + native handler bridge resources.
binding/SkiaSharp/DelegateProxies.cs Adds the managed callback proxy implementation for shader-error handler invocations.
binding/SkiaSharp.Skottie/SkottieApi.generated.cs Regenerates typedefs to include the new Graphite shader-error handler type alias.
binding/SkiaSharp.SceneGraph/SceneGraphApi.generated.cs Regenerates typedefs to include the new Graphite shader-error handler type alias.
binding/SkiaSharp.Resources/ResourcesApi.generated.cs Regenerates typedefs to include the new Graphite shader-error handler type alias.

Comment thread binding/SkiaSharp/DelegateProxies.cs
Comment thread binding/SkiaSharp/Gpu/Graphite/SKGraphiteContext.cs Outdated
Comment thread binding/SkiaSharp/SkiaApi.generated.cs Outdated

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

Requesting changes: the shader-handler test does not verify callback delivery, and the new backend-specific test setup should be consolidated with the existing Graphite backend harness so future Graphite scenarios can run consistently across Vulkan, Metal, and Dawn.

Comment thread tests/VulkanTests/GraphiteVkBackendContextTest.cs Outdated
Comment thread tests/VulkanTests/GraphiteVkBackendContextTest.cs Outdated
@github-project-automation github-project-automation Bot moved this to Changes Requested in SkiaSharp Backlog Aug 3, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

5 similar comments
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📦 Artifact size report

Packages from this PR (build 1539617) vs the latest nightly baseline 4.151.0-nightly.124 (observed 2026-08-04).

Total .nupkg size: 569.4 MB → 569.1 MB (−296.6 KB, -0.1%)

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.1 MB 108.6 MB 🟢 −451.5 KB -0.4%
SkiaSharp.NativeAssets.WebAssembly 69.6 MB 69.6 MB 🔴 +67.5 KB +0.1%

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

Per-file changes

SkiaSharp.NativeAssets.WebAssembly

File Size
🧬 wasm 5.0.6/mt · libSkiaSharp.a (buildTransitive/netstandard1.0/libSkiaSharp.a/5.0.6/mt/libSkiaSharp.a) 17.9 MB → 17.9 MB (🔴 +1.5 KB)
🧬 wasm 5.0.6/st,simd · libSkiaSharp.a (buildTransitive/netstandard1.0/libSkiaSharp.a/5.0.6/st,simd/libSkiaSharp.a) 18.2 MB → 18.2 MB (🔴 +1.5 KB)
🧬 wasm 5.0.6/mt,simd · libSkiaSharp.a (buildTransitive/netstandard1.0/libSkiaSharp.a/5.0.6/mt,simd/libSkiaSharp.a) 18.2 MB → 18.2 MB (🔴 +1.5 KB)
🧬 wasm 5.0.6/st · libSkiaSharp.a (buildTransitive/netstandard1.0/libSkiaSharp.a/5.0.6/st/libSkiaSharp.a) 17.9 MB → 17.9 MB (🔴 +1.5 KB)
🧬 wasm 3.1.56/mt · libSkiaSharp.a (buildTransitive/netstandard1.0/libSkiaSharp.a/3.1.56/mt/libSkiaSharp.a) 17.7 MB → 17.7 MB (🔴 +1.5 KB)
🧬 wasm 3.1.56/st · libSkiaSharp.a (buildTransitive/netstandard1.0/libSkiaSharp.a/3.1.56/st/libSkiaSharp.a) 17.8 MB → 17.8 MB (🔴 +1.5 KB)
🧬 wasm 3.1.56/mt,simd · libSkiaSharp.a (buildTransitive/netstandard1.0/libSkiaSharp.a/3.1.56/mt,simd/libSkiaSharp.a) 18.0 MB → 18.0 MB (🔴 +1.5 KB)
🧬 wasm 3.1.56/st,simd · libSkiaSharp.a (buildTransitive/netstandard1.0/libSkiaSharp.a/3.1.56/st,simd/libSkiaSharp.a) 18.0 MB → 18.0 MB (🔴 +1.5 KB)

SkiaSharp.NativeAssets.WinUI

File Size
runtimes/win-x64/native/libGLESv2.pdb 66.6 MB → 66.5 MB (🟢 −144.0 KB)
runtimes/win-arm64/native/libGLESv2.pdb 64.5 MB → 64.4 MB (🟢 −80.0 KB)
runtimes/win-x86/native/libGLESv2.pdb 67.3 MB → 67.2 MB (🟢 −80.0 KB)

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

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

5 similar comments
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

2 similar comments
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@ramezgerges
ramezgerges force-pushed the dev/issue-4555-graphite-shader-error-handler branch from a12b2b3 to 077354b Compare August 4, 2026 16:54
@azure-pipelines

Copy link
Copy Markdown
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.

1 similar comment
@azure-pipelines

Copy link
Copy Markdown
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.

ramezgerges and others added 2 commits September 14, 2026 11:10
Wraps the new sk_graphite_context_options.fShaderErrorHandler C API
so managed callers can capture Graphite shader compile failures. The
motivating case is the Metal iOS simulator gradient scene where
Recorder.Snap() silently returns null with no diagnostic — Skia's
default handler prints via SkDebugf + asserts, which is invisible in
shipping apps on Apple platforms.

* new CreateDawn/CreateMetal/CreateVulkan overloads taking
  SKGraphiteShaderErrorHandlerDelegate (backwards-compatible additions,
  no signature changes)
* SKGraphiteShaderErrorHandlerDelegate proxy in DelegateProxies never
  throws across the FFI boundary — a bad handler cannot crash Skia's
  shader-compile path
* GCHandle pinned for the Context's lifetime, freed in DisposeNative
  after the native context tears down its internal handler pointer

Includes GraphiteVkContextAcceptsShaderErrorHandler smoke test that
proves the wiring survives Context create + trivial draw + dispose on
Vulkan (Lavapipe compiles cleanly so the callback does not fire —
the important assertion is no double-free / no EntryPointNotFound on
teardown). The Metal simulator failure path requires an iOS host to
reproduce and is out of scope for CI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Root cause (diagnosed with the fShaderErrorHandler hook from this
branch, plus PSO-failure forwarding added to it): Graphite's MtlCaps
enabled framebuffer fetch for every Apple-family GPU and
Caps::getDstReadStrategy() then always used it for dst reads. The iOS
simulator advertises an Apple GPU family but cannot read a render
target from a fragment shader — the MSL compiles, but
newRenderPipelineStateWithDescriptor fails with CompilerError Code=2
"reading from a rendertarget is not supported" — so the Multiply-blend
draw in GradientBlend (the only matrix scene needing a dst read) nulled
its pipeline and Recorder.Snap() returned null. Ganesh/Metal never uses
framebuffer fetch and real devices/macOS support it, which is why only
this one cell failed.

Submodule changes (mono/skia dev/issue-4555-graphite-shader-error-handler):
* Metal PSO-creation failures are now reported through the
  ShaderErrorHandler (previously SkDebugf-only, invisible on iOS)
* framebuffer fetch is disabled under TARGET_OS_SIMULATOR; dst reads
  fall back to DstReadStrategy::kTextureCopy there

Test harness:
* remove the simulator-only null-Snap() skip seam from
  GraphiteMetalRenderer — a null Snap() is a hard failure everywhere
  again, so regressions can no longer hide behind the skip
* seed the graphite-metal.ios/GradientBlend golden (rendered on an
  iPhone 16 / iOS 26.1 simulator; pixel-identical to the
  graphite-metal.macos golden)

Verified on the iOS simulator: full SkiaSharp.Tests.Devices run is
green (1219 total, 0 failed, 1138 succeeded, 81 skipped), with the
GradientBlend cell rendering and matching its golden.

Fixes mono#4555.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ramezgerges
ramezgerges force-pushed the dev/issue-4555-graphite-shader-error-handler branch from 077354b to 71c6d22 Compare September 14, 2026 15:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Changes Requested

Development

Successfully merging this pull request may close these issues.

[BUG] Graphite/Metal: Recorder.Snap() returns null for the gradient scene on the iOS simulator

3 participants