Skip to content

[skia-sync] Merge upstream chrome/m151 bug fixes - #4642

Closed
mattleibow wants to merge 41 commits into
release/4.151.xfrom
skia-sync/m151
Closed

[skia-sync] Merge upstream chrome/m151 bug fixes#4642
mattleibow wants to merge 41 commits into
release/4.151.xfrom
skia-sync/m151

Conversation

@mattleibow

Copy link
Copy Markdown
Contributor

Automated upstream bug-fix sync for m151.

Companion skia PR: mono/skia#334

Summary

Same-milestone bug-fix sync (m151 → m151). Pulls the two latest upstream chrome/m151 commits into mono/skia and updates the SkiaSharp submodule pointer. Not a version bump — milestone, soname, and NuGet versions are unchanged; only cgmanifest.json's commitHash / upstream_merge_commit move forward.

Paired PR: mono/skia skia-sync/m151 (see the sync summary in that PR).

Breaking Change Analysis

None. The two upstream commits are:

Commit Area Risk
86bb2f25f4 [M151] Address incorrect handling of a map pointer in SkRP Internal SkSL raster pipeline codegen 🟢 None — internal, no public API surface
f7be6d7766 Roll infra dep Bot infrastructure only 🟢 None — infra-only

No public headers changed, no C API changes, no enum/struct/signature changes. Regeneration produced a zero-line diff to SkiaApi.generated.cs.

Version / Binding Updates

Handled by .agents/skills/update-skia/scripts/update-versions.ps1 -Current 151 -Target 151 -UpstreamRef chrome/m151:

  • cgmanifest.json: commitHash and upstream_merge_commit updated to 86bb2f25f46f4d620b4a26e738f59a9b6c22d2eb; chrome_milestone unchanged (151).
  • scripts/VERSIONS.txt: unchanged (same milestone → no version bump).
  • scripts/azure-templates-variables.yml: SKIASHARP_VERSION re-asserted at 4.151.0 (no change).
  • externals/skia/include/c/sk_types.h SK_C_INCREMENT: 0 (unchanged).

Verification greps skipped per script (same-milestone sync).

C# Wrapper Changes

None. regenerate-bindings.ps1 reported:

  • No changes to bindings (C API signatures unchanged)
  • No new functions found

binding/HarfBuzzSharp/HarfBuzzApi.generated.cs reverted per the standing policy (HarfBuzz updates are separate).

Build & Test Results

  • Native build: dotnet cake --target=externals-linux --arch=x64 → ✅ (12m03s). Produced output/native/linux/x64/libSkiaSharp.so.151.0.0 and libHarfBuzzSharp.so.0.61421.0.
  • C# build: dotnet build binding/SkiaSharp/SkiaSharp.csproj → ✅ 0 errors, 4 warnings (unrelated NETSDK1202 workload-support notices).
  • Full test suite: dotnet test tests/SkiaSharp.Tests.Console.slnx — see test-output.txt artifact.
    • SkiaSharp.Tests.dll (net10.0|x64): Failed: 169, Passed: 5922, Skipped: 33, Total: 6124
    • SkiaSharp.Vulkan.Tests.dll (net10.0|x64): Failed: 19, Passed: 4, Skipped: 2, Total: 25

Test failure analysis

Every observed failure has the same root cause:

System.TypeInitializationException : The type initializer for
'SkiaSharp.Tests.GlxContext' threw an exception.
---- System.Exception : Failed to open X display.
   at SkiaSharp.Tests.GlxContext.OpenDisplay() in .../GlxContext.cs:26

These are GLX / Vulkan GPU tests that require a display / GPU stack, which is not provisioned in this headless workflow runner. The failures are environmental, not a regression from this sync — the two upstream commits are an internal SkSL fix and an infra dep roll, neither of which touches GPU init.

Per the AGENTS.md GPU policy this is technically a "no display" failure that should be treated as a real failure (not an inferred skip). Flagging for human attention below — this is a workflow-environment concern that pre-dates this sync and would reproduce on main unchanged.

Items Needing Human Attention

  1. GPU tests fail on this headless runner (169 + 19 failures, all Failed to open X display). Not caused by this sync (upstream commits are SkSL-internal + infra). Options for the workflow owner:

    • Provision Xvfb + a software GL/Vulkan stack in the runner image so GlxContext can initialize, or
    • Set SKIASHARP_TEST_SKIP_GPU=1 in the workflow env if this runner is intentionally CPU-only per GpuPolicy.
      Full log attached as test-output.txt artifact.
  2. Paired mono/skia PR must merge first so the submodule pointer resolves on origin/skiasharp before this PR merges (standard skia-sync sequence).

Created by skia-upstream-sync.

@mattleibow mattleibow added partner/agentic-workflows Issues and PRs created by SkiaSharp agentic workflows. type/milestone-sync Any Skia upstream sync PR (milestone bump, same-milestone bug-fix re-sync, or tip sync). labels Aug 4, 2026
@mattleibow mattleibow added partner/agentic-workflows Issues and PRs created by SkiaSharp agentic workflows. type/milestone-sync Any Skia upstream sync PR (milestone bump, same-milestone bug-fix re-sync, or tip sync). labels Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 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 -- 4642

PowerShell / Windows:

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

Step 2 — Add the local NuGet source

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

@github-actions

github-actions Bot commented Aug 4, 2026

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.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📊 SkiaSharp benchmarks — PR #4642

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 — 🔴 68 slower · 🟢 8 faster

  • 🔴 PathBoundsBenchmark.TightBounds(Points: 64) · Windows · 1.53 µs → 2.17 µs (+42%)
  • 🔴 MatrixMapPointsBenchmark.MapRadius(Points: 256) · Windows · 8.64 µs → 11.99 µs (+39%)
  • 🔴 PathBoundsBenchmark.TightBounds(Points: 1024) · Windows · 15.74 µs → 21.64 µs (+37%)
  • 🟢 LargeImageScaleBenchmark.UpscaleOpaque(Size: 1024) · macOS · 786.12 µs → 508.86 µs (-35%)
  • 🔴 MatrixMapPointsBenchmark.MapRadius(Points: 4096) · Windows · 136.58 µs → 184.42 µs (+35%)
  • …and 71 more (see details below)
Full per-OS benchmark deltas

Linux

⏱️ Time (vs 🌙 nightly 4.151.0-nightly.120)

Benchmark baseline this PR Δ
ColorMathBenchmark.ToColor(Colors: 4096) 15.34 µs 11.68 µs 🟢 -24%
MatrixOpsBenchmark.Invert(Count: 4096) 127.32 µs 149.73 µs 🔴 +18%
MatrixOpsBenchmark.Concat(Count: 4096) 222.46 µs 253.58 µs 🔴 +14%
MatrixMapPointsBenchmark.MapPoint(Points: 256) 4.52 µs 5.13 µs 🔴 +14%
ColorMathBenchmark.PreMultiply(Colors: 4096) 12.87 µs 14.51 µs 🔴 +13%
MatrixMapPointsBenchmark.MapRect(Points: 256) 11.25 µs 12.67 µs 🔴 +13%
MatrixMapPointsBenchmark.MapRect(Points: 4096) 180.10 µs 202.32 µs 🔴 +12%
ColorMathBenchmark.UnPreMultiply(Colors: 4096) 11.45 µs 12.85 µs 🔴 +12%
MatrixMapPointsBenchmark.MapRadius(Points: 256) 13.60 µs 15.12 µs 🔴 +11%
MatrixMapPointsBenchmark.MapPoint(Points: 4096) 74.01 µs 81.80 µs 🔴 +11%
LargeImageScaleBenchmark.UpscaleOpaque(Size: 2048) 2.62 ms 2.85 ms 🔴 +9%
MatrixMapPointsBenchmark.MapVector(Points: 4096) 173.14 µs 188.30 µs 🔴 +9%
MatrixMapPointsBenchmark.MapPoints(Points: 256) 202.1 ns 218.6 ns 🔴 +8%
MatrixMapPointsBenchmark.MapVector(Points: 256) 10.95 µs 11.79 µs 🔴 +8%
LargeImageScaleBenchmark.UpscaleOpaque(Size: 1024) 684.28 µs 735.43 µs 🔴 +7%
LargeImageScaleBenchmark.UpscaleCrossfade(Size: 2048) 7.54 ms 8.08 ms 🔴 +7%
ColorMathBenchmark.ToColorF(Colors: 4096) 13.12 µs 14.05 µs 🔴 +7%
BitmapDrawBenchmark.DrawScaledTiles(Tiles: 64) 350.13 µs 374.77 µs 🔴 +7%
RuntimeEffectShaderBenchmark.DrawFrame 2.82 ms 3.01 ms 🔴 +7%
PathBoundsBenchmark.TightBounds(Points: 1024) 10.62 µs 11.31 µs 🔴 +6%
BitmapDrawBenchmark.DrawScaledTiles(Tiles: 256) 1.33 ms 1.41 ms 🔴 +6%
CanvasDrawBenchmark.Draw(Shapes: 64) 4.27 ms 4.53 ms 🔴 +6%
BitmapDrawBenchmark.DrawUnscaledTiles(Tiles: 64) 251.48 µs 265.63 µs 🔴 +6%
MatrixMapPointsBenchmark.MapRadius(Points: 4096) 229.76 µs 242.03 µs 🔴 +5%
MatrixMapPointsBenchmark.MapPoints(Points: 4096) 2.57 µs 2.70 µs 🔴 +5%
…and 2 more

Windows

⏱️ Time (vs 🌙 nightly 4.151.0-nightly.120)

