From be634e9cbbe92535dfd0571ffcf7a84ddb3cd315 Mon Sep 17 00:00:00 2001 From: Copilot App <223556219+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 21:47:58 +0200 Subject: [PATCH 01/20] docs: add GPU and offscreen surfaces guides (incl. Graphite) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new "GPU and Offscreen Surfaces" section to the docfx guides covering how to create every kind of SKSurface, wired into TOC.yml and the guides overview: - raster-surfaces: SKSurface.Create(SKImageInfo) and raster-direct via SKPixmap / pinned memory, plus Snapshot / ReadPixels. - ganesh-surfaces: GRContext for OpenGL, Vulkan, Metal, and Direct3D; offscreen Create(GRContext, budgeted, info); wrapping GRBackendRenderTarget and GRBackendTexture; flush and synchronous readback. - views-surfaces: catalog of the raster vs GPU view controls across SkiaSharp.Views, .NET MAUI, Uno, and Blazor, noting Graphite is offscreen only and not driven by any view yet. - graphite-surfaces: the new SKGraphiteContext path — Create{Vulkan,Metal,Dawn}, recorder -> surface -> Snap -> InsertRecording -> Submit, backend-texture wrapping, SKImage interop, and the asynchronous RequestReadPixels + CheckAsyncWorkCompletion readback (with the non-yielding Dawn/WASM caveat). - graphite-migration: maps existing Ganesh code onto the Graphite model. Prose guides only (no API reference). All code samples verified against the bindings and the working Graphite/Ganesh test renderers on this branch; all xref links validated against the skiasharp xrefmap (new Graphite types, which are not yet in the published xrefmap, use inline code). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: bdcc8d8b-500e-41ae-bb71-5e336ac92b1d --- documentation/docfx/guides/TOC.yml | 13 + .../docfx/guides/gpu/ganesh-surfaces.md | 185 ++++++++++++ .../docfx/guides/gpu/graphite-migration.md | 120 ++++++++ .../docfx/guides/gpu/graphite-surfaces.md | 265 ++++++++++++++++++ documentation/docfx/guides/gpu/index.md | 66 +++++ .../docfx/guides/gpu/raster-surfaces.md | 120 ++++++++ .../docfx/guides/gpu/views-surfaces.md | 106 +++++++ documentation/docfx/guides/index.md | 4 + 8 files changed, 879 insertions(+) create mode 100644 documentation/docfx/guides/gpu/ganesh-surfaces.md create mode 100644 documentation/docfx/guides/gpu/graphite-migration.md create mode 100644 documentation/docfx/guides/gpu/graphite-surfaces.md create mode 100644 documentation/docfx/guides/gpu/index.md create mode 100644 documentation/docfx/guides/gpu/raster-surfaces.md create mode 100644 documentation/docfx/guides/gpu/views-surfaces.md diff --git a/documentation/docfx/guides/TOC.yml b/documentation/docfx/guides/TOC.yml index 2a81edbdc096..57cbd0d443f0 100644 --- a/documentation/docfx/guides/TOC.yml +++ b/documentation/docfx/guides/TOC.yml @@ -115,3 +115,16 @@ href: effects/image-filters.md - name: Color Filters href: effects/color-filters.md +- name: GPU and Offscreen Surfaces + href: gpu/index.md + items: + - name: Raster Surfaces + href: gpu/raster-surfaces.md + - name: Ganesh GPU Surfaces + href: gpu/ganesh-surfaces.md + - name: Surfaces in the SkiaSharp Views + href: gpu/views-surfaces.md + - name: Graphite Offscreen Surfaces + href: gpu/graphite-surfaces.md + - name: Migrating from Ganesh to Graphite + href: gpu/graphite-migration.md diff --git a/documentation/docfx/guides/gpu/ganesh-surfaces.md b/documentation/docfx/guides/gpu/ganesh-surfaces.md new file mode 100644 index 000000000000..1b5431e8ee73 --- /dev/null +++ b/documentation/docfx/guides/gpu/ganesh-surfaces.md @@ -0,0 +1,185 @@ +--- +title: "Ganesh GPU Surfaces" +description: "Create GPU-backed SKSurface objects with the Ganesh backend in SkiaSharp. Build a GRContext for OpenGL, Vulkan, Metal, or Direct3D, then render fully offscreen or wrap an existing render target or texture." +--- + +# Ganesh GPU Surfaces + +_Render on the GPU with a `GRContext` and the Ganesh backend_ + +*Ganesh* is Skia's classic GPU backend. To draw on the GPU with Ganesh you create a [`GRContext`](xref:SkiaSharp.GRContext) — a handle to a live graphics API context — and then create an [`SKSurface`](xref:SkiaSharp.SKSurface) from it. There are two shapes of GPU surface: + +- **Offscreen** surfaces, where Skia allocates and owns the GPU texture. This is the GPU equivalent of a [raster surface](raster-surfaces.md) and is the easiest way to render on the GPU. +- **Wrapped** surfaces, where you hand Skia an existing render target (a window's framebuffer) or texture that some other code created. This is how you draw SkiaSharp content into a swap-chain image you present to the screen. + +Ganesh supports four graphics APIs: **OpenGL**, **Vulkan**, **Metal**, and **Direct3D**. The context creation differs per API; everything after that — creating the surface, drawing, flushing, and reading back — is the same. + +> [!NOTE] +> A `GRContext` is bound to the graphics context that was current when you created it, and neither it nor its surfaces are thread-safe. Create and use them on the thread that owns the graphics context. + +## Creating a context + +### OpenGL + +For OpenGL, a GL context must already be *current* on the calling thread — SkiaSharp does not create the GL context for you. Once it is current, create a [`GRGlInterface`](xref:SkiaSharp.GRGlInterface) (which resolves the GL entry points) and pass it to [`GRContext.CreateGl`](xref:SkiaSharp.GRContext.CreateGl*): + +```csharp +// a platform GL context (WGL / GLX / EGL / CGL) is already current on this thread +using var glInterface = GRGlInterface.Create(); +using var context = GRContext.CreateGl(glInterface); +``` + +`GRContext.CreateGl()` also has a parameterless overload that assembles the interface from the current context for you. + +### Vulkan + +For Vulkan you supply the objects Skia needs through a [`GRVkBackendContext`](xref:SkiaSharp.GRVkBackendContext): the instance, physical device, logical device, a graphics queue and its family index, and a `GetProcedureAddress` delegate that resolves Vulkan functions. + +```csharp +using var backendContext = new GRVkBackendContext +{ + VkInstance = instanceHandle, + VkPhysicalDevice = physicalDeviceHandle, + VkDevice = deviceHandle, + VkQueue = graphicsQueueHandle, + GraphicsQueueIndex = graphicsFamilyIndex, + GetProcedureAddress = (name, instance, device) => /* vkGetXxxProcAddr */, +}; + +using var context = GRContext.CreateVulkan(backendContext); +``` + +If you use the [SharpVk](https://www.nuget.org/packages/SharpVk) managed Vulkan binding, the **SkiaSharp.Vulkan.SharpVk** package provides a typed `GRSharpVkBackendContext` that accepts SharpVk objects directly instead of raw handles, so you don't have to marshal `IntPtr`s yourself: + +```csharp +using var backendContext = new GRSharpVkBackendContext +{ + VkInstance = instance, + VkPhysicalDevice = physicalDevice, + VkDevice = device, + VkQueue = queue, + GraphicsQueueIndex = graphicsFamily, + GetProcedureAddress = (name, instance, device) => /* ... */, + VkPhysicalDeviceFeatures = physicalDevice.GetFeatures(), +}; + +using var context = GRContext.CreateVulkan(backendContext); +``` + +### Metal + +On Apple platforms, build a [`GRMtlBackendContext`](xref:SkiaSharp.GRMtlBackendContext) from an `MTLDevice` and an `MTLCommandQueue`. On the Apple target frameworks you can assign the typed `IMTLDevice`/`IMTLCommandQueue` objects; from other targets, assign their native handles: + +```csharp +using var backendContext = new GRMtlBackendContext +{ + DeviceHandle = mtlDeviceHandle, + QueueHandle = mtlCommandQueueHandle, +}; + +using var context = GRContext.CreateMetal(backendContext); +``` + +### Direct3D + +On Windows, build a [`GRD3DBackendContext`](xref:SkiaSharp.GRD3DBackendContext) from your DXGI adapter, D3D12 device, and command queue: + +```csharp +using var backendContext = new GRD3DBackendContext +{ + Adapter = adapterHandle, + Device = d3d12DeviceHandle, + Queue = commandQueueHandle, +}; + +using var context = GRContext.CreateDirect3D(backendContext); +``` + +## Rendering offscreen + +The simplest GPU surface is an offscreen one: describe the image with an [`SKImageInfo`](xref:SkiaSharp.SKImageInfo) and let Skia allocate the backing GPU texture. Pass `budgeted: true` so the texture counts against the context's resource budget and can be recycled. + +```csharp +var info = new SKImageInfo(512, 512, SKColorType.Rgba8888, SKAlphaType.Premul); + +using var surface = SKSurface.Create(context, budgeted: true, info); + +surface.Canvas.Clear(SKColors.White); +surface.Canvas.DrawCircle(256, 256, 200, new SKPaint { Color = SKColors.CornflowerBlue }); + +// push the recorded work to the GPU and wait for it to finish +context.Flush(submit: true, synchronous: true); +``` + +After flushing, you can read the pixels back synchronously — GPU readback with Ganesh works exactly like the [raster case](raster-surfaces.md#getting-the-result-out): + +```csharp +var pixels = new byte[info.BytesSize]; +var handle = GCHandle.Alloc(pixels, GCHandleType.Pinned); +try +{ + surface.ReadPixels(info, handle.AddrOfPinnedObject(), info.RowBytes, 0, 0); +} +finally +{ + handle.Free(); +} +``` + +The whole offscreen loop looks the same regardless of which API you created the context with. For example, over OpenGL: + +```csharp +using var glInterface = GRGlInterface.Create(); +using var context = GRContext.CreateGl(glInterface); +using var surface = SKSurface.Create(context, budgeted: true, info); + +surface.Canvas.Clear(SKColors.White); +// ... draw ... +context.Flush(submit: true, synchronous: true); +``` + +## Wrapping an existing render target + +To draw SkiaSharp content into a render target that already exists — most often a window's framebuffer or a swap-chain image — describe it to Skia with a [`GRBackendRenderTarget`](xref:SkiaSharp.GRBackendRenderTarget) and wrap it with [`SKSurface.Create`](xref:SkiaSharp.SKSurface.Create*). + +For OpenGL, you build the backend render target from the currently bound framebuffer. This is exactly what the built-in `SKGLView` controls do internally: + +```csharp +// query the currently bound framebuffer, stencil bits, and sample count from GL, +// then describe it to Skia +var glInfo = new GRGlFramebufferInfo((uint)framebuffer, colorType.ToGlSizedFormat()); +using var renderTarget = new GRBackendRenderTarget(width, height, samples, stencil, glInfo); + +using var surface = SKSurface.Create(context, renderTarget, GRSurfaceOrigin.BottomLeft, colorType); + +surface.Canvas.Clear(SKColors.White); +// ... draw the frame ... + +surface.Canvas.Flush(); +context.Flush(); +// then present/swap buffers with your windowing code +``` + +`GRBackendRenderTarget` also has constructors for Vulkan (`GRVkImageInfo`), Metal (`GRMtlTextureInfo`), and Direct3D (`GRD3DTextureResourceInfo`), so you can wrap a swap-chain image from any of the supported APIs. + +## Wrapping an existing texture + +If instead of a render target you have a GPU **texture**, describe it with a [`GRBackendTexture`](xref:SkiaSharp.GRBackendTexture) and create a surface that renders into it: + +```csharp +using var surface = SKSurface.Create( + context, backendTexture, GRSurfaceOrigin.TopLeft, sampleCount: 0, colorType); +``` + +You can also wrap a texture as a *sampling* [`SKImage`](xref:SkiaSharp.SKImage) with [`SKImage.FromTexture`](xref:SkiaSharp.SKImage.FromTexture*) when you want to draw an existing GPU texture *onto* a surface rather than *into* it. + +## Cleaning up + +Dispose your surfaces and the `GRContext` when you are done, and make sure the graphics context they were created against is still current at disposal time. Disposing the `GRContext` frees all the GPU resources Skia allocated through it. + +## Related Links + +- [SkiaSharp APIs](/dotnet/api/skiasharp) +- [Raster Surfaces](raster-surfaces.md) +- [Surfaces in the SkiaSharp Views](views-surfaces.md) +- [Graphite Offscreen Surfaces](graphite-surfaces.md) diff --git a/documentation/docfx/guides/gpu/graphite-migration.md b/documentation/docfx/guides/gpu/graphite-migration.md new file mode 100644 index 000000000000..121a33b8f320 --- /dev/null +++ b/documentation/docfx/guides/gpu/graphite-migration.md @@ -0,0 +1,120 @@ +--- +title: "Migrating from Ganesh to Graphite" +description: "Map your existing SkiaSharp Ganesh GPU code onto the newer Graphite backend — context creation, the recorder and recording drawing model, submission, and the asynchronous pixel readback that replaces synchronous ReadPixels." +--- + +# Migrating from Ganesh to Graphite + +_Map your existing Ganesh code onto the Graphite model_ + +If you already render on the GPU with [Ganesh](ganesh-surfaces.md) — a [`GRContext`](xref:SkiaSharp.GRContext), an `SKSurface`, and `Flush` — this page shows the equivalent [Graphite](graphite-surfaces.md) calls. The concepts line up closely; the two real behavioural changes are the **recorder/recording** drawing model and the **asynchronous** pixel readback. + +> [!NOTE] +> Graphite is currently an **offscreen** path in SkiaSharp. If your Ganesh code renders into a view control's render target (`SKGLView`, `SKMetalView`, `SKSwapChainPanel`), there is no Graphite equivalent for that view yet — the [view controls](views-surfaces.md) still use Ganesh. This migration applies to offscreen rendering. + +## Concept mapping + +| Ganesh | Graphite | +| --- | --- | +| `GRContext` | `SKGraphiteContext` | +| `GRContext.CreateGl` / `CreateVulkan` / `CreateMetal` / `CreateDirect3D` | `SKGraphiteContext.CreateVulkan` / `CreateMetal` / `CreateDawn` | +| `GRVkBackendContext` / `GRMtlBackendContext` | `SKGraphiteVkBackendContext` / `SKGraphiteMtlBackendContext` / `SKGraphiteDawnBackendContext` | +| `GRSharpVkBackendContext` (typed Vulkan) | `SKGraphiteSharpVkBackendContext` (typed Vulkan) | +| `SKSurface.Create(context, budgeted, info)` | `context.CreateRecorder()` + `SKSurface.Create(recorder, info)` | +| Draw on `surface.Canvas` | Draw on `surface.Canvas` (unchanged) | +| `context.Flush(submit: true, synchronous: true)` | `recorder.Snap()` + `context.InsertRecording(recording)` + `context.Submit(new SKGraphiteSubmitInfo { Sync = true })` | +| `surface.ReadPixels(...)` (synchronous) | `context.RequestReadPixels(...)` + `context.CheckAsyncWorkCompletion()` (asynchronous) | +| `GRBackendTexture` / `SKSurface.Create(context, texture, ...)` | `SKGraphiteBackendTexture` / `SKSurface.Create(recorder, backendTexture, colorType)` | +| `SKImage.FromTexture(context, texture, ...)` | `SKImage.FromTexture(recorder, backendTexture, ...)` | +| `image.ToTextureImage(context)` | `image.ToTextureImage(recorder)` | + +Notice the pattern: wherever Ganesh takes the **context**, Graphite's per-surface and per-image APIs take the **recorder** instead. OpenGL and Direct3D have no Graphite backend — Graphite targets Vulkan, Metal, and Dawn (WebGPU). + +## Before and after + +Here is a complete offscreen render + readback in each backend. + +### Ganesh + +```csharp +var info = new SKImageInfo(512, 512, SKColorType.Rgba8888, SKAlphaType.Premul); + +using var context = GRContext.CreateMetal(backendContext); +using var surface = SKSurface.Create(context, budgeted: true, info); + +surface.Canvas.Clear(SKColors.White); +surface.Canvas.DrawCircle(256, 256, 200, new SKPaint { Color = SKColors.CornflowerBlue }); + +context.Flush(submit: true, synchronous: true); + +// synchronous readback +var pixels = new byte[info.BytesSize]; +var handle = GCHandle.Alloc(pixels, GCHandleType.Pinned); +try +{ + surface.ReadPixels(info, handle.AddrOfPinnedObject(), info.RowBytes, 0, 0); +} +finally +{ + handle.Free(); +} +``` + +### Graphite + +```csharp +var info = new SKImageInfo(512, 512, SKColorType.Rgba8888, SKAlphaType.Premul); + +using var context = SKGraphiteContext.CreateMetal(backendContext); +using var recorder = context.CreateRecorder(); +using var surface = SKSurface.Create(recorder, info); + +surface.Canvas.Clear(SKColors.White); +surface.Canvas.DrawCircle(256, 256, 200, new SKPaint { Color = SKColors.CornflowerBlue }); + +using (var recording = recorder.Snap()) +{ + if (context.InsertRecording(recording) != SKGraphiteInsertStatus.Success) + throw new InvalidOperationException("InsertRecording did not succeed."); +} +context.Submit(new SKGraphiteSubmitInfo { Sync = true }); + +// asynchronous readback — see the Graphite Offscreen Surfaces guide for the full helper +var pixels = ReadPixelsAsync(context, surface, info); +``` + +The drawing calls are identical. What changes is the plumbing around them. + +## The three changes to make + +### 1. Replace `Flush` with snap + insert + submit + +Ganesh flushes the context directly. Graphite splits this into three steps: `recorder.Snap()` captures the recorded commands into an `SKGraphiteRecording`, `context.InsertRecording(recording)` hands them to the context, and `context.Submit(new SKGraphiteSubmitInfo { Sync = true })` sends them to the GPU and (with `Sync = true`) waits. + +Always check that `InsertRecording` returns `SKGraphiteInsertStatus.Success`, and note that `Snap` **resets** the recorder for the next frame. + +### 2. Replace synchronous `ReadPixels` with asynchronous readback + +This is the most important change. Graphite surfaces do **not** support synchronous [`SKSurface.ReadPixels`](xref:SkiaSharp.SKSurface.ReadPixels*) in shipping builds — it returns `false`. Replace it with `RequestReadPixels`, then drive the request to completion with `Submit` and repeated `CheckAsyncWorkCompletion` calls. The returned plane may be row-padded, so copy row-by-row. See [Reading pixels back](graphite-surfaces.md#reading-pixels-back) for the complete helper. + +### 3. Pass the recorder where you used to pass the context + +Per-surface and per-image creation moves from the context to the recorder: + +- `SKSurface.Create(context, budgeted, info)` → `SKSurface.Create(recorder, info)` +- `SKSurface.Create(context, backendTexture, ...)` → `SKSurface.Create(recorder, backendTexture, colorType)` +- `SKImage.FromTexture(context, ...)` → `SKImage.FromTexture(recorder, ...)` +- `image.ToTextureImage(context)` → `image.ToTextureImage(recorder)` + +## Watch out for + +- **No OpenGL or Direct3D.** Graphite targets Vulkan, Metal, and Dawn. If your Ganesh code uses GL or D3D, there is no direct Graphite equivalent; keep using Ganesh, or move to Vulkan/Metal/Dawn. +- **Browser (Dawn/WebGPU) can't submit synchronously.** In a WebAssembly host, `Submit(Sync = true)` throws. Submit without syncing and pump `CheckAsyncWorkCompletion`. See [Dawn in the browser](graphite-surfaces.md#dawn-in-the-browser). +- **Check backend availability.** Use `SKGraphiteContext.IsBackendAvailable` before creating a context, since not every build includes every backend. +- **Threading is unchanged.** As with Ganesh, the context, recorders, and surfaces are single-threaded — use them on the thread that owns the graphics device. + +## Related Links + +- [SkiaSharp APIs](/dotnet/api/skiasharp) +- [Ganesh GPU Surfaces](ganesh-surfaces.md) +- [Graphite Offscreen Surfaces](graphite-surfaces.md) diff --git a/documentation/docfx/guides/gpu/graphite-surfaces.md b/documentation/docfx/guides/gpu/graphite-surfaces.md new file mode 100644 index 000000000000..5dc2df47816b --- /dev/null +++ b/documentation/docfx/guides/gpu/graphite-surfaces.md @@ -0,0 +1,265 @@ +--- +title: "Graphite Offscreen Surfaces" +description: "Render on the GPU with SkiaSharp's new Graphite backend. Create an SKGraphiteContext for Vulkan, Metal, or Dawn (WebGPU), record and submit drawing through a recorder and recording, wrap external GPU textures, and read pixels back through the asynchronous readback path." +--- + +# Graphite Offscreen Surfaces + +_Render on the GPU with the new Graphite backend_ + +*Graphite* is Skia's newer GPU backend, built on modern explicit graphics APIs. In SkiaSharp, Graphite is currently an **offscreen** rendering path: you create a context, record drawing into a surface, submit that recording to the GPU, and read the result back yourself. It does not yet drive any of the [view controls](views-surfaces.md). + +Graphite differs from [Ganesh](ganesh-surfaces.md) in two important ways: + +- **Drawing is recorded, not flushed.** You draw onto a surface's canvas as usual, but instead of flushing a context you *snap* a **recording** from a **recorder** and *insert* that recording into the context, then *submit* it. +- **Reading pixels back is asynchronous.** Graphite surfaces do not support the synchronous `SKSurface.ReadPixels` you use with raster and Ganesh surfaces. You request a readback and drive it to completion. This is the single most important thing to get right — see [Reading pixels back](#reading-pixels-back). + +Graphite supports three backends: **Vulkan**, **Metal**, and **Dawn** (WebGPU). + +> [!NOTE] +> Like Ganesh, an `SKGraphiteContext`, its recorders, and its surfaces are not thread-safe. Create and use them on a single thread that owns the underlying graphics device. + +## Checking a backend is available + +A given build of SkiaSharp may not include every Graphite backend. Before creating a context, you can check whether a backend is compiled in with `SKGraphiteContext.IsBackendAvailable`: + +```csharp +if (SKGraphiteContext.IsBackendAvailable(SKGraphiteBackend.Metal)) +{ + // safe to call SKGraphiteContext.CreateMetal +} +``` + +## Creating a context + +You create a context from a backend context that carries the native device objects for your API. Each factory also has an overload that takes an [`SKGraphiteContextOptions`](#context-options). + +### Vulkan + +Fill in a `SKGraphiteVkBackendContext` with your instance, physical device, device, queue, and graphics-queue index, plus a `GetProcedureAddress` delegate that resolves Vulkan functions: + +```csharp +using var backendContext = new SKGraphiteVkBackendContext +{ + VkInstance = instanceHandle, + VkPhysicalDevice = physicalDeviceHandle, + VkDevice = deviceHandle, + VkQueue = graphicsQueueHandle, + GraphicsQueueIndex = graphicsFamilyIndex, + GetProcedureAddress = (name, instance, device) => /* vkGetXxxProcAddr */, +}; + +using var context = SKGraphiteContext.CreateVulkan(backendContext); +``` + +If you use the [SharpVk](https://www.nuget.org/packages/SharpVk) managed binding, the **SkiaSharp.Vulkan.SharpVk** package ships a typed `SKGraphiteSharpVkBackendContext` that accepts SharpVk objects directly: + +```csharp +using var backendContext = new SKGraphiteSharpVkBackendContext +{ + VkInstance = instance, + VkPhysicalDevice = physicalDevice, + VkDevice = device, + VkQueue = queue, + GraphicsQueueIndex = graphicsFamily, + GetProcedureAddress = (name, instance, device) => /* ... */, +}; + +using var context = SKGraphiteContext.CreateVulkan(backendContext); +``` + +### Metal + +On Apple platforms, supply an `MTLDevice` and `MTLCommandQueue`. From the Apple target frameworks you can assign the typed `Device`/`Queue` (`IMTLDevice`/`IMTLCommandQueue`) properties; from other targets, assign the native handles: + +```csharp +using var backendContext = new SKGraphiteMtlBackendContext +{ + MtlDevice = mtlDeviceHandle, + MtlQueue = mtlCommandQueueHandle, +}; + +using var context = SKGraphiteContext.CreateMetal(backendContext); +``` + +### Dawn (WebGPU) + +For Dawn, supply the WebGPU instance, device, and queue handles: + +```csharp +using var backendContext = new SKGraphiteDawnBackendContext +{ + WgpuInstance = instanceHandle, + WgpuDevice = deviceHandle, + WgpuQueue = queueHandle, +}; + +using var context = SKGraphiteContext.CreateDawn(backendContext); +``` + +Dawn is the backend used in the browser (WebAssembly), which imposes an extra constraint on submission — see [Dawn in the browser](#dawn-in-the-browser). + +## The render loop + +Once you have a context, the Graphite drawing loop is: create a **recorder**, create a surface from it, draw, **snap** a recording, **insert** it, and **submit**. + +```csharp +var info = new SKImageInfo(512, 512, SKColorType.Rgba8888, SKAlphaType.Premul); + +using var recorder = context.CreateRecorder(); +using var surface = SKSurface.Create(recorder, info); + +// draw exactly as you would on any other surface +surface.Canvas.Clear(SKColors.White); +surface.Canvas.DrawCircle(256, 256, 200, new SKPaint { Color = SKColors.CornflowerBlue }); + +// capture everything recorded so far +using var recording = recorder.Snap(); + +// hand the recording to the context and submit it to the GPU +if (context.InsertRecording(recording) != SKGraphiteInsertStatus.Success) + throw new InvalidOperationException("Graphite InsertRecording did not succeed."); + +context.Submit(new SKGraphiteSubmitInfo { Sync = true }); +``` + +A few things to note: + +- `CreateRecorder` returns an `SKGraphiteRecorder`. A recorder is a reusable unit of work capture; you create the surface from it, not from the context directly. +- `Snap` produces an `SKGraphiteRecording` — an immutable list of GPU commands. Snapping resets the recorder so it can record the next frame. +- `InsertRecording` returns an [`SKGraphiteInsertStatus`](#status-and-enums); always confirm it is `Success`. +- `Submit(new SKGraphiteSubmitInfo { Sync = true })` flushes the work to the GPU and, with `Sync = true`, waits for it to finish. It returns `false` if submission failed. + +## Reading pixels back + +> [!IMPORTANT] +> Graphite surfaces do **not** support the synchronous [`SKSurface.ReadPixels`](xref:SkiaSharp.SKSurface.ReadPixels*) used with [raster](raster-surfaces.md) and [Ganesh](ganesh-surfaces.md) surfaces — it returns `false`. To get pixels off a Graphite surface you must use the **asynchronous** readback path. This is the number-one thing to get right when porting existing code. + +Call `RequestReadPixels` with the surface, the destination `SKImageInfo`, the source rectangle, and a callback. Then drive the request to completion by submitting and repeatedly calling `CheckAsyncWorkCompletion` until the callback fires: + +```csharp +var dstInfo = new SKImageInfo(info.Width, info.Height, SKColorType.Rgba8888, SKAlphaType.Premul); + +byte[] pixels = null; +var done = false; + +context.RequestReadPixels( + surface, + dstInfo, + new SKRectI(0, 0, dstInfo.Width, dstInfo.Height), + result => + { + done = true; + if (result is null || result.PlaneCount < 1) + return; + + var src = result.GetPlaneData(0); + if (src == IntPtr.Zero) + return; + + // the returned plane may have per-row padding; copy row-by-row into a + // tightly-packed buffer, dropping any padding + var buffer = new byte[dstInfo.BytesSize]; + var srcRowBytes = result.GetPlaneRowBytes(0); + var rowBytes = Math.Min(srcRowBytes, dstInfo.RowBytes); + for (var y = 0; y < dstInfo.Height; y++) + Marshal.Copy(src + (y * srcRowBytes), buffer, y * dstInfo.RowBytes, rowBytes); + + pixels = buffer; + }); + +// flush the queued readback and wait, then pump the context until the callback runs +context.Submit(new SKGraphiteSubmitInfo { Sync = true }); +for (var i = 0; i < 10_000 && !done; i++) + context.CheckAsyncWorkCompletion(); + +if (!done || pixels is null) + throw new InvalidOperationException("Graphite async readback did not complete."); +``` + +The callback receives an `SKGraphiteAsyncReadResult` whose planes may be **row-padded**, so copy row-by-row using `GetPlaneRowBytes(0)` rather than assuming tightly packed pixels. A shorter `RequestReadPixels` overload uses default rescaling; a longer overload lets you pass an [`SKGraphiteRescaleGamma`](#status-and-enums) and [`SKGraphiteRescaleMode`](#status-and-enums) when you want the read to also rescale the image. + +## Wrapping an external GPU texture + +Instead of letting Skia allocate the surface's texture, you can render into a GPU texture your own code created. Describe it with an `SKGraphiteBackendTexture` and create a surface that wraps it: + +```csharp +// Metal example: wrap an existing MTLTexture handle +using var backendTexture = SKGraphiteBackendTexture.CreateMetal(width, height, mtlTextureHandle); +using var surface = SKSurface.Create(recorder, backendTexture, SKColorType.Rgba8888); + +surface.Canvas.Clear(SKColors.White); +// ... draw, then Snap / InsertRecording / Submit as above ... +``` + +There are matching factory methods for each backend: + +- `SKGraphiteBackendTexture.CreateVulkan(width, height, info, imageLayout, queueFamilyIndex, vkImage)` +- `SKGraphiteBackendTexture.CreateMetal(width, height, mtlTexture)` +- `SKGraphiteBackendTexture.CreateDawn(wgpuTexture)` + +## Using textures as images + +You can also move between GPU textures and [`SKImage`](xref:SkiaSharp.SKImage) objects on a recorder: + +- [`SKImage.FromTexture`](xref:SkiaSharp.SKImage.FromTexture*) wraps a backend texture as a sampling image you can draw onto a surface: + + ```csharp + using var image = SKImage.FromTexture( + recorder, backendTexture, SKColorType.Rgba8888, SKAlphaType.Premul); + ``` + +- [`ToTextureImage`](xref:SkiaSharp.SKImage.ToTextureImage*) uploads an existing image (for example, one decoded on the CPU) into a GPU-backed image on the recorder: + + ```csharp + using var gpuImage = cpuImage.ToTextureImage(recorder); + ``` + +## Dawn in the browser + +When Graphite runs over Dawn in a browser/WebAssembly host, the Dawn event loop cannot be pumped from inside a managed call, so **synchronous submission is not allowed**. Calling `Submit` with `Sync = true` there throws an `InvalidOperationException`. + +In that environment, submit without syncing and drive any readbacks with `CheckAsyncWorkCompletion`: + +```csharp +// browser / WASM (non-yielding Dawn) +context.InsertRecording(recording); +context.Submit(new SKGraphiteSubmitInfo { Sync = false }); + +// later, pump completion instead of blocking +context.CheckAsyncWorkCompletion(); +``` + +## Context options + +The `Create*` factories accept an optional `SKGraphiteContextOptions`. The most commonly useful field is `InternalMultisampleCount` (the internal MSAA sample count), which must be `0` (use Skia's default) or one of `1`, `2`, `4`, `8`, or `16`; other values are rejected. Other options include a GPU byte budget and driver-workaround toggles. + +```csharp +var options = new SKGraphiteContextOptions { InternalMultisampleCount = 4 }; +using var context = SKGraphiteContext.CreateMetal(backendContext, options); +``` + +## Managing resources + +An `SKGraphiteContext` exposes a few properties and methods for inspecting and managing GPU resources: + +- `Backend`, `IsDeviceLost`, `MaxTextureSize`, and `SupportsProtectedContent` report the state of the underlying device. +- `MaxBudgetedBytes` gets or sets the GPU memory budget; `CurrentBudgetedBytes` reports current usage. +- `FreeGpuResources()` releases cached GPU resources; `PerformDeferredCleanup(TimeSpan)` purges resources unused for longer than the given duration. + +Dispose recordings, surfaces, recorders, and the context when you are done. The context owns the GPU resources allocated through it. + +## Status and enums + +Graphite uses a handful of enums: + +- `SKGraphiteBackend` — `Dawn`, `Metal`, `Vulkan`, or `Unknown`. +- `SKGraphiteInsertStatus` — the result of `InsertRecording`; `Success` plus failure reasons such as `InvalidRecording`, `AddCommandsFailed`, and `OutOfOrderRecording`. +- `SKGraphiteRescaleGamma` — `Src` or `Linear`, for the optional readback rescale. +- `SKGraphiteRescaleMode` — `Nearest`, `RepeatedLinear`, or `RepeatedCubic`, for the optional readback rescale. + +## Related Links + +- [SkiaSharp APIs](/dotnet/api/skiasharp) +- [Ganesh GPU Surfaces](ganesh-surfaces.md) +- [Migrating from Ganesh to Graphite](graphite-migration.md) diff --git a/documentation/docfx/guides/gpu/index.md b/documentation/docfx/guides/gpu/index.md new file mode 100644 index 000000000000..9dd79c0f6ff7 --- /dev/null +++ b/documentation/docfx/guides/gpu/index.md @@ -0,0 +1,66 @@ +--- +title: "GPU and Offscreen Surfaces" +description: "Learn how to create the different kinds of SKSurface that SkiaSharp can draw into — CPU raster surfaces, Ganesh GPU surfaces (OpenGL, Vulkan, Metal, Direct3D), the surfaces the SkiaSharp Views manage for you, and the new Graphite offscreen backend." +--- + +# GPU and Offscreen Surfaces + +_Understand the different kinds of `SKSurface` and how to create them_ + +Everything you draw with SkiaSharp is drawn onto an [`SKCanvas`](xref:SkiaSharp.SKCanvas), and every canvas is backed by an [`SKSurface`](xref:SkiaSharp.SKSurface). The surface is what decides *where* the pixels actually live: in ordinary system memory (a **raster** surface), or in a texture owned by a GPU (a **GPU-backed** surface). + +Most of the other guides use an `SKCanvasView` and never create a surface directly — the view does that for you. This section is about the layer underneath: how to create a surface yourself, which is what you need for offscreen rendering, custom hosting, image processing pipelines, and server-side or headless rendering. + +## The three ways to get a surface + +SkiaSharp has three families of surface, and this section has a page for each. + +- **Raster surfaces** live in CPU memory. They are always available, work identically on every platform, and need no GPU. This is the right choice for image generation, thumbnails, PDF/print pipelines, unit tests, and any headless workload. See [Raster Surfaces](raster-surfaces.md). + +- **Ganesh GPU surfaces** are backed by a GPU texture through the classic Skia GPU backend, *Ganesh*. You create a [`GRContext`](xref:SkiaSharp.GRContext) for an API — OpenGL, Vulkan, Metal, or Direct3D — and then create a surface from it, either fully offscreen or wrapping an existing render target. See [Ganesh GPU Surfaces](ganesh-surfaces.md). + +- **Graphite offscreen surfaces** use Skia's newer GPU backend, *Graphite*, built on modern explicit APIs (Vulkan, Metal, and Dawn/WebGPU). Graphite records drawing into a *recorder*, snaps it into a *recording*, and submits that recording to the GPU. See [Graphite Offscreen Surfaces](graphite-surfaces.md). + +Separately, if you are building an app UI you usually don't create any of these by hand — the SkiaSharp *Views* do it for you and hand you a ready-to-draw surface in a paint event. See [Surfaces in the SkiaSharp Views](views-surfaces.md). + +## Ganesh or Graphite? + +Both Ganesh and Graphite are GPU backends that render into an `SKSurface`, but the programming models differ: + +| | Ganesh | Graphite | +| --- | --- | --- | +| Context type | [`GRContext`](xref:SkiaSharp.GRContext) | `SKGraphiteContext` | +| Backends | OpenGL, Vulkan, Metal, Direct3D | Vulkan, Metal, Dawn (WebGPU) | +| Drawing model | Draw on the canvas, then `Flush`/`Submit` the context | Draw on the canvas, then `Snap` a recording and `InsertRecording` + `Submit` it | +| Reading pixels back | Synchronous `SKSurface.ReadPixels` works | **Asynchronous only** — use `SKGraphiteContext.RequestReadPixels` | +| In the SkiaSharp Views | Yes (`SKGLView`, `SKMetalView`) | Not yet — offscreen only | + +Ganesh is mature and is what the Views use today. Graphite is the direction Skia is moving in, and in SkiaSharp it is currently an **offscreen** path — you create the surface, draw, submit, and read back the result yourself. + +If you already have Ganesh code and want to understand the equivalent Graphite calls, see [Migrating from Ganesh to Graphite](graphite-migration.md). + +## In this section + +## [Raster Surfaces](raster-surfaces.md) + +Create CPU-backed surfaces with `SKSurface.Create`, draw into memory you own with a raster-direct surface, and read the result back. + +## [Ganesh GPU Surfaces](ganesh-surfaces.md) + +Create a `GRContext` for OpenGL, Vulkan, Metal, or Direct3D, then make an offscreen surface or wrap an existing render target or texture. + +## [Surfaces in the SkiaSharp Views](views-surfaces.md) + +See which view controls give you a raster surface and which give you a GPU surface, across SkiaSharp.Views, .NET MAUI, Uno Platform, and Blazor. + +## [Graphite Offscreen Surfaces](graphite-surfaces.md) + +Create an `SKGraphiteContext`, record and submit drawing, wrap external GPU textures, and read pixels back through the asynchronous readback path. + +## [Migrating from Ganesh to Graphite](graphite-migration.md) + +Map your existing Ganesh code — context creation, flushing, and readback — onto the Graphite recorder/recording model. + +## Related Links + +- [SkiaSharp APIs](/dotnet/api/skiasharp) diff --git a/documentation/docfx/guides/gpu/raster-surfaces.md b/documentation/docfx/guides/gpu/raster-surfaces.md new file mode 100644 index 000000000000..924378f8f4c5 --- /dev/null +++ b/documentation/docfx/guides/gpu/raster-surfaces.md @@ -0,0 +1,120 @@ +--- +title: "Raster Surfaces" +description: "Create CPU-backed SKSurface objects for offscreen and headless rendering with SkiaSharp, draw into memory you own with a raster-direct surface, and read the result back as an SKImage or encoded bytes." +--- + +# Raster Surfaces + +_Draw into CPU memory with `SKSurface.Create`_ + +A **raster** surface keeps its pixels in ordinary system (CPU) memory. It is the simplest kind of surface, it needs no GPU, and it behaves identically on every platform SkiaSharp supports. Raster surfaces are the right choice whenever you are rendering offscreen: generating images or thumbnails, building a PDF or print pipeline, rendering on a server, or drawing in a unit test. + +## Creating a raster surface + +The most common way to create a raster surface is to describe the image you want with an [`SKImageInfo`](xref:SkiaSharp.SKImageInfo) and let SkiaSharp allocate the pixel buffer for you: + +```csharp +var info = new SKImageInfo(256, 256, SKColorType.Rgba8888, SKAlphaType.Premul); + +using var surface = SKSurface.Create(info); +var canvas = surface.Canvas; + +canvas.Clear(SKColors.White); +canvas.DrawCircle(128, 128, 100, new SKPaint { Color = SKColors.CornflowerBlue }); +``` + +`SKImageInfo` describes the width, height, color type, and alpha type of the surface. `SKColorType.Rgba8888` with `SKAlphaType.Premul` is a common, portable choice, but you can pick whatever format your pipeline needs. + +The [`SKSurface.Create(SKImageInfo)`](xref:SkiaSharp.SKSurface.Create(SkiaSharp.SKImageInfo)) overload returns `null` if the surface could not be created (for example, if the dimensions are invalid), so it's good practice to check the result before using it. + +## Getting the result out + +Once you've finished drawing, there are two common ways to get the pixels back. + +The simplest is to take an immutable snapshot as an [`SKImage`](xref:SkiaSharp.SKImage), which you can then encode to PNG, JPEG, or another format: + +```csharp +using var image = surface.Snapshot(); +using var data = image.Encode(SKEncodedImageFormat.Png, 100); + +using var stream = File.OpenWrite("output.png"); +data.SaveTo(stream); +``` + +If you need the raw pixels rather than an encoded image, read them back into a buffer with [`ReadPixels`](xref:SkiaSharp.SKSurface.ReadPixels*). Because a raster surface already lives in CPU memory, this read is synchronous and cheap: + +```csharp +var info = new SKImageInfo(256, 256, SKColorType.Rgba8888, SKAlphaType.Premul); +var pixels = new byte[info.BytesSize]; + +var handle = GCHandle.Alloc(pixels, GCHandleType.Pinned); +try +{ + surface.ReadPixels(info, handle.AddrOfPinnedObject(), info.RowBytes, 0, 0); +} +finally +{ + handle.Free(); +} +``` + +> [!NOTE] +> Synchronous `ReadPixels` is a raster and Ganesh convenience. Graphite surfaces do **not** support it — see [Graphite Offscreen Surfaces](graphite-surfaces.md#reading-pixels-back). + +## Raster-direct: drawing into memory you own + +The `SKSurface.Create(SKImageInfo)` overload lets Skia allocate the pixel buffer. Sometimes you already have a buffer — a `byte[]`, a native allocation, or the pixels of an [`SKBitmap`](xref:SkiaSharp.SKBitmap) — and you want Skia to draw *directly* into it with no extra copy. That is a **raster-direct** surface. + +Pass the address of your buffer along with the info and row stride: + +```csharp +var info = new SKImageInfo(256, 256, SKColorType.Rgba8888, SKAlphaType.Premul); +var pixels = new byte[info.BytesSize]; +var handle = GCHandle.Alloc(pixels, GCHandleType.Pinned); +try +{ + using var surface = SKSurface.Create(info, handle.AddrOfPinnedObject(), info.RowBytes); + + // every draw call writes straight into `pixels` + surface.Canvas.Clear(SKColors.White); + surface.Canvas.DrawCircle(128, 128, 100, new SKPaint { Color = SKColors.Red }); + surface.Canvas.Flush(); +} +finally +{ + handle.Free(); +} +``` + +> [!IMPORTANT] +> The memory you pass must stay alive and pinned for as long as the surface uses it. If you pin a managed array with `GCHandle`, keep the handle allocated until you are done drawing and have flushed the canvas; freeing it too early lets the garbage collector move the buffer out from under Skia. + +You can also wrap an [`SKPixmap`](xref:SkiaSharp.SKPixmap) — which already bundles an `SKImageInfo` with a pixel pointer — using the [`SKSurface.Create(SKPixmap)`](xref:SkiaSharp.SKSurface.Create(SkiaSharp.SKPixmap)) overload: + +```csharp +using var bitmap = new SKBitmap(info); +using var pixmap = bitmap.PeekPixels(); +using var surface = SKSurface.Create(pixmap); + +surface.Canvas.Clear(SKColors.White); +// ... draw ... +// the pixels are now visible directly in `bitmap` +``` + +This is a convenient way to draw straight into an `SKBitmap` you already have. + +## When to use a raster surface + +Reach for a raster surface when: + +- You are rendering **offscreen** or **headless** — no window, no GPU context. +- You want **deterministic, portable** output that is identical across platforms. +- You are producing images to save, stream, or process further (thumbnails, tiles, reports). +- You need to draw directly into a buffer you already own (raster-direct). + +If you need GPU acceleration — because you are rendering many frames per second, compositing with other GPU content, or drawing very large scenes — use a GPU-backed surface instead. See [Ganesh GPU Surfaces](ganesh-surfaces.md) and [Graphite Offscreen Surfaces](graphite-surfaces.md). + +## Related Links + +- [SkiaSharp APIs](/dotnet/api/skiasharp) +- [Creating and Drawing on Bitmaps](../bitmaps/drawing.md) diff --git a/documentation/docfx/guides/gpu/views-surfaces.md b/documentation/docfx/guides/gpu/views-surfaces.md new file mode 100644 index 000000000000..ed8b5ff2e89d --- /dev/null +++ b/documentation/docfx/guides/gpu/views-surfaces.md @@ -0,0 +1,106 @@ +--- +title: "Surfaces in the SkiaSharp Views" +description: "Understand how the SkiaSharp view controls create and drive an SKSurface for you — which controls give you a CPU raster surface and which give you a GPU surface — across SkiaSharp.Views, .NET MAUI, Uno Platform, and Blazor." +--- + +# Surfaces in the SkiaSharp Views + +_How the SkiaSharp view controls create and drive a surface for you_ + +The [Raster](raster-surfaces.md), [Ganesh](ganesh-surfaces.md), and [Graphite](graphite-surfaces.md) pages show how to create an [`SKSurface`](xref:SkiaSharp.SKSurface) by hand. When you are building an app UI you usually don't need to: the SkiaSharp **view controls** create the surface, size it to the control, and hand it to you in a paint event. Your job is just to draw. + +There are two families of view control, and the difference between them is exactly the difference between the surface types: + +- **Raster views** create a CPU [raster surface](raster-surfaces.md) each frame and blit the result into the control. They work everywhere and need no GPU. +- **GPU views** create and manage a GPU context and a surface that [wraps the control's render target](ganesh-surfaces.md#wrapping-an-existing-render-target), so your drawing goes straight to the GPU and is presented without a CPU copy. + +> [!NOTE] +> The view controls use the **Ganesh** backend for GPU rendering. The newer [Graphite](graphite-surfaces.md) backend is currently an offscreen path and is **not** wired into any view control yet. + +## The paint event + +Whichever control you use, you draw in a `PaintSurface` event. The controls raise one of two event-argument types: + +- [`SKPaintSurfaceEventArgs`](xref:SkiaSharp.Views.Maui.SKPaintSurfaceEventArgs) — raised by **raster** views. It gives you the `Surface`, the `Info` describing it, and the `RawInfo`. +- `SKPaintGLSurfaceEventArgs` — raised by **GPU** views. In addition to the `Surface` it exposes the `BackendRenderTarget`, the `Origin`, and the `ColorType` of the target the view is drawing into. + +In both cases you get an `SKSurface` and draw on its `Surface.Canvas`: + +```csharp +void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e) +{ + var canvas = e.Surface.Canvas; + var info = e.Info; + + canvas.Clear(SKColors.White); + canvas.DrawCircle(info.Width / 2f, info.Height / 2f, 100, new SKPaint + { + Color = SKColors.CornflowerBlue, + }); +} +``` + +The GPU views (`SKGLView`) raise `SKPaintGLSurfaceEventArgs`, but the drawing code is identical — you still just draw on `e.Surface.Canvas`. Choosing a GPU view means the same drawing runs on the GPU. + +## .NET MAUI + +The **SkiaSharp.Views.Maui.Controls** package provides two cross-platform controls that you can place in XAML or build in code: + +| Control | Surface | Paint event | +| --- | --- | --- | +| `SKCanvasView` | Raster (CPU) | `PaintSurface` → `SKPaintSurfaceEventArgs` | +| `SKGLView` | GPU (Ganesh, OpenGL) | `PaintSurface` → `SKPaintGLSurfaceEventArgs` | + +Under the hood, MAUI handlers map these controls to the per-platform SkiaSharp.Views controls below. `SKCanvasView` is the simplest starting point and is what the rest of these guides use; switch to `SKGLView` when you need GPU acceleration. + +> [!IMPORTANT] +> In .NET MAUI you must initialize SkiaSharp by calling `UseSkiaSharp()` on the `MauiAppBuilder` in your `MauiProgram.cs`, with a `using` directive for `SkiaSharp.Views.Maui.Controls.Hosting`. + +## SkiaSharp.Views (per platform) + +The **SkiaSharp.Views** package contains the native controls that MAUI wraps, and that you can use directly in a non-MAUI app on each platform: + +| Platform | Raster control | GPU control(s) | +| --- | --- | --- | +| iOS / macOS / tvOS | `SKCanvasView` | `SKGLView` (OpenGL ES), `SKMetalView` (Metal) | +| Android | `SKCanvasView` | `SKGLSurfaceView`, `SKGLTextureView` (OpenGL ES) | +| Tizen | `SKCanvasView` | `SKGLSurfaceView` (OpenGL ES) | +| Windows (WinUI / UWP) | `SKXamlCanvas` | `SKSwapChainPanel` (ANGLE / OpenGL ES) | + +On Apple platforms, `SKMetalView` is a Metal-backed alternative to the OpenGL `SKGLView`; it raises an `SKPaintMetalSurfaceEventArgs`. The Windows `SKSwapChainPanel` is the GPU counterpart to the raster `SKXamlCanvas`. + +Internally, the GPU controls do exactly what the [Ganesh wrapping example](ganesh-surfaces.md#wrapping-an-existing-render-target) shows: they create a `GRContext`, describe the control's framebuffer as a `GRBackendRenderTarget`, and call `SKSurface.Create(context, renderTarget, origin, colorType)` for you each frame. + +## Uno Platform + +The **SkiaSharp.Views.Uno** package brings the same idea to Uno Platform, mirroring the WinUI control names: + +| Control | Surface | Paint event | +| --- | --- | --- | +| `SKXamlCanvas` | Raster (CPU) | `PaintSurface` → `SKPaintSurfaceEventArgs` | +| `SKSwapChainPanel` | GPU (Ganesh, OpenGL ES) | `PaintSurface` → `SKPaintGLSurfaceEventArgs` | + +Because Uno runs the same controls across its targets (including WebAssembly), `SKXamlCanvas` is the portable raster choice and `SKSwapChainPanel` is the GPU-accelerated one. + +## Blazor + +The **SkiaSharp.Views.Blazor** package provides two Razor components for Blazor WebAssembly: + +| Component | Surface | Backing technology | +| --- | --- | --- | +| `SKCanvasView` | Raster (CPU) | HTML 2D canvas | +| `SKGLView` | GPU (Ganesh) | WebGL | + +`SKCanvasView` draws with a raster surface and copies the result to a 2D canvas; `SKGLView` renders through WebGL with a GPU surface. Use them like any other Razor component and handle their `OnPaintSurface` callback. + +## Choosing a control + +- Start with the **raster** control (`SKCanvasView` / `SKXamlCanvas`). It is the simplest, works everywhere, and is fast enough for most static and lightly-animated UI. +- Move to a **GPU** control (`SKGLView` / `SKMetalView` / `SKSwapChainPanel`) when you are animating continuously, drawing large or complex scenes, or compositing with other GPU content. +- The drawing code you write in the paint event is the **same** either way — only the control type changes. + +## Related Links + +- [SkiaSharp APIs](/dotnet/api/skiasharp) +- [Integrating with .NET MAUI](../basics/integration.md) +- [Ganesh GPU Surfaces](ganesh-surfaces.md) diff --git a/documentation/docfx/guides/index.md b/documentation/docfx/guides/index.md index 064ba9306866..b3f16496ff90 100644 --- a/documentation/docfx/guides/index.md +++ b/documentation/docfx/guides/index.md @@ -47,6 +47,10 @@ Bitmaps are rectangular arrays of bits corresponding to the pixels of a display Effects are properties that alter the normal display of graphics, including linear and circular gradients, bitmap tiling, blend modes, blur, and others. +## [GPU and Offscreen Surfaces](gpu/index.md) + +Go beneath the view controls to create an `SKSurface` yourself: CPU raster surfaces for offscreen and headless rendering, Ganesh GPU surfaces over OpenGL, Vulkan, Metal, and Direct3D, and the new Graphite offscreen backend — including a Ganesh-to-Graphite migration guide. + ## Related Links - [SkiaSharp APIs](https://learn.microsoft.com/dotnet/api/skiasharp) From 8bb3333ff664a141c11e68e1d22f4273cdf90c44 Mon Sep 17 00:00:00 2001 From: Copilot App <223556219+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 21:53:17 +0200 Subject: [PATCH 02/20] docs: refine GPU surface guides per Graphite review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - views: state plainly that the view controls are raster + Ganesh only and that onscreen Graphite views are not yet available (under active investigation), not impossible; add minimal GL and Metal paint snippets alongside the raster one. - graphite-surfaces: add a short "Drawing CPU images: the image provider" section — unlike Ganesh, Graphite does not auto-upload a non-Graphite SKImage, so drawing a CPU image without a provider silently drops the draw; document the CreateRecorder image-provider overload and SKGraphiteImageCache. - graphite-migration: lead with the two biggest behaviour changes (async readback and the image-provider difference) and the structural shift (explicit Recorder -> Snap -> InsertRecording -> Submit; per-thread recorder); add the image provider as an explicit migration step and watch-out. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: bdcc8d8b-500e-41ae-bb71-5e336ac92b1d --- .../docfx/guides/gpu/graphite-migration.md | 21 ++++++++++--- .../docfx/guides/gpu/graphite-surfaces.md | 23 ++++++++++++++ .../docfx/guides/gpu/views-surfaces.md | 31 +++++++++++++++++-- 3 files changed, 68 insertions(+), 7 deletions(-) diff --git a/documentation/docfx/guides/gpu/graphite-migration.md b/documentation/docfx/guides/gpu/graphite-migration.md index 121a33b8f320..d43abce52e8d 100644 --- a/documentation/docfx/guides/gpu/graphite-migration.md +++ b/documentation/docfx/guides/gpu/graphite-migration.md @@ -7,7 +7,12 @@ description: "Map your existing SkiaSharp Ganesh GPU code onto the newer Graphit _Map your existing Ganesh code onto the Graphite model_ -If you already render on the GPU with [Ganesh](ganesh-surfaces.md) — a [`GRContext`](xref:SkiaSharp.GRContext), an `SKSurface`, and `Flush` — this page shows the equivalent [Graphite](graphite-surfaces.md) calls. The concepts line up closely; the two real behavioural changes are the **recorder/recording** drawing model and the **asynchronous** pixel readback. +If you already render on the GPU with [Ganesh](ganesh-surfaces.md) — a [`GRContext`](xref:SkiaSharp.GRContext), an `SKSurface`, and `Flush` — this page shows the equivalent [Graphite](graphite-surfaces.md) calls. The concepts line up closely. Two **behaviour** changes matter most, and they are the parts most likely to bite when you port working code: + +1. **Reading pixels back is asynchronous.** Graphite has no synchronous `SKSurface.ReadPixels`; you use `context.RequestReadPixels(...)` and pump `CheckAsyncWorkCompletion()`. +2. **CPU images need an image provider.** Ganesh auto-uploads a raster `SKImage` when you draw it; Graphite does not — without a provider the draw is silently dropped. + +There is also a **structural** shift: Ganesh records a draw stream on one `GRContext` and auto-flushes it, whereas Graphite is explicit — you record into a `Recorder`, `Snap` a `Recording`, `InsertRecording`, then `Submit`. The `Recorder` is **per-thread**, so a multi-threaded renderer gives each thread its own recorder. > [!NOTE] > Graphite is currently an **offscreen** path in SkiaSharp. If your Ganesh code renders into a view control's render target (`SKGLView`, `SKMetalView`, `SKSwapChainPanel`), there is no Graphite equivalent for that view yet — the [view controls](views-surfaces.md) still use Ganesh. This migration applies to offscreen rendering. @@ -24,6 +29,7 @@ If you already render on the GPU with [Ganesh](ganesh-surfaces.md) — a [`GRCon | Draw on `surface.Canvas` | Draw on `surface.Canvas` (unchanged) | | `context.Flush(submit: true, synchronous: true)` | `recorder.Snap()` + `context.InsertRecording(recording)` + `context.Submit(new SKGraphiteSubmitInfo { Sync = true })` | | `surface.ReadPixels(...)` (synchronous) | `context.RequestReadPixels(...)` + `context.CheckAsyncWorkCompletion()` (asynchronous) | +| Draw a CPU `SKImage` (auto-uploaded) | Draw a CPU `SKImage` (needs an image provider — see below) | | `GRBackendTexture` / `SKSurface.Create(context, texture, ...)` | `SKGraphiteBackendTexture` / `SKSurface.Create(recorder, backendTexture, colorType)` | | `SKImage.FromTexture(context, texture, ...)` | `SKImage.FromTexture(recorder, backendTexture, ...)` | | `image.ToTextureImage(context)` | `image.ToTextureImage(recorder)` | @@ -85,19 +91,23 @@ var pixels = ReadPixelsAsync(context, surface, info); The drawing calls are identical. What changes is the plumbing around them. -## The three changes to make +## The changes to make ### 1. Replace `Flush` with snap + insert + submit Ganesh flushes the context directly. Graphite splits this into three steps: `recorder.Snap()` captures the recorded commands into an `SKGraphiteRecording`, `context.InsertRecording(recording)` hands them to the context, and `context.Submit(new SKGraphiteSubmitInfo { Sync = true })` sends them to the GPU and (with `Sync = true`) waits. -Always check that `InsertRecording` returns `SKGraphiteInsertStatus.Success`, and note that `Snap` **resets** the recorder for the next frame. +Always check that `InsertRecording` returns `SKGraphiteInsertStatus.Success`, and note that `Snap` **resets** the recorder for the next frame. Because the recorder is per-thread, a multi-threaded renderer creates one recorder per thread and submits their recordings to the shared context. ### 2. Replace synchronous `ReadPixels` with asynchronous readback This is the most important change. Graphite surfaces do **not** support synchronous [`SKSurface.ReadPixels`](xref:SkiaSharp.SKSurface.ReadPixels*) in shipping builds — it returns `false`. Replace it with `RequestReadPixels`, then drive the request to completion with `Submit` and repeated `CheckAsyncWorkCompletion` calls. The returned plane may be row-padded, so copy row-by-row. See [Reading pixels back](graphite-surfaces.md#reading-pixels-back) for the complete helper. -### 3. Pass the recorder where you used to pass the context +### 3. Give the recorder an image provider for CPU images + +Ganesh silently uploads a raster `SKImage` to the GPU the first time you draw it. Graphite does **not** — drawing a non-Graphite image without an *image provider* drops the draw with no error. If your Ganesh code draws decoded/CPU images, create the recorder with an image provider (the ready-made `SKGraphiteImageCache` is the simplest option), or upload each image yourself with `ToTextureImage` first. See [Drawing CPU images](graphite-surfaces.md#drawing-cpu-images-the-image-provider). + +### 4. Pass the recorder where you used to pass the context Per-surface and per-image creation moves from the context to the recorder: @@ -109,9 +119,10 @@ Per-surface and per-image creation moves from the context to the recorder: ## Watch out for - **No OpenGL or Direct3D.** Graphite targets Vulkan, Metal, and Dawn. If your Ganesh code uses GL or D3D, there is no direct Graphite equivalent; keep using Ganesh, or move to Vulkan/Metal/Dawn. +- **CPU images need a provider.** The single easiest thing to miss — a raster `SKImage` drawn without an image provider simply doesn't appear. See change 3 above. - **Browser (Dawn/WebGPU) can't submit synchronously.** In a WebAssembly host, `Submit(Sync = true)` throws. Submit without syncing and pump `CheckAsyncWorkCompletion`. See [Dawn in the browser](graphite-surfaces.md#dawn-in-the-browser). - **Check backend availability.** Use `SKGraphiteContext.IsBackendAvailable` before creating a context, since not every build includes every backend. -- **Threading is unchanged.** As with Ganesh, the context, recorders, and surfaces are single-threaded — use them on the thread that owns the graphics device. +- **The recorder is per-thread.** As with Ganesh, the context, recorders, and surfaces are single-threaded — use a recorder only on the thread that created it, and give each rendering thread its own. ## Related Links diff --git a/documentation/docfx/guides/gpu/graphite-surfaces.md b/documentation/docfx/guides/gpu/graphite-surfaces.md index 5dc2df47816b..f51776894194 100644 --- a/documentation/docfx/guides/gpu/graphite-surfaces.md +++ b/documentation/docfx/guides/gpu/graphite-surfaces.md @@ -215,6 +215,29 @@ You can also move between GPU textures and [`SKImage`](xref:SkiaSharp.SKImage) o using var gpuImage = cpuImage.ToTextureImage(recorder); ``` +## Drawing CPU images: the image provider + +> [!IMPORTANT] +> Unlike Ganesh, Graphite does **not** automatically upload a non-Graphite `SKImage` to the GPU. If you draw a raster/CPU-backed `SKImage` — for example one you decoded with `SKImage.FromEncodedData` — onto a Graphite surface **without an image provider, the draw is silently dropped**: nothing appears and no error is raised. + +There are two ways to handle this. You can upload each image yourself with [`ToTextureImage`](#using-textures-as-images) and draw the GPU-backed result. Or you can give the recorder an *image provider* callback that uploads CPU images on demand, so ordinary `DrawImage` calls just work. + +Pass the callback to the `CreateRecorder` overload that accepts one. SkiaSharp ships a ready-made `SKGraphiteImageCache` whose `FindOrCreate` method implements the callback (uploading via `ToTextureImage`) and caches the results so repeated draws of the same image don't re-upload: + +```csharp +var imageCache = new SKGraphiteImageCache(); + +using var recorder = context.CreateRecorder( + recorderBudgetBytes: -1, + findOrCreate: imageCache.FindOrCreate, // uploads + caches CPU images on demand + findOrCreateDispose: imageCache.Dispose); // released with the recorder + +using var surface = SKSurface.Create(recorder, info); +surface.Canvas.DrawImage(cpuImage, 0, 0); // now uploaded through the provider +``` + +The callback has the signature `SKImage SKGraphiteFindOrCreateImageDelegate(SKGraphiteRecorder recorder, SKImage image, bool mipmapped)`, and returning `null` drops that image's draw. Provide your own delegate if you want custom upload or caching behaviour; otherwise `SKGraphiteImageCache` is the simplest correct default. + ## Dawn in the browser When Graphite runs over Dawn in a browser/WebAssembly host, the Dawn event loop cannot be pumped from inside a managed call, so **synchronous submission is not allowed**. Calling `Submit` with `Sync = true` there throws an `InvalidOperationException`. diff --git a/documentation/docfx/guides/gpu/views-surfaces.md b/documentation/docfx/guides/gpu/views-surfaces.md index ed8b5ff2e89d..8e670868b971 100644 --- a/documentation/docfx/guides/gpu/views-surfaces.md +++ b/documentation/docfx/guides/gpu/views-surfaces.md @@ -15,7 +15,7 @@ There are two families of view control, and the difference between them is exact - **GPU views** create and manage a GPU context and a surface that [wraps the control's render target](ganesh-surfaces.md#wrapping-an-existing-render-target), so your drawing goes straight to the GPU and is presented without a CPU copy. > [!NOTE] -> The view controls use the **Ganesh** backend for GPU rendering. The newer [Graphite](graphite-surfaces.md) backend is currently an offscreen path and is **not** wired into any view control yet. +> The view controls are **raster + Ganesh only** today. **None of them drive Graphite yet** — in this release [Graphite](graphite-surfaces.md) is an offscreen-only path with no view control. Onscreen Graphite views are **not yet available and are under active investigation**, so treat this as "not wired up yet," not "impossible." ## The paint event @@ -40,7 +40,34 @@ void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e) } ``` -The GPU views (`SKGLView`) raise `SKPaintGLSurfaceEventArgs`, but the drawing code is identical — you still just draw on `e.Surface.Canvas`. Choosing a GPU view means the same drawing runs on the GPU. +The GPU views raise a different event-argument type, but the drawing code is identical — you still just draw on `e.Surface.Canvas`. A **GL view** (`SKGLView` / `SKGLSurfaceView`) raises `SKPaintGLSurfaceEventArgs`: + +```csharp +void OnPaintGLSurface(object sender, SKPaintGLSurfaceEventArgs e) +{ + var canvas = e.Surface.Canvas; + var info = e.Info; + + canvas.Clear(SKColors.White); + canvas.DrawCircle(info.Width / 2f, info.Height / 2f, 100, new SKPaint + { + Color = SKColors.CornflowerBlue, + }); +} +``` + +On Apple platforms, a **Metal view** (`SKMetalView`) is the Metal-backed alternative and raises `SKPaintMetalSurfaceEventArgs` — again, the same drawing: + +```csharp +void OnPaintMetalSurface(object sender, SKPaintMetalSurfaceEventArgs e) +{ + var canvas = e.Surface.Canvas; + canvas.Clear(SKColors.White); + // draw exactly as with the raster and GL views +} +``` + +Choosing a GPU view means the same drawing runs on the GPU. ## .NET MAUI From 962f061d46e54f0adc4dfa9c96cfd2cbe6256f9a Mon Sep 17 00:00:00 2001 From: Copilot App <223556219+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 21:57:42 +0200 Subject: [PATCH 03/20] docs: mention SKGraphiteAsyncReadResult.CopyPlaneTo in readback note Point readers at the padding-aware CopyPlaneTo convenience alongside the manual row-by-row copy in the Graphite async-readback example. The manual loop stays, since it makes the row-padding gotcha explicit. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: bdcc8d8b-500e-41ae-bb71-5e336ac92b1d --- documentation/docfx/guides/gpu/graphite-surfaces.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docfx/guides/gpu/graphite-surfaces.md b/documentation/docfx/guides/gpu/graphite-surfaces.md index f51776894194..453fce0e77e7 100644 --- a/documentation/docfx/guides/gpu/graphite-surfaces.md +++ b/documentation/docfx/guides/gpu/graphite-surfaces.md @@ -177,7 +177,7 @@ if (!done || pixels is null) throw new InvalidOperationException("Graphite async readback did not complete."); ``` -The callback receives an `SKGraphiteAsyncReadResult` whose planes may be **row-padded**, so copy row-by-row using `GetPlaneRowBytes(0)` rather than assuming tightly packed pixels. A shorter `RequestReadPixels` overload uses default rescaling; a longer overload lets you pass an [`SKGraphiteRescaleGamma`](#status-and-enums) and [`SKGraphiteRescaleMode`](#status-and-enums) when you want the read to also rescale the image. +The callback receives an `SKGraphiteAsyncReadResult` whose planes may be **row-padded**, so copy row-by-row using `GetPlaneRowBytes(0)` rather than assuming tightly packed pixels. If you don't need to see the padding handling spelled out, `SKGraphiteAsyncReadResult.CopyPlaneTo(planeIndex, destination, rowCount)` performs exactly this padding-aware copy for you; the manual loop above is shown to make the row padding explicit. A shorter `RequestReadPixels` overload uses default rescaling; a longer overload lets you pass an [`SKGraphiteRescaleGamma`](#status-and-enums) and [`SKGraphiteRescaleMode`](#status-and-enums) when you want the read to also rescale the image. ## Wrapping an external GPU texture From 4e4ab147c244c6f4fa8a2af7b81ac9b0666f68a4 Mon Sep 17 00:00:00 2001 From: Copilot App <223556219+Copilot@users.noreply.github.com> Date: Fri, 24 Jul 2026 20:33:55 +0200 Subject: [PATCH 04/20] docs: fold in Graphite backend learnings (platforms, Silk.NET, gotchas) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the GPU/surface guides with new learnings from the Graphite feature branch, verified against the current dev/graphite-backend bindings and tests: - Add a Graphite backend->platform support matrix: Metal on Apple (incl. the Apple-Silicon iOS/tvOS Simulator), Vulkan on Linux/Android/Windows, Dawn on WASM. State explicitly there is no Direct3D Graphite backend (Windows Graphite = Vulkan); reflect this in the index comparison table and migration guide. - Switch Vulkan guidance from SharpVk to Silk.NET. Ganesh gains the typed GRSilkNetBackendContext (SkiaSharp.Vulkan.Silk.NET); SharpVk is noted as legacy/unmaintained and Windows/Linux-only. Graphite has no typed Vulkan wrapper — feed the binding-neutral SKGraphiteVkBackendContext raw .Handle values from Silk.NET (or raw libvulkan). Removes the now-deleted SKGraphiteSharpVkBackendContext from the docs. - Document the Vulkan surface gotcha: wrapping a VkImage as a Graphite surface requires ImageUsageFlags to include COLOR_ATTACHMENT (0x10) AND INPUT_ATTACHMENT (0x80) or SKSurface.Create returns null; sample-only images need only SAMPLED (typical renderable mask 0x97). - Document the parameterless SKGraphiteReleaseDelegate overloads on the Graphite wrap-backend-texture SKSurface.Create and SKImage.FromTexture paths. - Add an iOS Simulator note (Graphite Metal works despite the simulator MTLDevice under-reporting its GPU family) and a WASM Dawn bring-up note (a real WGPUInstance must parent the device/queue or CreateDawn deadlocks). Docs-only; code samples mirror the current Graphite/Ganesh renderers and the release/usage Vulkan tests on dev/graphite-backend. Links, anchors, TOC YAML, and xrefs re-validated (new types remain inline code, not xrefs). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: bdcc8d8b-500e-41ae-bb71-5e336ac92b1d --- .../docfx/guides/gpu/ganesh-surfaces.md | 21 +++--- .../docfx/guides/gpu/graphite-migration.md | 6 +- .../docfx/guides/gpu/graphite-surfaces.md | 65 ++++++++++++++++--- documentation/docfx/guides/gpu/index.md | 3 +- 4 files changed, 75 insertions(+), 20 deletions(-) diff --git a/documentation/docfx/guides/gpu/ganesh-surfaces.md b/documentation/docfx/guides/gpu/ganesh-surfaces.md index 1b5431e8ee73..68f61c47a77e 100644 --- a/documentation/docfx/guides/gpu/ganesh-surfaces.md +++ b/documentation/docfx/guides/gpu/ganesh-surfaces.md @@ -49,23 +49,28 @@ using var backendContext = new GRVkBackendContext using var context = GRContext.CreateVulkan(backendContext); ``` -If you use the [SharpVk](https://www.nuget.org/packages/SharpVk) managed Vulkan binding, the **SkiaSharp.Vulkan.SharpVk** package provides a typed `GRSharpVkBackendContext` that accepts SharpVk objects directly instead of raw handles, so you don't have to marshal `IntPtr`s yourself: +For a typed binding that hands you `IntPtr`s to fill in, the recommended managed Vulkan binding is [Silk.NET](https://www.nuget.org/packages/Silk.NET.Vulkan). The **SkiaSharp.Vulkan.Silk.NET** package provides a typed `GRSilkNetBackendContext` that accepts Silk.NET objects directly, so you don't marshal handles yourself: ```csharp -using var backendContext = new GRSharpVkBackendContext +using Silk.NET.Vulkan; + +using var backendContext = new GRSilkNetBackendContext { - VkInstance = instance, - VkPhysicalDevice = physicalDevice, - VkDevice = device, - VkQueue = queue, + VkInstance = instance, // Silk.NET.Vulkan.Instance + VkPhysicalDevice = physicalDevice, // PhysicalDevice + VkDevice = device, // Device + VkQueue = graphicsQueue, // Queue GraphicsQueueIndex = graphicsFamily, - GetProcedureAddress = (name, instance, device) => /* ... */, - VkPhysicalDeviceFeatures = physicalDevice.GetFeatures(), + GetProcedureAddress = getProc, // (name, Instance, Device) => IntPtr + VkPhysicalDeviceFeatures = features, // PhysicalDeviceFeatures }; using var context = GRContext.CreateVulkan(backendContext); ``` +> [!NOTE] +> Silk.NET is the maintained, cross-platform binding and is the recommended choice for new Vulkan code. An older **SkiaSharp.Vulkan.SharpVk** package with a `GRSharpVkBackendContext` still exists, but SharpVk is effectively unmaintained and only works on Windows and Linux (it throws on Android). Because `GRVkBackendContext` takes raw handles, you can also pair it with any other binding — or raw `libvulkan` P/Invoke — without a wrapper package. + ### Metal On Apple platforms, build a [`GRMtlBackendContext`](xref:SkiaSharp.GRMtlBackendContext) from an `MTLDevice` and an `MTLCommandQueue`. On the Apple target frameworks you can assign the typed `IMTLDevice`/`IMTLCommandQueue` objects; from other targets, assign their native handles: diff --git a/documentation/docfx/guides/gpu/graphite-migration.md b/documentation/docfx/guides/gpu/graphite-migration.md index d43abce52e8d..16485b29697b 100644 --- a/documentation/docfx/guides/gpu/graphite-migration.md +++ b/documentation/docfx/guides/gpu/graphite-migration.md @@ -24,7 +24,7 @@ There is also a **structural** shift: Ganesh records a draw stream on one `GRCon | `GRContext` | `SKGraphiteContext` | | `GRContext.CreateGl` / `CreateVulkan` / `CreateMetal` / `CreateDirect3D` | `SKGraphiteContext.CreateVulkan` / `CreateMetal` / `CreateDawn` | | `GRVkBackendContext` / `GRMtlBackendContext` | `SKGraphiteVkBackendContext` / `SKGraphiteMtlBackendContext` / `SKGraphiteDawnBackendContext` | -| `GRSharpVkBackendContext` (typed Vulkan) | `SKGraphiteSharpVkBackendContext` (typed Vulkan) | +| `GRSilkNetBackendContext` (typed Vulkan, Silk.NET) — or legacy `GRSharpVkBackendContext` | No typed Graphite wrapper — fill `SKGraphiteVkBackendContext` with raw handles (e.g. Silk.NET `.Handle` values) | | `SKSurface.Create(context, budgeted, info)` | `context.CreateRecorder()` + `SKSurface.Create(recorder, info)` | | Draw on `surface.Canvas` | Draw on `surface.Canvas` (unchanged) | | `context.Flush(submit: true, synchronous: true)` | `recorder.Snap()` + `context.InsertRecording(recording)` + `context.Submit(new SKGraphiteSubmitInfo { Sync = true })` | @@ -118,7 +118,9 @@ Per-surface and per-image creation moves from the context to the recorder: ## Watch out for -- **No OpenGL or Direct3D.** Graphite targets Vulkan, Metal, and Dawn. If your Ganesh code uses GL or D3D, there is no direct Graphite equivalent; keep using Ganesh, or move to Vulkan/Metal/Dawn. +- **No OpenGL or Direct3D.** Graphite targets Vulkan, Metal, and Dawn. There is no Direct3D Graphite backend — on Windows, Graphite means Vulkan. If your Ganesh code uses GL or D3D, there is no direct Graphite equivalent; keep using Ganesh, or move to Vulkan/Metal/Dawn. +- **Apple uses Metal, not Vulkan.** On macOS/iOS/Mac Catalyst/tvOS the only Graphite backend is Metal; Vulkan Graphite is Linux/Android/Windows. See the [platform matrix](graphite-surfaces.md#backend-platform-support). +- **New Vulkan code should use Silk.NET.** For both Ganesh and Graphite, prefer Silk.NET (or raw `libvulkan`) over the unmaintained, Windows/Linux-only SharpVk. For Graphite there is no typed wrapper at all — feed raw handles into `SKGraphiteVkBackendContext`. - **CPU images need a provider.** The single easiest thing to miss — a raster `SKImage` drawn without an image provider simply doesn't appear. See change 3 above. - **Browser (Dawn/WebGPU) can't submit synchronously.** In a WebAssembly host, `Submit(Sync = true)` throws. Submit without syncing and pump `CheckAsyncWorkCompletion`. See [Dawn in the browser](graphite-surfaces.md#dawn-in-the-browser). - **Check backend availability.** Use `SKGraphiteContext.IsBackendAvailable` before creating a context, since not every build includes every backend. diff --git a/documentation/docfx/guides/gpu/graphite-surfaces.md b/documentation/docfx/guides/gpu/graphite-surfaces.md index 453fce0e77e7..bd6223e5847f 100644 --- a/documentation/docfx/guides/gpu/graphite-surfaces.md +++ b/documentation/docfx/guides/gpu/graphite-surfaces.md @@ -19,6 +19,22 @@ Graphite supports three backends: **Vulkan**, **Metal**, and **Dawn** (WebGPU). > [!NOTE] > Like Ganesh, an `SKGraphiteContext`, its recorders, and its surfaces are not thread-safe. Create and use them on a single thread that owns the underlying graphics device. +## Backend platform support + +Which Graphite backend you use is determined by the platform: + +| Backend | Platforms | +| --- | --- | +| **Metal** | macOS, iOS (including the iOS Simulator on Apple Silicon), Mac Catalyst, tvOS | +| **Vulkan** | Linux, Android, Windows | +| **Dawn** (WebGPU) | WebAssembly / browser only | + +A few consequences worth calling out: + +- **Apple platforms use Metal, not Vulkan.** The native Skia build for Apple is not compiled with Vulkan, so on macOS/iOS/Mac Catalyst/tvOS the only Graphite backend is Metal. +- **On Windows, Graphite means Vulkan.** There is **no Direct3D Graphite backend** in SkiaSharp — a D3D path would only exist as Graphite→Dawn→D3D12, which is not exposed. If you need D3D specifically, use [Ganesh](ganesh-surfaces.md) with `GRContext.CreateDirect3D`. +- **Dawn is browser-only.** It is the WebAssembly path and carries an extra submission constraint — see [Dawn in the browser](#dawn-in-the-browser). + ## Checking a backend is available A given build of SkiaSharp may not include every Graphite backend. Before creating a context, you can check whether a backend is compiled in with `SKGraphiteContext.IsBackendAvailable`: @@ -36,7 +52,7 @@ You create a context from a backend context that carries the native device objec ### Vulkan -Fill in a `SKGraphiteVkBackendContext` with your instance, physical device, device, queue, and graphics-queue index, plus a `GetProcedureAddress` delegate that resolves Vulkan functions: +Graphite Vulkan is available on **Linux, Android, and Windows** (not Apple — see the [platform matrix](#backend-platform-support)). There is no typed Graphite-specific Vulkan wrapper: you fill in the binding-neutral `SKGraphiteVkBackendContext` with raw handles and a `GetProcedureAddress` delegate that resolves Vulkan functions. ```csharp using var backendContext = new SKGraphiteVkBackendContext @@ -46,28 +62,35 @@ using var backendContext = new SKGraphiteVkBackendContext VkDevice = deviceHandle, VkQueue = graphicsQueueHandle, GraphicsQueueIndex = graphicsFamilyIndex, - GetProcedureAddress = (name, instance, device) => /* vkGetXxxProcAddr */, + MaxApiVersion = apiVersion, + GetProcedureAddress = (name, instance, device) => /* vkGetInstance/DeviceProcAddr */, }; using var context = SKGraphiteContext.CreateVulkan(backendContext); ``` -If you use the [SharpVk](https://www.nuget.org/packages/SharpVk) managed binding, the **SkiaSharp.Vulkan.SharpVk** package ships a typed `SKGraphiteSharpVkBackendContext` that accepts SharpVk objects directly: +Because the handles are raw `IntPtr`s, you can source them from any Vulkan binding. The recommended one is [Silk.NET](https://www.nuget.org/packages/Silk.NET.Vulkan) — feed its objects' `.Handle` values straight in: ```csharp -using var backendContext = new SKGraphiteSharpVkBackendContext +using Silk.NET.Vulkan; + +using var backendContext = new SKGraphiteVkBackendContext { - VkInstance = instance, - VkPhysicalDevice = physicalDevice, - VkDevice = device, - VkQueue = queue, + VkInstance = instance.Handle, + VkPhysicalDevice = physicalDevice.Handle, + VkDevice = device.Handle, + VkQueue = graphicsQueue.Handle, GraphicsQueueIndex = graphicsFamily, - GetProcedureAddress = (name, instance, device) => /* ... */, + MaxApiVersion = apiVersion, + GetProcedureAddress = getProc, }; using var context = SKGraphiteContext.CreateVulkan(backendContext); ``` +> [!NOTE] +> Steer new Vulkan code to Silk.NET (or raw `libvulkan` P/Invoke). The older SharpVk binding is unmaintained and Windows/Linux-only (it throws on Android), and there is no SharpVk wrapper for the Graphite path — Graphite always takes the raw handles above. + ### Metal On Apple platforms, supply an `MTLDevice` and `MTLCommandQueue`. From the Apple target frameworks you can assign the typed `Device`/`Queue` (`IMTLDevice`/`IMTLCommandQueue`) properties; from other targets, assign the native handles: @@ -82,6 +105,9 @@ using var backendContext = new SKGraphiteMtlBackendContext using var context = SKGraphiteContext.CreateMetal(backendContext); ``` +> [!NOTE] +> Graphite Metal works on the **iOS and tvOS Simulator on Apple Silicon** (it is backed by the host's Apple-Silicon GPU). Be aware that the simulator's `MTLDevice` under-reports its capabilities — it advertises only `Apple1`/`Apple2`/`Common1`, not `Apple7+`/`Mac2` — so a naive `supportsFamily:` capability gate would wrongly skip it even though rendering works. Don't gate simulator support on the reported GPU family. + ### Dawn (WebGPU) For Dawn, supply the WebGPU instance, device, and queue handles: @@ -198,6 +224,22 @@ There are matching factory methods for each backend: - `SKGraphiteBackendTexture.CreateMetal(width, height, mtlTexture)` - `SKGraphiteBackendTexture.CreateDawn(wgpuTexture)` +> [!IMPORTANT] +> **Vulkan surfaces need input-attachment usage.** When you wrap an externally-created Vulkan `VkImage` as a Graphite **surface** (a render target), the texture's `SKGraphiteVkTextureInfo.ImageUsageFlags` must include **both** `VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT` (`0x10`) **and** `VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT` (`0x80`). Skia Graphite requires input-attachment usage on every color-renderable Vulkan texture; without it, `SKSurface.Create` returns **null** (the texture is not considered renderable). A typical renderable usage mask is `TRANSFER_SRC | TRANSFER_DST | SAMPLED | COLOR_ATTACHMENT | INPUT_ATTACHMENT` = `0x97`. This applies to **surfaces only** — a texture you only *sample* from as an image (see [`SKImage.FromTexture`](#using-textures-as-images)) needs just `SAMPLED`. + +### Releasing a wrapped texture + +When Skia is done with a wrapped backend texture it can notify you so you can free the caller-owned native texture. The wrap overloads accept a parameterless `SKGraphiteReleaseDelegate` that fires **exactly once**, when Skia destroys the wrapped texture (on dispose of the wrapping surface or image, after the GPU work has drained): + +```csharp +using var surface = SKSurface.Create( + recorder, backendTexture, SKColorType.Rgba8888, + colorSpace: null, props: null, + releaseProc: () => FreeMyNativeTexture()); +``` + +`SKImage.FromTexture` has the same release-callback overload for the image path. + ## Using textures as images You can also move between GPU textures and [`SKImage`](xref:SkiaSharp.SKImage) objects on a recorder: @@ -209,6 +251,8 @@ You can also move between GPU textures and [`SKImage`](xref:SkiaSharp.SKImage) o recorder, backendTexture, SKColorType.Rgba8888, SKAlphaType.Premul); ``` + A longer overload also takes a color space and a parameterless `SKGraphiteReleaseDelegate` that fires once when Skia releases the wrapped texture. + - [`ToTextureImage`](xref:SkiaSharp.SKImage.ToTextureImage*) uploads an existing image (for example, one decoded on the CPU) into a GPU-backed image on the recorder: ```csharp @@ -253,6 +297,9 @@ context.Submit(new SKGraphiteSubmitInfo { Sync = false }); context.CheckAsyncWorkCompletion(); ``` +> [!NOTE] +> **Dawn bring-up on WASM.** When building the `SKGraphiteDawnBackendContext` in the browser (the emdawnwebgpu port), you must create a **real** `WGPUInstance` via `wgpuCreateInstance` and register the device and queue under *that* instance as their event-source parent. If the instance is a placeholder or the device/queue are registered under a different instance, `SKGraphiteContext.CreateDawn` deadlocks — emdawnwebgpu's event manager waits on a mismatched instance and never completes. + ## Context options The `Create*` factories accept an optional `SKGraphiteContextOptions`. The most commonly useful field is `InternalMultisampleCount` (the internal MSAA sample count), which must be `0` (use Skia's default) or one of `1`, `2`, `4`, `8`, or `16`; other values are rejected. Other options include a GPU byte budget and driver-workaround toggles. diff --git a/documentation/docfx/guides/gpu/index.md b/documentation/docfx/guides/gpu/index.md index 9dd79c0f6ff7..1feeb1c75ecb 100644 --- a/documentation/docfx/guides/gpu/index.md +++ b/documentation/docfx/guides/gpu/index.md @@ -30,7 +30,8 @@ Both Ganesh and Graphite are GPU backends that render into an `SKSurface`, but t | | Ganesh | Graphite | | --- | --- | --- | | Context type | [`GRContext`](xref:SkiaSharp.GRContext) | `SKGraphiteContext` | -| Backends | OpenGL, Vulkan, Metal, Direct3D | Vulkan, Metal, Dawn (WebGPU) | +| Backends | OpenGL, Vulkan, Metal, Direct3D | Vulkan, Metal, Dawn (WebGPU) — no Direct3D | +| Platforms | All | Metal on Apple; Vulkan on Linux/Android/Windows; Dawn on WASM | | Drawing model | Draw on the canvas, then `Flush`/`Submit` the context | Draw on the canvas, then `Snap` a recording and `InsertRecording` + `Submit` it | | Reading pixels back | Synchronous `SKSurface.ReadPixels` works | **Asynchronous only** — use `SKGraphiteContext.RequestReadPixels` | | In the SkiaSharp Views | Yes (`SKGLView`, `SKMetalView`) | Not yet — offscreen only | From 8995e955e5b086065683a58b09f919271b1699b0 Mon Sep 17 00:00:00 2001 From: Copilot App <223556219+Copilot@users.noreply.github.com> Date: Fri, 24 Jul 2026 20:45:33 +0200 Subject: [PATCH 05/20] docs: flesh out the Graphite wrap+draw+release loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expand the "Releasing a wrapped texture" section into a full, verified end-to-end example, and document the key subtlety: the SKGraphiteReleaseDelegate fires only after the wrapping surface/image is disposed AND pending GPU work has drained — so callers must pump Submit(Sync=true) + CheckAsyncWorkCompletion and call FreeGpuResources to force it. Includes creating a renderable Vulkan backend texture (ImageUsageFlags 0x97) and freeing it with DeleteBackendTexture. Notes that SKImage.FromTexture's release callback fires the same way and that sample-only images need only SAMPLED usage. All types/members mirror the shipping bindings (SKGraphiteVkTextureInfo, SKGraphiteTextureInfo.CreateVulkan, recorder.CreateBackendTexture/ DeleteBackendTexture, context.FreeGpuResources) and the passing release tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: bdcc8d8b-500e-41ae-bb71-5e336ac92b1d --- .../docfx/guides/gpu/graphite-surfaces.md | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/documentation/docfx/guides/gpu/graphite-surfaces.md b/documentation/docfx/guides/gpu/graphite-surfaces.md index bd6223e5847f..90558ccaa310 100644 --- a/documentation/docfx/guides/gpu/graphite-surfaces.md +++ b/documentation/docfx/guides/gpu/graphite-surfaces.md @@ -229,16 +229,51 @@ There are matching factory methods for each backend: ### Releasing a wrapped texture -When Skia is done with a wrapped backend texture it can notify you so you can free the caller-owned native texture. The wrap overloads accept a parameterless `SKGraphiteReleaseDelegate` that fires **exactly once**, when Skia destroys the wrapped texture (on dispose of the wrapping surface or image, after the GPU work has drained): +When Skia is done with a wrapped backend texture it can notify you so you can free the caller-owned native texture. The wrap overloads accept a parameterless `SKGraphiteReleaseDelegate`. + +> [!IMPORTANT] +> The release callback fires when Skia destroys **its** reference to the wrapped texture — which is **after** the wrapping surface (or image) is disposed **and** the pending GPU work has drained. Disposing the surface alone is not enough. To force the callback to run, submit and pump completion, then free cached GPU resources: ```csharp -using var surface = SKSurface.Create( - recorder, backendTexture, SKColorType.Rgba8888, - colorSpace: null, props: null, - releaseProc: () => FreeMyNativeTexture()); +using var context = SKGraphiteContext.CreateVulkan(backendContext); +using var recorder = context.CreateRecorder(); + +// A RENDERABLE backend texture needs COLOR_ATTACHMENT (0x10) + INPUT_ATTACHMENT (0x80). +var vkInfo = new SKGraphiteVkTextureInfo +{ + Format = 37, // VK_FORMAT_R8G8B8A8_UNORM + ImageTiling = 0, // VK_IMAGE_TILING_OPTIMAL + SampleCount = 1, + AspectMask = 1, // VK_IMAGE_ASPECT_COLOR_BIT + SharingMode = 0, // VK_SHARING_MODE_EXCLUSIVE + ImageUsageFlags = 0x1 | 0x2 | 0x4 | 0x10 | 0x80, // = 0x97 +}; +using var texInfo = SKGraphiteTextureInfo.CreateVulkan(vkInfo); +using var backendTexture = recorder.CreateBackendTexture(width, height, texInfo); + +var released = false; +using (var surface = SKSurface.Create( + recorder, backendTexture, SKColorType.Rgba8888, + colorSpace: null, props: null, + releaseProc: () => released = true)) +{ + surface.Canvas.Clear(SKColors.Red); + using var recording = recorder.Snap(); + context.InsertRecording(recording); + context.Submit(new SKGraphiteSubmitInfo { Sync = true }); +} // surface disposed here — but the texture is not released yet + +// Drain deferred GPU work so Skia actually destroys the wrapped texture → releaseProc fires +context.Submit(new SKGraphiteSubmitInfo { Sync = true }); +for (var i = 0; i < 100; i++) + context.CheckAsyncWorkCompletion(); +context.FreeGpuResources(); +// released == true + +recorder.DeleteBackendTexture(backendTexture); // free the caller-owned texture ``` -`SKImage.FromTexture` has the same release-callback overload for the image path. +The example above uses `recorder.CreateBackendTexture` for brevity; to wrap a texture your own code allocated, build the `SKGraphiteBackendTexture` with `SKGraphiteBackendTexture.CreateVulkan/CreateMetal/CreateDawn` instead — the release flow is identical. `SKImage.FromTexture` has the same release-callback overload and fires the same way (on image dispose plus GPU drain); a sample-only image needs only `SAMPLED` usage, not `INPUT_ATTACHMENT`. ## Using textures as images From a7deaca9d11c2bd2db1a0bf9544902f6ab3ca760 Mon Sep 17 00:00:00 2001 From: Copilot App <223556219+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 02:00:22 +0200 Subject: [PATCH 06/20] docs: reconcile Graphite read-back prose with SKImageReadPixelsResult The async read-back API changed on the feature branch: SKGraphiteAsyncReadResult was removed in favour of the backend-neutral SKImageReadPixelsResult, and the rescale enums are now SKImageRescaleGamma / SKImageRescaleMode (default Src / Nearest, was Src / RepeatedLinear). - Rewrite the graphite-surfaces "Reading pixels back" example to use the new result type and its ToArray()/ToBitmap()/CopyPlaneTo(span) helpers instead of the old IntPtr + manual Marshal.Copy row loop; note the result is IDisposable and callback-scoped. - Update the "Status and enums" list to the renamed neutral enums and add SKImageReadPixelsResult; correct the default rescale mode to Nearest. - Refresh the migration guide's read-back step to point at the new helpers. Verified against binding/SkiaSharp/SKImageReadPixelsResult.cs, the SKImageRescale* enums, and SKGraphiteContext.RequestReadPixels on the current base (dev/graphite-backend @ bd784694ad1). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: bdcc8d8b-500e-41ae-bb71-5e336ac92b1d --- .../docfx/guides/gpu/graphite-migration.md | 2 +- .../docfx/guides/gpu/graphite-surfaces.md | 34 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/documentation/docfx/guides/gpu/graphite-migration.md b/documentation/docfx/guides/gpu/graphite-migration.md index 16485b29697b..24ead6cd4156 100644 --- a/documentation/docfx/guides/gpu/graphite-migration.md +++ b/documentation/docfx/guides/gpu/graphite-migration.md @@ -101,7 +101,7 @@ Always check that `InsertRecording` returns `SKGraphiteInsertStatus.Success`, an ### 2. Replace synchronous `ReadPixels` with asynchronous readback -This is the most important change. Graphite surfaces do **not** support synchronous [`SKSurface.ReadPixels`](xref:SkiaSharp.SKSurface.ReadPixels*) in shipping builds — it returns `false`. Replace it with `RequestReadPixels`, then drive the request to completion with `Submit` and repeated `CheckAsyncWorkCompletion` calls. The returned plane may be row-padded, so copy row-by-row. See [Reading pixels back](graphite-surfaces.md#reading-pixels-back) for the complete helper. +This is the most important change. Graphite surfaces do **not** support synchronous [`SKSurface.ReadPixels`](xref:SkiaSharp.SKSurface.ReadPixels*) in shipping builds — it returns `false`. Replace it with `RequestReadPixels`, then drive the request to completion with `Submit` and repeated `CheckAsyncWorkCompletion` calls. The callback receives a backend-neutral `SKImageReadPixelsResult`; call `ToArray()`, `ToBitmap()`, or `CopyPlaneTo(...)` on it to get tightly-packed pixels (row padding is stripped for you). See [Reading pixels back](graphite-surfaces.md#reading-pixels-back) for the complete helper. ### 3. Give the recorder an image provider for CPU images diff --git a/documentation/docfx/guides/gpu/graphite-surfaces.md b/documentation/docfx/guides/gpu/graphite-surfaces.md index 90558ccaa310..4665c67e43dd 100644 --- a/documentation/docfx/guides/gpu/graphite-surfaces.md +++ b/documentation/docfx/guides/gpu/graphite-surfaces.md @@ -179,19 +179,9 @@ context.RequestReadPixels( if (result is null || result.PlaneCount < 1) return; - var src = result.GetPlaneData(0); - if (src == IntPtr.Zero) - return; - - // the returned plane may have per-row padding; copy row-by-row into a - // tightly-packed buffer, dropping any padding - var buffer = new byte[dstInfo.BytesSize]; - var srcRowBytes = result.GetPlaneRowBytes(0); - var rowBytes = Math.Min(srcRowBytes, dstInfo.RowBytes); - for (var y = 0; y < dstInfo.Height; y++) - Marshal.Copy(src + (y * srcRowBytes), buffer, y * dstInfo.RowBytes, rowBytes); - - pixels = buffer; + // ToArray copies the plane into a tightly-packed byte[] that outlives the callback, + // stripping any per-row transfer padding for you. + pixels = result.ToArray(); }); // flush the queued readback and wait, then pump the context until the callback runs @@ -203,7 +193,16 @@ if (!done || pixels is null) throw new InvalidOperationException("Graphite async readback did not complete."); ``` -The callback receives an `SKGraphiteAsyncReadResult` whose planes may be **row-padded**, so copy row-by-row using `GetPlaneRowBytes(0)` rather than assuming tightly packed pixels. If you don't need to see the padding handling spelled out, `SKGraphiteAsyncReadResult.CopyPlaneTo(planeIndex, destination, rowCount)` performs exactly this padding-aware copy for you; the manual loop above is shown to make the row padding explicit. A shorter `RequestReadPixels` overload uses default rescaling; a longer overload lets you pass an [`SKGraphiteRescaleGamma`](#status-and-enums) and [`SKGraphiteRescaleMode`](#status-and-enums) when you want the read to also rescale the image. +The callback receives an [`SKImageReadPixelsResult`](#status-and-enums) — the backend-neutral async-read result type shared by the [`SKImage`](xref:SkiaSharp.SKImage), [`SKSurface`](xref:SkiaSharp.SKSurface), and `GRContext` read paths. It is `IDisposable` and **only valid for the duration of the callback**, so copy what you need out before returning; touching it afterwards throws `ObjectDisposedException`. + +It offers a few ways to extract pixels: + +- `ToArray(planeIndex = 0)` — a tightly-packed `byte[]` copy (padding stripped), as above. +- `ToBitmap()` / `ToImage()` — an owned [`SKBitmap`](xref:SkiaSharp.SKBitmap) or [`SKImage`](xref:SkiaSharp.SKImage) for single-plane (interleaved) results. +- `CopyPlaneTo(planeIndex, destination)` — copies one plane into a `Span` you own, stripping row padding. +- `GetPlaneData(planeIndex)` / `GetPlaneRowBytes(planeIndex)` — the raw `ReadOnlySpan` and its stride, if you want to handle padding yourself. + +A shorter `RequestReadPixels` overload uses default rescaling; a longer overload lets you pass an [`SKImageRescaleGamma`](#status-and-enums) and [`SKImageRescaleMode`](#status-and-enums) when you want the read to also rescale the image. The default is `(SKImageRescaleGamma.Src, SKImageRescaleMode.Nearest)`. ## Wrapping an external GPU texture @@ -356,12 +355,13 @@ Dispose recordings, surfaces, recorders, and the context when you are done. The ## Status and enums -Graphite uses a handful of enums: +Graphite uses a handful of enums and one shared result type: - `SKGraphiteBackend` — `Dawn`, `Metal`, `Vulkan`, or `Unknown`. - `SKGraphiteInsertStatus` — the result of `InsertRecording`; `Success` plus failure reasons such as `InvalidRecording`, `AddCommandsFailed`, and `OutOfOrderRecording`. -- `SKGraphiteRescaleGamma` — `Src` or `Linear`, for the optional readback rescale. -- `SKGraphiteRescaleMode` — `Nearest`, `RepeatedLinear`, or `RepeatedCubic`, for the optional readback rescale. +- `SKImageRescaleGamma` — `Src` or `Linear`, for the optional readback rescale. Backend-neutral (shared with the Ganesh async-read path), not Graphite-specific. +- `SKImageRescaleMode` — `Nearest`, `Linear`, `RepeatedLinear`, or `RepeatedCubic`, for the optional readback rescale. Also backend-neutral. +- `SKImageReadPixelsResult` — the backend-neutral result handed to the `RequestReadPixels` callback (see [Reading pixels back](#reading-pixels-back)). `IDisposable` and valid only for the duration of the callback. ## Related Links From cda104ea1a1ae1e2fff36cc86c6ebd852c285899 Mon Sep 17 00:00:00 2001 From: Copilot App <223556219+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 04:07:36 +0200 Subject: [PATCH 07/20] docs: reconcile Graphite guide with current PR state (budget, Snap null, iOS sim) Re-reviewed the Graphite guide against the current PR #3968 head (ed8d01724a2) and its description, and folded in the details that were missing or now differ: - iOS Simulator: add the gradient-shader limitation. The simulator's Metal compiler cannot build some pipelines Graphite emits (notably gradient shaders), so recorder.Snap() returns null for that frame; the same content renders on macOS, real hardware, and with Ganesh/Metal. Keeps the existing supportsFamily under-reporting note. - Render loop: document that Snap() returns null on failure and null-check it in the example; point at the image provider for CPU-image draws. - GPU budget: note the no-options Create* factories use Skia's default budget (256 MB) via the -1 sentinel (a literal 0 disables budgeting), matching the sentinel fix in ed8d01724a2; note MaxBudgetedBytes defaults to 256 MB. - Image provider: note SKGraphiteImageCache is an LRU cache (cap 256, keyed on unique id + mipmap), is IDisposable, and that FindOrCreate throws ArgumentNullException on null args (guard added in ed8d01724a2). Verified against binding/SkiaSharp/Gpu/Graphite/*.cs and the generated structs at the current tip. Docs-only; links/anchors/TOC/xrefs re-validated. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: bdcc8d8b-500e-41ae-bb71-5e336ac92b1d --- .../docfx/guides/gpu/graphite-surfaces.md | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/documentation/docfx/guides/gpu/graphite-surfaces.md b/documentation/docfx/guides/gpu/graphite-surfaces.md index 4665c67e43dd..a84352f84fcb 100644 --- a/documentation/docfx/guides/gpu/graphite-surfaces.md +++ b/documentation/docfx/guides/gpu/graphite-surfaces.md @@ -25,7 +25,7 @@ Which Graphite backend you use is determined by the platform: | Backend | Platforms | | --- | --- | -| **Metal** | macOS, iOS (including the iOS Simulator on Apple Silicon), Mac Catalyst, tvOS | +| **Metal** | macOS, iOS (including the iOS Simulator on Apple Silicon, with one caveat below), Mac Catalyst, tvOS | | **Vulkan** | Linux, Android, Windows | | **Dawn** (WebGPU) | WebAssembly / browser only | @@ -106,7 +106,10 @@ using var context = SKGraphiteContext.CreateMetal(backendContext); ``` > [!NOTE] -> Graphite Metal works on the **iOS and tvOS Simulator on Apple Silicon** (it is backed by the host's Apple-Silicon GPU). Be aware that the simulator's `MTLDevice` under-reports its capabilities — it advertises only `Apple1`/`Apple2`/`Common1`, not `Apple7+`/`Mac2` — so a naive `supportsFamily:` capability gate would wrongly skip it even though rendering works. Don't gate simulator support on the reported GPU family. +> Graphite Metal works on the **iOS and tvOS Simulator on Apple Silicon** (it is backed by the host's Apple-Silicon GPU). Two simulator-specific caveats: +> +> - The simulator's `MTLDevice` under-reports its capabilities — it advertises only `Apple1`/`Apple2`/`Common1`, not `Apple7+`/`Mac2` — so a naive `supportsFamily:` capability gate would wrongly skip it even though rendering works. Don't gate simulator support on the reported GPU family. +> - The simulator's Metal shader compiler cannot build some pipelines Graphite emits — notably **gradient shaders**. When that happens, `recorder.Snap()` returns `null` for that frame. The same content renders correctly with Graphite/Metal on macOS and on real iOS hardware, and with Ganesh/Metal on the simulator — it is a simulator-only limitation. Always null-check `Snap()` (see [The render loop](#the-render-loop)). ### Dawn (WebGPU) @@ -141,6 +144,8 @@ surface.Canvas.DrawCircle(256, 256, 200, new SKPaint { Color = SKColors.Cornflow // capture everything recorded so far using var recording = recorder.Snap(); +if (recording is null) + throw new InvalidOperationException("Graphite Snap() returned null."); // hand the recording to the context and submit it to the GPU if (context.InsertRecording(recording) != SKGraphiteInsertStatus.Success) @@ -151,8 +156,8 @@ context.Submit(new SKGraphiteSubmitInfo { Sync = true }); A few things to note: -- `CreateRecorder` returns an `SKGraphiteRecorder`. A recorder is a reusable unit of work capture; you create the surface from it, not from the context directly. -- `Snap` produces an `SKGraphiteRecording` — an immutable list of GPU commands. Snapping resets the recorder so it can record the next frame. +- `CreateRecorder` returns an `SKGraphiteRecorder`. A recorder is a reusable unit of work capture; you create the surface from it, not from the context directly. If you draw raster (CPU-backed) `SKImage`s, create the recorder with an image provider instead — see [Drawing CPU images](#drawing-cpu-images-the-image-provider). +- `Snap` produces an `SKGraphiteRecording` — an immutable list of GPU commands. Snapping resets the recorder so it can record the next frame. It returns **`null`** if the recording could not be built (for example, if Skia failed to compile a pipeline for something you drew), so check the result before inserting it. - `InsertRecording` returns an [`SKGraphiteInsertStatus`](#status-and-enums); always confirm it is `Success`. - `Submit(new SKGraphiteSubmitInfo { Sync = true })` flushes the work to the GPU and, with `Sync = true`, waits for it to finish. It returns `false` if submission failed. @@ -300,13 +305,13 @@ You can also move between GPU textures and [`SKImage`](xref:SkiaSharp.SKImage) o There are two ways to handle this. You can upload each image yourself with [`ToTextureImage`](#using-textures-as-images) and draw the GPU-backed result. Or you can give the recorder an *image provider* callback that uploads CPU images on demand, so ordinary `DrawImage` calls just work. -Pass the callback to the `CreateRecorder` overload that accepts one. SkiaSharp ships a ready-made `SKGraphiteImageCache` whose `FindOrCreate` method implements the callback (uploading via `ToTextureImage`) and caches the results so repeated draws of the same image don't re-upload: +Pass the callback to the `CreateRecorder` overload that accepts one. SkiaSharp ships a ready-made `SKGraphiteImageCache` whose `FindOrCreate` method implements the callback (uploading via `ToTextureImage`) and caches the results — an LRU cache (capped at 256 entries, keyed on the image's unique id and mipmap flag) so repeated draws of the same image don't re-upload every frame: ```csharp var imageCache = new SKGraphiteImageCache(); using var recorder = context.CreateRecorder( - recorderBudgetBytes: -1, + recorderBudgetBytes: -1, // -1 = use Skia's default budget findOrCreate: imageCache.FindOrCreate, // uploads + caches CPU images on demand findOrCreateDispose: imageCache.Dispose); // released with the recorder @@ -314,7 +319,7 @@ using var surface = SKSurface.Create(recorder, info); surface.Canvas.DrawImage(cpuImage, 0, 0); // now uploaded through the provider ``` -The callback has the signature `SKImage SKGraphiteFindOrCreateImageDelegate(SKGraphiteRecorder recorder, SKImage image, bool mipmapped)`, and returning `null` drops that image's draw. Provide your own delegate if you want custom upload or caching behaviour; otherwise `SKGraphiteImageCache` is the simplest correct default. +The callback has the signature `SKImage SKGraphiteFindOrCreateImageDelegate(SKGraphiteRecorder recorder, SKImage image, bool mipmapped)`, and returning `null` drops that image's draw. `SKGraphiteImageCache.FindOrCreate` throws `ArgumentNullException` if the recorder or image is null, and is `IDisposable` — pass its `Dispose` as `findOrCreateDispose` so its cached GPU images are released with the recorder. Provide your own delegate if you want custom upload or caching behaviour; otherwise `SKGraphiteImageCache` is the simplest correct default. ## Dawn in the browser @@ -336,19 +341,21 @@ context.CheckAsyncWorkCompletion(); ## Context options -The `Create*` factories accept an optional `SKGraphiteContextOptions`. The most commonly useful field is `InternalMultisampleCount` (the internal MSAA sample count), which must be `0` (use Skia's default) or one of `1`, `2`, `4`, `8`, or `16`; other values are rejected. Other options include a GPU byte budget and driver-workaround toggles. +The `Create*` factories accept an optional `SKGraphiteContextOptions`. The most commonly useful field is `InternalMultisampleCount` (the internal MSAA sample count), which must be `0` (use Skia's default) or one of `1`, `2`, `4`, `8`, or `16`; other values are rejected. Other options include a GPU byte budget (`GpuBudgetInBytes`) and driver-workaround toggles. ```csharp var options = new SKGraphiteContextOptions { InternalMultisampleCount = 4 }; using var context = SKGraphiteContext.CreateMetal(backendContext, options); ``` +The factory overloads that **don't** take options use Skia's defaults, including its default GPU resource budget (256 MB). If you build an `SKGraphiteContextOptions` yourself and want that same default budget, set `GpuBudgetInBytes = -1` (the "use Skia's default" sentinel) — a literal `0` means a zero-byte cache, which disables budgeting. + ## Managing resources An `SKGraphiteContext` exposes a few properties and methods for inspecting and managing GPU resources: - `Backend`, `IsDeviceLost`, `MaxTextureSize`, and `SupportsProtectedContent` report the state of the underlying device. -- `MaxBudgetedBytes` gets or sets the GPU memory budget; `CurrentBudgetedBytes` reports current usage. +- `MaxBudgetedBytes` gets or sets the GPU memory budget (defaulting to Skia's 256 MB); `CurrentBudgetedBytes` reports current usage. - `FreeGpuResources()` releases cached GPU resources; `PerformDeferredCleanup(TimeSpan)` purges resources unused for longer than the given duration. Dispose recordings, surfaces, recorders, and the context when you are done. The context owns the GPU resources allocated through it. From 4c7301adbfbc8655bc880039003ed0fc180e1e4e Mon Sep 17 00:00:00 2001 From: Copilot App <223556219+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 04:10:11 +0200 Subject: [PATCH 08/20] docs: reference tracking issue #4555 for the iOS-simulator gradient limitation Add a "tracked in mono/SkiaSharp#4555" reference (and the compiler's actual "Compiler failed to build request" message) to the iOS-simulator Graphite/Metal gradient caveat, so readers who hit the null Snap() can follow the known issue. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: bdcc8d8b-500e-41ae-bb71-5e336ac92b1d --- documentation/docfx/guides/gpu/graphite-surfaces.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docfx/guides/gpu/graphite-surfaces.md b/documentation/docfx/guides/gpu/graphite-surfaces.md index a84352f84fcb..4174afb6a687 100644 --- a/documentation/docfx/guides/gpu/graphite-surfaces.md +++ b/documentation/docfx/guides/gpu/graphite-surfaces.md @@ -109,7 +109,7 @@ using var context = SKGraphiteContext.CreateMetal(backendContext); > Graphite Metal works on the **iOS and tvOS Simulator on Apple Silicon** (it is backed by the host's Apple-Silicon GPU). Two simulator-specific caveats: > > - The simulator's `MTLDevice` under-reports its capabilities — it advertises only `Apple1`/`Apple2`/`Common1`, not `Apple7+`/`Mac2` — so a naive `supportsFamily:` capability gate would wrongly skip it even though rendering works. Don't gate simulator support on the reported GPU family. -> - The simulator's Metal shader compiler cannot build some pipelines Graphite emits — notably **gradient shaders**. When that happens, `recorder.Snap()` returns `null` for that frame. The same content renders correctly with Graphite/Metal on macOS and on real iOS hardware, and with Ganesh/Metal on the simulator — it is a simulator-only limitation. Always null-check `Snap()` (see [The render loop](#the-render-loop)). +> - The simulator's Metal shader compiler cannot build some pipelines Graphite emits — notably **gradient shaders** (the compiler reports "Compiler failed to build request"). When that happens, `recorder.Snap()` returns `null` for that frame. The same content renders correctly with Graphite/Metal on macOS and on real iOS hardware, and with Ganesh/Metal on the simulator — it is a simulator-only limitation, tracked in [mono/SkiaSharp#4555](https://github.com/mono/SkiaSharp/issues/4555). Always null-check `Snap()` (see [The render loop](#the-render-loop)). ### Dawn (WebGPU) From a69ea701ff532375a2702d9c3723d9d4d860cc07 Mon Sep 17 00:00:00 2001 From: Copilot App <223556219+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 21:20:39 +0200 Subject: [PATCH 09/20] docs: enrich Graphite guide with threading model, pipeline compilation, skia.org refs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rebased onto main (Graphite PR #3968 is now merged) and added conceptual depth sourced from Skia's own docs, keeping every claim tied to the actual API: - Threading: correct the intro note to reflect Graphite's actual model — a single recorder is single-threaded, but (unlike a single-threaded Ganesh GRContext) Graphite is designed for parallel recording with one recorder per thread feeding a shared context; serialize InsertRecording/Submit. Align the migration guide's threading watch-out to match. - Add a "Pipeline compilation" section: Graphite compiles a GPU pipeline per unique draw/paint/blend/format combination on first use (cached after), so the first frame using a new combination costs more, and a driver that can't compile it makes Snap() return null — which is exactly the iOS-simulator gradient case. Note upstream Skia's pipeline precompilation exists but isn't surfaced in SkiaSharp yet. Cross-linked from the render loop and the iOS-simulator note. - Add authoritative skia.org "canvas creation" references to the raster, Ganesh, and Graphite Related Links. Docs-only; verified against the merged Graphite bindings on main (byte-identical to the reviewed ed8d01724a2). Links/anchors/TOC/xrefs re-validated. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: bdcc8d8b-500e-41ae-bb71-5e336ac92b1d --- .../docfx/guides/gpu/ganesh-surfaces.md | 1 + .../docfx/guides/gpu/graphite-migration.md | 2 +- .../docfx/guides/gpu/graphite-surfaces.md | 18 ++++++++++++++++-- .../docfx/guides/gpu/raster-surfaces.md | 1 + 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/documentation/docfx/guides/gpu/ganesh-surfaces.md b/documentation/docfx/guides/gpu/ganesh-surfaces.md index 68f61c47a77e..07a43d69ddbf 100644 --- a/documentation/docfx/guides/gpu/ganesh-surfaces.md +++ b/documentation/docfx/guides/gpu/ganesh-surfaces.md @@ -188,3 +188,4 @@ Dispose your surfaces and the `GRContext` when you are done, and make sure the g - [Raster Surfaces](raster-surfaces.md) - [Surfaces in the SkiaSharp Views](views-surfaces.md) - [Graphite Offscreen Surfaces](graphite-surfaces.md) +- [Skia canvas creation, GPU backend (skia.org)](https://skia.org/docs/user/api/skcanvas_creation/) diff --git a/documentation/docfx/guides/gpu/graphite-migration.md b/documentation/docfx/guides/gpu/graphite-migration.md index 24ead6cd4156..a9caf0119365 100644 --- a/documentation/docfx/guides/gpu/graphite-migration.md +++ b/documentation/docfx/guides/gpu/graphite-migration.md @@ -124,7 +124,7 @@ Per-surface and per-image creation moves from the context to the recorder: - **CPU images need a provider.** The single easiest thing to miss — a raster `SKImage` drawn without an image provider simply doesn't appear. See change 3 above. - **Browser (Dawn/WebGPU) can't submit synchronously.** In a WebAssembly host, `Submit(Sync = true)` throws. Submit without syncing and pump `CheckAsyncWorkCompletion`. See [Dawn in the browser](graphite-surfaces.md#dawn-in-the-browser). - **Check backend availability.** Use `SKGraphiteContext.IsBackendAvailable` before creating a context, since not every build includes every backend. -- **The recorder is per-thread.** As with Ganesh, the context, recorders, and surfaces are single-threaded — use a recorder only on the thread that created it, and give each rendering thread its own. +- **The recorder is per-thread — and that's a feature.** A single `SKGraphiteRecorder` and its surfaces are single-threaded, but unlike a single-threaded Ganesh `GRContext`, Graphite is built for parallel recording: give each rendering thread its own recorder, then submit their recordings to the shared context (serializing the `InsertRecording`/`Submit` calls). See the threading note in [Graphite Offscreen Surfaces](graphite-surfaces.md). ## Related Links diff --git a/documentation/docfx/guides/gpu/graphite-surfaces.md b/documentation/docfx/guides/gpu/graphite-surfaces.md index 4174afb6a687..159d00d42712 100644 --- a/documentation/docfx/guides/gpu/graphite-surfaces.md +++ b/documentation/docfx/guides/gpu/graphite-surfaces.md @@ -9,6 +9,8 @@ _Render on the GPU with the new Graphite backend_ *Graphite* is Skia's newer GPU backend, built on modern explicit graphics APIs. In SkiaSharp, Graphite is currently an **offscreen** rendering path: you create a context, record drawing into a surface, submit that recording to the GPU, and read the result back yourself. It does not yet drive any of the [view controls](views-surfaces.md). +Where the older [Ganesh](ganesh-surfaces.md) backend issues GPU work as you draw and auto-flushes it, Graphite separates **recording** from **submission**: drawing is captured into a *recording* that you later insert into the context and submit. That split is deliberate — it maps cleanly onto modern explicit APIs (Vulkan, Metal, and Dawn/WebGPU) and lets an application record drawing on multiple threads in parallel, then submit the results through one shared context. + Graphite differs from [Ganesh](ganesh-surfaces.md) in two important ways: - **Drawing is recorded, not flushed.** You draw onto a surface's canvas as usual, but instead of flushing a context you *snap* a **recording** from a **recorder** and *insert* that recording into the context, then *submit* it. @@ -17,7 +19,7 @@ Graphite differs from [Ganesh](ganesh-surfaces.md) in two important ways: Graphite supports three backends: **Vulkan**, **Metal**, and **Dawn** (WebGPU). > [!NOTE] -> Like Ganesh, an `SKGraphiteContext`, its recorders, and its surfaces are not thread-safe. Create and use them on a single thread that owns the underlying graphics device. +> **Threading.** A single `SKGraphiteRecorder` — and the surfaces created from it — belongs to one thread; don't touch it concurrently. But unlike a single-threaded Ganesh `GRContext`, Graphite is designed for parallel recording: give **each thread its own recorder**, record on all of them at once, then feed their recordings to the one shared `SKGraphiteContext`. Serialize the context-level calls (`InsertRecording`, `Submit`) rather than calling them from several threads simultaneously. ## Backend platform support @@ -157,7 +159,7 @@ context.Submit(new SKGraphiteSubmitInfo { Sync = true }); A few things to note: - `CreateRecorder` returns an `SKGraphiteRecorder`. A recorder is a reusable unit of work capture; you create the surface from it, not from the context directly. If you draw raster (CPU-backed) `SKImage`s, create the recorder with an image provider instead — see [Drawing CPU images](#drawing-cpu-images-the-image-provider). -- `Snap` produces an `SKGraphiteRecording` — an immutable list of GPU commands. Snapping resets the recorder so it can record the next frame. It returns **`null`** if the recording could not be built (for example, if Skia failed to compile a pipeline for something you drew), so check the result before inserting it. +- `Snap` produces an `SKGraphiteRecording` — an immutable list of GPU commands. Snapping resets the recorder so it can record the next frame. It returns **`null`** if the recording could not be built (for example, if the driver could not compile a pipeline for something you drew — see [Pipeline compilation](#pipeline-compilation)), so check the result before inserting it. - `InsertRecording` returns an [`SKGraphiteInsertStatus`](#status-and-enums); always confirm it is `Success`. - `Submit(new SKGraphiteSubmitInfo { Sync = true })` flushes the work to the GPU and, with `Sync = true`, waits for it to finish. It returns `false` if submission failed. @@ -360,6 +362,17 @@ An `SKGraphiteContext` exposes a few properties and methods for inspecting and m Dispose recordings, surfaces, recorders, and the context when you are done. The context owns the GPU resources allocated through it. +## Pipeline compilation + +Graphite renders by building a GPU **pipeline** (a compiled shader program) for each distinct combination of draw operation, paint effects, blend mode, and target surface format. Each pipeline is compiled the **first time** that combination is drawn, and then cached on the context for reuse. + +Two practical consequences follow: + +- **The first frame that uses a new combination can be slower**, because the pipeline is compiled on demand (during `Snap`/`InsertRecording`). Subsequent frames reuse the cached pipeline and are fast. +- **If the driver cannot compile the pipeline, `recorder.Snap()` returns `null`** for that frame. This is exactly the [iOS Simulator gradient limitation](#metal) — the simulator's Metal compiler rejects the pipeline Graphite emits for gradient shaders. Always null-check `Snap()`. + +Skia itself supports *pipeline precompilation* — warming the pipeline cache before the first frame so there is no first-use hitch — but that is not yet surfaced in SkiaSharp, so for now just be aware that first use of a new draw/paint combination pays a one-time compilation cost. + ## Status and enums Graphite uses a handful of enums and one shared result type: @@ -375,3 +388,4 @@ Graphite uses a handful of enums and one shared result type: - [SkiaSharp APIs](/dotnet/api/skiasharp) - [Ganesh GPU Surfaces](ganesh-surfaces.md) - [Migrating from Ganesh to Graphite](graphite-migration.md) +- [Skia GPU documentation (skia.org)](https://skia.org/docs/user/api/skcanvas_creation/) diff --git a/documentation/docfx/guides/gpu/raster-surfaces.md b/documentation/docfx/guides/gpu/raster-surfaces.md index 924378f8f4c5..5ce13e9b6547 100644 --- a/documentation/docfx/guides/gpu/raster-surfaces.md +++ b/documentation/docfx/guides/gpu/raster-surfaces.md @@ -118,3 +118,4 @@ If you need GPU acceleration — because you are rendering many frames per secon - [SkiaSharp APIs](/dotnet/api/skiasharp) - [Creating and Drawing on Bitmaps](../bitmaps/drawing.md) +- [Skia canvas creation, Raster backend (skia.org)](https://skia.org/docs/user/api/skcanvas_creation/) From 4ed99d8b9988d608c0158799d984b4bf62c652f1 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 4 Aug 2026 16:27:40 +0200 Subject: [PATCH 10/20] Improve GPU surface guides and documentation skill Add a Microsoft Learn-inspired conceptual documentation route while preserving the API-reference workflow. Correct GPU surface selection, Graphite lifecycle, ownership, failure, and platform guidance, and document the CI orchestration boundary.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>\nCopilot-Session: 9ae60ac4-ac5a-466d-9a84-8a9e3a6c7c88 --- .agents/skills/api-docs/SKILL.md | 68 ++++- .agents/skills/api-docs/references/adding.md | 59 ++-- .../skills/api-docs/references/checklist.md | 7 + .../references/conceptual/authoring.md | 113 +++++++ .../references/conceptual/code-samples.md | 93 ++++++ .../references/conceptual/fact-checking.md | 64 ++++ .../api-docs/references/conceptual/index.md | 64 ++++ .../microsoft-contribute-sources.md | 41 +++ .../references/conceptual/reviewing.md | 142 +++++++++ .../conceptual/structure-and-style.md | 124 ++++++++ .../conceptual/templates/concept.md | 61 ++++ .../references/conceptual/templates/how-to.md | 77 +++++ .../conceptual/templates/migration.md | 89 ++++++ .../conceptual/templates/overview.md | 64 ++++ .../conceptual/templates/troubleshooting.md | 87 ++++++ .../references/conceptual/validation.md | 82 +++++ .../skills/api-docs/references/patterns.md | 5 +- .../skills/api-docs/references/reviewing.md | 27 +- .../references/technical-fact-checking.md | 97 ++++++ .../skills/api-docs/references/validation.md | 16 + documentation/dev/writing-docs.md | 28 +- documentation/docfx/guides/TOC.yml | 10 +- .../docfx/guides/gpu/ganesh-surfaces.md | 69 +++-- .../docfx/guides/gpu/graphite-migration.md | 58 ++-- .../docfx/guides/gpu/graphite-surfaces.md | 286 +++++++++++------- documentation/docfx/guides/gpu/index.md | 30 +- .../docfx/guides/gpu/raster-surfaces.md | 51 ++-- .../docfx/guides/gpu/views-surfaces.md | 43 ++- documentation/docfx/guides/index.md | 4 +- 29 files changed, 1691 insertions(+), 268 deletions(-) create mode 100644 .agents/skills/api-docs/references/conceptual/authoring.md create mode 100644 .agents/skills/api-docs/references/conceptual/code-samples.md create mode 100644 .agents/skills/api-docs/references/conceptual/fact-checking.md create mode 100644 .agents/skills/api-docs/references/conceptual/index.md create mode 100644 .agents/skills/api-docs/references/conceptual/microsoft-contribute-sources.md create mode 100644 .agents/skills/api-docs/references/conceptual/reviewing.md create mode 100644 .agents/skills/api-docs/references/conceptual/structure-and-style.md create mode 100644 .agents/skills/api-docs/references/conceptual/templates/concept.md create mode 100644 .agents/skills/api-docs/references/conceptual/templates/how-to.md create mode 100644 .agents/skills/api-docs/references/conceptual/templates/migration.md create mode 100644 .agents/skills/api-docs/references/conceptual/templates/overview.md create mode 100644 .agents/skills/api-docs/references/conceptual/templates/troubleshooting.md create mode 100644 .agents/skills/api-docs/references/conceptual/validation.md create mode 100644 .agents/skills/api-docs/references/technical-fact-checking.md diff --git a/.agents/skills/api-docs/SKILL.md b/.agents/skills/api-docs/SKILL.md index 93bff84083b3..b5d517678b06 100644 --- a/.agents/skills/api-docs/SKILL.md +++ b/.agents/skills/api-docs/SKILL.md @@ -1,22 +1,26 @@ --- name: api-docs description: > - Write AND review XML API documentation for SkiaSharp (ECMA/mdoc XML in the docs submodule). Two modes: - (1) ADD docs for new APIs with "To be added." placeholders; (2) REVIEW existing docs by scope for - accuracy, freshness, examples, and hygiene. + Write and review SkiaSharp developer documentation with strict artifact routing: ECMA/mdoc XML API + reference in the docs submodule, or conceptual DocFX articles under documentation/docfx/guides. + Add docs for new APIs, review existing API docs, and author or review overviews, concepts, how-to + guides, migration guides, and troubleshooting articles. Enforces source-backed facts, safe samples, + platform accuracy, accessible structure, and Microsoft Learn-style clarity. Triggers: "document class", "add XML docs", "write XML documentation", "fill in missing docs", "remove To be added placeholders", "review documentation", "check docs for errors", "fix doc issues", - "audit the docs", "review the font docs", "are the examples correct", "update out-of-date docs", - any request to add, validate, correct, or expand SkiaSharp API documentation. + "audit the docs", "review the font docs", "write a guide", "review this guide", "conceptual docs", + "write a tutorial", "migration guide", "troubleshooting article", "are the examples correct", + "update out-of-date docs", or any request to add, validate, correct, or expand SkiaSharp API or + conceptual documentation. metadata: layer: router --- -# API Documentation +# SkiaSharp documentation -Add and review SkiaSharp API documentation. This file is a **router**: it picks a procedure and points to -the reference and tooling files that do the work. The detailed instructions live in `references/` so they -load only when needed. +Add and review SkiaSharp API reference and conceptual documentation. This file is a **router**: it picks a +procedure and points to the reference and tooling files that do the work. The detailed instructions live +in `references/` so they load only when needed. ## Key facts @@ -28,27 +32,57 @@ load only when needed. - **Edit the XML directly.** Safety comes from `docs-format-docs`, which formats every file and fails the build on broken XML/CDATA ([`references/validation.md`](references/validation.md)). - **Never edit generated files:** `index.xml`, `ns-*.xml`, `_filter.xml`, `FrameworksIndex/`. +- Conceptual articles are Markdown under `documentation/docfx/guides/`. They are built by DocFX and + should help a reader understand, decide, complete, migrate, or troubleshoot rather than duplicate + member-by-member API reference. ## How to work -One agent does the whole pass. Read the relevant reference, resolve scope into an explicit file list, then -work in batches of ~25–40 files so each pass stays auditable and resumable. +Route by **artifact first**, before loading a procedure: + +| Artifact or intent | Route | +|---|---| +| `docs/SkiaSharpAPI/**/*.xml`, ECMA/mdoc, a type/member reference page, or `To be added.` | API reference | +| `documentation/docfx/guides/**/*.md`, guide, overview, concept, how-to, migration, or troubleshooting article | Conceptual | + +If a task contains both artifact kinds, split it into two explicit file lists and apply each route +independently. Never apply conceptual front matter, article blueprints, or Markdown conventions to mdoc +XML; never apply member-by-member ECMA patterns to a conceptual article. If no path is supplied, inspect +the requested artifact or repository location. Ask only when neither the artifact nor the reader intent +resolves the route. + +One agent does the whole pass. Read only the selected route and shared technical references, resolve scope +into an explicit file list, then work in batches of ~25–40 files so each pass stays auditable and +resumable. | If the task is… | Read | |---|---| | Documenting **new** APIs / filling `To be added.` placeholders | [`references/adding.md`](references/adding.md) | | **Reviewing/correcting/expanding** existing docs (one type, a theme, what changed, or all) | [`references/reviewing.md`](references/reviewing.md) | +| Authoring, rewriting, or reviewing a **conceptual article** under `documentation/docfx/guides/` | [`references/conceptual/index.md`](references/conceptual/index.md) | -The user asks in plain language ("review the font docs", "fill in what's missing"). The docs live at -`docs/SkiaSharpAPI//.xml`; list them directly, and use +The user asks in plain language ("review the font docs", "fill in what's missing"). API reference docs +live at `docs/SkiaSharpAPI//.xml`; use `git -C docs diff --name-only origin/main...HEAD` for "what changed". Each `.xml` maps to its source -at `binding//.cs`, and **you** pick the files a request covers — for a theme, scan the -list and select the matching types yourself; the chosen procedure file covers the rest. +at `binding//.cs`. Conceptual docs live under `documentation/docfx/guides/`; use the +requested section or the parent-repo diff to resolve their scope. In both routes, **you** select the +files a request covers; the chosen procedure file covers the rest. + +The `auto-api-docs-writer` workflow in `mono/SkiaSharp-API-docs` is always an **API-reference** run. It +loads `adding.md` and `reviewing.md`; it must not load or apply the conceptual route merely because its +prompt uses words such as "documentation", "review", or "example." +Keep authoring, review, fact-checking, and validation policy in this skill. The workflow may define only +run-specific orchestration such as scope discovery, path translation, native-source checkout, timeboxes, +fix authorization, staging, and pull-request output. -All findings use one machine-parseable contract: `SEVERITY | class | file | docId | message`. +API-reference findings use one machine-parseable contract: +`SEVERITY | class | file | docId | message`. Conceptual-guide reviews use the contract in their +procedure file. ## References (canonical facts) +- [`references/technical-fact-checking.md`](references/technical-fact-checking.md) — shared evidence + hierarchy, managed/native contract boundary, and cross-layer verification for both routes. - [`references/patterns.md`](references/patterns.md) — .NET XML doc syntax, verb conventions, formatting. - [`references/skia-patterns.md`](references/skia-patterns.md) — domain facts (color layouts, struct defaults, standard-based enums, caller-owned vs parent-owned). @@ -67,6 +101,8 @@ All findings use one machine-parseable contract: `SEVERITY | class | file | docI errors for broken XML/CDATA. See [`references/validation.md`](references/validation.md). - Snippet build (C#-only, download is fine): `dotnet cake --target=externals-download` then `dotnet build binding/SkiaSharp/SkiaSharp.csproj`. +- Conceptual site: follow [`references/conceptual/validation.md`](references/conceptual/validation.md); + it handles snippet checks, links, rendering, and repositories with pre-existing DocFX warnings. ## Landing changes diff --git a/.agents/skills/api-docs/references/adding.md b/.agents/skills/api-docs/references/adding.md index e55fc7a761d4..66ad46d64b12 100644 --- a/.agents/skills/api-docs/references/adding.md +++ b/.agents/skills/api-docs/references/adding.md @@ -10,10 +10,12 @@ copy your examples into real code, so every claim must be true and every example ## Required reading (first) -1. [`patterns.md`](patterns.md) — .NET XML doc syntax, verb conventions, summary/param/return patterns. -2. [`skia-patterns.md`](skia-patterns.md) — SkiaSharp/HarfBuzz domain facts (color layouts, struct +1. [`technical-fact-checking.md`](technical-fact-checking.md) — evidence hierarchy, public managed + contract boundary, and cross-layer verification. +2. [`patterns.md`](patterns.md) — .NET XML doc syntax, verb conventions, summary/param/return patterns. +3. [`skia-patterns.md`](skia-patterns.md) — SkiaSharp/HarfBuzz domain facts (color layouts, struct defaults, standard-based enums, caller-owned vs parent-owned). -3. [`obsolete-api-map.md`](obsolete-api-map.md) — members that must never appear in an example, and +4. [`obsolete-api-map.md`](obsolete-api-map.md) — members that must never appear in an example, and replacements. Apply these facts; do not restate them in the docs. If a fact is in a reference file, trust it over your @@ -39,31 +41,45 @@ own recollection. 3. **Write (per file).** A field is **in scope to fill** when it is empty, self-closing, or still a placeholder (`To be added.`, or a bracketed remarks scaffold like `[Describe …]`). Do not rewrite already-written prose. - 1. **Read the C# source first.** From the filename, locate the type in `binding/` and read it. Build a - fact sheet: constructors, method overloads, property accessors (`{ get; }` vs `{ get; set; }`), - validation behavior (throws? clamps? pads? truncates?), numeric constants, and defaults. The source - is authoritative — never document from the member name alone. - 2. **Open the `.xml` and locate each `` block.** Each ``/type carries a + 1. **Read the managed source first.** From the filename, locate the type in `binding/` and read it. + Build the fact sheet from `technical-fact-checking.md`: exact public surface, validation, + exceptions, nullable failures/callback payloads, status results, ownership, lifetimes, constants, + and defaults. Never document from the member name alone. + 2. **Close the evidence chain when managed source delegates semantics.** Read focused tests and the + checked-out native declaration/implementation for native-backed enums, status values, ownership, + callbacks, or behavior the wrapper does not define. A native name or enum value is not enough to + invent a behavioral explanation. + 3. **Open the `.xml` and locate each `` block.** Each ``/type carries a `MemberSignature[@Language='DocId']` you use as the stable id. Fill the in-scope children: ``, ``, ``, ``, ``, ``, and ``. - 3. **Match the accessor verb to the signature**, not to intuition: `{ get; set; }` → "Gets or sets …", + Include an `` entry for each deterministic managed exception identified by the fact + sheet, including verified checked arithmetic and deliberate failures from directly invoked + helpers. Do not add a possible exception without locating the exact throwing operation. + 4. **Match the accessor verb to the signature**, not to intuition: `{ get; set; }` → "Gets or sets …", `{ get; }` → "Gets …". Many struct properties look read-only but are settable — check the signature. - 4. **Defaults come from the source.** A struct property with no field initializer defaults to + 5. **Defaults come from the source.** A struct property with no field initializer defaults to `0`/`null`/`false`; do not copy a "typical" sibling constant. - 5. **Standard-citing enum members:** read the C/C++ header where the enum is defined and verify the - number AND the behavior against the member name. - 6. **Remarks:** type-level entries get a real `` (description + disposal note if applicable + - one compiling example). Simple members get self-closing ``. Inside CDATA remarks use - `` with **no** `T:`/`M:`/`P:` prefix; `` (with prefix) is for non-CDATA prose. - 7. **Examples must compile and be self-contained:** declare every variable; never use an obsolete member + 6. **Keep prose inside the public managed contract.** Do not expose private fields/locals as reader + identifiers or describe native capabilities with no managed entry point. Express sizes and + lifetimes using public parameters and observable results. + 7. **Remarks:** a user-facing resource or lifecycle type must have real remarks with disposal/lifetime + guidance and one focused, self-contained compiling example. If an accurate example cannot be + completed, leave the remarks placeholder and emit a `DEFERRED` row instead of silently omitting it. + Enums, delegates, and simple members can use `` when no additional guidance is needed. + Never invent a partial example to satisfy the requirement. Inside CDATA use `` with + no DocId prefix; use `` in regular XML prose. + 8. **Examples must compile and be self-contained:** declare every variable; never use an obsolete member (check the obsolete map); never `using`/`Dispose` a parent-owned object (e.g. `SKSurface.Canvas`). - 8. **Save the file**, preserving CDATA and all signature elements. Change only `` content. + 9. **Save and audit the file**, preserving CDATA and every signature element. Change only `` + content. Search the touched file for placeholders. Fill each in-scope field, replace a + non-applicable remarks placeholder with ``, or emit a `DEFERRED` row for that exact field. 4. **Review** the files just written with the review checks ([`reviewing.md`](reviewing.md) §Checks), then fix CRITICAL findings by editing the XML directly. 5. **Validate & format** ([`validation.md`](validation.md)): run `docs-format-docs` — it formats and runs - the deterministic checks; fix any build-failing broken-XML errors. + the deterministic checks; fix any build-failing broken-XML errors and reconcile every remaining + placeholder in a touched file with the deferred manifest. 6. **Land:** commit on a `dev/...` branch in the `docs` submodule and open a PR (the submodule protects `main`). @@ -76,8 +92,8 @@ After all files, emit a compact manifest — one line per file: WROTE | | summaries: params: returns: remarks: | source: ``` -Then list any field you intentionally left as a placeholder (ran out of certainty/time) so the next run -re-detects it: +Counts must come from the final XML, not memory. Then list every field intentionally left as a placeholder +(ran out of certainty/time) so the next run re-detects it: ``` DEFERRED | | | | @@ -88,6 +104,9 @@ DEFERRED | | | | - Edit only the in-scope `.xml` files, and only `` content — never touch `MemberSignature`, `TypeSignature`, or generated files (`index.xml`, `ns-*.xml`, `_filter.xml`, `FrameworksIndex/`). - Never invent an API, overload, or numeric value. If you cannot verify it, leave the field deferred. +- Never invent semantics from a type/member name or native capability, and never present a private + implementation identifier as public API. - The writer only fills in-scope (empty/placeholder) fields; it does not rewrite existing prose. - If a large type runs out of certainty, leave its placeholder intact (`DEFERRED`) so the next run re-detects it — the file stays clean and well-formed either way. +- Never claim a type/file is fully filled while an unreported placeholder remains in it. diff --git a/.agents/skills/api-docs/references/checklist.md b/.agents/skills/api-docs/references/checklist.md index 222e6b2d7f4a..a39827148609 100644 --- a/.agents/skills/api-docs/references/checklist.md +++ b/.agents/skills/api-docs/references/checklist.md @@ -9,6 +9,11 @@ Classify issues by severity when reviewing documentation. Issues that damage credibility or break functionality: - **Fabricated APIs** — code examples that reference methods, overloads, or types that don't exist. Always verify against actual C# source before writing examples. +- **Private identifiers presented as API** — prose or examples tell readers to use an implementation-only + field/local/property that is absent from the public managed surface. +- **Invented native semantics or unsupported managed capabilities** — docs infer behavior from a native + name/value without checking its contract, or describe native functionality that SkiaSharp does not + publicly expose. - **Obsolete APIs in examples** — using a member marked `[Obsolete("...", true)]` in a code example. These are compile errors, so the example never builds. Most common: legacy text rendering (`SKPaint.TextSize`/`Typeface`/`TextAlign`, old `SKCanvas.DrawText(string,float,float,SKPaint)`) — use `SKFont` instead. Check examples against `references/obsolete-api-map.md`; mind §2 there, where the obsolete and modern calls share a method name and differ only by signature. - **Wrong standard values or behavior** — enum descriptions citing the wrong standard number, or mischaracterizing the standard (e.g. calling a gamma-2.6 transfer "linear"). Cross-reference against `MemberValue` **and the member name**, which usually encodes the exact standard (`SmpteRp4312` = SMPTE RP 431-2, not 432-2; `SmpteSt4281` in `SKColorspaceTransferFnCicp` = SMPTE ST 428-1, a gamma-2.6 transfer, not linear). Note the same member name can mean different things in sibling enums (`SmpteSt4281` is also a *primaries* member). Verify both the identifier and the described behavior against the member's own enum. - **Spelling errors** in public-facing text (teh, recieve, seperate, occured, paramter, retreive, initalize) @@ -38,6 +43,8 @@ Issues that violate standards or leave gaps: - **Invalid cref references** - wrong prefix (T:, M:, P:, F:) or nonexistent target - **DocId prefix inside a CDATA xref** — ``, ``, `` are broken links. Inside CDATA an xref takes the bare UID (``); the prefix is only for `` outside CDATA. - **Missing required documentation** - public APIs without summaries +- **Missing failure contract** — nullable factory/callback results, meaningful status/Boolean failures, or + explicit managed exceptions are omitted where readers need them to use the API safely. - **Incomplete overloads** - params filled on one overload but "To be added." on another overload of the same method - **Wrong default-value claims** — stating "the default is X" for a struct property that has no field initializer. C# structs zero-initialize, so the default is `0` / `null` / `false` unless the source explicitly sets it. A "typical" constant exposed elsewhere (e.g. `SKDocument.DefaultRasterDpi` = 72) is NOT the struct's default and must be documented separately. Verify against the C# source in `binding/`, not against a value that "looks typical". (Recurring error: `SKDocumentXpsOptions.Dpi` documented as "default 72" — it is actually 0.) - **Examples that won't compile** — a code example is broken (so it never builds) when it: diff --git a/.agents/skills/api-docs/references/conceptual/authoring.md b/.agents/skills/api-docs/references/conceptual/authoring.md new file mode 100644 index 000000000000..128b380689ba --- /dev/null +++ b/.agents/skills/api-docs/references/conceptual/authoring.md @@ -0,0 +1,113 @@ +# Authoring conceptual articles + +Use this procedure for a new article or a major rewrite. Read the matching blueprint from +[`index.md`](index.md), then load [`../technical-fact-checking.md`](../technical-fact-checking.md), +[`fact-checking.md`](fact-checking.md), +[`structure-and-style.md`](structure-and-style.md), and [`code-samples.md`](code-samples.md) when the +article contains code. + +## 1. Define the reader contract + +Write one planning sentence before drafting: + +```text +For who already has , this
helps them , within . +``` + +Then record: + +- The prerequisite knowledge, packages, workloads, devices, graphics contexts, or permissions. +- The observable completion condition. +- The important exclusions. Link elsewhere instead of growing a second task inside the article. +- The supported versions, platforms, and backends when the outcome is not universal. + +If the sentence contains multiple unrelated outcomes, split the article or select one dominant outcome. + +## 2. Build an evidence ledger + +Before writing technical prose, follow the shared [`technical-fact-checking.md`](../technical-fact-checking.md) +contract and the conceptual claim guidance in [`fact-checking.md`](fact-checking.md). Record each +consequential claim, its evidence, and its status: + +```text +CLAIM | | | VERIFIED / QUALIFIED / UNVERIFIED +``` + +Include API signatures, return/failure behavior, defaults, ownership, callback lifetime, threading, +platform/backend support, and external setup. Do not draft a warning from an unverified assumption. + +## 3. Design the reader journey + +Start from the matching blueprint. Preserve its reader logic, not its placeholder headings. + +- Put prerequisites and decision-changing limitations before the first dependent step. +- Give the reader enough context to understand *why* an action is required, but keep reference detail + out of the task flow. +- For procedures, order actions exactly as the reader performs them and include the expected result. +- For branching paths, explain the choice once, then separate the paths with specific headings. +- End with verification and only the next links needed to continue. + +Update the section TOC when adding, moving, or renaming an article. + +## 4. Draft the introduction + +The opening should answer, in a short paragraph: + +1. What can the reader accomplish? +2. When should they use this approach? +3. What constraint most affects success or choice? + +Do not repeat the title, begin with product history, or spend the first screen defining terms that the +reader does not yet need. + +## 5. Write source-backed examples + +Apply [`code-samples.md`](code-samples.md). In particular: + +- Verify every SkiaSharp member and overload in current source. +- Declare what host-specific values the reader must supply. +- Check nullable factories and meaningful `bool`/status results. +- Dispose caller-owned native wrappers and keep parent-owned objects alive. +- Model pinning, callbacks, asynchronous work, and GPU cleanup for their complete lifetimes. +- Pair intentionally incorrect code with an explicit explanation and a corrected version. + +An illustrative fragment must say what it omits. A complete workflow must compile and reach the stated +result. + +## 6. Apply editorial and accessibility passes + +Use [`structure-and-style.md`](structure-and-style.md) as separate passes rather than trying to fix +everything while drafting: + +1. Metadata, title, introduction, and heading hierarchy. +2. Procedure order and scannability. +3. Voice, terminology, global readiness, and inclusive language. +4. Links, xrefs, alerts, formatting, and image accessibility. + +Separate passes catch inconsistencies that disappear when prose and code are reviewed together. + +## 7. Validate and self-review + +Run [`validation.md`](validation.md), then review the article against its blueprint: + +- Does the opening promise the same outcome the article delivers? +- Can the intended reader complete or verify that outcome? +- Does every limitation include a recovery path, supported alternative, or explicit boundary? +- Are technical claims still supported by the evidence ledger? +- Are all changed links, heading fragments, and TOC entries valid? + +## Output + +After editing, report: + +```text +WROTE | | type: | outcome: +VERIFIED | | +QUALIFIED | | | +DEFERRED | | +VALIDATED | docfx: snippets: rendered: +``` + +`DEFERRED` is the output form of an `UNVERIFIED` ledger entry that could not be resolved or safely +removed. Do not hide unresolved claims behind fluent prose; leave them qualified or deferred so review +can focus on the remaining risk. diff --git a/.agents/skills/api-docs/references/conceptual/code-samples.md b/.agents/skills/api-docs/references/conceptual/code-samples.md new file mode 100644 index 000000000000..d746229ef080 --- /dev/null +++ b/.agents/skills/api-docs/references/conceptual/code-samples.md @@ -0,0 +1,93 @@ +# Code samples in conceptual articles + +Readers copy code even when an article calls it illustrative. Make each block explicit about its purpose +and honest about what it omits. + +## Choose a snippet or a sample + +| Form | Use for | Quality bar | +|---|---|---| +| Focused snippet | One API call, decision, or local pattern | Short, exact, declares the relevant values, and names omitted setup | +| Complete sample | A workflow the reader is expected to run | Buildable, all consequential control flow present, expected result stated | +| Pseudocode | Host- or API-specific scaffolding that cannot be portable | Labeled as pseudocode; do not use a `csharp` fence if it is not valid C# | + +Prefer a repository sample or test as the source for complete code. This docset currently uses inline +fenced code rather than Microsoft Learn's `:::code source=...:::` extraction, so inline examples can +drift. Compare them with the nearest test/renderer and compile a focused complete sample when practical. + +## Verify from source + +For every C# block: + +1. Confirm type names, property capitalization, overloads, argument order, and return types in current + source. +2. Confirm every variable is declared or explicitly identified as host-provided. +3. Check [`../obsolete-api-map.md`](../obsolete-api-map.md) and source attributes. +4. Confirm platform-only APIs are shown in the correct target context. +5. State whether the block is complete or what setup it intentionally omits. + +Do not use ellipses where omitted code controls ownership, failure handling, synchronization, or cleanup. + +## Model failure honestly + +A complete workflow should: + +- Check nullable factories before dereferencing the result. +- Check `bool`, enum, and status results when failure changes the outcome. +- Catch only specific exceptions the example can recover from. +- Surface unrecoverable failures instead of silently returning success-shaped data. +- Show an expected result or verification so readers know the code worked. + +Avoid broad `catch (Exception)` and `catch (SystemException)` examples. They hide the actual contract and +teach readers to discard actionable failures. + +## Model SkiaSharp ownership and lifetime + +Use the authoritative caller-owned versus parent-owned table in +[`../skia-patterns.md`](../skia-patterns.md); do not infer ownership from whether a managed type implements +`IDisposable`. + +- Keep backing memory, native devices/contexts, and delegates alive for the full native use. +- Never pass a managed pointer beyond its pinning scope. +- Drain or synchronize queued GPU work before releasing resources when the backend contract requires it. +- Check same-instance returns before disposing an input that may also be the result. +- Keep a graphics context current for calls and cleanup only on backends that require it; do not + generalize an OpenGL rule to Vulkan, Metal, or Direct3D. + +When the lifecycle is the lesson, prefer a slightly longer correct example over a short example that +leaks or races. + +## Model asynchronous and callback-only results + +Show: + +1. Who initiates the operation. +2. What drives completion. +3. The lifetime of callback parameters. +4. What data must be copied before the callback returns. +5. How cancellation, timeout, or failure is reported. +6. When resources can be released. + +Do not imply that polling a fixed number of times guarantees completion unless the API contract says so. +For production loops, explain the host's scheduling or timeout policy rather than presenting a magic +iteration count as universal. + +## Show intentionally incorrect code safely + +When a troubleshooting or migration article needs a bad example: + +- Introduce it in prose as incorrect. +- Mark it inside the code block with a comment such as `// Incorrect: ...`. +- Keep dangerous or non-compiling lines commented out when readers may copy the block wholesale. +- Follow it immediately with the corrected pattern and explain the behavioral difference. + +Never rely on a heading such as "Before" alone to signal that code is unsafe or obsolete. + +## Validate + +Follow [`validation.md`](validation.md). At minimum: + +- Compile complete examples or match them line by line to a compiling repository sample/test. +- Verify illustrative SkiaSharp calls against source. +- Run the example when the article promises runtime output and the current host supports it. +- State when platform hardware prevents execution and what evidence substituted for it. diff --git a/.agents/skills/api-docs/references/conceptual/fact-checking.md b/.agents/skills/api-docs/references/conceptual/fact-checking.md new file mode 100644 index 000000000000..0804e422f08b --- /dev/null +++ b/.agents/skills/api-docs/references/conceptual/fact-checking.md @@ -0,0 +1,64 @@ +# Fact-checking conceptual documentation + +Conceptual prose can be fluent and still be false. Build a claim inventory before writing or reviewing, +then verify each claim against the closest source of truth. + +Apply the shared evidence hierarchy, public managed contract boundary, and cross-layer procedure in +[`../technical-fact-checking.md`](../technical-fact-checking.md). This file adds the claim-management, +platform-matrix, and external-source checks needed by conceptual articles. + +## Build a claim ledger + +Track consequential claims rather than every sentence: + +```text +CLAIM | | | | VERIFIED / QUALIFIED / UNVERIFIED +``` + +Include claims that affect whether a reader's code compiles, runs, remains safe, or selects a supported +path: + +- API and overload existence. +- Failure behavior and return values. +- Defaults, limits, and units. +- Ownership and disposal. +- Async/callback lifetime and ordering. +- Thread/context requirements. +- Platform/backend availability. +- Version-sensitive behavior. + +Record `QUALIFIED` when the claim is true only under a stated condition. Record `UNVERIFIED` when the +available evidence is insufficient; do not convert it into a warning or absolute statement. + +## Verify platform matrices row by row + +For every named platform: + +1. Find the target-specific handler, renderer, or project configuration. +2. Identify the actual backend selected there. +3. Check compile-time and runtime availability gates. +4. Record unsupported targets and the failure mode. +5. Distinguish "not implemented in this integration" from "the native API cannot support it." + +Avoid broad claims such as "cross-platform," "works everywhere," or "identical" unless every listed +implementation supports the same behavior. + +## Handle external and time-sensitive facts + +- Prefer first-party product documentation or source. +- Capture the relevant version or publication state when behavior can change. +- Link to a stable conceptual page rather than a transient search result. +- Explain whether SkiaSharp exposes the native capability today; native support alone does not establish + managed support. +- Remove an external claim that is not needed for the reader's outcome and cannot be verified. + +## Evidence in reviews + +Every CRITICAL or IMPORTANT factual finding needs one of: + +- A repository `path:line`. +- A focused test and its observed result. +- A current first-party URL with the relevant condition quoted or summarized. + +No citation means the lead remains `UNVERIFIED`. This prevents confident false positives from becoming +documentation churn. diff --git a/.agents/skills/api-docs/references/conceptual/index.md b/.agents/skills/api-docs/references/conceptual/index.md new file mode 100644 index 000000000000..563672461dc1 --- /dev/null +++ b/.agents/skills/api-docs/references/conceptual/index.md @@ -0,0 +1,64 @@ +# Conceptual documentation route + +Use this route for Markdown under `documentation/docfx/guides/`. A conceptual article should help a +reader understand a model, choose an approach, complete a task, migrate working code, or resolve a +specific symptom. It should complement API reference rather than narrate members one by one. + +## Route the work + +Choose both a **change size** and an **article type** before reading the remaining references. + +### Change size + +| Size | Typical work | Route | +|---|---|---| +| Focused fix | One fact, broken link, typo, or small sample correction | Read [`../technical-fact-checking.md`](../technical-fact-checking.md), [`fact-checking.md`](fact-checking.md), and [`validation.md`](validation.md); add [`code-samples.md`](code-samples.md) for code or [`structure-and-style.md`](structure-and-style.md) for prose/metadata | +| Substantive review | Several claims, a complete example, platform coverage, or article structure | Read [`reviewing.md`](reviewing.md), then the matching blueprint and relevant shared references | +| New article or major rewrite | New reader journey, changed article type, or broad restructuring | Read [`authoring.md`](authoring.md), then the matching blueprint and relevant shared references | + +Review is report-only unless the user asks to fix or rewrite the article. When they do, apply the +corrections and run the authoring validation pass. + +### Article type + +| Reader intent | Use when the reader needs to... | Blueprint | +|---|---|---| +| Overview or decision | Compare choices and find the right next task | [`templates/overview.md`](templates/overview.md) | +| Concept | Understand a model, lifecycle, or relationship | [`templates/concept.md`](templates/concept.md) | +| How-to | Complete one concrete task | [`templates/how-to.md`](templates/how-to.md) | +| Migration | Move working code from one supported approach to another | [`templates/migration.md`](templates/migration.md) | +| Troubleshooting | Diagnose and fix a named symptom or error | [`templates/troubleshooting.md`](templates/troubleshooting.md) | + +Do not combine independent reader intents merely because they share APIs. Split the material when a +reader who needs one outcome would have to skip large sections written for another. + +## Shared references + +Load only the references the task needs: + +- [`../technical-fact-checking.md`](../technical-fact-checking.md) — shared public-contract boundary, + evidence hierarchy, and cross-layer verification used by API reference and conceptual docs. +- [`fact-checking.md`](fact-checking.md) — conceptual claim ledger, version/platform checks, external + sources, and review evidence. +- [`code-samples.md`](code-samples.md) — snippet versus sample decisions, source verification, failure + handling, ownership, async lifetimes, and intentionally incorrect code. +- [`structure-and-style.md`](structure-and-style.md) — metadata, introductions, headings, procedures, + voice, global readiness, formatting, links, alerts, and accessible images. +- [`validation.md`](validation.md) — code, links, DocFX, rendered output, and validation reporting. +- [`microsoft-contribute-sources.md`](microsoft-contribute-sources.md) — provenance and the Microsoft + Learn infrastructure rules intentionally not copied into SkiaSharp. Read this only when maintaining + the skill. + +## Quality contract + +Whatever the article type: + +1. Start from a real reader, starting state, and outcome. +2. Verify consequential claims against the closest source of truth. +3. Make code honest about what is complete, illustrative, platform-specific, or intentionally wrong. +4. Put prerequisites, constraints, and recovery guidance before the point where the reader needs them. +5. Make the path to success scannable and verifiable. +6. State uncertainty instead of turning an assumption into documentation. + +The MicrosoftDocs/Contribute guidance supplies the editorial system. SkiaSharp source, tests, native +code, and platform implementations supply the technical truth. diff --git a/.agents/skills/api-docs/references/conceptual/microsoft-contribute-sources.md b/.agents/skills/api-docs/references/conceptual/microsoft-contribute-sources.md new file mode 100644 index 000000000000..4fd9717441e6 --- /dev/null +++ b/.agents/skills/api-docs/references/conceptual/microsoft-contribute-sources.md @@ -0,0 +1,41 @@ +# MicrosoftDocs/Contribute source map + +This route adapts the reusable editorial system from +[`MicrosoftDocs/Contribute`](https://github.com/MicrosoftDocs/Contribute) and combines it with +SkiaSharp-specific source verification. Use this file when maintaining the skill; normal authoring and +review runs should load the focused references instead. + +## Adopted guidance + +| Topic | Source | Local adaptation | +|---|---|---| +| Contribution/article triage | [`how-to-write-overview.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/how-to-write-overview.md) | Focused fix vs substantive review vs new/major rewrite | +| Voice, intent, concise/scannable prose, global readiness | [`style-quick-start.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/style-quick-start.md) | `structure-and-style.md` voice and localization pass | +| .NET voice and tone | [`dotnet-voice-tone.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/dotnet/dotnet-voice-tone.md) | Reader-focused introduction, second person, active voice, present tense | +| .NET article skeleton | [`dotnet-style-guide.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/dotnet/dotnet-style-guide.md) | Separate SkiaSharp blueprints for each reader intent | +| Code blocks and intentionally bad code | [`code-in-docs.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/code-in-docs.md) | Snippet/sample distinction, bad-code labeling, build/source verification | +| .NET sample quality and exception handling | [`dotnet-contribute.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/dotnet/dotnet-contribute.md) | Complete samples build, handle expected failures, avoid broad catches | +| Alerts, headings, images, and alt text | [`markdown-reference.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/markdown-reference.md) | Sparse alerts, accessible images, and DocFX-compatible Markdown; omit Learn's `TIP` alert to preserve this docset's established four-kind convention | +| Link and xref quality | [`how-to-write-links.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/how-to-write-links.md) | Exact UIDs, relative conceptual links, descriptive HTTPS links | +| Bold/italic/code usage | [`text-formatting-guidelines.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/text-formatting-guidelines.md) | UI/new-term/code formatting semantics | +| Discoverable titles and headings | [`seo-reference.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/seo-reference.md) | Specific titles/H2s without importing Learn SEO metadata quotas | +| .NET PR review triage | [`dotnet-pr-review.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/dotnet/dotnet-pr-review.md) | Focused/substantive/draft review depth and publish verdict | + +## Intentionally not copied + +SkiaSharp's DocFX site is not Microsoft Learn's Open Publishing System. Do not add: + +- `ms.author`, `ms.date`, `ms.topic`, `ms.service`, `ms.custom`, or Learn ownership metadata from + [`metadata.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/metadata.md). +- CLA, `#sign-off`, OPS validation, staging, auto-merge, or label mechanics from + [`process-pull-request.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/process-pull-request.md). +- Learn Authoring Pack instructions or Learn-only Markdown extensions merely because they appear in the + source repository. +- `:::code source=...:::` references until this repository adopts and validates an extracted-snippet + system. +- Learn-specific title/description quotas as hard requirements. Local metadata should remain concise and + useful without invented fields or padded prose. + +The Contribute repository delegates deeper inclusive-language and procedure rules to the separate +Microsoft Writing Style Guide. This skill includes practical accessibility and inclusive-language checks +but does not claim that Contribute contains a complete policy. diff --git a/.agents/skills/api-docs/references/conceptual/reviewing.md b/.agents/skills/api-docs/references/conceptual/reviewing.md new file mode 100644 index 000000000000..0b0bd92f14e6 --- /dev/null +++ b/.agents/skills/api-docs/references/conceptual/reviewing.md @@ -0,0 +1,142 @@ +# Reviewing conceptual articles + +Review conceptual documentation for reader success, technical correctness, safe examples, platform +accuracy, structure, accessibility, and maintenance risk. Review is report-only unless the user asks to +apply fixes. + +Read [`../technical-fact-checking.md`](../technical-fact-checking.md), +[`fact-checking.md`](fact-checking.md), the matching article blueprint from [`index.md`](index.md), and +[`code-samples.md`](code-samples.md) when the scope contains code. + +## Choose the review depth + +This is a second axis after the top-level change-size routing in [`index.md`](index.md). Classify how +deeply the selected review scope needs to be examined: + +| Review depth | Examples | Checks | +|---|---|---| +| Focused | Typo, one broken link, one factual correction | Verify the changed claim and its immediate context | +| Substantive | New article, changed task flow, platform matrix, complete sample | Run every review pass below | +| Draft | Incomplete structure or intentionally partial content | Focus on direction, missing evidence, and blockers before polishing | + +Resolve the scope to an explicit file list. For a PR, use the parent repository diff; for a theme, include +the index/TOC and every article needed to evaluate the reader journey. + +## Review passes + +### 1. Reader outcome + +- Identify the intended reader, starting state, and promised outcome from the article itself. +- Confirm the article type matches that intent. +- Check that prerequisites appear before dependent actions and that the completion condition is visible. +- Flag scope that combines independent tasks or omits a required step. + +### 2. Technical facts + +Work claim by claim using the shared [`technical-fact-checking.md`](../technical-fact-checking.md) +contract and [`fact-checking.md`](fact-checking.md): + +- Verify signatures, overloads, nullability, defaults, validation, and result values in managed source. +- Verify ownership, disposal order, pinning, callbacks, and threading through wrappers, tests, and native + contracts as needed. +- Verify every platform/backend row from its implementation or build configuration. +- Qualify version-sensitive and external claims with current first-party evidence. + +No source means `UNVERIFIED`, not "wrong." + +### 3. Code and commands + +Use [`code-samples.md`](code-samples.md): + +- Determine whether each block claims to be a complete sample or an illustrative snippet. +- Confirm members, overloads, variables, imports, result checks, ownership, and lifetimes. +- Check that deliberately wrong code is unmistakably marked in prose and code. +- Confirm commands match the repository and target platform. +- Run or compile representative complete samples when practical. + +### 4. Article-type structure + +Compare the article to its blueprint: + +- Overview: clear choice criteria, comparison, and routed next tasks. +- Concept: accurate mental model, relationships/lifecycle, constraints, and applied example. +- How-to: prerequisites, ordered procedure, expected results, and verification. +- Migration: supported starting/target states, mapping, before/after, what stays the same, and rollback or + fallback. +- Troubleshooting: symptom-first opening, diagnosis, causes, resolution, verification, and escalation. + +Flag a structural issue only when it makes the article harder to use; blueprints are reader models, not +mandatory boilerplate. + +### 5. Editorial, inclusive language, and accessibility + +Apply [`structure-and-style.md`](structure-and-style.md): + +- One H1, specific sentence-case headings, concise metadata, and a result-oriented introduction. +- Active, direct prose with consistent terms and no unnecessary idioms or future tense. +- Inclusive language that does not assume gender, ability, expertise, or a preferred platform. +- Correct code/UI/new-term formatting. +- Descriptive links, exact-case xrefs, valid fragments, and first-party external sources. +- Sparse, correctly chosen alerts that are not stacked. +- Useful alt text and a text explanation for complex visuals; do not use screenshots to present code. + +### 6. Maintenance and validation + +- Check TOC placement and neighboring article links. +- Look for repeated facts that should link to a canonical article instead. +- Identify time-sensitive claims without a version or source. +- Run the applicable checks in [`validation.md`](validation.md). + +## Severity + +Use reader impact, not writing preference: + +- **CRITICAL** — The task cannot succeed; code does not compile; an API/member is fabricated; guidance + can crash, leak, corrupt data, free parent-owned memory, or violate a native lifetime; or the article + directs readers through an unsupported path with no warning. +- **IMPORTANT** — A factual/default/platform claim is wrong; a required prerequisite, failure check, or + recovery path is missing; a core link is broken; or the structure is likely to produce the wrong + implementation. +- **MINOR** — Terminology, metadata, repetition, formatting, accessibility wording, or scannability can + improve without changing the technical outcome. + +Examples: + +```text +CRITICAL | example | guide.md | Create the surface | Disposes SKSurface.Canvas, which is parent-owned; SKSurface.cs:... shows the surface owns it, so later draws can access a released native object. +IMPORTANT | platform | guide.md | Supported platforms | Claims Direct3D support on Linux, but the Direct3D context is Windows-only in ; readers will choose an unavailable backend. +MINOR | structure | guide.md | Overview | The heading does not describe the decision made in this section, so it is hard to scan. +``` + +Every CRITICAL or IMPORTANT finding needs a repository `path:line`, focused test, or current first-party +source. Deduplicate overlapping symptoms into the root finding. + +## Output + +Emit one machine-readable line per finding: + +```text +SEVERITY | class | | | +``` + +Then provide a compact report: + +```markdown +# Conceptual documentation review — + +## Summary +- Files reviewed: +- Findings: CRITICAL , IMPORTANT , MINOR +- Unverified claims: +- Verdict: Ready to publish / Needs fixes / Major rework + +## Findings +... + +## Evidence gaps +... +``` + +If the user asks for fixes, correct the root causes, preserve unrelated prose, and run the full validation +procedure. Do not leave staged review comments for issues already fixed in the branch unless the user +specifically wants review comments. diff --git a/.agents/skills/api-docs/references/conceptual/structure-and-style.md b/.agents/skills/api-docs/references/conceptual/structure-and-style.md new file mode 100644 index 000000000000..2cd1e31a14f7 --- /dev/null +++ b/.agents/skills/api-docs/references/conceptual/structure-and-style.md @@ -0,0 +1,124 @@ +# Structure and style for conceptual articles + +This reference adapts the reusable editorial guidance in MicrosoftDocs/Contribute to SkiaSharp's local +DocFX site. It deliberately omits Microsoft Learn publishing metadata and authoring-pack extensions. + +## Metadata, title, and introduction + +Use the metadata supported by this docset: + +```yaml +--- +title: "Specific sentence-case title" +description: "Describe the reader outcome, approach, and meaningful scope." +--- +``` + +- Do not add `ms.author`, `ms.date`, `ms.topic`, `ms.service`, or other Learn-only fields. +- Use one H1 after front matter. Keep it aligned with the metadata title. +- Make the title specific enough to distinguish the article in search and the TOC. +- Keep the description concise and natural. Roughly 115-160 characters often works, but do not pad it to + reach a target. +- Open with the outcome, use case, and most consequential constraint. Do not repeat the title as an + italic subtitle. + +## Headings and scanning + +- Use sentence case. +- Make H2s describe the decision, task, model, symptom, or result in that section. Avoid generic headings + such as "Overview" or "Details." +- Preserve a logical hierarchy; do not skip levels to get smaller text. +- Keep paragraphs focused and put conditions before instructions. +- Use tables for genuine comparisons and lists for parallel choices. +- Keep long reference enumerations out of the task flow; link to API reference or a focused reference + section instead. + +The TOC and H2s are part of the reader interface. A reader should be able to predict the article's path +from them. + +## Procedures + +- Introduce the goal and prerequisites before the steps. +- Use a numbered list when order matters. +- Put one action in each step; place the reason or expected result immediately after it. +- Use imperative verbs and exact UI labels, commands, paths, and values. +- If a step branches, state the condition first and separate the paths clearly. +- End with a verification step rather than assuming success. + +Do not hide required actions in notes, code comments, or paragraphs between numbered steps. + +## Voice and global readiness + +- Address the reader as "you" and use active voice. +- Prefer familiar words and short sentences. +- Use present tense for current behavior; avoid future tense when describing what a command does. +- Define a specialized term at first use, then use the same term consistently. +- Expand uncommon acronyms at first use. +- Avoid idioms, jokes, cultural references, and spatial instructions such as "see above" when a heading + reference is clearer. +- Avoid "simple," "easy," "obvious," and "just" when setup or recovery is nontrivial. +- Avoid dismissive or exclusionary terms and assumptions about the reader's ability, environment, or + preferred platform. + +Short, explicit prose is easier to scan, translate, and maintain. + +## Inclusive language + +- Use gender-neutral terms unless gender is relevant. +- Describe the task or state rather than labeling a person by ability or experience. +- Avoid ableist idioms such as "sanity check," "blind to," or "crippled"; name the actual validation, + omission, or limitation. +- Do not assume the reader uses a particular platform, input method, visual theme, or spoken language. +- Avoid humor and metaphors that depend on culture or can obscure technical meaning. + +These checks are a practical local baseline. MicrosoftDocs/Contribute points to the separate Microsoft +Writing Style Guide for its complete inclusive-language policy. + +## Text formatting + +- Use **bold** for UI elements and labels the reader sees. +- Use *italics* for a newly introduced term or a placeholder the reader replaces. +- Use `code` for APIs, commands, filenames, paths, configuration keys, values, and literal input. +- Do not use formatting only for emphasis; rewrite the sentence so its point is clear. + +## Links and xrefs + +- Use `xref:` for SkiaSharp and .NET API reference. +- Use exact-case UIDs. Use the wildcard member form only when intentionally linking to an overload group. +- Use relative `.md` links for conceptual articles in this docset. +- Use descriptive link text that tells the reader what they will get; never use "click here." +- Use HTTPS and prefer current first-party sources. +- Recheck every heading fragment after renaming a heading. +- Link to third-party material only when it is necessary, maintained, and gives the reader a clear next + step. Do not outsource a core procedure to an unstable blog post. + +## Alerts + +Readers often skip alerts. Keep required task information in the main flow, use no more than one or two +alerts per article when practical, and never stack alerts. + +| Alert | Use for | +|---|---| +| `NOTE` | Context that can be skipped without preventing success | +| `IMPORTANT` | Information required for success | +| `CAUTION` | An action that can cause recoverable harm | +| `WARNING` | A risk of serious or difficult-to-reverse harm | + +Do not promote ordinary prose to an alert merely to make it visible. + +## Images and accessibility + +- Use images only when they communicate spatial or visual information better than text. +- Do not use screenshots to present code; code blocks are searchable, copyable, and maintainable. +- Write alt text that conveys the image's purpose rather than repeating its filename or nearby caption. +- Explain complex diagrams, graphs, and multi-step screenshots in surrounding text or a long + description. +- Do not rely on color, shape, or position alone to communicate meaning. +- Crop screenshots to the relevant UI, avoid sensitive data, and prefer images that will not become + obsolete with minor theme or layout changes. + +## Related links + +End with a small set of likely next actions. Avoid dumping every related API or article. An overview/index +page may have an "In this section" list because routing is its primary purpose; a task article should +usually link only to prerequisites, alternatives, and the next task. diff --git a/.agents/skills/api-docs/references/conceptual/templates/concept.md b/.agents/skills/api-docs/references/conceptual/templates/concept.md new file mode 100644 index 000000000000..c9a0ae7148f7 --- /dev/null +++ b/.agents/skills/api-docs/references/conceptual/templates/concept.md @@ -0,0 +1,61 @@ +# Concept blueprint + +Use a concept article when the reader needs a mental model before making decisions or completing tasks. +The article should explain relationships, lifecycle, or behavior and then connect the model to practice. + +## Plan + +Define: + +```text +Question the article answers: +What the reader already knows: +Terms that need definitions: +Components and relationships: +Lifecycle or data flow: +Constraints and invariants: +Task that applies this concept: +``` + +## Suggested shape + +```markdown +--- +title: " in SkiaSharp" +description: "" +--- + +# in SkiaSharp + + + +## + + + +## + + + +## + +| Constraint or state | Consequence for the reader | +|---|---| +| | | + +## Apply the concept + + + +## Related links + +- +``` + +## Quality checks + +- The model answers a practical reader question rather than cataloging types. +- Terms are defined once and used consistently. +- Ownership arrows, lifecycle order, and thread boundaries match source. +- Any diagram has equivalent explanatory text and does not rely on color alone. +- The applied example shows why the model matters. diff --git a/.agents/skills/api-docs/references/conceptual/templates/how-to.md b/.agents/skills/api-docs/references/conceptual/templates/how-to.md new file mode 100644 index 000000000000..2899c45f6974 --- /dev/null +++ b/.agents/skills/api-docs/references/conceptual/templates/how-to.md @@ -0,0 +1,77 @@ +# How-to blueprint + +Use a how-to when the reader wants to complete one concrete task. The article should be executable in +order and end with an observable result. + +## Plan + +Record: + +```text +Reader: +Starting state: +Outcome: +Prerequisites: +Supported platforms/versions: +Completion check: +Out of scope: +``` + +If several supported approaches require different setup, explain the choice first and give each path its +own procedure. Do not interleave platform branches step by step. + +## Suggested shape + +```markdown +--- +title: " with SkiaSharp" +description: "" +--- + +# with SkiaSharp + + + +## Prerequisites + +- +- + +## + + + +## + +1. + + + + + +2. + +## + +... + +## Verify the result + + + +## Related links + +- +``` + +Rename headings to the actual actions. Omit optional sections rather than publishing empty boilerplate. + +## Quality checks + +- Each numbered step contains one action in the order performed. +- Required values are defined before use. +- Complete code handles failure and ownership. +- Platform-specific branches are clearly scoped. +- The final verification proves the promised outcome. +- Troubleshooting content stays focused on failures likely during this task; link to a dedicated + troubleshooting article for broader diagnosis. diff --git a/.agents/skills/api-docs/references/conceptual/templates/migration.md b/.agents/skills/api-docs/references/conceptual/templates/migration.md new file mode 100644 index 000000000000..f6c20c8007ea --- /dev/null +++ b/.agents/skills/api-docs/references/conceptual/templates/migration.md @@ -0,0 +1,89 @@ +# Migration blueprint + +Use a migration article when the reader has working code on one supported approach and needs to move to +another. Preserve their mental model by separating what changes from what remains valid. + +## Plan + +Define: + +```text +Supported source state: +Supported target state: +Why migrate: +What remains unchanged: +Concept/API mappings: +Behavioral differences: +Compatibility or rollback path: +Completion check: +``` + +Do not present a replacement as universal when a source platform/backend has no target equivalent. + +## Suggested shape + +```markdown +--- +title: "Migrate from to " +description: "" +--- + +# Migrate from to + + + +## Before you migrate + +- +- +- + +## Map source concepts to target concepts + +| Source | Target | What changes | +|---|---|---| +| | | | + +## What stays the same + + + +## Compare the workflows + +### Source + +```csharp +// Existing supported pattern. +``` + +### Target + +```csharp +// Replacement pattern with complete failure and lifetime handling. +``` + +## Migrate step by step + +1. +2. +3. + +## Verify the migration + + + +## Related links + +- +- +``` + +## Quality checks + +- "Before" code is still valid for the documented source version and is labeled as the source pattern, + not as bad code. +- "Target" code uses current APIs and preserves required behavior. +- The article states what remains unchanged. +- Semantic differences, not just renamed methods, are explicit. +- Unsupported migrations have a supported alternative or clear boundary. +- Verification checks behavior, not only compilation. diff --git a/.agents/skills/api-docs/references/conceptual/templates/overview.md b/.agents/skills/api-docs/references/conceptual/templates/overview.md new file mode 100644 index 000000000000..744d6ff1dc38 --- /dev/null +++ b/.agents/skills/api-docs/references/conceptual/templates/overview.md @@ -0,0 +1,64 @@ +# Overview and decision blueprint + +Use an overview when the reader needs to understand available approaches, choose one, and navigate to the +right task. It should route readers, not duplicate every routed article. + +## Plan + +Define: + +```text +Decision: +Audience: +Options in scope: +Comparison dimensions: +Recommended default: +Exceptions to the default: +Next task for each option: +``` + +Comparison dimensions must change the reader's decision: platform support, acceleration, ownership, +latency, complexity, compatibility, or lifecycle. Avoid tables filled with facts that do not help choose. + +## Suggested shape + +```markdown +--- +title: " overview" +description: "" +--- + +# overview + + + +## Choose an approach + +| Approach | Use when | Avoid or reconsider when | +|---|---|---| +| `, ``, ``, ``, ``, ``, and ``. - Include an `` entry for each deterministic managed exception identified by the fact - sheet, including verified checked arithmetic and deliberate failures from directly invoked - helpers. Do not add a possible exception without locating the exact throwing operation. + Include an `` entry on each affected member for every deterministic managed exception + identified by the fact sheet, including verified checked arithmetic and deliberate failures from + directly invoked helpers. Type-level prose does not replace member-level exception contracts. + Do not add a possible exception without locating the exact throwing operation. 4. **Match the accessor verb to the signature**, not to intuition: `{ get; set; }` → "Gets or sets …", `{ get; }` → "Gets …". Many struct properties look read-only but are settable — check the signature. 5. **Defaults come from the source.** A struct property with no field initializer defaults to @@ -74,8 +86,10 @@ own recollection. content. Search the touched file for placeholders. Fill each in-scope field, replace a non-applicable remarks placeholder with ``, or emit a `DEFERRED` row for that exact field. -4. **Review** the files just written with the review checks ([`reviewing.md`](reviewing.md) §Checks), then - fix CRITICAL findings by editing the XML directly. +4. **Review** the files just written with the review checks ([`reviewing.md`](reviewing.md) §Checks). + Every self-introduced CRITICAL or IMPORTANT finding must be fixed before landing. If the evidence or + time needed to fix it is unavailable, restore the affected field to its original placeholder and emit + a `DEFERRED` row; never keep weak prose merely to reduce the placeholder count. 5. **Validate & format** ([`validation.md`](validation.md)): run `docs-format-docs` — it formats and runs the deterministic checks; fix any build-failing broken-XML errors and reconcile every remaining @@ -89,16 +103,29 @@ own recollection. After all files, emit a compact manifest — one line per file: ``` -WROTE | | summaries: params: returns: remarks: | source: +WROTE | | members: fields: exceptions: | source: | native: ``` -Counts must come from the final XML, not memory. Then list every field intentionally left as a placeholder -(ran out of certainty/time) so the next run re-detects it: +Counts must come from the semantic `` diff, not memory: `members` is the number of type/member +`` blocks changed, `fields` is the number of changed `` children, and `exceptions` is the +number of changed `` children. `source:` uses repository-relative POSIX paths and real line +ranges, and `native:` must +equal the number of `NATIVE` rows for the file. A file cannot receive a `WROTE` row while it has an +unresolved self-introduced CRITICAL/IMPORTANT finding or unsupported native claim. For every selected +DocId, list each field intentionally left as a placeholder because evidence or time was insufficient: ``` DEFERRED | | | | ``` +Use `summary`, `returns`, `value`, or `remarks` for singleton fields; `param:` and +`typeparam:` for named fields; and `exception:` for exceptions. Unselected DocIds use the +file-level `UNSELECTED` row instead of one `DEFERRED` row per field. + +Include the `EVIDENCE` and `NATIVE` rows from `technical-fact-checking.md`, the `TRACE` and finding rows +from `reviewing.md`, and the `UNSELECTED` rows in the PR body. This evidence block is part of the +completion gate, not optional review commentary. + ## Boundaries - Edit only the in-scope `.xml` files, and only `` content — never touch `MemberSignature`, diff --git a/.agents/skills/api-docs/references/reviewing.md b/.agents/skills/api-docs/references/reviewing.md index 35b808319350..2a301e728221 100644 --- a/.agents/skills/api-docs/references/reviewing.md +++ b/.agents/skills/api-docs/references/reviewing.md @@ -5,7 +5,9 @@ dual of [`adding.md`](adding.md) (which fills blanks); review improves what is a **report-only by default**; fixing is a separate, gated step. You run this yourself, end to end — resolve scope, read source, compare, report, and (if approved) fix. -One agent does the whole pass. Work in batches of ~25–40 files so each pass stays auditable and resumable. +One agent does the whole pass. Interactive review can use batches of ~25–40 straightforward files. +Automated authoring reviews only the current authoring wave: at most 10 files and 60 +placeholder-bearing members, with smaller waves for cross-layer native evidence. ## Required reading (first) @@ -37,8 +39,8 @@ across many files almost certainly skimmed. Every factual finding must cite sour **select the matching files yourself** — e.g. expand "font" to `SKFont`, `SKTypeface`, `SKFontMetrics`, `SKTextBlob`, and the text APIs on `SKPaint`/`SKCanvas`, judging by each type's purpose not just its filename. For "whatever changed" use `git -C docs diff --name-only origin/main...HEAD`; for the whole - library review every file. Shard into ~25–40-file batches; review is incremental against the - `last-reviewed` marker. + library review every file. Interactive review uses ~25–40-file batches and is incremental against the + `last-reviewed` marker; automated authoring uses only the smaller current wave defined above. 2. **Run the deterministic checks** on the batch with `docs-format-docs` ([`validation.md`](validation.md)). It finds objective defects with no model cost and emits findings in the shared contract. @@ -52,6 +54,9 @@ across many files almost certainly skimmed. Every factual finding must cite sour 4. **Collect and dedupe findings** in the shared contract. Deduplicate by `(file, docId, class)` plus fuzzy message match; when the linter and your own review report the same defect, keep one row. On a severity disagreement, take the **highest**. + A zero-finding result is valid only after every selected file has a complete source range, every + deterministic managed exception has been reconciled with member-level `` tags, and every + native-backed claim has either a `NATIVE` evidence row or remains deferred. 5. **(Gated) Fix.** If fixing is approved, edit the XML directly for CRITICAL (and chosen IMPORTANT) findings, and expand examples where types are example-poor — port the `SKCanvas`/`SKShader` bar to @@ -157,9 +162,16 @@ SEVERITY | class | | | | source: | checked: | issues: +TRACE | | source: | checked: | issues: ``` +`source:` must contain real line ranges covering the members checked; a path without ranges is incomplete. +`checked` is the number of selected `EVIDENCE` DocIds in the file, and `issues` is the number of +machine-readable finding rows for that file. +Include the `EVIDENCE` classifications and `NATIVE` rows required by +[`technical-fact-checking.md`](technical-fact-checking.md). Missing evidence is a coverage gap, not proof +of zero findings. + After deduplication, write a single Markdown report plus the machine block (one line per deduped finding) to `output/docs-review/` (gitignored). Nothing in `docs/` changes unless the gated fix step runs. diff --git a/.agents/skills/api-docs/references/technical-fact-checking.md b/.agents/skills/api-docs/references/technical-fact-checking.md index 3ba556ddce69..e679bb7e4689 100644 --- a/.agents/skills/api-docs/references/technical-fact-checking.md +++ b/.agents/skills/api-docs/references/technical-fact-checking.md @@ -44,6 +44,18 @@ Before writing, capture consequential facts in a scratch ledger: CLAIM | | | | VERIFIED / QUALIFIED / UNVERIFIED ``` +Classify each selected type/member `` block before editing: + +```text +EVIDENCE | | | MANAGED / NATIVE | +``` + +Use `NATIVE` whenever the documentation would explain a native-backed status, ownership transfer, +callback lifetime/failure, backend constraint, or behavior that the managed method body delegates. +Do not author that claim until the pinned native declaration or implementation is available. If it +cannot be inspected, leave the field as a placeholder and report it as deferred rather than inferring +from an enum/member name. + At minimum check: - Exact signatures, overloads, accessors, and public identifiers. @@ -89,9 +101,24 @@ platform, or unsupported native variant. - State behavior in terms of public parameters, return values, properties, and observable effects. - Add `` entries for explicit managed exceptions; do not hide them only in remarks. +- Use the actual framework DocId for BCL types, such as `T:System.ObjectDisposedException`; never place + `System` types under the `SkiaSharp` namespace. - Say when a callback can receive `null`, when a status must be checked, and when data expires. - Use source paths and line numbers in review findings and internal ledgers, not as a substitute for clear public-facing prose. - Do not make a stronger claim than the evidence. "Returns `InvalidRecording` for invalid input" is safer than inventing an exhaustive list of invalid states when the wrapper/native contract does not provide one. + +For each native-backed claim that is authored, preserve a machine-readable evidence row: + +```text +NATIVE | | | managed: | native: | +``` + +The native path must identify the pinned source actually read. A type or member name, generated enum +value, or an initialized-but-unread submodule is not evidence. + +Machine-readable citations use repository-relative POSIX paths and one-based inclusive ranges: +`binding/SkiaSharp/Foo.cs:20-34`. Separate multiple citations with semicolons. Do not use absolute paths, +backslashes, `NONE`, or a path without line numbers. diff --git a/.agents/skills/api-docs/references/validation.md b/.agents/skills/api-docs/references/validation.md index acb260dceb83..08e4f3efabd5 100644 --- a/.agents/skills/api-docs/references/validation.md +++ b/.agents/skills/api-docs/references/validation.md @@ -41,6 +41,19 @@ For each touched type file, separately search for `To be added.`, TODO, empty re scaffolds. Every remaining in-scope field must be filled or listed as `DEFERRED`; do not call a file or type complete merely because the Cake target exits successfully. +Before landing, also reject: + +- A BCL DocId incorrectly nested under SkiaSharp, such as `T:SkiaSharp.System.*`. +- A `WROTE` row without real managed source ranges, final member/exception counts, or the expected number + of `NATIVE` rows. +- A native-backed status, ownership, callback, backend, or lifetime claim without a pinned native + path-and-line citation. +- A zero-finding report that has not reconciled deterministic exceptions for every touched member. + +These completion checks depend on the evidence block and source audit; `docs-format-docs` does not perform +them. Treat missing evidence or inconsistent counts as validation failure, not as a warning. They do not +replace human/source review of whether a claim or exception list is complete. + ## What FAILS the build (errors) Two things stop a doc from parsing or rendering on the published Learn site, so both fail the target: From 7c862a2818be1d7eaaccd7c9001715d0d09a2258 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 5 Aug 2026 22:22:03 +0200 Subject: [PATCH 12/20] Split documentation skill from GPU guides Move the API/conceptual documentation skill and contributor workflow changes to standalone PR #4674 so this PR contains only the GPU surface guides and navigation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9ae60ac4-ac5a-466d-9a84-8a9e3a6c7c88 --- .agents/skills/api-docs/SKILL.md | 70 ++------- .agents/skills/api-docs/references/adding.md | 94 +++--------- .../skills/api-docs/references/checklist.md | 7 - .../references/conceptual/authoring.md | 113 -------------- .../references/conceptual/code-samples.md | 93 ------------ .../references/conceptual/fact-checking.md | 64 -------- .../api-docs/references/conceptual/index.md | 64 -------- .../microsoft-contribute-sources.md | 41 ----- .../references/conceptual/reviewing.md | 142 ------------------ .../conceptual/structure-and-style.md | 124 --------------- .../conceptual/templates/concept.md | 61 -------- .../references/conceptual/templates/how-to.md | 77 ---------- .../conceptual/templates/migration.md | 89 ----------- .../conceptual/templates/overview.md | 64 -------- .../conceptual/templates/troubleshooting.md | 87 ----------- .../references/conceptual/validation.md | 82 ---------- .../skills/api-docs/references/patterns.md | 5 +- .../skills/api-docs/references/reviewing.md | 47 ++---- .../references/technical-fact-checking.md | 124 --------------- .../skills/api-docs/references/validation.md | 29 ---- documentation/dev/writing-docs.md | 28 +--- 21 files changed, 54 insertions(+), 1451 deletions(-) delete mode 100644 .agents/skills/api-docs/references/conceptual/authoring.md delete mode 100644 .agents/skills/api-docs/references/conceptual/code-samples.md delete mode 100644 .agents/skills/api-docs/references/conceptual/fact-checking.md delete mode 100644 .agents/skills/api-docs/references/conceptual/index.md delete mode 100644 .agents/skills/api-docs/references/conceptual/microsoft-contribute-sources.md delete mode 100644 .agents/skills/api-docs/references/conceptual/reviewing.md delete mode 100644 .agents/skills/api-docs/references/conceptual/structure-and-style.md delete mode 100644 .agents/skills/api-docs/references/conceptual/templates/concept.md delete mode 100644 .agents/skills/api-docs/references/conceptual/templates/how-to.md delete mode 100644 .agents/skills/api-docs/references/conceptual/templates/migration.md delete mode 100644 .agents/skills/api-docs/references/conceptual/templates/overview.md delete mode 100644 .agents/skills/api-docs/references/conceptual/templates/troubleshooting.md delete mode 100644 .agents/skills/api-docs/references/conceptual/validation.md delete mode 100644 .agents/skills/api-docs/references/technical-fact-checking.md diff --git a/.agents/skills/api-docs/SKILL.md b/.agents/skills/api-docs/SKILL.md index bae6aaf9188d..93bff84083b3 100644 --- a/.agents/skills/api-docs/SKILL.md +++ b/.agents/skills/api-docs/SKILL.md @@ -1,26 +1,22 @@ --- name: api-docs description: > - Write and review SkiaSharp developer documentation with strict artifact routing: ECMA/mdoc XML API - reference in the docs submodule, or conceptual DocFX articles under documentation/docfx/guides. - Add docs for new APIs, review existing API docs, and author or review overviews, concepts, how-to - guides, migration guides, and troubleshooting articles. Enforces source-backed facts, safe samples, - platform accuracy, accessible structure, and Microsoft Learn-style clarity. + Write AND review XML API documentation for SkiaSharp (ECMA/mdoc XML in the docs submodule). Two modes: + (1) ADD docs for new APIs with "To be added." placeholders; (2) REVIEW existing docs by scope for + accuracy, freshness, examples, and hygiene. Triggers: "document class", "add XML docs", "write XML documentation", "fill in missing docs", "remove To be added placeholders", "review documentation", "check docs for errors", "fix doc issues", - "audit the docs", "review the font docs", "write a guide", "review this guide", "conceptual docs", - "write a tutorial", "migration guide", "troubleshooting article", "are the examples correct", - "update out-of-date docs", or any request to add, validate, correct, or expand SkiaSharp API or - conceptual documentation. + "audit the docs", "review the font docs", "are the examples correct", "update out-of-date docs", + any request to add, validate, correct, or expand SkiaSharp API documentation. metadata: layer: router --- -# SkiaSharp documentation +# API Documentation -Add and review SkiaSharp API reference and conceptual documentation. This file is a **router**: it picks a -procedure and points to the reference and tooling files that do the work. The detailed instructions live -in `references/` so they load only when needed. +Add and review SkiaSharp API documentation. This file is a **router**: it picks a procedure and points to +the reference and tooling files that do the work. The detailed instructions live in `references/` so they +load only when needed. ## Key facts @@ -32,59 +28,27 @@ in `references/` so they load only when needed. - **Edit the XML directly.** Safety comes from `docs-format-docs`, which formats every file and fails the build on broken XML/CDATA ([`references/validation.md`](references/validation.md)). - **Never edit generated files:** `index.xml`, `ns-*.xml`, `_filter.xml`, `FrameworksIndex/`. -- Conceptual articles are Markdown under `documentation/docfx/guides/`. They are built by DocFX and - should help a reader understand, decide, complete, migrate, or troubleshoot rather than duplicate - member-by-member API reference. ## How to work -Route by **artifact first**, before loading a procedure: - -| Artifact or intent | Route | -|---|---| -| `docs/SkiaSharpAPI/**/*.xml`, ECMA/mdoc, a type/member reference page, or `To be added.` | API reference | -| `documentation/docfx/guides/**/*.md`, guide, overview, concept, how-to, migration, or troubleshooting article | Conceptual | - -If a task contains both artifact kinds, split it into two explicit file lists and apply each route -independently. Never apply conceptual front matter, article blueprints, or Markdown conventions to mdoc -XML; never apply member-by-member ECMA patterns to a conceptual article. If no path is supplied, inspect -the requested artifact or repository location. Ask only when neither the artifact nor the reader intent -resolves the route. - -One agent does the whole pass. Read only the selected route and shared technical references, resolve scope -into an explicit file list, then work in reviewable waves. Interactive review can use batches of -~25–40 straightforward files. Automated authoring is stricter: one coherent wave may contain at most -10 files and 60 placeholder-bearing members, whichever limit comes first, and should be smaller when -status, ownership, callbacks, native backends, or resource lifetimes need cross-layer evidence. +One agent does the whole pass. Read the relevant reference, resolve scope into an explicit file list, then +work in batches of ~25–40 files so each pass stays auditable and resumable. | If the task is… | Read | |---|---| | Documenting **new** APIs / filling `To be added.` placeholders | [`references/adding.md`](references/adding.md) | | **Reviewing/correcting/expanding** existing docs (one type, a theme, what changed, or all) | [`references/reviewing.md`](references/reviewing.md) | -| Authoring, rewriting, or reviewing a **conceptual article** under `documentation/docfx/guides/` | [`references/conceptual/index.md`](references/conceptual/index.md) | -The user asks in plain language ("review the font docs", "fill in what's missing"). API reference docs -live at `docs/SkiaSharpAPI//.xml`; use +The user asks in plain language ("review the font docs", "fill in what's missing"). The docs live at +`docs/SkiaSharpAPI//.xml`; list them directly, and use `git -C docs diff --name-only origin/main...HEAD` for "what changed". Each `.xml` maps to its source -at `binding//.cs`. Conceptual docs live under `documentation/docfx/guides/`; use the -requested section or the parent-repo diff to resolve their scope. In both routes, **you** select the -files a request covers; the chosen procedure file covers the rest. - -The `auto-api-docs-writer` workflow in `mono/SkiaSharp-API-docs` is always an **API-reference** run. It -loads `adding.md` and `reviewing.md`; it must not load or apply the conceptual route merely because its -prompt uses words such as "documentation", "review", or "example." -Keep authoring, review, fact-checking, and validation policy in this skill. The workflow may define only -run-specific orchestration such as scope discovery, path translation, native-source checkout, timeboxes, -fix authorization, staging, and pull-request output. +at `binding//.cs`, and **you** pick the files a request covers — for a theme, scan the +list and select the matching types yourself; the chosen procedure file covers the rest. -API-reference findings use one machine-parseable contract: -`SEVERITY | class | file | docId | message`. Conceptual-guide reviews use the contract in their -procedure file. +All findings use one machine-parseable contract: `SEVERITY | class | file | docId | message`. ## References (canonical facts) -- [`references/technical-fact-checking.md`](references/technical-fact-checking.md) — shared evidence - hierarchy, managed/native contract boundary, and cross-layer verification for both routes. - [`references/patterns.md`](references/patterns.md) — .NET XML doc syntax, verb conventions, formatting. - [`references/skia-patterns.md`](references/skia-patterns.md) — domain facts (color layouts, struct defaults, standard-based enums, caller-owned vs parent-owned). @@ -103,8 +67,6 @@ procedure file. errors for broken XML/CDATA. See [`references/validation.md`](references/validation.md). - Snippet build (C#-only, download is fine): `dotnet cake --target=externals-download` then `dotnet build binding/SkiaSharp/SkiaSharp.csproj`. -- Conceptual site: follow [`references/conceptual/validation.md`](references/conceptual/validation.md); - it handles snippet checks, links, rendering, and repositories with pre-existing DocFX warnings. ## Landing changes diff --git a/.agents/skills/api-docs/references/adding.md b/.agents/skills/api-docs/references/adding.md index 3102472c9fd2..e55fc7a761d4 100644 --- a/.agents/skills/api-docs/references/adding.md +++ b/.agents/skills/api-docs/references/adding.md @@ -10,12 +10,10 @@ copy your examples into real code, so every claim must be true and every example ## Required reading (first) -1. [`technical-fact-checking.md`](technical-fact-checking.md) — evidence hierarchy, public managed - contract boundary, and cross-layer verification. -2. [`patterns.md`](patterns.md) — .NET XML doc syntax, verb conventions, summary/param/return patterns. -3. [`skia-patterns.md`](skia-patterns.md) — SkiaSharp/HarfBuzz domain facts (color layouts, struct +1. [`patterns.md`](patterns.md) — .NET XML doc syntax, verb conventions, summary/param/return patterns. +2. [`skia-patterns.md`](skia-patterns.md) — SkiaSharp/HarfBuzz domain facts (color layouts, struct defaults, standard-based enums, caller-owned vs parent-owned). -4. [`obsolete-api-map.md`](obsolete-api-map.md) — members that must never appear in an example, and +3. [`obsolete-api-map.md`](obsolete-api-map.md) — members that must never appear in an example, and replacements. Apply these facts; do not restate them in the docs. If a fact is in a reference file, trust it over your @@ -36,64 +34,36 @@ own recollection. git -C docs diff --name-only --diff-filter=ACM ``` Map each `.xml` to its source at `binding//.cs` (if the guess is wrong, `grep` - for the type). For automated authoring, select one coherent wave of at most 10 files and 60 - placeholder-bearing members, whichever limit comes first; do not split a type merely to reach the - limit. Reduce the wave further for native-backed status, ownership, callback, backend, or lifetime - documentation. Leave every unselected placeholder intact and summarize each unselected file: - ```text - UNSELECTED | | members: | - ``` + for the type). Shard the result into ~25–40-file batches. 3. **Write (per file).** A field is **in scope to fill** when it is empty, self-closing, or still a placeholder (`To be added.`, or a bracketed remarks scaffold like `[Describe …]`). Do not rewrite already-written prose. - 1. **Read the managed source first.** From the filename, locate the type in `binding/` and read it - completely; do not truncate required references with `head`, partial-range reads, or a combined - output cap that silently omits later files. - Build the fact sheet from `technical-fact-checking.md`: exact public surface, validation, - exceptions, nullable failures/callback payloads, status results, ownership, lifetimes, constants, - and defaults. Never document from the member name alone. - 2. **Classify and close the evidence chain.** Emit one `EVIDENCE` classification from - `technical-fact-checking.md` for every selected type/member DocId. When managed source delegates - semantics, read focused tests and the - checked-out native declaration/implementation for native-backed enums, status values, ownership, - callbacks, or behavior the wrapper does not define, and retain a `NATIVE` row for each authored - claim. A native name or enum value is not enough to invent a behavioral explanation. If native - evidence is unavailable, do not fill that field. - 3. **Open the `.xml` and locate each `` block.** Each ``/type carries a + 1. **Read the C# source first.** From the filename, locate the type in `binding/` and read it. Build a + fact sheet: constructors, method overloads, property accessors (`{ get; }` vs `{ get; set; }`), + validation behavior (throws? clamps? pads? truncates?), numeric constants, and defaults. The source + is authoritative — never document from the member name alone. + 2. **Open the `.xml` and locate each `` block.** Each ``/type carries a `MemberSignature[@Language='DocId']` you use as the stable id. Fill the in-scope children: ``, ``, ``, ``, ``, ``, and ``. - Include an `` entry on each affected member for every deterministic managed exception - identified by the fact sheet, including verified checked arithmetic and deliberate failures from - directly invoked helpers. Type-level prose does not replace member-level exception contracts. - Do not add a possible exception without locating the exact throwing operation. - 4. **Match the accessor verb to the signature**, not to intuition: `{ get; set; }` → "Gets or sets …", + 3. **Match the accessor verb to the signature**, not to intuition: `{ get; set; }` → "Gets or sets …", `{ get; }` → "Gets …". Many struct properties look read-only but are settable — check the signature. - 5. **Defaults come from the source.** A struct property with no field initializer defaults to + 4. **Defaults come from the source.** A struct property with no field initializer defaults to `0`/`null`/`false`; do not copy a "typical" sibling constant. - 6. **Keep prose inside the public managed contract.** Do not expose private fields/locals as reader - identifiers or describe native capabilities with no managed entry point. Express sizes and - lifetimes using public parameters and observable results. - 7. **Remarks:** a user-facing resource or lifecycle type must have real remarks with disposal/lifetime - guidance and one focused, self-contained compiling example. If an accurate example cannot be - completed, leave the remarks placeholder and emit a `DEFERRED` row instead of silently omitting it. - Enums, delegates, and simple members can use `` when no additional guidance is needed. - Never invent a partial example to satisfy the requirement. Inside CDATA use `` with - no DocId prefix; use `` in regular XML prose. - 8. **Examples must compile and be self-contained:** declare every variable; never use an obsolete member + 5. **Standard-citing enum members:** read the C/C++ header where the enum is defined and verify the + number AND the behavior against the member name. + 6. **Remarks:** type-level entries get a real `` (description + disposal note if applicable + + one compiling example). Simple members get self-closing ``. Inside CDATA remarks use + `` with **no** `T:`/`M:`/`P:` prefix; `` (with prefix) is for non-CDATA prose. + 7. **Examples must compile and be self-contained:** declare every variable; never use an obsolete member (check the obsolete map); never `using`/`Dispose` a parent-owned object (e.g. `SKSurface.Canvas`). - 9. **Save and audit the file**, preserving CDATA and every signature element. Change only `` - content. Search the touched file for placeholders. Fill each in-scope field, replace a - non-applicable remarks placeholder with ``, or emit a `DEFERRED` row for that exact field. + 8. **Save the file**, preserving CDATA and all signature elements. Change only `` content. -4. **Review** the files just written with the review checks ([`reviewing.md`](reviewing.md) §Checks). - Every self-introduced CRITICAL or IMPORTANT finding must be fixed before landing. If the evidence or - time needed to fix it is unavailable, restore the affected field to its original placeholder and emit - a `DEFERRED` row; never keep weak prose merely to reduce the placeholder count. +4. **Review** the files just written with the review checks ([`reviewing.md`](reviewing.md) §Checks), then + fix CRITICAL findings by editing the XML directly. 5. **Validate & format** ([`validation.md`](validation.md)): run `docs-format-docs` — it formats and runs - the deterministic checks; fix any build-failing broken-XML errors and reconcile every remaining - placeholder in a touched file with the deferred manifest. + the deterministic checks; fix any build-failing broken-XML errors. 6. **Land:** commit on a `dev/...` branch in the `docs` submodule and open a PR (the submodule protects `main`). @@ -103,37 +73,21 @@ own recollection. After all files, emit a compact manifest — one line per file: ``` -WROTE | | members: fields: exceptions: | source: | native: +WROTE | | summaries: params: returns: remarks: | source: ``` -Counts must come from the semantic `` diff, not memory: `members` is the number of type/member -`` blocks changed, `fields` is the number of changed `` children, and `exceptions` is the -number of changed `` children. `source:` uses repository-relative POSIX paths and real line -ranges, and `native:` must -equal the number of `NATIVE` rows for the file. A file cannot receive a `WROTE` row while it has an -unresolved self-introduced CRITICAL/IMPORTANT finding or unsupported native claim. For every selected -DocId, list each field intentionally left as a placeholder because evidence or time was insufficient: +Then list any field you intentionally left as a placeholder (ran out of certainty/time) so the next run +re-detects it: ``` DEFERRED | | | | ``` -Use `summary`, `returns`, `value`, or `remarks` for singleton fields; `param:` and -`typeparam:` for named fields; and `exception:` for exceptions. Unselected DocIds use the -file-level `UNSELECTED` row instead of one `DEFERRED` row per field. - -Include the `EVIDENCE` and `NATIVE` rows from `technical-fact-checking.md`, the `TRACE` and finding rows -from `reviewing.md`, and the `UNSELECTED` rows in the PR body. This evidence block is part of the -completion gate, not optional review commentary. - ## Boundaries - Edit only the in-scope `.xml` files, and only `` content — never touch `MemberSignature`, `TypeSignature`, or generated files (`index.xml`, `ns-*.xml`, `_filter.xml`, `FrameworksIndex/`). - Never invent an API, overload, or numeric value. If you cannot verify it, leave the field deferred. -- Never invent semantics from a type/member name or native capability, and never present a private - implementation identifier as public API. - The writer only fills in-scope (empty/placeholder) fields; it does not rewrite existing prose. - If a large type runs out of certainty, leave its placeholder intact (`DEFERRED`) so the next run re-detects it — the file stays clean and well-formed either way. -- Never claim a type/file is fully filled while an unreported placeholder remains in it. diff --git a/.agents/skills/api-docs/references/checklist.md b/.agents/skills/api-docs/references/checklist.md index a39827148609..222e6b2d7f4a 100644 --- a/.agents/skills/api-docs/references/checklist.md +++ b/.agents/skills/api-docs/references/checklist.md @@ -9,11 +9,6 @@ Classify issues by severity when reviewing documentation. Issues that damage credibility or break functionality: - **Fabricated APIs** — code examples that reference methods, overloads, or types that don't exist. Always verify against actual C# source before writing examples. -- **Private identifiers presented as API** — prose or examples tell readers to use an implementation-only - field/local/property that is absent from the public managed surface. -- **Invented native semantics or unsupported managed capabilities** — docs infer behavior from a native - name/value without checking its contract, or describe native functionality that SkiaSharp does not - publicly expose. - **Obsolete APIs in examples** — using a member marked `[Obsolete("...", true)]` in a code example. These are compile errors, so the example never builds. Most common: legacy text rendering (`SKPaint.TextSize`/`Typeface`/`TextAlign`, old `SKCanvas.DrawText(string,float,float,SKPaint)`) — use `SKFont` instead. Check examples against `references/obsolete-api-map.md`; mind §2 there, where the obsolete and modern calls share a method name and differ only by signature. - **Wrong standard values or behavior** — enum descriptions citing the wrong standard number, or mischaracterizing the standard (e.g. calling a gamma-2.6 transfer "linear"). Cross-reference against `MemberValue` **and the member name**, which usually encodes the exact standard (`SmpteRp4312` = SMPTE RP 431-2, not 432-2; `SmpteSt4281` in `SKColorspaceTransferFnCicp` = SMPTE ST 428-1, a gamma-2.6 transfer, not linear). Note the same member name can mean different things in sibling enums (`SmpteSt4281` is also a *primaries* member). Verify both the identifier and the described behavior against the member's own enum. - **Spelling errors** in public-facing text (teh, recieve, seperate, occured, paramter, retreive, initalize) @@ -43,8 +38,6 @@ Issues that violate standards or leave gaps: - **Invalid cref references** - wrong prefix (T:, M:, P:, F:) or nonexistent target - **DocId prefix inside a CDATA xref** — ``, ``, `` are broken links. Inside CDATA an xref takes the bare UID (``); the prefix is only for `` outside CDATA. - **Missing required documentation** - public APIs without summaries -- **Missing failure contract** — nullable factory/callback results, meaningful status/Boolean failures, or - explicit managed exceptions are omitted where readers need them to use the API safely. - **Incomplete overloads** - params filled on one overload but "To be added." on another overload of the same method - **Wrong default-value claims** — stating "the default is X" for a struct property that has no field initializer. C# structs zero-initialize, so the default is `0` / `null` / `false` unless the source explicitly sets it. A "typical" constant exposed elsewhere (e.g. `SKDocument.DefaultRasterDpi` = 72) is NOT the struct's default and must be documented separately. Verify against the C# source in `binding/`, not against a value that "looks typical". (Recurring error: `SKDocumentXpsOptions.Dpi` documented as "default 72" — it is actually 0.) - **Examples that won't compile** — a code example is broken (so it never builds) when it: diff --git a/.agents/skills/api-docs/references/conceptual/authoring.md b/.agents/skills/api-docs/references/conceptual/authoring.md deleted file mode 100644 index 128b380689ba..000000000000 --- a/.agents/skills/api-docs/references/conceptual/authoring.md +++ /dev/null @@ -1,113 +0,0 @@ -# Authoring conceptual articles - -Use this procedure for a new article or a major rewrite. Read the matching blueprint from -[`index.md`](index.md), then load [`../technical-fact-checking.md`](../technical-fact-checking.md), -[`fact-checking.md`](fact-checking.md), -[`structure-and-style.md`](structure-and-style.md), and [`code-samples.md`](code-samples.md) when the -article contains code. - -## 1. Define the reader contract - -Write one planning sentence before drafting: - -```text -For who already has , this
helps them , within . -``` - -Then record: - -- The prerequisite knowledge, packages, workloads, devices, graphics contexts, or permissions. -- The observable completion condition. -- The important exclusions. Link elsewhere instead of growing a second task inside the article. -- The supported versions, platforms, and backends when the outcome is not universal. - -If the sentence contains multiple unrelated outcomes, split the article or select one dominant outcome. - -## 2. Build an evidence ledger - -Before writing technical prose, follow the shared [`technical-fact-checking.md`](../technical-fact-checking.md) -contract and the conceptual claim guidance in [`fact-checking.md`](fact-checking.md). Record each -consequential claim, its evidence, and its status: - -```text -CLAIM | | | VERIFIED / QUALIFIED / UNVERIFIED -``` - -Include API signatures, return/failure behavior, defaults, ownership, callback lifetime, threading, -platform/backend support, and external setup. Do not draft a warning from an unverified assumption. - -## 3. Design the reader journey - -Start from the matching blueprint. Preserve its reader logic, not its placeholder headings. - -- Put prerequisites and decision-changing limitations before the first dependent step. -- Give the reader enough context to understand *why* an action is required, but keep reference detail - out of the task flow. -- For procedures, order actions exactly as the reader performs them and include the expected result. -- For branching paths, explain the choice once, then separate the paths with specific headings. -- End with verification and only the next links needed to continue. - -Update the section TOC when adding, moving, or renaming an article. - -## 4. Draft the introduction - -The opening should answer, in a short paragraph: - -1. What can the reader accomplish? -2. When should they use this approach? -3. What constraint most affects success or choice? - -Do not repeat the title, begin with product history, or spend the first screen defining terms that the -reader does not yet need. - -## 5. Write source-backed examples - -Apply [`code-samples.md`](code-samples.md). In particular: - -- Verify every SkiaSharp member and overload in current source. -- Declare what host-specific values the reader must supply. -- Check nullable factories and meaningful `bool`/status results. -- Dispose caller-owned native wrappers and keep parent-owned objects alive. -- Model pinning, callbacks, asynchronous work, and GPU cleanup for their complete lifetimes. -- Pair intentionally incorrect code with an explicit explanation and a corrected version. - -An illustrative fragment must say what it omits. A complete workflow must compile and reach the stated -result. - -## 6. Apply editorial and accessibility passes - -Use [`structure-and-style.md`](structure-and-style.md) as separate passes rather than trying to fix -everything while drafting: - -1. Metadata, title, introduction, and heading hierarchy. -2. Procedure order and scannability. -3. Voice, terminology, global readiness, and inclusive language. -4. Links, xrefs, alerts, formatting, and image accessibility. - -Separate passes catch inconsistencies that disappear when prose and code are reviewed together. - -## 7. Validate and self-review - -Run [`validation.md`](validation.md), then review the article against its blueprint: - -- Does the opening promise the same outcome the article delivers? -- Can the intended reader complete or verify that outcome? -- Does every limitation include a recovery path, supported alternative, or explicit boundary? -- Are technical claims still supported by the evidence ledger? -- Are all changed links, heading fragments, and TOC entries valid? - -## Output - -After editing, report: - -```text -WROTE | | type: | outcome: -VERIFIED | | -QUALIFIED | | | -DEFERRED | | -VALIDATED | docfx: snippets: rendered: -``` - -`DEFERRED` is the output form of an `UNVERIFIED` ledger entry that could not be resolved or safely -removed. Do not hide unresolved claims behind fluent prose; leave them qualified or deferred so review -can focus on the remaining risk. diff --git a/.agents/skills/api-docs/references/conceptual/code-samples.md b/.agents/skills/api-docs/references/conceptual/code-samples.md deleted file mode 100644 index d746229ef080..000000000000 --- a/.agents/skills/api-docs/references/conceptual/code-samples.md +++ /dev/null @@ -1,93 +0,0 @@ -# Code samples in conceptual articles - -Readers copy code even when an article calls it illustrative. Make each block explicit about its purpose -and honest about what it omits. - -## Choose a snippet or a sample - -| Form | Use for | Quality bar | -|---|---|---| -| Focused snippet | One API call, decision, or local pattern | Short, exact, declares the relevant values, and names omitted setup | -| Complete sample | A workflow the reader is expected to run | Buildable, all consequential control flow present, expected result stated | -| Pseudocode | Host- or API-specific scaffolding that cannot be portable | Labeled as pseudocode; do not use a `csharp` fence if it is not valid C# | - -Prefer a repository sample or test as the source for complete code. This docset currently uses inline -fenced code rather than Microsoft Learn's `:::code source=...:::` extraction, so inline examples can -drift. Compare them with the nearest test/renderer and compile a focused complete sample when practical. - -## Verify from source - -For every C# block: - -1. Confirm type names, property capitalization, overloads, argument order, and return types in current - source. -2. Confirm every variable is declared or explicitly identified as host-provided. -3. Check [`../obsolete-api-map.md`](../obsolete-api-map.md) and source attributes. -4. Confirm platform-only APIs are shown in the correct target context. -5. State whether the block is complete or what setup it intentionally omits. - -Do not use ellipses where omitted code controls ownership, failure handling, synchronization, or cleanup. - -## Model failure honestly - -A complete workflow should: - -- Check nullable factories before dereferencing the result. -- Check `bool`, enum, and status results when failure changes the outcome. -- Catch only specific exceptions the example can recover from. -- Surface unrecoverable failures instead of silently returning success-shaped data. -- Show an expected result or verification so readers know the code worked. - -Avoid broad `catch (Exception)` and `catch (SystemException)` examples. They hide the actual contract and -teach readers to discard actionable failures. - -## Model SkiaSharp ownership and lifetime - -Use the authoritative caller-owned versus parent-owned table in -[`../skia-patterns.md`](../skia-patterns.md); do not infer ownership from whether a managed type implements -`IDisposable`. - -- Keep backing memory, native devices/contexts, and delegates alive for the full native use. -- Never pass a managed pointer beyond its pinning scope. -- Drain or synchronize queued GPU work before releasing resources when the backend contract requires it. -- Check same-instance returns before disposing an input that may also be the result. -- Keep a graphics context current for calls and cleanup only on backends that require it; do not - generalize an OpenGL rule to Vulkan, Metal, or Direct3D. - -When the lifecycle is the lesson, prefer a slightly longer correct example over a short example that -leaks or races. - -## Model asynchronous and callback-only results - -Show: - -1. Who initiates the operation. -2. What drives completion. -3. The lifetime of callback parameters. -4. What data must be copied before the callback returns. -5. How cancellation, timeout, or failure is reported. -6. When resources can be released. - -Do not imply that polling a fixed number of times guarantees completion unless the API contract says so. -For production loops, explain the host's scheduling or timeout policy rather than presenting a magic -iteration count as universal. - -## Show intentionally incorrect code safely - -When a troubleshooting or migration article needs a bad example: - -- Introduce it in prose as incorrect. -- Mark it inside the code block with a comment such as `// Incorrect: ...`. -- Keep dangerous or non-compiling lines commented out when readers may copy the block wholesale. -- Follow it immediately with the corrected pattern and explain the behavioral difference. - -Never rely on a heading such as "Before" alone to signal that code is unsafe or obsolete. - -## Validate - -Follow [`validation.md`](validation.md). At minimum: - -- Compile complete examples or match them line by line to a compiling repository sample/test. -- Verify illustrative SkiaSharp calls against source. -- Run the example when the article promises runtime output and the current host supports it. -- State when platform hardware prevents execution and what evidence substituted for it. diff --git a/.agents/skills/api-docs/references/conceptual/fact-checking.md b/.agents/skills/api-docs/references/conceptual/fact-checking.md deleted file mode 100644 index 0804e422f08b..000000000000 --- a/.agents/skills/api-docs/references/conceptual/fact-checking.md +++ /dev/null @@ -1,64 +0,0 @@ -# Fact-checking conceptual documentation - -Conceptual prose can be fluent and still be false. Build a claim inventory before writing or reviewing, -then verify each claim against the closest source of truth. - -Apply the shared evidence hierarchy, public managed contract boundary, and cross-layer procedure in -[`../technical-fact-checking.md`](../technical-fact-checking.md). This file adds the claim-management, -platform-matrix, and external-source checks needed by conceptual articles. - -## Build a claim ledger - -Track consequential claims rather than every sentence: - -```text -CLAIM | | | | VERIFIED / QUALIFIED / UNVERIFIED -``` - -Include claims that affect whether a reader's code compiles, runs, remains safe, or selects a supported -path: - -- API and overload existence. -- Failure behavior and return values. -- Defaults, limits, and units. -- Ownership and disposal. -- Async/callback lifetime and ordering. -- Thread/context requirements. -- Platform/backend availability. -- Version-sensitive behavior. - -Record `QUALIFIED` when the claim is true only under a stated condition. Record `UNVERIFIED` when the -available evidence is insufficient; do not convert it into a warning or absolute statement. - -## Verify platform matrices row by row - -For every named platform: - -1. Find the target-specific handler, renderer, or project configuration. -2. Identify the actual backend selected there. -3. Check compile-time and runtime availability gates. -4. Record unsupported targets and the failure mode. -5. Distinguish "not implemented in this integration" from "the native API cannot support it." - -Avoid broad claims such as "cross-platform," "works everywhere," or "identical" unless every listed -implementation supports the same behavior. - -## Handle external and time-sensitive facts - -- Prefer first-party product documentation or source. -- Capture the relevant version or publication state when behavior can change. -- Link to a stable conceptual page rather than a transient search result. -- Explain whether SkiaSharp exposes the native capability today; native support alone does not establish - managed support. -- Remove an external claim that is not needed for the reader's outcome and cannot be verified. - -## Evidence in reviews - -Every CRITICAL or IMPORTANT factual finding needs one of: - -- A repository `path:line`. -- A focused test and its observed result. -- A current first-party URL with the relevant condition quoted or summarized. - -No citation means the lead remains `UNVERIFIED`. This prevents confident false positives from becoming -documentation churn. diff --git a/.agents/skills/api-docs/references/conceptual/index.md b/.agents/skills/api-docs/references/conceptual/index.md deleted file mode 100644 index 563672461dc1..000000000000 --- a/.agents/skills/api-docs/references/conceptual/index.md +++ /dev/null @@ -1,64 +0,0 @@ -# Conceptual documentation route - -Use this route for Markdown under `documentation/docfx/guides/`. A conceptual article should help a -reader understand a model, choose an approach, complete a task, migrate working code, or resolve a -specific symptom. It should complement API reference rather than narrate members one by one. - -## Route the work - -Choose both a **change size** and an **article type** before reading the remaining references. - -### Change size - -| Size | Typical work | Route | -|---|---|---| -| Focused fix | One fact, broken link, typo, or small sample correction | Read [`../technical-fact-checking.md`](../technical-fact-checking.md), [`fact-checking.md`](fact-checking.md), and [`validation.md`](validation.md); add [`code-samples.md`](code-samples.md) for code or [`structure-and-style.md`](structure-and-style.md) for prose/metadata | -| Substantive review | Several claims, a complete example, platform coverage, or article structure | Read [`reviewing.md`](reviewing.md), then the matching blueprint and relevant shared references | -| New article or major rewrite | New reader journey, changed article type, or broad restructuring | Read [`authoring.md`](authoring.md), then the matching blueprint and relevant shared references | - -Review is report-only unless the user asks to fix or rewrite the article. When they do, apply the -corrections and run the authoring validation pass. - -### Article type - -| Reader intent | Use when the reader needs to... | Blueprint | -|---|---|---| -| Overview or decision | Compare choices and find the right next task | [`templates/overview.md`](templates/overview.md) | -| Concept | Understand a model, lifecycle, or relationship | [`templates/concept.md`](templates/concept.md) | -| How-to | Complete one concrete task | [`templates/how-to.md`](templates/how-to.md) | -| Migration | Move working code from one supported approach to another | [`templates/migration.md`](templates/migration.md) | -| Troubleshooting | Diagnose and fix a named symptom or error | [`templates/troubleshooting.md`](templates/troubleshooting.md) | - -Do not combine independent reader intents merely because they share APIs. Split the material when a -reader who needs one outcome would have to skip large sections written for another. - -## Shared references - -Load only the references the task needs: - -- [`../technical-fact-checking.md`](../technical-fact-checking.md) — shared public-contract boundary, - evidence hierarchy, and cross-layer verification used by API reference and conceptual docs. -- [`fact-checking.md`](fact-checking.md) — conceptual claim ledger, version/platform checks, external - sources, and review evidence. -- [`code-samples.md`](code-samples.md) — snippet versus sample decisions, source verification, failure - handling, ownership, async lifetimes, and intentionally incorrect code. -- [`structure-and-style.md`](structure-and-style.md) — metadata, introductions, headings, procedures, - voice, global readiness, formatting, links, alerts, and accessible images. -- [`validation.md`](validation.md) — code, links, DocFX, rendered output, and validation reporting. -- [`microsoft-contribute-sources.md`](microsoft-contribute-sources.md) — provenance and the Microsoft - Learn infrastructure rules intentionally not copied into SkiaSharp. Read this only when maintaining - the skill. - -## Quality contract - -Whatever the article type: - -1. Start from a real reader, starting state, and outcome. -2. Verify consequential claims against the closest source of truth. -3. Make code honest about what is complete, illustrative, platform-specific, or intentionally wrong. -4. Put prerequisites, constraints, and recovery guidance before the point where the reader needs them. -5. Make the path to success scannable and verifiable. -6. State uncertainty instead of turning an assumption into documentation. - -The MicrosoftDocs/Contribute guidance supplies the editorial system. SkiaSharp source, tests, native -code, and platform implementations supply the technical truth. diff --git a/.agents/skills/api-docs/references/conceptual/microsoft-contribute-sources.md b/.agents/skills/api-docs/references/conceptual/microsoft-contribute-sources.md deleted file mode 100644 index 4fd9717441e6..000000000000 --- a/.agents/skills/api-docs/references/conceptual/microsoft-contribute-sources.md +++ /dev/null @@ -1,41 +0,0 @@ -# MicrosoftDocs/Contribute source map - -This route adapts the reusable editorial system from -[`MicrosoftDocs/Contribute`](https://github.com/MicrosoftDocs/Contribute) and combines it with -SkiaSharp-specific source verification. Use this file when maintaining the skill; normal authoring and -review runs should load the focused references instead. - -## Adopted guidance - -| Topic | Source | Local adaptation | -|---|---|---| -| Contribution/article triage | [`how-to-write-overview.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/how-to-write-overview.md) | Focused fix vs substantive review vs new/major rewrite | -| Voice, intent, concise/scannable prose, global readiness | [`style-quick-start.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/style-quick-start.md) | `structure-and-style.md` voice and localization pass | -| .NET voice and tone | [`dotnet-voice-tone.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/dotnet/dotnet-voice-tone.md) | Reader-focused introduction, second person, active voice, present tense | -| .NET article skeleton | [`dotnet-style-guide.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/dotnet/dotnet-style-guide.md) | Separate SkiaSharp blueprints for each reader intent | -| Code blocks and intentionally bad code | [`code-in-docs.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/code-in-docs.md) | Snippet/sample distinction, bad-code labeling, build/source verification | -| .NET sample quality and exception handling | [`dotnet-contribute.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/dotnet/dotnet-contribute.md) | Complete samples build, handle expected failures, avoid broad catches | -| Alerts, headings, images, and alt text | [`markdown-reference.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/markdown-reference.md) | Sparse alerts, accessible images, and DocFX-compatible Markdown; omit Learn's `TIP` alert to preserve this docset's established four-kind convention | -| Link and xref quality | [`how-to-write-links.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/how-to-write-links.md) | Exact UIDs, relative conceptual links, descriptive HTTPS links | -| Bold/italic/code usage | [`text-formatting-guidelines.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/text-formatting-guidelines.md) | UI/new-term/code formatting semantics | -| Discoverable titles and headings | [`seo-reference.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/seo-reference.md) | Specific titles/H2s without importing Learn SEO metadata quotas | -| .NET PR review triage | [`dotnet-pr-review.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/dotnet/dotnet-pr-review.md) | Focused/substantive/draft review depth and publish verdict | - -## Intentionally not copied - -SkiaSharp's DocFX site is not Microsoft Learn's Open Publishing System. Do not add: - -- `ms.author`, `ms.date`, `ms.topic`, `ms.service`, `ms.custom`, or Learn ownership metadata from - [`metadata.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/metadata.md). -- CLA, `#sign-off`, OPS validation, staging, auto-merge, or label mechanics from - [`process-pull-request.md`](https://github.com/MicrosoftDocs/Contribute/blob/main/Contribute/content/process-pull-request.md). -- Learn Authoring Pack instructions or Learn-only Markdown extensions merely because they appear in the - source repository. -- `:::code source=...:::` references until this repository adopts and validates an extracted-snippet - system. -- Learn-specific title/description quotas as hard requirements. Local metadata should remain concise and - useful without invented fields or padded prose. - -The Contribute repository delegates deeper inclusive-language and procedure rules to the separate -Microsoft Writing Style Guide. This skill includes practical accessibility and inclusive-language checks -but does not claim that Contribute contains a complete policy. diff --git a/.agents/skills/api-docs/references/conceptual/reviewing.md b/.agents/skills/api-docs/references/conceptual/reviewing.md deleted file mode 100644 index 0b0bd92f14e6..000000000000 --- a/.agents/skills/api-docs/references/conceptual/reviewing.md +++ /dev/null @@ -1,142 +0,0 @@ -# Reviewing conceptual articles - -Review conceptual documentation for reader success, technical correctness, safe examples, platform -accuracy, structure, accessibility, and maintenance risk. Review is report-only unless the user asks to -apply fixes. - -Read [`../technical-fact-checking.md`](../technical-fact-checking.md), -[`fact-checking.md`](fact-checking.md), the matching article blueprint from [`index.md`](index.md), and -[`code-samples.md`](code-samples.md) when the scope contains code. - -## Choose the review depth - -This is a second axis after the top-level change-size routing in [`index.md`](index.md). Classify how -deeply the selected review scope needs to be examined: - -| Review depth | Examples | Checks | -|---|---|---| -| Focused | Typo, one broken link, one factual correction | Verify the changed claim and its immediate context | -| Substantive | New article, changed task flow, platform matrix, complete sample | Run every review pass below | -| Draft | Incomplete structure or intentionally partial content | Focus on direction, missing evidence, and blockers before polishing | - -Resolve the scope to an explicit file list. For a PR, use the parent repository diff; for a theme, include -the index/TOC and every article needed to evaluate the reader journey. - -## Review passes - -### 1. Reader outcome - -- Identify the intended reader, starting state, and promised outcome from the article itself. -- Confirm the article type matches that intent. -- Check that prerequisites appear before dependent actions and that the completion condition is visible. -- Flag scope that combines independent tasks or omits a required step. - -### 2. Technical facts - -Work claim by claim using the shared [`technical-fact-checking.md`](../technical-fact-checking.md) -contract and [`fact-checking.md`](fact-checking.md): - -- Verify signatures, overloads, nullability, defaults, validation, and result values in managed source. -- Verify ownership, disposal order, pinning, callbacks, and threading through wrappers, tests, and native - contracts as needed. -- Verify every platform/backend row from its implementation or build configuration. -- Qualify version-sensitive and external claims with current first-party evidence. - -No source means `UNVERIFIED`, not "wrong." - -### 3. Code and commands - -Use [`code-samples.md`](code-samples.md): - -- Determine whether each block claims to be a complete sample or an illustrative snippet. -- Confirm members, overloads, variables, imports, result checks, ownership, and lifetimes. -- Check that deliberately wrong code is unmistakably marked in prose and code. -- Confirm commands match the repository and target platform. -- Run or compile representative complete samples when practical. - -### 4. Article-type structure - -Compare the article to its blueprint: - -- Overview: clear choice criteria, comparison, and routed next tasks. -- Concept: accurate mental model, relationships/lifecycle, constraints, and applied example. -- How-to: prerequisites, ordered procedure, expected results, and verification. -- Migration: supported starting/target states, mapping, before/after, what stays the same, and rollback or - fallback. -- Troubleshooting: symptom-first opening, diagnosis, causes, resolution, verification, and escalation. - -Flag a structural issue only when it makes the article harder to use; blueprints are reader models, not -mandatory boilerplate. - -### 5. Editorial, inclusive language, and accessibility - -Apply [`structure-and-style.md`](structure-and-style.md): - -- One H1, specific sentence-case headings, concise metadata, and a result-oriented introduction. -- Active, direct prose with consistent terms and no unnecessary idioms or future tense. -- Inclusive language that does not assume gender, ability, expertise, or a preferred platform. -- Correct code/UI/new-term formatting. -- Descriptive links, exact-case xrefs, valid fragments, and first-party external sources. -- Sparse, correctly chosen alerts that are not stacked. -- Useful alt text and a text explanation for complex visuals; do not use screenshots to present code. - -### 6. Maintenance and validation - -- Check TOC placement and neighboring article links. -- Look for repeated facts that should link to a canonical article instead. -- Identify time-sensitive claims without a version or source. -- Run the applicable checks in [`validation.md`](validation.md). - -## Severity - -Use reader impact, not writing preference: - -- **CRITICAL** — The task cannot succeed; code does not compile; an API/member is fabricated; guidance - can crash, leak, corrupt data, free parent-owned memory, or violate a native lifetime; or the article - directs readers through an unsupported path with no warning. -- **IMPORTANT** — A factual/default/platform claim is wrong; a required prerequisite, failure check, or - recovery path is missing; a core link is broken; or the structure is likely to produce the wrong - implementation. -- **MINOR** — Terminology, metadata, repetition, formatting, accessibility wording, or scannability can - improve without changing the technical outcome. - -Examples: - -```text -CRITICAL | example | guide.md | Create the surface | Disposes SKSurface.Canvas, which is parent-owned; SKSurface.cs:... shows the surface owns it, so later draws can access a released native object. -IMPORTANT | platform | guide.md | Supported platforms | Claims Direct3D support on Linux, but the Direct3D context is Windows-only in ; readers will choose an unavailable backend. -MINOR | structure | guide.md | Overview | The heading does not describe the decision made in this section, so it is hard to scan. -``` - -Every CRITICAL or IMPORTANT finding needs a repository `path:line`, focused test, or current first-party -source. Deduplicate overlapping symptoms into the root finding. - -## Output - -Emit one machine-readable line per finding: - -```text -SEVERITY | class | | | -``` - -Then provide a compact report: - -```markdown -# Conceptual documentation review — - -## Summary -- Files reviewed: -- Findings: CRITICAL , IMPORTANT , MINOR -- Unverified claims: -- Verdict: Ready to publish / Needs fixes / Major rework - -## Findings -... - -## Evidence gaps -... -``` - -If the user asks for fixes, correct the root causes, preserve unrelated prose, and run the full validation -procedure. Do not leave staged review comments for issues already fixed in the branch unless the user -specifically wants review comments. diff --git a/.agents/skills/api-docs/references/conceptual/structure-and-style.md b/.agents/skills/api-docs/references/conceptual/structure-and-style.md deleted file mode 100644 index 2cd1e31a14f7..000000000000 --- a/.agents/skills/api-docs/references/conceptual/structure-and-style.md +++ /dev/null @@ -1,124 +0,0 @@ -# Structure and style for conceptual articles - -This reference adapts the reusable editorial guidance in MicrosoftDocs/Contribute to SkiaSharp's local -DocFX site. It deliberately omits Microsoft Learn publishing metadata and authoring-pack extensions. - -## Metadata, title, and introduction - -Use the metadata supported by this docset: - -```yaml ---- -title: "Specific sentence-case title" -description: "Describe the reader outcome, approach, and meaningful scope." ---- -``` - -- Do not add `ms.author`, `ms.date`, `ms.topic`, `ms.service`, or other Learn-only fields. -- Use one H1 after front matter. Keep it aligned with the metadata title. -- Make the title specific enough to distinguish the article in search and the TOC. -- Keep the description concise and natural. Roughly 115-160 characters often works, but do not pad it to - reach a target. -- Open with the outcome, use case, and most consequential constraint. Do not repeat the title as an - italic subtitle. - -## Headings and scanning - -- Use sentence case. -- Make H2s describe the decision, task, model, symptom, or result in that section. Avoid generic headings - such as "Overview" or "Details." -- Preserve a logical hierarchy; do not skip levels to get smaller text. -- Keep paragraphs focused and put conditions before instructions. -- Use tables for genuine comparisons and lists for parallel choices. -- Keep long reference enumerations out of the task flow; link to API reference or a focused reference - section instead. - -The TOC and H2s are part of the reader interface. A reader should be able to predict the article's path -from them. - -## Procedures - -- Introduce the goal and prerequisites before the steps. -- Use a numbered list when order matters. -- Put one action in each step; place the reason or expected result immediately after it. -- Use imperative verbs and exact UI labels, commands, paths, and values. -- If a step branches, state the condition first and separate the paths clearly. -- End with a verification step rather than assuming success. - -Do not hide required actions in notes, code comments, or paragraphs between numbered steps. - -## Voice and global readiness - -- Address the reader as "you" and use active voice. -- Prefer familiar words and short sentences. -- Use present tense for current behavior; avoid future tense when describing what a command does. -- Define a specialized term at first use, then use the same term consistently. -- Expand uncommon acronyms at first use. -- Avoid idioms, jokes, cultural references, and spatial instructions such as "see above" when a heading - reference is clearer. -- Avoid "simple," "easy," "obvious," and "just" when setup or recovery is nontrivial. -- Avoid dismissive or exclusionary terms and assumptions about the reader's ability, environment, or - preferred platform. - -Short, explicit prose is easier to scan, translate, and maintain. - -## Inclusive language - -- Use gender-neutral terms unless gender is relevant. -- Describe the task or state rather than labeling a person by ability or experience. -- Avoid ableist idioms such as "sanity check," "blind to," or "crippled"; name the actual validation, - omission, or limitation. -- Do not assume the reader uses a particular platform, input method, visual theme, or spoken language. -- Avoid humor and metaphors that depend on culture or can obscure technical meaning. - -These checks are a practical local baseline. MicrosoftDocs/Contribute points to the separate Microsoft -Writing Style Guide for its complete inclusive-language policy. - -## Text formatting - -- Use **bold** for UI elements and labels the reader sees. -- Use *italics* for a newly introduced term or a placeholder the reader replaces. -- Use `code` for APIs, commands, filenames, paths, configuration keys, values, and literal input. -- Do not use formatting only for emphasis; rewrite the sentence so its point is clear. - -## Links and xrefs - -- Use `xref:` for SkiaSharp and .NET API reference. -- Use exact-case UIDs. Use the wildcard member form only when intentionally linking to an overload group. -- Use relative `.md` links for conceptual articles in this docset. -- Use descriptive link text that tells the reader what they will get; never use "click here." -- Use HTTPS and prefer current first-party sources. -- Recheck every heading fragment after renaming a heading. -- Link to third-party material only when it is necessary, maintained, and gives the reader a clear next - step. Do not outsource a core procedure to an unstable blog post. - -## Alerts - -Readers often skip alerts. Keep required task information in the main flow, use no more than one or two -alerts per article when practical, and never stack alerts. - -| Alert | Use for | -|---|---| -| `NOTE` | Context that can be skipped without preventing success | -| `IMPORTANT` | Information required for success | -| `CAUTION` | An action that can cause recoverable harm | -| `WARNING` | A risk of serious or difficult-to-reverse harm | - -Do not promote ordinary prose to an alert merely to make it visible. - -## Images and accessibility - -- Use images only when they communicate spatial or visual information better than text. -- Do not use screenshots to present code; code blocks are searchable, copyable, and maintainable. -- Write alt text that conveys the image's purpose rather than repeating its filename or nearby caption. -- Explain complex diagrams, graphs, and multi-step screenshots in surrounding text or a long - description. -- Do not rely on color, shape, or position alone to communicate meaning. -- Crop screenshots to the relevant UI, avoid sensitive data, and prefer images that will not become - obsolete with minor theme or layout changes. - -## Related links - -End with a small set of likely next actions. Avoid dumping every related API or article. An overview/index -page may have an "In this section" list because routing is its primary purpose; a task article should -usually link only to prerequisites, alternatives, and the next task. diff --git a/.agents/skills/api-docs/references/conceptual/templates/concept.md b/.agents/skills/api-docs/references/conceptual/templates/concept.md deleted file mode 100644 index c9a0ae7148f7..000000000000 --- a/.agents/skills/api-docs/references/conceptual/templates/concept.md +++ /dev/null @@ -1,61 +0,0 @@ -# Concept blueprint - -Use a concept article when the reader needs a mental model before making decisions or completing tasks. -The article should explain relationships, lifecycle, or behavior and then connect the model to practice. - -## Plan - -Define: - -```text -Question the article answers: -What the reader already knows: -Terms that need definitions: -Components and relationships: -Lifecycle or data flow: -Constraints and invariants: -Task that applies this concept: -``` - -## Suggested shape - -```markdown ---- -title: " in SkiaSharp" -description: "" ---- - -# in SkiaSharp - - - -## - - - -## - - - -## - -| Constraint or state | Consequence for the reader | -|---|---| -| | | - -## Apply the concept - - - -## Related links - -- -``` - -## Quality checks - -- The model answers a practical reader question rather than cataloging types. -- Terms are defined once and used consistently. -- Ownership arrows, lifecycle order, and thread boundaries match source. -- Any diagram has equivalent explanatory text and does not rely on color alone. -- The applied example shows why the model matters. diff --git a/.agents/skills/api-docs/references/conceptual/templates/how-to.md b/.agents/skills/api-docs/references/conceptual/templates/how-to.md deleted file mode 100644 index 2899c45f6974..000000000000 --- a/.agents/skills/api-docs/references/conceptual/templates/how-to.md +++ /dev/null @@ -1,77 +0,0 @@ -# How-to blueprint - -Use a how-to when the reader wants to complete one concrete task. The article should be executable in -order and end with an observable result. - -## Plan - -Record: - -```text -Reader: -Starting state: -Outcome: -Prerequisites: -Supported platforms/versions: -Completion check: -Out of scope: -``` - -If several supported approaches require different setup, explain the choice first and give each path its -own procedure. Do not interleave platform branches step by step. - -## Suggested shape - -```markdown ---- -title: " with SkiaSharp" -description: "" ---- - -# with SkiaSharp - - - -## Prerequisites - -- -- - -## - - - -## - -1. - - - - - -2. - -## - -... - -## Verify the result - - - -## Related links - -- -``` - -Rename headings to the actual actions. Omit optional sections rather than publishing empty boilerplate. - -## Quality checks - -- Each numbered step contains one action in the order performed. -- Required values are defined before use. -- Complete code handles failure and ownership. -- Platform-specific branches are clearly scoped. -- The final verification proves the promised outcome. -- Troubleshooting content stays focused on failures likely during this task; link to a dedicated - troubleshooting article for broader diagnosis. diff --git a/.agents/skills/api-docs/references/conceptual/templates/migration.md b/.agents/skills/api-docs/references/conceptual/templates/migration.md deleted file mode 100644 index f6c20c8007ea..000000000000 --- a/.agents/skills/api-docs/references/conceptual/templates/migration.md +++ /dev/null @@ -1,89 +0,0 @@ -# Migration blueprint - -Use a migration article when the reader has working code on one supported approach and needs to move to -another. Preserve their mental model by separating what changes from what remains valid. - -## Plan - -Define: - -```text -Supported source state: -Supported target state: -Why migrate: -What remains unchanged: -Concept/API mappings: -Behavioral differences: -Compatibility or rollback path: -Completion check: -``` - -Do not present a replacement as universal when a source platform/backend has no target equivalent. - -## Suggested shape - -```markdown ---- -title: "Migrate from to " -description: "" ---- - -# Migrate from to - - - -## Before you migrate - -- -- -- - -## Map source concepts to target concepts - -| Source | Target | What changes | -|---|---|---| -| | | | - -## What stays the same - - - -## Compare the workflows - -### Source - -```csharp -// Existing supported pattern. -``` - -### Target - -```csharp -// Replacement pattern with complete failure and lifetime handling. -``` - -## Migrate step by step - -1. -2. -3. - -## Verify the migration - - - -## Related links - -- -- -``` - -## Quality checks - -- "Before" code is still valid for the documented source version and is labeled as the source pattern, - not as bad code. -- "Target" code uses current APIs and preserves required behavior. -- The article states what remains unchanged. -- Semantic differences, not just renamed methods, are explicit. -- Unsupported migrations have a supported alternative or clear boundary. -- Verification checks behavior, not only compilation. diff --git a/.agents/skills/api-docs/references/conceptual/templates/overview.md b/.agents/skills/api-docs/references/conceptual/templates/overview.md deleted file mode 100644 index 744d6ff1dc38..000000000000 --- a/.agents/skills/api-docs/references/conceptual/templates/overview.md +++ /dev/null @@ -1,64 +0,0 @@ -# Overview and decision blueprint - -Use an overview when the reader needs to understand available approaches, choose one, and navigate to the -right task. It should route readers, not duplicate every routed article. - -## Plan - -Define: - -```text -Decision: -Audience: -Options in scope: -Comparison dimensions: -Recommended default: -Exceptions to the default: -Next task for each option: -``` - -Comparison dimensions must change the reader's decision: platform support, acceleration, ownership, -latency, complexity, compatibility, or lifecycle. Avoid tables filled with facts that do not help choose. - -## Suggested shape - -```markdown ---- -title: " overview" -description: "" ---- - -# overview - - - -## Choose an approach - -| Approach | Use when | Avoid or reconsider when | -|---|---|---| -|