Skip to content

Graphite shader-error handler + fix iOS-simulator dst read (mono/SkiaSharp#4555) - #308

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

Graphite shader-error handler + fix iOS-simulator dst read (mono/SkiaSharp#4555)#308
ramezgerges wants to merge 3 commits into
mono:skiasharpfrom
ramezgerges:dev/issue-4555-graphite-shader-error-handler

Conversation

@ramezgerges

Copy link
Copy Markdown

Description

Three related changes that together fix mono/SkiaSharp#4555 (Graphite/Metal GradientBlend cell returning null Recorder::Snap() on the iOS simulator) and give SkiaSharp callers a way to see the shader/PSO compile diagnostics behind similar failures in the future.

Commits:

  1. Add sk_graphite_context_options.fShaderErrorHandler callback — expose Graphite's skgpu::ShaderErrorHandler on the C API so callers can capture SkSL→driver shader compile failures. Skia's default handler is SkDebugf + assert, which is invisible on iOS/tvOS. Bridged through a heap-allocated FfiShaderErrorHandler referenced by ContextOptions.fShaderErrorHandler (Skia stores it as a raw non-owning pointer, so the wrapper is deliberately leaked — one per Context creation with a handler set).

  2. Report Graphite Metal PSO-creation failures through the ShaderErrorHandler — a Graphite Metal shader can pass the MSL front-end compile yet still be rejected at newRenderPipelineStateWithDescriptor time. Previously that failure only surfaced via SKIA_LOG_E; now the NSError text goes through ShaderErrorHandler::compileError alongside the MSL compile path. This is exactly the #4555 failure mode: MSL compiled, PSO creation failed with CompilerError Code=2 "reading from a rendertarget is not supported", Recorder::snap() returned bare null.

  3. Disable Graphite framebuffer fetch on the Metal simulatorMtlCaps enabled framebuffer fetch (sk_LastFragColor [[color(0)]]) for every Apple-family GPU, and Caps::getDstReadStrategy() then always picked kFramebufferFetch for dst reads. The iOS/tvOS simulator advertises an Apple GPU family but its Metal implementation cannot read from a render target in a fragment shader. Root cause of #4555: GradientBlend's Multiply-blended circle needs a dst read → picked framebuffer fetch → PSO rejected → null Snap. Ganesh Metal never uses framebuffer fetch (dst-copies instead) and real Apple devices/macOS support it, which is why every other cell of the matrix rendered fine. Fix: keep FB fetch off under TARGET_OS_SIMULATOR so dst reads fall back to DstReadStrategy::kTextureCopy, which the simulator handles correctly.

Diagnosed on the iOS simulator using commit 1's callback + commit 2's PSO forwarding — the chain of null-paths led directly to the FB-fetch PSO rejection.

SkiaSharp issue

Related to mono/SkiaSharp#4555

Required SkiaSharp PR

Requires mono/SkiaSharp#4586

Areas affected

  • C API (include/c, src/c)
  • Native dependency / DEPS
  • Build (gn / build files)
  • Upstream Skia merge or rebase
  • Rendering output / behavior
  • Other

Changes

C API

// new — shader/PSO compile error diagnostic callback
typedef void (*sk_graphite_shader_error_handler_proc)(
    void*       userData,
    const char* shader,
    const char* errors,
    bool        shaderWasCached);

// sk_graphite_context_options_t gains
void* fShaderErrorHandler;         // nullable; cast from proc typedef, stored as void*
void* fShaderErrorHandlerUserData;

Behavior

  • Graphite Metal PSO-creation failures are now visible to installed shader-error handlers (previously invisible outside SkDebugf).
  • iOS/tvOS simulator: Graphite dst reads fall back to kTextureCopy instead of kFramebufferFetch. Slightly more work per dst read, but correct — was previously silently broken for any non-coefficient blend needing a dst read (Multiply, Screen, Overlay, etc.).
  • Real Apple devices and macOS unchanged.

Testing

Verified in the companion SkiaSharp PR — 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 golden.

Locally on Linux/Lavapipe (Vulkan; validates commit 1 wiring only — the Metal-specific commits 2/3 need Apple hardware): shader-error-handler smoke test passes, no double-free / no EntryPointNotFoundException on teardown.

Checklist

  • Targets the skiasharp branch
  • Changes above lists every added/changed C API export (or "None.")
  • Companion mono/SkiaSharp PR linked above (submodule bump at minimum; regenerates bindings + adds tests for C API changes)

ramezgerges and others added 3 commits August 3, 2026 17:40
Skia's Graphite backend accepts a ShaderErrorHandler on its C++
ContextOptions and calls back into it whenever a driver-side shader
compile fails (MSL for Metal, SPIR-V/GLSL for Vulkan, WGSL for Dawn).
The default handler asserts + prints via SkDebugf, which is invisible
in shipping apps on iOS/tvOS — see mono/SkiaSharp#4555 where a gradient
scene silently returns a null Snap() on the Metal iOS simulator with
no way to capture the failing shader text.

Expose the hook on the C ABI so managed SkiaSharp callers can install a
diagnostic. fShaderErrorHandler is stored as void* rather than the proc
typedef so the generated managed struct stays unmanaged in both
LibraryImport and delegate P/Invoke modes; a leaked FfiShaderErrorHandler
bridge (one per Context creation) forwards Skia's compileError(shader,
errors, wasCached) into the caller-provided function pointer.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ndler

A Graphite Metal shader can pass the MSL front-end compile yet still be
rejected when the MTLRenderPipelineState is created; until now that path
only printed via SKIA_LOG_E (SkDebugf), which is invisible in shipping
apps on iOS/tvOS, and Recorder::snap() surfaced it as a bare null.

This is exactly the mono/SkiaSharp#4555 failure mode: on the iOS
simulator the framebuffer-fetch gradient/blend pipeline compiles as MSL
but newRenderPipelineStateWithDescriptor fails with CompilerError Code=2
"reading from a rendertarget is not supported". Route the NSError text
through ShaderErrorHandler::compileError (with the pipeline label as the
"shader" text) so the sk_graphite_context_options.fShaderErrorHandler
hook added for #4555 sees driver-side PSO rejections too, not just MSL
compile errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MtlCaps enabled framebuffer fetch (sk_LastFragColor [[color(0)]]) for
every Apple-family GPU, and Caps::getDstReadStrategy() then always picks
kFramebufferFetch for dst reads. The iOS/tvOS simulator advertises an
Apple GPU family but its Metal implementation cannot read from a render
target in a fragment shader: the MSL compiles, but PSO creation fails
with CompilerError Code=2 "reading from a rendertarget is not supported",
so any draw needing a dst read (e.g. a non-coefficient blend like
kMultiply) nulls the pipeline and Recorder::snap() returns null.

This is the root cause of mono/SkiaSharp#4555: the GradientBlend visual
scene (linear gradient + Multiply-blended circle) failed only on
Graphite/Metal on the iOS simulator. Ganesh Metal never uses framebuffer
fetch (dst-copies instead) and real Apple devices / Apple-silicon macOS
support it, which is why every other cell of the matrix rendered.

Keep FB fetch off under TARGET_OS_SIMULATOR so dst reads fall back to
DstReadStrategy::kTextureCopy, which the simulator handles correctly —
verified by the SkiaSharp iOS visual matrix, where GradientBlend now
renders pixel-identically to the graphite-metal.macos golden.

Fixes mono/SkiaSharp#4555.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ramezgerges
ramezgerges force-pushed the dev/issue-4555-graphite-shader-error-handler branch from 34ee9f3 to 0ae988f Compare August 3, 2026 17:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

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

1 participant