Benchmark baseline this PR Δ
PathBoundsBenchmark.TightBounds(Points: 64) 1.53 µs 2.17 µs 🔴 +42%
MatrixMapPointsBenchmark.MapRadius(Points: 256) 8.64 µs 11.99 µs 🔴 +39%
PathBoundsBenchmark.TightBounds(Points: 1024) 15.74 µs 21.64 µs 🔴 +37%
MatrixMapPointsBenchmark.MapRadius(Points: 4096) 136.58 µs 184.42 µs 🔴 +35%
RasterImageLifecycleBenchmark.CreateDataWithReleaseProc(Count: 256) 98.44 µs 131.50 µs 🔴 +34%
RasterImageLifecycleBenchmark.CreateRasterImage(Count: 256) 248.18 µs 322.28 µs 🔴 +30%
MatrixOpsBenchmark.Concat(Count: 4096) 224.41 µs 288.36 µs 🔴 +28%
MatrixOpsBenchmark.Invert(Count: 4096) 130.90 µs 164.16 µs 🔴 +25%
MatrixMapPointsBenchmark.MapVector(Points: 256) 7.59 µs 9.47 µs 🔴 +25%
MatrixMapPointsBenchmark.MapVector(Points: 4096) 120.72 µs 150.29 µs 🔴 +25%
MatrixMapPointsBenchmark.MapRect(Points: 256) 7.40 µs 9.09 µs 🔴 +23%
MatrixMapPointsBenchmark.MapRect(Points: 4096) 122.12 µs 144.98 µs 🔴 +19%
MatrixMapPointsBenchmark.MapPoint(Points: 256) 4.75 µs 5.53 µs 🔴 +16%
LargeImageScaleBenchmark.UpscaleCrossfade(Size: 1024) 2.55 ms 2.92 ms 🔴 +14%
MatrixMapPointsBenchmark.MapPoint(Points: 4096) 77.40 µs 88.41 µs 🔴 +14%
BitmapDrawBenchmark.DrawScaledTiles(Tiles: 256) 1.27 ms 1.42 ms 🔴 +12%
BitmapDrawBenchmark.DrawUnscaledTiles(Tiles: 64) 246.32 µs 275.40 µs 🔴 +12%
RuntimeEffectShaderBenchmark.DrawFrame 1.15 ms 1.28 ms 🔴 +11%
LargeImageScaleBenchmark.UpscaleOpaque(Size: 1024) 742.13 µs 817.70 µs 🔴 +10%
ColorMathBenchmark.PreMultiply(Colors: 4096) 13.41 µs 14.77 µs 🔴 +10%
BitmapDrawBenchmark.DrawScaledTiles(Tiles: 64) 336.69 µs 366.90 µs 🔴 +9%
LargeImageScaleBenchmark.UpscaleCrossfade(Size: 2048) 10.32 ms 11.25 ms 🔴 +9%
MatrixMapPointsBenchmark.MapPoints(Points: 256) 168.5 ns 182.4 ns 🔴 +8%
BitmapDrawBenchmark.DrawUnscaledTiles(Tiles: 256) 927.99 µs 1.00 ms 🔴 +8%
ColorMathBenchmark.ToColorF(Colors: 4096) 13.36 µs 14.36 µs 🔴 +7%
…and 6 more

macOS

⏱️ Time (vs 🌙 nightly 4.151.0-nightly.120)

Benchmark baseline this PR Δ
LargeImageScaleBenchmark.UpscaleOpaque(Size: 1024) 786.12 µs 508.86 µs 🟢 -35%
LargeImageScaleBenchmark.UpscaleOpaque(Size: 2048) 2.94 ms 1.99 ms 🟢 -32%
LargeImageScaleBenchmark.UpscaleCrossfade(Size: 1024) 1.82 ms 1.25 ms 🟢 -31%
PathBoundsBenchmark.TightBounds(Points: 64) 841.9 ns 1.09 µs 🔴 +30%
BitmapDrawBenchmark.DrawScaledTiles(Tiles: 64) 207.54 µs 254.09 µs 🔴 +22%
MatrixOpsBenchmark.Invert(Count: 4096) 65.40 µs 54.33 µs 🟢 -17%
BitmapDrawBenchmark.DrawUnscaledTiles(Tiles: 64) 179.04 µs 208.02 µs 🔴 +16%
ColorMathBenchmark.ToColor(Colors: 4096) 18.86 µs 16.20 µs 🟢 -14%
LargeImageScaleBenchmark.UpscaleCrossfade(Size: 2048) 5.69 ms 4.89 ms 🟢 -14%
MatrixMapPointsBenchmark.MapPoint(Points: 256) 1.15 µs 1.30 µs 🔴 +14%
MatrixMapPointsBenchmark.MapPoints(Points: 256) 130.7 ns 147.6 ns 🔴 +13%
ColorParseBenchmark.Parse(Iterations: 1000) 64.13 µs 72.15 µs 🔴 +13%
BitmapDrawBenchmark.DrawUnscaledTiles(Tiles: 256) 598.63 µs 672.24 µs 🔴 +12%
PathBoundsBenchmark.TightBounds(Points: 1024) 8.18 µs 9.14 µs 🔴 +12%
CanvasDrawBenchmark.Draw(Shapes: 64) 1.41 ms 1.56 ms 🔴 +11%
MatrixMapPointsBenchmark.MapPoints(Points: 4096) 1.54 µs 1.66 µs 🔴 +8%
RuntimeEffectShaderBenchmark.DrawFrame 511.94 µs 546.92 µs 🔴 +7%
BitmapDrawBenchmark.DrawScaledTiles(Tiles: 256) 816.16 µs 768.12 µs 🟢 -6%

📈 Full interactive perf-dashboard & run details →

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📦 Artifact size report

Packages from this PR (build 1538421) vs the latest nightly baseline 4.151.0-nightly.120 (observed 2026-08-03).

Total .nupkg size: 569.2 MB → 568.9 MB (−368.8 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.0 MB 108.5 MB 🟢 −515.2 KB -0.5%
SkiaSharp.NativeAssets.WebAssembly 69.6 MB 69.6 MB 🔴 +68.8 KB +0.1%
HarfBuzzSharp.NativeAssets.Win32 20.7 MB 20.7 MB 🔴 +54.2 KB +0.3%

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

Per-file changes

HarfBuzzSharp.NativeAssets.Win32

File Size
runtimes/win-x86/native/libHarfBuzzSharp.pdb 21.9 MB → 22.2 MB (🔴 +272.0 KB)
runtimes/win-arm64/native/libHarfBuzzSharp.pdb 22.3 MB → 22.5 MB (🔴 +192.0 KB)

SkiaSharp.NativeAssets.WinUI

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

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

mattleibow and others added 21 commits August 4, 2026 18:13
)

Add a PR-time artifact-size check to the size-tracking workflow (#4505)

The benchmark workflow already runs on PRs, but artifact sizes were only
tracked nightly, so there was no PR-time signal when a change balloons a
package or a shipped native binary. This adds that guard without rebuilding
anything in Actions and without touching the Azure DevOps pipeline.

Rather than repacking in CI, it reacts to the AzDO job that already builds the
NuGets. The work is folded into the single "Track - Artifact Sizes" workflow via
a shared `resolve` job that picks the mode (mirroring the benchmark tracker's
resolve -> ... -> report shape):

- nightly (schedule / manual dispatch): the existing measure + persist +
  dashboard flow, unchanged.
- pr (the AzDO "Package NuGets" check run completing, or a manual dispatch with
  a build id): pull the exact build id out of the check-run event, download that
  build's `nuget` artifact (public, anonymous, dnceng-public/public), measure
  every .nupkg and every file inside, diff against the latest nightly baseline on
  the aw-data branch, and post/update a size-diff PR comment.

Informational only: the pr mode never fails a check or blocks the PR, and never
writes to aw-data.

Notable design points:

- `resolve` verifies check-run builds are real PRs (refs/pull/<n>/merge) before
  proceeding, so pushes to main/branches don't trigger a pointless ~1 GB download.
- The pr job carries its own `pull-requests: write`; nightly stays read-only.
- Package measurement (measure_nupkg) and render helpers are shared across modes;
  the comment sorts by |delta|, flags growth over 500 KB / 2% with a warning,
  applies a 50 KB noise floor, and filters OPC-metadata churn.
- persist-aw-data.yml is unaffected: the workflow name is unchanged, and pr-mode
  runs upload no `agent` artifact and are gated out by its head_branch == main check.

Files:

- .github/workflows/track-artifact-sizes.yml -- merged workflow (nightly + pr).
- scripts/infra/perf/sizes/measure_pr.py -- download + measure a build's `nuget`
  artifact; resolves the PR number from the build source branch (fork-safe).
- scripts/infra/perf/sizes/render_pr_md.py -- per-package + per-file size diff vs
  the nightly baseline, with native-binary labels.

Validation: the resolve -> download -> measure -> diff -> comment chain was
exercised end-to-end via workflow_dispatch against real build 1517880 (~1 GB
`nuget` artifact, 40 packages, 21 native), and the `resolve` mode selection was
unit-tested across schedule / dispatch / matching + non-matching check_run /
fork-without-PR-hint / non-PR branch build payloads, including live AzDO
source-branch verification. The `check_run` trigger only becomes active once this
file is on the default branch, so that event delivery is exercised organically
after merge (until then it can be driven manually with a build id).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9864b16f-0950-498e-8a61-12dceeb87107
…terization) (#4507)

The Blazor WASM integration tests could only run against net10.0, and would
fail against a .NET 11 preview because the pinned Microsoft.Playwright 1.49.0
bundles Chromium 131, which does not support the WebAssembly exception-handling
(exnref) feature the .NET 11 WASM runtime requires. On net11 the Blazor app
aborts during startup with:

    MONO_WASM: Assert failed: This browser/engine doesn't support WASM exception handling
    CompileError: WebAssembly.compileStreaming(): invalid value type 'exn', enable with --experimental-wasm-exnref

so the app never renders and the test times out — not a SkiaSharp defect, purely
a stale test browser.

Fixes:
- Bump Microsoft.Playwright 1.49.0 -> 1.55.0 (Chromium 140, which supports the
  required WASM exception handling).
- Parameterize the generated temp projects' target framework and SDK pin via
  -p:BaseFramework / -p:SdkVersion / -p:SdkAllowPrerelease (default net10.0, so
  existing behavior is unchanged). This lets the harness smoke-test the packages
  on a newer band, e.g.:
    dotnet test -p:BaseFramework=net11.0 -p:SdkVersion=11.0.100-preview.6 -p:SdkAllowPrerelease=true

Verified locally against SkiaSharp 4.151.0-rc.1.1 / HarfBuzzSharp 14.2.1-rc.1.1:
BlazorTests pass on both net10.0 and net11.0 (SKCanvasView + SKGLView).


Copilot-Session: 88526fb6-7129-4a2b-bbf6-47f32912d079

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Chrome Stable (and Extended stable) is m150 today, so the supported
stable line is 4.150 only. The 4.148 entry was stale drift left over
from a prior milestone. Removing it makes the release-notes support
grouping match the live Chrome channels; the security-audit
milestone-schedule drift-check now reports OK for both paths
(stable=[4.150], preview=[4.151]).


Copilot-Session: f3248de6-df37-46f5-8bb2-9778033f9450

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…4504)

* Add compact PR benchmark comment to the benchmark tracking workflow

On `pull_request` runs of Track - Benchmarks, post (and update) a single
informational comment summarising this PR's performance vs the baseline.

- New `scripts/infra/perf/benchmarks/render_pr_md.py` reads the same per-(OS,
  role) histories the `report` job already downloads, compares the source-built
  `pr` leg against the nightly baseline (falling back to released stables), and
  emits a COMPACT marker comment: a Highlights section with the biggest time and
  allocation movers, plus a collapsed <details> block of full per-OS deltas.
  Only benchmarks that move beyond the shared 5% noise threshold are surfaced.
  Time/byte formatters, trend dots and the history loader are imported from
  render_md.py and _common.py rather than duplicated.
- Wire it into the `report` job with a PR-only render step and a github-script
  find-update-or-create step keyed on `<!-- skiasharp-pr-benchmarks -->`, using
  the same marker-comment pattern as pr-artifacts-comment.yml. Adds
  `pull-requests: write`; the post step is best-effort (continue-on-error) so it
  never blocks the PR and tolerates read-only fork tokens.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 807194d1-0dc9-4eae-8c10-c23549d0398f

* Note that PR benchmark times are raw and noisy for microbenchmarks

The ⭐ PR and baseline legs run on separate CI runners, so raw wall-clock time
carries cross-runner variance and microbenchmarks can swing run-to-run. Add a
caveat to the comment (and a code note) making clear the times are raw
BenchmarkDotNet means, that small time deltas should be treated as noise, that
allocations are the deterministic/reliable signal, and that the interactive
perf-dashboard applies smoothing for the trend view. Raw numbers are kept as-is.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 807194d1-0dc9-4eae-8c10-c23549d0398f

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Latest: Merge pull request #182 from mono/automation/write-api-docs

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Use FreeType for Nano Server font support (#4280)

Context: #1105
Skia change: mono/skia#272 (merged as 5952f46)

Nano Server has no DirectWrite, so SkiaSharp's default font manager fell
back to an empty manager that could neither enumerate system fonts nor
create typefaces from caller-supplied data — text drawing silently did
nothing on Nano Server even when the app loaded a font itself. This also
blocked using a FreeType-backed build on Windows for the scenarios in
#1105 (e.g. the PDF performance issue #3906 and font-render
consistency between Linux and Windows).

Thread a `useFreeType` switch through the Windows native build and enable
it for the Nano Server variant, so the Nano `libSkiaSharp` is built with
FreeType and the "custom empty" font manager. Combined with the merged
Skia C-API change, typefaces loaded via `SKTypeface.FromStream/FromData/
FromFile` now render on Nano Server. Normal Windows builds are unaffected
(FreeType off, DirectWrite retained).

  * native/windows/build.cake: add a `useFreeType` argument/`USE_FREETYPE`
    env switch and map it to GN args — `skia_use_freetype`,
    `skia_enable_fontmgr_custom_empty`, and `skia_enable_fontmgr_win`
    (disabled when FreeType is on). `skia_use_system_freetype2` is pinned
    to `false` alongside the other `skia_use_system_*` flags so the
    bundled FreeType is always used.
  * native/nanoserver/build.cake: pass `useFreeType=true` for the Nano
    Server build.
  * externals/skia: bump to 5952f46 (merged mono/skia#272), which adds the
    `SK_BUILD_FOR_NANOSERVER` font-manager branch (custom-empty via
    FreeType) and scopes the Windows FreeType include-config build
    workaround to the freetype-android define branch.
  * cgmanifest.json: update the Skia component commit hash to match.

Verified on CI: the Native Windows "Nano Server x64" leg builds bundled
FreeType, links libSkiaSharp with the FreeType-backed manager, and passes
nano-api-scan (~46m compile, no issues).

Known limitation: the custom-empty manager has no system fonts, so the
default typeface still renders nothing on Nano Server — fonts must be
loaded explicitly. Redirecting the default manager at a fonts directory
(SkFontMgr_New_Custom_Directory) is a possible follow-up.

Co-authored-by: Jeff Skaistis <jskaistis@easypost.com>
Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Harden milestone audit and document WASM release testing (#4510)

Three related release-tooling improvements, all surfaced while publishing
SkiaSharp 4.151.0-rc.1.

Milestone audit: only assign work to releases that actually shipped
------------------------------------------------------------------
scripts/infra/milestones/audit-milestones.ps1 assigns merged PRs (and the
issues they close) to the milestone of the release they shipped in. Two gaps
let it assign work to releases that never shipped:

- An unshipped preview collected PRs it never released. 4.151.0-preview.3 had a
  branch and a milestone but was never tagged, yet the audit parked 21 PRs on it
  even though those commits actually shipped in rc.1. A range is now only
  assigned to a release that has a git tag; an untagged preview's commits roll
  forward to the next shipped release (preview.3 -> rc.1).
- Commits on main past the last release branch were parked in a speculative
  "stable" milestone before stable was cut. They are now left alone until the
  next release is actually cut and tagged.

The branch listing now logs the published tags and marks each branch shipped or
unshipped, so the roll-forward is visible before anything is applied.

Also fixes three latent bugs found while testing the audit:

- $errors was incremented on a failed update but never initialized, so the
  summary printed a blank count instead of a number.
- A single-branch match (e.g. -Version 4.150.1) collapsed $rawBranches to a
  scalar string, so $allBranches[0] returned the character 'o' from
  "origin/..." and produced a bogus "Milestone 'o' not found". Git results are
  now wrapped in @().
- The previous-release boundary used a descending string sort that ranked
  4.148.0-rc.1 above the bare stable 4.148.0, leaking that version's rc->stable
  commits into the next preview.1. It now prefers the bare stable when versions
  tie.

Verified by dry-run: 4.151.0 rolls preview.3's 21 PRs into rc.1 and skips the 5
trailing commits; 4.150.0 (fully shipped) is unchanged (0 to fix, 53 correct).

Wire the audit into release-publish
-----------------------------------
Added Step 8a to the release-publish skill: run the audit after tagging and
before closing the milestone, so every publish keeps milestone assignments clean
incrementally instead of drifting between releases.

Document WASM release testing
-----------------------------
Added a Test Properties table and a WASM (Blazor) workflow to the release-testing
skill, styled after the existing Android emulator workflow. The table documents
every -p: property the integration project accepts; the workflow shows how to run
BlazorTests on a non-default .NET band by changing BaseFramework + SdkVersion,
with the per-band wasm-tools and Playwright/Chromium exnref caveats. Uses netX.0
placeholders so it does not go stale.

Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…port (#4519)

Update DeviceRunners to 0.1.0-preview.12 for dotnet test --filter support (#4519)

Changes: mattleibow/DeviceRunners@0.1.0-preview.11...0.1.0-preview.12

The WASM (SkiaSharp.Tests.Wasm) and device (SkiaSharp.Tests.Devices) test
projects referenced DeviceRunners 0.1.0-preview.11, in which `dotnet test
--filter ...` is silently ignored on WASM. `--filter` support for device and
WASM runs only landed in preview.12 (mattleibow/DeviceRunners#143), so the
version we shipped predated the feature entirely.

The full filter chain is new in preview.12, which is why nothing we could wire
locally would have made it work on preview.11:

  * Testing.Targets now forwards $(VSTestTestCaseFilter) (from `dotnet test
    --filter`) to the DeviceRunners CLI.
  * The CLI passes it on — WASM as a `?device-runners-filter=<expr>` query
    param, Maui/Windows as a `--device-runners-filter <expr>` arg.
  * The Blazor runner's AddCliConfiguration(url) reads that query param and the
    Maui runner's AddCliConfiguration() reads the arg; both call
    SetTestCaseFilter(...). preview.11's reader only understood
    `device-runners-autorun`.

Bump all DeviceRunners packages in both projects preview.11 -> preview.12. No
test code changes are required: Blazor's UseVisualTestRunner auto-wires
AddCliConfiguration, and MauiProgram.cs already calls it — the bump alone
enables the filter. preview.12 was verified to restore cleanly and stay
API-compatible; a full end-to-end `--filter` run was not executed.

Note: NuGet.config restricts restore to the dnceng dotnet-public mirror, into
which preview.12 was only partially upstreamed. These IDs had to be mirrored at
0.1.0-preview.12 before CI restore would succeed:

  * DeviceRunners.VisualRunners.Blazor
  * DeviceRunners.VisualRunners.Xunit3
  * DeviceRunners.UITesting.Xunit3

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…ount (#4512)

Build the internal name->Variable dictionary with an explicit capacity
(names.Length) instead of the default (zero) capacity. The constructor
already knows the exact final entry count up front, so the default-sized
map needlessly rehashes several times while it is filled, allocating
throwaway bucket/entry arrays on a per-uniform-set (potentially per-frame)
path.

Behaviour is identical: dictionary capacity is a pure sizing hint.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…d path (#4498)

Encode text directly into a byte[byteCount + nullBytes] buffer instead of
concatenating a '\0' onto the source string before encoding. The trailing
zero bytes match the encoding of a '\0' exactly (UTF-8=1, UTF-16=2,
UTF-32=4 bytes), so output is byte-identical while removing one managed
string allocation and copy per call on the native-marshalling hot path.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…r failure path (#4455)

* Fix CoTaskMem pixel-buffer leak when SKImage.Create raster fails

SKImage.Create(SKImageInfo) allocates an unmanaged pixel buffer with
Marshal.AllocCoTaskMem and relies on the native raster image's release
proc to free it. When sk_image_new_raster fails (e.g. a valid-BytesSize
info Skia rejects), no image is created, the release proc never runs, and
the buffer leaks. Free it explicitly on the failure path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Skip pixel-buffer leak test on platforms without Process memory API

The FailedRasterCreateDoesNotLeakPixelBuffer regression test measures the
leak via System.Diagnostics.Process.PrivateMemorySize64. That API is not
supported on WASM (no System.Diagnostics.Process at all), iOS, or Mac
Catalyst, so the test threw PlatformNotSupportedException and failed the
Tests WASM (Linux) and Tests iOS (macOS) legs (and Mac Catalyst).

Guard the test with SkipOnPlatform(IsBrowser || IsIOS || IsMacCatalyst),
matching the existing platform-skip pattern in this suite. The SKImage.Create
fix itself is platform-agnostic and remains exercised by the desktop and
Android test legs, where PrivateMemorySize64 is available.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4649389c-a7bd-443d-8c26-7c6b16cda819

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot-Session: 4649389c-a7bd-443d-8c26-7c6b16cda819
…erver (#4515)

Add containerized test legs: Azure Linux, Alpine/musl, Nano Server (#4515)

Run the SkiaSharp console test suite inside Docker containers as gating CI
legs so the bindings are verified on runtime environments the existing
matrix never covered: glibc Linux without a desktop, musl (Alpine), the
same two with no font/dependency packages installed, and Windows Nano
Server. These environments regularly surface real behavioral differences
(font managers, allocator behavior, missing system libraries) that the
desktop agents hide, and until now nothing exercised them.

Five gating legs are added, each reusing the bootstrapper's existing
`docker:` mechanism (compliant YAML tasks, not an ad-hoc script) rather
than a new runner:

  * Linux (Azure Linux)                    — glibc, fontconfig + fonts
  * Linux (Azure Linux, No Dependencies)   — glibc, nothing installed
  * Linux (Alpine)                         — musl, fontconfig + fonts
  * Linux (Alpine, No Dependencies)        — musl, nothing installed
  * Windows (Nano Server)                  — the FreeType-backed nano build

~~ Native selection without rebuilding ~~

The test build selects a prebuilt native library by platform via a new
`SkiaSharpNativePlatform` MSBuild property, copying from
output/native/<platform>/<arch> instead of the OS-derived globs. This lets
a Linux host test the Alpine, Nano Server, etc. libraries produced by the
native pipeline without rebuilding them per leg. The arch condition uses
$(Platform) when explicitly set (not AnyCPU) and otherwise the detected
$(BuildArch), so AnyCPU test runs still resolve the right binary.

~~ Fontless and No-Dependency environments ~~

The No-Dependency images install nothing on top of the base .NET SDK image;
that is the point of the leg — prove the native library loads and runs on a
bare base (it does: libstdc++/libgcc/ICU already ship in the base images).
Because those environments have no system font manager, font-dependent
tests self-skip at runtime via a probe (SKFontManager.Default cannot match a
character) instead of a hardcoded platform filter, so the same assembly does
the right thing on every leg. The font-equipped images install fontconfig +
DejaVu, and — since Azure Linux ships no emoji font in any of its repos — the
Azure Linux image downloads Noto Emoji pinned by commit and verified by
SHA-256 into a fonts directory so the Unicode/emoji tests have coverage.

~~ Windows Nano Server ~~

Nano Server uses the FreeType-backed native build (no system fonts, but it
can load typefaces from streams/files). Getting it green required:

  * detecting Nano at runtime from the OS (registry InstallationType), not by
    probing a feature, so behavior is correct regardless of what is installed;
  * splitting the XPS document tests, which require the Windows XPS Object
    Model / DirectWrite that Nano lacks (SupportsXps => Windows && !Nano);
  * skipping sn.exe strong-name re-sign and mdoc.exe doc generation in the
    Nano container build, which are unavailable there;
  * a Nano-specific raster golden for the text render.

~~ Skip the heavy bitmap-threading stress variant on musl ~~

Adding the first musl legs surfaced a latent hang, not a new regression:
SKBitmapThreadingTest.ImageScalingMultipleThreadsTest(100 threads, 1000
iterations) deliberately creates ~100K undisposed native allocations to
stress GC-finalizer throughput (#3608). It already self-skips on x86, where
"the GC can't keep up and Skia's native allocator fails"; on x64 musl the
same pressure stalls the allocator/finalizer and the run hangs until
hangdump kills the leg (exit 7). Reproduced deterministically by running the
managed tests against the real CI x64 musl native library under emulation.
The existing self-skip is extended to musl (glibc keeps up and still runs
it); the lighter (10,10)/(10,100) variants keep running there, so the #3608
regression guard is retained. Adds IsGlibc/IsMusl test helpers (musl == Linux
and not glibc, via the gnu_get_libc_version probe already used for fonts).

~~ Job display names ~~

Test-job names now lead with the platform, with brackets only for genuine
variations: Windows (.NET Framework)/(.NET Core)/(Nano Server), macOS,
Linux / Linux (Azure Linux[, No Dependencies]) / Linux (Alpine[, No
Dependencies]), Android, iOS, Mac Catalyst, WASM / WASM (.NET Preview).

Design and per-leg font strategy are documented in
documentation/dev/containerized-testing.md.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
[build] Convert all .sln solutions to .slnx (#4528)

Moves every solution in the repo off the legacy `.sln` format onto the
modern XML-based `.slnx` format, matching the convention the Gallery
samples already use (they were authored natively as `.slnx`). This keeps
the solution files diff-friendly and consistent across the tree.

Ran `dotnet sln <file> migrate` on all 30 `.sln` files and removed the
originals. Migration preserved solution configurations, including the
native C++ solutions' custom x64 / Win32 / ARM64 platforms.

The 19 `.slnf` solution filters were kept rather than rewritten, and each
one's `solution.path` was repointed from `X.sln` to `X.slnx`. This is the
lowest-risk option and preserves the existing filter semantics exactly;
the current SDK (10.0.301) loads a `.slnf` whose solution path is a
`.slnx`, so no filtering behavior changes.

Updated the 14 files that name a specific solution: the native
`build.cake` scripts, `scripts/infra/caching/repo-deps.json`, both
`.devcontainer` init scripts, `.vscode/settings.json`, the
`documentation/dev` guides, and the affected agent skills. Left alone:
`samples.cake` already discovers and rewrites `.sln`/`.slnf`/`.slnx`
generically; `libs.cake`/`nuget.cake` reference the `.slnf` filters by
their unchanged filenames; and the residual `.sln` mentions in
`.gitattributes` (eol rule) and `.gitignore` (`*.sln.docstates`) are
harmless. New `.slnx` files use LF via the existing `* text=auto` rule.

Verified: all 34 `.slnx` parse via `dotnet sln list`, all 19 `.slnf` are
valid JSON pointing to existing targets, and
`dotnet build utils/Utils.slnx -t:Restore` succeeds. The two native C++
solutions build only on Windows CI (MSBuild 17.14+ supports `.slnx`), so
they could not be built locally on macOS.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… + Linux (#4527)

Add SkiaSharp.Vulkan.Silk.NET package and run Vulkan tests on Android + Linux (#4527)

SkiaSharp's only managed Vulkan helper was SkiaSharp.Vulkan.SharpVk, pinned to
SharpVk 0.4.2 (a 2018 release). SharpVk is effectively unmaintained and its
native loader throws NotSupportedException on Android -- it only handles Windows
and Linux -- so the Vulkan tests have always been desktop/Windows-only. With the
Graphite GPU backend (#3968) bringing Vulkan to mobile, we need a managed Vulkan
binding that actually works on Android.

~~ New package: SkiaSharp.Vulkan.Silk.NET ~~

Adds a convenience package (namespace SkiaSharp, netstandard2.0 -> net10.0) built
on the actively maintained, cross-platform Silk.NET.Vulkan, mirroring the shape
of the existing SharpVk package. GRSilkNetBackendContext maps Silk.NET's
Instance/PhysicalDevice/Device/Queue (nint handles) plus a pinned
PhysicalDeviceFeatures onto SkiaSharp's raw-IntPtr GRVkBackendContext and bridges
the GetProcedureAddress delegate Skia uses to resolve Vulkan entry points.
GRVkExtensionsSilkNetExtensions.Initialize(...) is a typed extension (plus an
overload taking instance/device extension arrays). SkiaSharp.Vulkan.SharpVk is
intentionally left untouched; nothing is removed.

~~ Core API: public GRVkExtensions constructor ~~

The parameterless GRVkExtensions() constructor is changed from private to public,
mirroring native skgpu::VulkanExtensions (default-construct, then init) and
SkiaSharp's own allocating SKPaint/SKPath constructors. This lets the Silk.NET
helper expose a plain Initialize(...) extension instead of a bespoke Create(...)
factory. GRVkExtensions.Create(...) is unchanged. This is the only change to a
shipping package and is purely additive (a widened accessibility), so it is
ABI/API-safe.

~~ Test restructuring: keep unsigned Vulkan deps off Apple + net48 ~~

The Vulkan tests bring Vulkan up through a headless Silk.NET context
(SilkVkContext) that uses the OS-provided loader, so they run anywhere a Vulkan
ICD is present. An earlier iteration folded the unsigned Silk.NET.Vulkan/SharpVk
dependencies into the shared, strong-named SkiaSharp.Tests base library, which
broke three CI legs:

  * iOS / Mac Catalyst -- the Apple AOT linker must resolve every native symbol
    at link time; there is no libvulkan on Apple, so the link failed with
    "clang++ ... Undefined symbols: _vkBindImageMemory" (and friends).
  * Windows .NET Framework (net48) -- the full framework enforces strong-name
    references at load time, so the strong-named host could not load the unsigned
    Silk.NET.Vulkan and threw FileLoadException "A strongly-named assembly is
    required" (0x80131044). .NET Core does not enforce this, which is why the
    net10 build was clean and the failure only surfaced at runtime on net48.

To keep those dependencies out of the base library and out of every strong-named
/ Apple-linked assembly, the Vulkan tests now live in two dedicated, non-strong-
named projects:

  * tests/SkiaSharp.Vulkan.Tests.Console -- a standalone Microsoft.Testing.Platform
    executable (net10.0 + net48, SignAssembly=false) that references the signed
    SkiaSharp.Tests.Console for shared infrastructure (unsigned -> signed is
    allowed) plus Silk.NET/SharpVk. This is the desktop runner (Linux/Lavapipe and
    Windows).
  * tests/SkiaSharp.Vulkan.Tests -- a library targeting only net10.0-android /
    -windows, referenced by the MAUI Devices host and registered in MauiProgram,
    so Vulkan never reaches the iOS/Mac Catalyst heads.

Supporting changes so the separate assemblies integrate with the shared visual-
regression harness:

  * CatalogReflection discovers renderers across the catalog, entry, and all
    loaded SkiaSharp.* assemblies, so the ganesh-vulkan renderer (in the separate
    Vulkan library) joins the shared VisualMatrixTests inside the single-process
    Devices host. This also preps the identical pattern for a future Direct3D test
    library: reference the library + register its assembly.
  * VisualMatrixTests matches only its own assembly's renderers
    (NamesIn(own assembly) instead of AllNames), so the base matrix and each
    satellite never double-run a cell when both are loaded in one process.
  * GPU rendering is serialized with an xUnit [Collection(DisableParallelization =
    true)] instead of the previous process-wide GpuRenderGate static lock. xUnit
    collections are scoped to a single assembly, so each assembly carries its own
    definition -- GpuRenderingCollection (base, GL/Metal) and
    VulkanGpuRenderingCollection (Vulkan). No cross-assembly lock is needed because
    the base and Vulkan tests never run concurrently: the Console hosts run them as
    separate processes, and DeviceRunners executes registered assemblies
    sequentially.
  * tests/Dockerfile.linux installs libvulkan1 + mesa-vulkan-drivers (Mesa
    Lavapipe software ICD) so the Vulkan GPU tests render headlessly in the
    container.

~~ Validation ~~

  * Linux (repo test container + Mesa Lavapipe): Silk.NET Vulkan tests render and
    pass; the visual matrix is green against the seeded ganesh-vulkan.linux
    goldens.
  * Android (API 36 arm64 emulator, ranchu ICD): Vulkan comes up through
    libvulkan.so and all five ganesh-vulkan.android cells pass against seeded
    goldens -- the platform SharpVk could never reach -- with each renderer run
    exactly once (no double-run).
  * iOS / Mac Catalyst: the Devices heads build cleanly again; the earlier
    "Undefined symbols: _vk*" link failures are gone.
  * CI: green on the xamarin "SkiaSharp (Public)" pipeline; the previously-red
    iOS, Mac Catalyst, and Windows .NET Framework legs all report 0 failed tests.

Windows Vulkan CI enablement (a software SwiftShader ICD + ganesh-vulkan.windows
goldens) is delivered by the stacked follow-up #4531, which lands on top of this
PR. Follow-up: the new public types and the now-public GRVkExtensions constructor
still need generated mdoc XML via the normal docs flow.

Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Modernize the pull request template

Replace the old plain-markdown PR template with a structured, graphics-library-
focused one and require its use when opening PRs.

Redesign informed by a survey of well-run graphics/game engines and .NET
interop libraries (Bevy, Godot, three.js, MonoGame, Avalonia, WPF/WinForms,
Flutter, ImageSharp, Silk.NET, mono/skia). Key changes:

- Real `##` headings instead of bold pseudo-headings, matching the modern
  YAML issue forms.
- New "Areas Affected" checkboxes to route reviewers (managed vs native/C API
  vs generated bindings vs native-dep/Skia update vs views vs tests).
- "API Changes" now uses fenced ```csharp blocks (Added / Obsoleted) so
  multi-line signatures are readable; keeps the strict-ABI, additive-only rule.
- "Testing" upgraded from a lone checkbox to a narrative prompt covering the
  CPU/GPU/platform matrix and golden-image updates.
- Collapsible before/after screenshots section for rendering changes.
- Native path reminders: companion mono/skia PR, submodule staging, and
  `pwsh ./utils/generate.ps1` regeneration.
- Reference docs live in mono/SkiaSharp-API-docs, so the checklist asks
  contributors to file a docs issue there rather than add XML comments here.
- Dropped stale/unverifiable items ("rebased on main", "adhere to coding
  standard") that CI/merge-queue already handle.

AGENTS.md now instructs contributors to populate every section of the template
when opening a PR.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d1306b3c-b91d-4cf5-88f3-366c864bb414

* Use H3 headings in PR template

H2 renders heavy for an ~8-section form. Switch section headings to H3 (###),
matching the dotnet/maui sibling template while keeping real headings for
anchors/outline.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d1306b3c-b91d-4cf5-88f3-366c864bb414

* Group PR template into fewer headings with bold sub-sections

Collapse the eight H3 sections into five, nesting the context fields (Fixes,
Areas affected, Required skia PR) as bold sub-headings under Description — the
same pattern API changes already uses for Added/Obsoleted. Lighter visual
weight, same information.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d1306b3c-b91d-4cf5-88f3-366c864bb414

* Merge changes into one H2 section; use H2 for underlined section rules

Fold API and behavioral changes under a single "Changes" heading, leaving four
top-level sections (Description, Changes, Testing, Checklist). Promote those to
H2 so GitHub renders the underline rule between sections; bold sub-headings
carry the detail so the H2s read as dividers, not clutter.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d1306b3c-b91d-4cf5-88f3-366c864bb414

* Rename Fixes sub-heading to Related issues

Avoids the repetitive "Fixes" heading directly above "Fixes #".

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d1306b3c-b91d-4cf5-88f3-366c864bb414

* Make PR template lazy-friendly with copy-paste sub-templates

Default the body to a short, good-enough state: "Changes" defaults to "None."
and the before/after screenshot table is no longer always-on. The full formats
(Added/Obsoleted/Behavioral fences, the screenshot <details> block) now live as
copy-paste blocks inside each section's HTML comment — inert until an author
drops one in over the default. Optimizes for the common case (a short
description) while keeping rich structure one paste away.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d1306b3c-b91d-4cf5-88f3-366c864bb414

* Reorder Description sub-sections to issue, skia PR, areas

Put the companion "Required skia PR" right after the issue link, before the
"Areas affected" checkboxes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d1306b3c-b91d-4cf5-88f3-366c864bb414

* Reframe Changes as public API + behavior, discourage file lists

Authors were dumping a file-by-file changelog into "Changes". Rescope the
section to just the public API surface (a single copy-paste code fence) and
observable behavior, and expand the comment to say explicitly that it is NOT a
file list — the diff already shows files and the narrative belongs in
Description. Merges the old Added/Obsoleted blocks into one Public API fence.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d1306b3c-b91d-4cf5-88f3-366c864bb414

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d1306b3c-b91d-4cf5-88f3-366c864bb414
Keep the Skia gitlink and Component Governance registration aligned with mono/skia's skiasharp branch, including manifest-only drift repairs.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 47cd9a3e-b097-43f3-b41f-7188e91b388a
Latest: Merge pull request #293 from mono/mattleibow-patch-1
Commit: ea1f6a46c97545be81d028abe7f8f6e2f0b79854

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
mattleibow and others added 20 commits August 4, 2026 18:14
…#4531)

Execute Vulkan visual tests on the Windows CI legs via a software ICD (#4531)

Context: #4527

Follow-up to #4527, which added the Silk.NET Vulkan test path and enabled the
ganesh-vulkan visual cells on Linux (Mesa lavapipe) and Android (emulator
SwiftShader). Windows already compiled the tests and built libSkiaSharp with
Vulkan, but the headless CI agent ships no Vulkan driver, so every ganesh-vulkan
cell skipped — the backend was built and never exercised.

Provision a software Vulkan ICD on the Windows test agent so those cells render
and golden-compare instead of skipping, mirroring what the Linux leg does with
lavapipe. A pwsh preBuildStep on both Windows test legs (.NET Core and .NET
Framework) runs scripts/infra/native/windows/install-vulkan-icd.ps1, which
downloads and SHA-256-verifies the Khronos loader (vulkan-1.dll) and Google
SwiftShader (vk_swiftshader.dll + vk_swiftshader_icd.json) from pinned, prebuilt
Silk.NET native packages, extracts them under externals/vulkan-icd, registers the
ICD, and puts the loader on PATH.

Register the ICD in HKLM\SOFTWARE\Khronos\Vulkan\Drivers rather than via
VK_ICD_FILENAMES / VK_DRIVER_FILES: the agent runs elevated, and the loader
ignores those environment variables under elevation, reading the driver list only
from the registry. The script fails the leg on any download, checksum, or registry
error instead of silently degrading back to a skip — provisioning can only add
coverage, never hide a regression.

Seed five ganesh-vulkan.windows goldens harvested from the agent's own SwiftShader
renders; cross-implementation pixel differences mean Windows goldens must come from
the Windows ICD, not from Linux/Android. A future driver-driven mismatch reseeds
via scripts/infra/tests/extract-visual-goldens.py, the same flow as the other legs.

CI-only change: no product, binding, or test-project code is touched (+88/-0), so
apps running against real Vulkan drivers are unaffected. Verified on CI — the x64
Windows .NET Core and .NET Framework legs now run SkiaSharp.Vulkan.Tests.Console
with 15 passed / 0 skipped, including all five VulkanVisualTests.RenderMatchesGolden
("ganesh-vulkan") cells, and golden extraction reports 0 mismatch / 0 unseeded. The
32-bit netfx (x86) slice still skips Vulkan (NotExecuted, never failed) because the
provisioned SwiftShader ICD is 64-bit only. Linux and Android provisioning is
unchanged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add a manual workflow input for the SkiaSharp target branch and carry it through checkout, concurrency, automation branch naming, and PR creation. Scheduled syncs continue to target main.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dbf36b1f-f08e-411f-b059-62d111fe63dc
[skia-sync] Sync Skia with chrome/m151 bug fixes (#4538)

Context: mono/skia#294
Changes: mono/skia@ea1f6a4...1af4051

Advance the Skia submodule to the merged same-milestone `chrome/m151`
bug-fix sync.

This brings in upstream commit `b4204f7ff9`, which prevents Ganesh readbacks
from returning stale texture data when pending render tasks fail to flush.
Update `cgmanifest.json` to record the new mono/skia and upstream commits.

This is not a milestone or version bump. It includes no C API, binding, DEPS,
or ABI changes.

Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…4545)

Add backend-neutral async RequestReadPixels to SKImage and SKSurface (#4545)

Requires: mono/skia#295
Related: #3968 (Graphite backend)
Docs: mono/SkiaSharp-API-docs#184

GPU-backed surfaces need a way to read pixels back to the CPU, and on some
backends (notably Graphite) the asynchronous rescale-and-read is the *only*
readback path — Skia gates synchronous GPU ReadPixels behind GPU_TEST_UTILS.
The in-flight Graphite PR (#3968) had added this under Graphite-prefixed names,
but the underlying C++ types (SkImage::AsyncReadResult, SkImage::RescaleGamma,
SkImage::RescaleMode) are core and shared by the raster, Ganesh and Graphite
backends — SkSurface merely aliases the SkImage ones. So this lands the feature
generically on main; #3968 can then drop its SKGraphite* copies and consume it.

Add RequestReadPixels overloads on SKImage and SKSurface that deliver the pixels
to an Action<SKImageReadPixelsResult>. Raster reads invoke the callback
synchronously; Ganesh defers until the work is submitted and drained via the new
GRContext.CheckAsyncWorkCompletion(). The result is a non-owning, callback-scoped
view (deliberately not an SKObject — the native pointer is non-ref-counted and
reused); it is invalidated when the callback returns and throws
ObjectDisposedException afterwards. Failure is signalled by a null result, and
there is no state argument (capture it in the closure).

  * SKImageReadPixelsResult: PlaneCount, GetPlaneRowBytes and a raw zero-copy
    GetPlaneData(ReadOnlySpan<byte>), plus copy-out helpers that outlive the
    callback — CopyPlaneTo (tightly packed, bounded), ToArray, ToImage, ToBitmap.
  * SKImageRescaleGamma { Src, Linear } and SKImageRescaleMode { Nearest, Linear,
    RepeatedLinear, RepeatedCubic }. The convenience overload defaults to
    (Src, Nearest) — the least-transform choice, matching what Skia itself forces
    when no rescale is required (SurfaceContext normalises to kSrc/kNearest at 1:1).
  * Bumps externals/skia to the C API (mono/skia#295) and regenerates the
    P/Invoke bindings; the only hand-written generated piece is the fire-once
    DelegateProxies async-read proxy.

Scope is the RGBA (count()==1) read; YUV420/YUVA420 are a clean additive
follow-up, as the result type and accessors are already multi-plane.

Tested by 18 SKAsyncReadPixelsTest cases: per-pixel pattern correctness read from
an offset srcRect, a deterministic padding-strip unit test, destination-size and
invalid-plane-index edge cases, use-after-callback throwing cleanly, and Ganesh
deferred completion (asserting the false -> pump -> true transition where the
backend supports it).

Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add Graphite GPU backend bindings for Vulkan, Metal, and Dawn (#3968)

Context: mono/skia#236
Context: #4545
Tracking: #4555

Expose Skia's next-generation Graphite GPU backend through additive
SkiaSharp APIs. The new surface follows Graphite's explicit
record, snap, insert, and submit model while leaving the existing
Ganesh APIs and behavior unchanged.

Add managed wrappers for contexts, recorders, recordings, backend
textures, texture information, and Vulkan, Metal, and Dawn backend
contexts. Support wrapping caller-owned textures with release callbacks
and provide SKGraphiteImageCache as the default image-provider strategy
for uploading and reusing raster images.

Enable Graphite in the native builds for supported desktop and mobile
platforms. WebAssembly uses Dawn's emdawnwebgpu port so Graphite can
render through browser WebGPU. Readback uses the existing
backend-neutral asynchronous API because shipping Graphite surfaces do
not support synchronous ReadPixels.

Cover the new surface with API tests, backend-context and release
callback tests, and a cross-backend visual regression matrix with
platform-specific goldens. The iOS simulator's Graphite/Metal gradient
limitation remains isolated to that environment and is tracked
separately.

Co-authored-by: Ramez Ragaa <ramezragaa@proton.me>
Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…4550)

On span-capable TFMs, write the native buffer straight to the target Stream as
a ReadOnlySpan<byte> instead of copying it through a pooled managed buffer with
Marshal.Copy. This removes one full memcpy per 80 KB chunk (and the intermediate
buffer), making SaveTo ~2x faster with no allocation change. netstandard2.0 and
.NET Framework keep the original buffered path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
[skia-sync] Update m151 with upstream stability fixes (#4568)

Changes: mono/skia#301
Changes: google/skia@b4204f7...755b89e

Advance the Skia submodule and Component Governance metadata to include
four m151 font-rendering and Ganesh stability fixes.

This is a same-milestone bug-fix sync, so ABI, assembly, soname and NuGet
versions remain unchanged. Regeneration added only typedef aliases to the
Resources, SceneGraph and Skottie bindings; no P/Invoke entries or public
SkiaSharp APIs changed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
[Windows] Initialize the MSVC environment once per native build (#4570)

Context: #4570
Skia change: mono/skia#306

Update the Skia submodule to remove per-edge vcvarsall invocation, then
initialize the architecture-specific Visual C++ environment once before
launching Ninja. Split GN generation from Ninja execution so Windows can
capture the required MSVC and Windows SDK environment without changing
other native build paths.

Running vcvarsall for every compile, archive, and link edge added
substantial process overhead. Clean hosted-agent builds for x86, x64,
and ARM64 completed successfully with one initialization per job and
reduced native build times by 54–57%.

Co-authored-by: Matt Mitchell (.NET) <mmitche@microsoft.com>
Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Co-authored-by: Alexander Köplinger <alex.koeplinger@outlook.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
[CI] Move public builds to dnceng agents (#4565)

Context: #4565

Move eligible Linux and managed Windows jobs from Microsoft-hosted
agents to the dnceng NetCore-Public pool. Keep workloads requiring
capabilities absent from the lean images on approved hosted agents:
Android emulator tests need KVM, native Windows builds need the complete
VS 2022 toolchain with Spectre libraries, and native macOS uses macOS 15.

The hosted images previously supplied several undeclared prerequisites.
Provision Python, Node 20, Android platform and build tools, and Chrome
where required; pass AndroidSdkDirectory explicitly to MSBuild; and
reuse existing Ninja and 7-Zip installations before installing
fallbacks.

Pin direct Chrome and 7-Zip downloads and verify their SHA-256 digests
before installation. Also validate Chrome package identity, version,
and architecture so compromised or unexpectedly changed artifacts fail
before execution.

Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Co-authored-by: Matt Mitchell <mmitche@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
[Windows] Stop pinning a Windows SDK version by default (#4588)

Context: #4588
Context: 7c23c53
Follow-up: #4589

The benchmark-source (windows-latest, Windows, native/windows) leg of
Track - Benchmarks died the instant it began the native build:

    Done. Made 114 targets from 63 files in 358ms
    Initializing the Visual C++ environment once for amd64.
    An error occurred when executing task 'libSkiaSharp'.
    Error: Process 'cmd.exe' failed with error: 1

RunNinjaWithVcVars invoked vcvarsall.bat with the default of the
windowsSdkVersion argument, 10.0.22621.0, which matches the AzDO agents.
The hosted runner is windows-2025-vs2026 and ships only Windows SDK
10.0.26100.0, and vcvarsall hard-fails on an SDK that is not installed:

    [ERROR:winsdk.bat] Windows SDK 10.0.22621.0 :
    'C:\Program Files (x86)\Windows Kits\10\include\10.0.22621.0\um'
    not found or was incomplete

Nothing needed that default. Before the MSVC environment was initialized
explicitly there was no such argument at all: gn resolved win_sdk_version
itself and the toolchain interpolated that resolved value into the
vcvarsall command it generated, so the version was always one that
existed on the machine and the two could never disagree. Hardcoding a
constant only meant the build broke wherever that exact SDK was absent -
this runner today, and equally a developer box carrying just a newer one.

Default the argument to empty and omit it from both gn and vcvarsall when
unset, so each detects an SDK that is present. Setting it still pins both
together, so --windowsSdkVersion remains available to build against a
specific SDK.

  * drop the `>nul` that hid the failure. It suppressed the vcvarsall
    banner, but `>nul` redirects only stdout and VsDevCmd writes its
    errors there too, so it discarded the diagnosis along with the
    banner - rerunning with `2>nul` still prints the [ERROR:winsdk.bat]
    lines, while `>nul` yields nothing but the exit code. The banner is
    five lines per architecture and worth keeping in the log.
  * this went unnoticed because benchmark-source runs on pull requests
    only and is skipped on main, so #4570 merged green without the path
    ever being exercised.
  * whether the vcvarsall call is needed at all is left open in #4589:
    every tool is invoked by absolute path and gn emits the SDK include
    and lib directories as explicit -imsvc and /LIBPATH: flags, so it may
    be redundant for our LLVM-based configuration.

Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ecar (#4603)

Skia made Vulkan 1.1 the Ganesh minimum in m139 (0a3150b, "[vulkan] Require
Vulkan 1.1 in Ganesh") and promoted the minimum-version check from a debug check
to a fatal SK_ABORT in m140 (dfea59a). The consequence is severe and was
undocumented on our side: code that previously got a graceful null back from
GRContext.CreateVulkan on a Vulkan 1.0 instance or device -- and could fall back
to raster -- now aborts the process. It is not an exception, so managed code can
neither catch nor pre-empt it.

Callers are exposed if the VkInstance was created with VkApplicationInfo
.apiVersion below VK_API_VERSION_1_1 (including leaving it at 0, which the Vulkan
spec treats as 1.0), or if GRVkBackendContext.MaxAPIVersion is left at its
default 0. That default makes Skia fall back to vkEnumerateInstanceVersion -- the
loader's version, not the version the instance was actually created with -- so it
can over-declare and ask for entry points the instance cannot serve. The fix is
both halves: declare at least 1.1 on the instance AND set MaxAPIVersion to match.

4.148.0 is the right page, not 4.151.0. Determined from the externals/skia SHA
each release tag actually pins, and whether the two upstream commits are in its
history:

  v3.119.4              7dbfc07d  diverged from both (VulkanUtilsPriv.cpp absent)
  v4.147.0-preview.1.1  6f8139ad  ahead of both (SK_ABORT present)
  v4.148.0              1a155bae  ahead of both (SK_ABORT present)

So the abort first ships in a 4.147.0 preview -- but 4.147.0 has only
-preview.{1,2,3} tags and no v4.147.0, matching its release data (status:
preview, superseded_by 4.148.0, "never released as stable"). v4.148.0 is the
first stable tag, so it is where someone upgrading from 3.119.x meets this. It is
also already the home for this class of change: the sidecar scopes itself to the
v4 breaking changes from the m132/m147 upgrade that no signature diff can show,
and already documents the GRVkBackendContextNative field removals. The new entry
leads that Vulkan group and cross-references it -- that entry covers fields that
disappeared (including fMinAPIVersion), this one covers the surviving
fMaxAPIVersion whose default is now unsafe.

Sidecar only, per spec section 3.7: this file is maintainer-owned and never
machine-written, and editing it flips its sha256 in data.json on the next Prepare
run, which drops the stale prose.json and re-polishes exactly this page. So the
rendered 4.148.0.md, its prose.json and the data.json hash are deliberately left
to CI rather than hand-written here.

Related to #4600

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c512dee0-fb90-4ff0-9916-57567ecdca6a
[tests] Make GPU tests required; skip only when declared (#4575)

Context: #4575
Context: documentation/dev/gpu-test-policy.md

GPU tests brought a backend up inside a try/catch and called Assert.Skip on any
exception. That made "this agent has no GPU" and "this binding is broken"
indistinguishable, and reported both as green. The committed goldens recorded
what it cost: ganesh-gl had a golden for macOS only, and graphite-dawn had none
at all, so those backends had been skipping everywhere for as long as they had
existed. Nobody could answer which backends actually ran.

Invert the default. A backend is now required on every platform listed in
GpuPolicy.RequiredOn, which mirrors the gn args in native/*/build.cake, and a
skip has to be declared. Failing to bring a required backend up is a test
failure, so RendererUnavailableException is deleted along with every catch that
swallowed it.

Skips come from exactly two places, kept separate because that distinction is
what a reader needs when a test skips:

  * the compiled-in table is a statement about a platform — Metal is not
    required off Apple because we do not build it there
  * SKIASHARP_TEST_SKIP_GPU is a statement about one agent, declared per leg in
    scripts/azure-templates-stages-test.yml with its reason and tracking issue
    inline, so it can be removed when that agent gains the capability without
    touching the policy or the documentation

Device and browser hosts never see the agent environment, so the same list
reaches them through the SkiaSharpTestSkipGpu MSBuild property baked into
runtimeconfig.json. An unrecognised backend id is an error, so a typo cannot
quietly leave a backend required.

~~ Latent bugs the required tests surfaced ~~

Five real bugs, every one of them previously invisible as a skip:

  * DllImport used the unversioned libGL and libX11 SONAMEs, which are linker
    symlinks shipped only in -dev packages. Linux GL could never load, and
    every GL test skipped silently for as long as that binding existed.
  * GlxContext.Destroy never called XCloseDisplay — the binding was not even
    declared — leaking an X11 connection per GPU test.
  * SKBlenderTest asserted exact colour equality across two GPU shader paths.
    llvmpipe returns #ff5e0000 where the other path returns #ff5d0000. Raster
    stays exact; the GPU subclass allows a tolerance of one.
  * The Windows software Vulkan ICD was staged for win-x64 only, so x86 had no
    loader and 21 of 25 Vulkan tests failed.
  * Win32VkContext built a Vulkan 1.0 instance and set no MaxAPIVersion, so
    GRContext.CreateVulkan returned null and the test reported an
    ArgumentNullException three frames from the real problem. Skia has required
    1.1 unconditionally since m139 [0] and acquires the core 1.1 device procs
    with no version gate; vkGetDeviceProcAddr gates those on the instance's
    apiVersion, so all nine came back null. It passed against CI's SwiftShader,
    which reports device 1.3.0, and failed on an AMD device reporting 1.4.329 —
    the result depended on the agent's driver.

~~ Limits that are declared, not fixed ~~

Fourteen opt-outs remain, each naming its cause. This is not a regression —
every one was already skipping silently — but they are now enumerable, and

  * Metal is unusable on all three Apple hosts, for three unrelated reasons:
    virtualized Metal hangs the macOS host on shutdown (#4598), Mac Catalyst
    never sees the Mac2 GPU family (#4599), and the iOS simulator cannot
    compile Graphite's gradient pipeline (#4555)
  * Linux GL crashes the host once GLX loads, exiting 7 after 523, 312, 5068
    and 0 tests with zero test failures and no TRX published. The XCloseDisplay
    fix above did not resolve it (#4590)
  * the headless browser the runner launches exposes no WebGPU adapter and the
    runner has no flag passthrough; reported upstream

~~ Structural fallout ~~

The .NET Framework leg is now two jobs. SwiftShader's LLVM JIT cannot resolve
__chkstk in a 32-bit process, so software Vulkan crashes the host on x86 while
x64 runs the same suite green. GpuPolicy models the OS but not the
architecture, so separate jobs are what let x86 opt out while x64 keeps Vulkan
required, and any future x64/x86 divergence becomes visible on its own leg.

Mac Catalyst also stops being non-gating. It carried the repo's only
continueOnError: true, added in #3247, which is the same failure mode this
change exists to remove.

~~ Filed from the Vulkan investigation ~~

  * #4600 — MaxAPIVersion's default of 0 makes Skia trust the loader's version
    rather than the instance's. Worse, since m140 [1] the minimum-version check
    is SK_ABORT rather than a graceful null, so a consumer who correctly
    declares Vulkan 1.0 now aborts the process instead of being able to fall
    back to raster.
  * #4601 — fDeviceLostProc and fDeviceLostContext are not exposed, so
    consumers get no notification on VK_ERROR_DEVICE_LOST.

[0]: https://skia.googlesource.com/skia/+/0a3150b03b
[1]: https://skia.googlesource.com/skia/+/dfea59a30f

Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Co-authored-by: Copilot <noreply@github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
[bindings] Make generation deterministic across platforms (#4612)

Context: #4612

SkiaSharpGenerator relied on filesystem enumeration and culture-sensitive
string ordering, causing identical inputs to produce different function and
type ordering across Windows, macOS, and Linux.

Use normalized relative paths, ordinal comparisons, and canonical UTF-8/LF
output. The resulting SkiaApi migration only reorders its existing 23,630
lines.

Add CI that independently generates bindings on all three platforms, compares
their outputs byte-for-byte, then verifies the canonical result matches the
four committed Skia bindings. This detects cross-host nondeterminism, omitted
regeneration, and manual generated-file edits. HarfBuzz freshness remains out
of scope because its bindings are versioned separately.

Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
[tests] Run ganesh-gl on the Windows and Linux CI legs (#4605)

Context: #4605
Context: documentation/dev/gpu-test-policy.md
Fixes: #4590

#4575 made a GPU backend required on every platform GpuPolicy.RequiredOn names
and turned the silent skips into an enumerable list. ganesh-gl was the largest
entry on it: required on Windows, macOS and Linux, but opted out of the two
desktop legs that had no working GL stack. This closes those, so the Ganesh GL
backend now runs on all three desktop platforms.

This is not "GL was untested until now". GL was already covered in several
places, and this fills in the ones that were not:

  * macOS already ran ganesh-gl through CGL. That is why ganesh-gl.macos was
    the only committed golden set — under the rules #4575 introduced, an
    unseeded backend fails, so those goldens exist because macOS was rendering.
  * Android, iOS and Mac Catalyst already exercise the views layer against real
    GL surfaces: MemoryLeakTests.SKGLViewHandlerDoesNotLeak runs and passes on
    all three device legs.
  * Windows had no OpenGL implementation at all, and Linux had GLX loading and
    then killing the host. Both are the low-level GRContext.CreateGl path the
    desktop console suite covers, and both are what this fixes.

The five container legs and Nano Server keep their opt-out, and the comment now
reads as a decision rather than a pending item: those images exist to prove
SkiaSharp works with the bare minimum installed, so giving them an X server and
Mesa would defeat their purpose.

Two gaps this does not close, both pre-existing: SKGLView on WinUI has no device
leg in this pipeline, and the Blazor SKGLView/WebGL test lives in
SkiaSharp.Tests.Integration, which this pipeline does not run. ganesh-gl is not
required on Browser at all; WASM's GPU backend is graphite-dawn, still opted out
because the headless browser exposes no adapter. #4591 tracks the remainder.

Every problem below was reproduced and measured before it was fixed: Mesa on
Windows, and Ubuntu under WSL with Xvfb and GLX for Linux, which reproduced the
CI crash faithfully enough to bisect it.

~~ Windows: no OpenGL at all ~~

The agents have no display driver carrying an OpenGL ICD, so opengl32.dll
answers with the in-box generic implementation: OpenGL 1.1, no
WGL_ARB_pixel_format, no WGL_ARB_pbuffer. WglContext cannot come up on that, and
Ganesh needs 3.0+ regardless.

scripts/infra/native/windows/install-mesa-gl.ps1 provisions Mesa the way Mesa
documents for machines with no OpenGL driver [0]: libgallium_wgl.dll becomes
System32\mesadrv.dll and SysWOW64\mesadrv.dll, registered under
OpenGLDrivers\MSOGL. It follows install-vulkan-icd.ps1 — pinned version, SHA-256
pin, both bitnesses, fatal on any failure — and checks what provisioning is
responsible for and no more: the driver is where opengl32 will look for it, its
version resource matches the version just pinned rather than a leftover from an
earlier run, and the registration reads back. Proving it renders belongs to the
suite, and GRContextTest.CreateDefaultContextIsValid already does that on every
leg where the policy requires ganesh-gl.

~~ Windows: SetPixelFormat and wglSetPixelFormat are not interchangeable ~~

With Mesa registered, provisioning still failed, and the diagnostics named it:

    mesadrv=C:\Windows\SYSTEM32\mesadrv.dll; pixelFormats=1332; chosenFormat=121
    FAILED: wglCreateContext returned NULL (2000)

Mesa is loaded and serving its own 1332 pixel formats, and 2000 is
ERROR_INVALID_PIXEL_FORMAT. gdi32's ChoosePixelFormat reports Mesa's formats and
SetPixelFormat returns true, but Mesa's per-HDC state is only created by its own
wglSetPixelFormat, so wglCreateContext has nothing to look up.

The two APIs turn out to be exactly inverted, consistently per driver: a
hardware ICD is set up through GDI and refuses a context when the format was set
the other way, and Mesa is the reverse. So the Wgl bootstrap tries GDI first —
every real driver, and every developer running these tests locally — and falls
back only when the context actually fails. A pixel format can be set once per
DC and the class is CS_OWNDC, so the retry needs a fresh window; the first is
destroyed before it is replaced.

~~ Linux: the crash was one test, not a race ~~

#4590 attributed the exit-7 host crash to nine test classes creating GLX
contexts concurrently. That diagnosis was wrong. The crash reproduces with the
suite serialized onto a single thread, and a trace shows every context created,
used and destroyed on that one thread.

The real cause is GRGlInterfaceTest.AssembleInterfaceIsValid, which resolved
Skia's proc addresses with glXGetProcAddress alone. Under GLVND — the
vendor-neutral libGL every current distro ships — that returns a dispatch stub
for any name at all; glTotallyNotARealFunction comes back non-null. Skia was
handed pointers that are not functions and walked off the end of the extension
list in GrGLExtensions::init. It now resolves through dlsym on libGL.so.1,
exactly as the macOS branch already did.

The death point only looked random because the run order varied.

~~ Both: llvmpipe cannot compile one of Skia's shaders ~~

Mesa's LLVM shader JIT segfaults inside LLVMTypeOf, under
GrGLOpsRenderPass::onDraw, building the fragment shader Skia generates for a
runtime blender. Reproduced on two independent stacks — Mesa 25.2 with LLVM 20
on Linux, and 26.1 with LLVM 22 on Windows — so both legs pin softpipe, the
non-JIT gallium rasterizer, which runs the identical tests green on both and
caps at GL 3.3, well past the 3.0 Ganesh needs. Filed as #4604.

Ruled out along the way, so nobody re-treads it: thread parallelism
(--parallel none --max-threads 1 crashes identically), managed thread stack size
at 2, 16 and 64 MB, LP_NUM_THREADS of 0 and 1, file-descriptor limits at 256,
1024 and 65536, and GL context churn — 400 create/use/destroy cycles are fine.

~~ Supporting changes ~~

  * every class that creates a GPU context joins the existing
    GpuRenderingCollection, mirroring VulkanGpuRenderingCollection. This is what
    #4590 and #4591 asked for; it was not the crash fix, but concurrent GPU
    context use is a real hazard worth closing. Where only a nested class does
    GPU work (SKBlenderTest, SKRuntimeEffectTest) only that class joins, so its
    raster sibling stays parallel — roughly 300 of 6100 tests are serialized
  * GlxContext shares one X11 connection for the process instead of opening and
    closing one per test. That is the leak XCloseDisplay was added for in #4575,
    fixed at the source: one connection instead of 128 and counting, and no
    closing a display while a context minted from it is still current
  * XInitThreads runs before any other Xlib call
  * ganesh-gl.windows and ganesh-gl.linux goldens are harvested and committed,
    since a backend that renders for the first time is unseeded and fails until
    they exist

~~ Result ~~

  * Windows (.NET Framework, x64) — 7415 tests, 0 failed, 23 skipped
  * Windows (.NET Framework, x86) — 7415 tests, 0 failed, 25 skipped
  * Windows (.NET Core)           — 6117 tests, 0 failed, 31 skipped
  * Linux                         — 6117 tests, 0 failed, 34 skipped

Linux previously exited 7 and published nothing at all. Measured locally, GL on
versus off is 6079 against 5910 passing: 169 tests that had never run once.

[0]: https://docs.mesa3d.org/drivers/llvmpipe.html#windows

Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
[Issue Templates] Refresh SkiaSharp version dropdowns (#4622)

Update `.github/ISSUE_TEMPLATE/bug-report.yml` with the current release
metadata so new reports default to the latest stable version and users can
accurately identify the last known good release.

Keep pre-release, current, previous, deprecated, and obsolete labels aligned
with the published release state. This is the generated output of
`update-bug-template.py` from the automated issue-template refresh workflow.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…ixel source) (#4558)

SKPixmap.ExtractSubset and WithColorType/WithColorSpace/WithAlphaType return
a pixmap that points into the source pixmap's pixel memory (native does not
copy), but did not root the pixel owner. Unlike PeekPixels (which sets
pixelSource), these results kept no managed reference, so the pixel owner
could be finalized while the returned pixmap was still in use -> use-after-free.

Propagate the ultimate pixel owner (pixelSource ?? this) onto the returned
pixmap, matching the existing PeekPixels rooting idiom. Managed C# only, no
public signature change (ABI-safe).

Adds red->green regression tests ExtractSubsetKeepsPixelSourceAlive and
WithColorTypeKeepsPixelSourceAlive.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ck (#4630)

[performance] Cache HandleDictionary skip-registration reflection check (#4630)

Context: #4629

HandleDictionary.GetInstance<T> and GetOrAddObject<T> run for every native-object
wrap, but each call repeatedly used Type.IsAssignableFrom to determine whether the
wrapper type skips global registration. That predicate is invariant for each closed
wrapper type.

Cache the result in a generic static holder so reflection runs once per T and the
hot path reads a plain static boolean. This preserves behavior and does not change
the public API or ABI.

Add parity tests covering skip-registration and tracked wrapper types, plus an
end-to-end deduplication check. Add a four-way benchmark comparing the existing
cached-Type baseline, inline typeof, the PR cache, and inline typeof inside the
generic cache. The generic cache variants are effectively equivalent; both avoid
the repeated reflection cost without allocations.

Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t the SKRegion) (#4355)

[memory-leak] Root SKRegion for iterator lifetimes (#4355)

Context: #4354

SKRegion iterators hold native iterator state that references the source
region's run data. Because SKRegion skips global handle registration, the
region could be finalized while a SpanIterator was still alive, leaving the
native iterator with a dangling pointer and causing use-after-free.

Use SKObject.Referenced(this, region) for RectIterator, ClipIterator, and
SpanIterator so each iterator keeps its source region alive for its entire
lifetime. Add a regression test that forces collection and verifies the
SpanIterator remains usable. This keeps the iterator family consistent,
clears the keep-alive reference during disposal, and does not change the
public API or ABI.

Fixes #4354

Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Merges 2 upstream commits from google/skia chrome/m151:
- [M151] Address incorrect handling of a map pointer in SkRP (86bb2f25f4)
- Roll infra dep to 055b758759c89b0cfafc8264ae2100a9aa6582d3 (f7be6d7766)

Same-milestone sync — only cgmanifest.json hash and submodule pointer updated.
No C API, binding, or version changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mattleibow
mattleibow changed the base branch from main to release/4.151.x August 4, 2026 16:34
@mattleibow mattleibow closed this Aug 4, 2026
@mattleibow
mattleibow deleted the skia-sync/m151 branch August 4, 2026 16:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

partner/agentic-workflows Issues and PRs created by SkiaSharp agentic workflows. type/milestone-sync Any Skia upstream sync PR (milestone bump, same-milestone bug-fix re-sync, or tip sync).

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants