SkiaGameRendering.WindowsDX12 0.17.3-preview

This is a prerelease version of SkiaGameRendering.WindowsDX12.
dotnet add package SkiaGameRendering.WindowsDX12 --version 0.17.3-preview
                    
NuGet\Install-Package SkiaGameRendering.WindowsDX12 -Version 0.17.3-preview
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SkiaGameRendering.WindowsDX12" Version="0.17.3-preview" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SkiaGameRendering.WindowsDX12" Version="0.17.3-preview" />
                    
Directory.Packages.props
<PackageReference Include="SkiaGameRendering.WindowsDX12" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SkiaGameRendering.WindowsDX12 --version 0.17.3-preview
                    
#r "nuget: SkiaGameRendering.WindowsDX12, 0.17.3-preview"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SkiaGameRendering.WindowsDX12@0.17.3-preview
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SkiaGameRendering.WindowsDX12&version=0.17.3-preview&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=SkiaGameRendering.WindowsDX12&version=0.17.3-preview&prerelease
                    
Install as a Cake Tool

Skia Game Rendering

Join the chat

A library that lets MonoGame, KNI, FNA, raylib, Stride, Godot, and Unity applications use SkiaSharp's GPU rendering to produce game-engine textures — with zero-copy GPU texture sharing. Skia renders anti-aliased vector art, text, and 2D graphics directly into game-engine textures without any CPU readback.

Platform Support

Engine Backend Package Platforms Notes
MonoGame 3.8.4 DesktopGL OpenGL SkiaGameRendering Windows, Linux, macOS
MonoGame 3.8.4 WindowsDX D3D11 SkiaGameRendering.WindowsDX Windows
MonoGame 3.8.6 WindowsDX12 D3D12 SkiaGameRendering.WindowsDX12 Windows Prerelease; needs prerelease MonoGame and MonoGame.Runtime.Windows.DX12
MonoGame 3.8.6 DesktopVK Vulkan SkiaGameRendering.DesktopVK Windows, Linux Prerelease; needs prerelease MonoGame and MonoGame.Runtime.Windows.Vulkan or MonoGame.Runtime.Linux.Vulkan
KNI DesktopGL OpenGL SkiaGameRendering.Kni.DesktopGL Windows, Linux, macOS
KNI WindowsDX D3D11 SkiaGameRendering.Kni.WindowsDX Windows
KNI WebGL (Blazor) WebGL2 SkiaGameRendering.Kni.WebGL Browser Extra host setup; see WebGL / WASM Status
raylib OpenGL SkiaGameRendering.Raylib.OGL Windows, Linux, macOS
FNA D3D11 SkiaGameRendering.Fna.WindowsDX Windows Needs FNA3D_FORCE_DRIVER=D3D11
FNA OpenGL SkiaGameRendering.Fna.OGL Windows, Linux, macOS Needs FNA3D_FORCE_DRIVER=OpenGL
Stride D3D11 SkiaGameRendering.Stride.D3D11 Windows
Stride Vulkan SkiaGameRendering.Stride.VK Windows, Linux, macOS
Stride D3D12 SkiaGameRendering.Stride.D3D12 Windows
Godot 4.7+ Vulkan, D3D12, Metal, OpenGL 3.3 src/SkiaGameRendering.Godot (source only, not on NuGet yet) Windows, Linux, macOS One package; backend picked at runtime. Compatibility/OpenGL on Linux (X11/GLX) is unrun
Unity 6 D3D11, D3D12, Metal UPM git URL: https://github.com/vchelaru/SkiaGameRendering.git#upm (or #upm/v<version> to pin) Windows x64 (Mono, IL2CPP), macOS Not on NuGet

Not supported yet:

  • KNI Android (GL ES): not started.
  • FNA SDL_GPU (Vulkan/D3D12/Metal): blocked, FNA3D's default driver exposes no native device (see FNA).
  • Godot Vulkan on macOS, and Compatibility on ANGLE/EGL/Wayland: SkiaSharp's macOS native build has no Vulkan backend (so MoltenVK is out), and the EGL/NSOpenGL-flavored GL contexts need platform code this repo does not have yet. See TODO.md.

MonoGame ships the legacy WindowsDX (D3D11) project unchanged alongside the two native platforms above. 3.8.5 hid the native platforms' graphics device, which blocked them; 3.8.6-preview.2 added GraphicsDevice.GetNativeHandles() and RenderTarget2D.FromNativeHandle(), which the WindowsDX12 and DesktopVK backends use (see SkiaGameRendering-Notes.md section 9). A game using them also references the native runtime package for its platform (MonoGame.Runtime.Windows.DX12, MonoGame.Runtime.Windows.Vulkan or MonoGame.Runtime.Linux.Vulkan).

Requirements

  • .NET 8 (.NET 10 for the Stride backend)
  • Visual Studio 2022
  • MonoGame 3.8.4.1+ (DesktopGL or WindowsDX; samples use 3.8.5.1), MonoGame 3.8.6-preview.2+ (native WindowsDX12 or DesktopVK), KNI (DesktopGL, WindowsDX, or WebGL/Blazor), FNA 26.09+ (D3D11 on Windows, or OpenGL anywhere), raylib, Stride 4.4.0-beta5+ (D3D11 or D3D12 on Windows, or Vulkan on Windows/Linux/macOS; prerelease), or Godot 4.7+ .NET (Forward+/Mobile on Vulkan, D3D12 or Metal, or Compatibility on native OpenGL)
  • SkiaSharp 3.119.4 for WebGL and the KNI desktop backends; 3.119.2 for the MonoGame desktop projects

The MonoGame DesktopGL (SkiaGameRendering) and WindowsDX (SkiaGameRendering.WindowsDX) packages are trim- and NativeAOT-compatible; CI publishes Sample.MonoGame.DesktopGL (on Linux) and Sample.MonoGame.WindowsDX (on Windows, WARP) with PublishAot and runs them. The other packages are not yet verified under NativeAOT.

Quick Start

Install the NuGet package for your platform, then follow the setup for your engine below.

The Package column in the table above is the NuGet name, and the Engine column links each engine's full guide.

dotnet add package <package from the table>  # add --prerelease for the prerelease packages

MonoGame, KNI, and FNA

These share one API (SkiaRenderer plus SkiaRenderTarget2D), so the code inside Game is identical on all of them. Program.cs stays whatever the stock template gives you, except on FNA, which needs one extra line to pick its graphics driver (see FNA).

Inside Game, poll SkiaRenderer.IsReady before calling SkiaRenderer.Initialize, in Draw():

using SkiaGameRendering; // SkiaRenderer

protected override void Draw(GameTime gameTime)
{
    if (!SkiaRenderer.IsInitialized && SkiaRenderer.IsReady)
        SkiaRenderer.Initialize(GraphicsDevice);

    if (SkiaRenderer.IsInitialized)
    {
        // normal draw logic
    }
    base.Draw(gameTime);
}

IsReady is always true on desktop. On KNI WebGL it reflects a real async host-readiness check, which is why the same code works there unchanged (see docs/webgl/quickstart.md). Game code never names a specific SkiaBackend type on any platform. Drawing goes through SkiaRenderTarget2D.

raylib

raylib has no Game class, so it gets its own SkiaRaylibRenderTarget2D that you drive from the main loop, between BeginDrawing/EndDrawing like any other raylib draw call. On Linux, also add SkiaSharp.NativeAssets.Linux.

using Raylib_cs;
using SkiaGameRendering.Raylib.OGL;
using SkiaSharp;

Raylib.InitWindow(800, 600, "raylib + Skia");
var canvas = new SkiaRaylibRenderTarget2D(800, 600);
using var paint = new SKPaint { Color = SKColors.Crimson, IsAntialias = true };

while (!Raylib.WindowShouldClose())
{
    Raylib.BeginDrawing();
    canvas.Begin();
    canvas.Canvas.DrawCircle(100, 100, 100, paint);
    canvas.End(); // composites onto the screen at (0,0)
    Raylib.EndDrawing();
}

canvas.Dispose();
SkiaRaylibRenderer.Dispose();
Raylib.CloseWindow();

Stride

Stride renders through its GraphicsCompositor rather than a user-owned Draw(), so you add a SkiaStrideSceneRenderer to the compositor and draw in its SkiaDraw event. This example uses the Stride Community Toolkit to get a compositor without GameStudio; the package itself doesn't depend on it.

using SkiaGameRendering.Stride.D3D11;
using SkiaSharp;
using Stride.CommunityToolkit.Bepu;
using Stride.CommunityToolkit.Engine;
using Stride.Engine;

using var game = new Game();
SkiaStrideRenderTarget2D? canvas = null;
var paint = new SKPaint { Color = SKColors.Crimson, IsAntialias = true };

// Called as a static method because Game.Run(GameContext) hides the toolkit's Run extension.
Stride.CommunityToolkit.Engine.GameExtensions.Run(game, start: rootScene =>
{
    game.SetupBase3DScene();
    var backBuffer = game.GraphicsDevice.Presenter.BackBuffer;
    canvas = new SkiaStrideRenderTarget2D(game.GraphicsDevice, backBuffer.Width, backBuffer.Height);

    var renderer = new SkiaStrideSceneRenderer { Canvas = canvas };
    renderer.SkiaDraw += skCanvas =>
    {
        skCanvas.Clear(SKColors.Transparent);
        skCanvas.DrawCircle(100, 100, 100, paint);
    };
    game.AddSceneRenderer(renderer); // draws on top of the 3D scene every frame
});

canvas?.Dispose();
SkiaStrideRenderer.Dispose();
paint.Dispose();

For Vulkan, use the SkiaGameRendering.Stride.VK namespace and the SkiaStrideVulkan* types (SkiaStrideVulkanRenderTarget2D, SkiaStrideVulkanSceneRenderer, SkiaStrideVulkanRenderer). On Windows, also set <StrideGraphicsApi>Vulkan</StrideGraphicsApi> in your project; see docs/stride/vulkan-quickstart.md.

For Direct3D 12, use the SkiaGameRendering.Stride.D3D12 namespace and the SkiaStrideD3D12* types, and set <StrideGraphicsApi>Direct3D12</StrideGraphicsApi>; see docs/stride/d3d12-quickstart.md.

Unity

Unity renders on its own thread, so the canvas records your drawing and End plays it back on Unity's render thread. Call Begin/End from the main thread. Skia writes premultiplied alpha, so draw Texture with SkiaUnityRenderTarget.PremultipliedMaterial (a RawImage's material, or a copy on a mesh) or, for Graphics.DrawTexture in OnGUI, PremultipliedGuiMaterial; Unity's default blending darkens its edges. Both work in Gamma and Linear color space projects, but only with the right one for where you draw, since IMGUI stays in gamma in a Linear project. Supported so far: Direct3D 11 on Windows (set Player Settings > Other Settings > Graphics APIs for Windows to Direct3D11) and Metal on macOS, the default there. The package only compiles for the Editor and Windows x64 and macOS players, so code that uses it needs the same limits, or the project's other platform builds fail to compile. Put that code in an asmdef limited to Editor, Windows 64-bit and macOS, or inside #if UNITY_EDITOR_WIN || UNITY_EDITOR_OSX || (UNITY_STANDALONE_WIN && UNITY_64) || UNITY_STANDALONE_OSX.

var target = new SkiaUnityRenderTarget(512, 512);

void Update()
{
    target.Begin();
    target.Canvas.DrawCircle(256, 256, 200, paint);
    target.End();
}

void OnGUI()
{
    if (Event.current.type == EventType.Repaint)
        Graphics.DrawTexture(new Rect(0, 0, 512, 512), target.Texture, SkiaUnityRenderTarget.PremultipliedGuiMaterial);
}

SkiaRenderTarget2D

Applies to MonoGame, KNI, and FNA. raylib and Stride have their own render-target types with the same Begin/Canvas/End shape (see their quickstarts).

SkiaRenderTarget2D is a GPU surface that SkiaSharp renders directly into, sized to match whatever you intend to draw it onto (typically the back buffer, or a RenderTarget2D the same size as the viewport). Its Begin/End shape works like SpriteBatch's own: place individual shapes with their own coordinates via Skia's drawing API — the same way a SpriteBatch.Draw call carries its own position — and End() composites the whole result onto whatever render target is currently bound, the same way SpriteBatch.End() needs no separate step to show its queued sprite draws:

using SkiaGameRendering; // SkiaRenderTarget2D
using SkiaSharp;          // SKPaint and the rest of the drawing API

var canvas = new SkiaRenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height);

// per frame:
canvas.Begin();
canvas.Canvas.DrawCircle(100, 100, 100, paint); // position is DrawCircle's job, not End's
canvas.End();
Member Description
Texture The underlying Texture2D, mainly useful with EndWithoutDrawing
Canvas The SKCanvas to draw on — only valid between Begin() and End(); throws otherwise
Begin(bool clear = true) Starts a render pass; throws if called again before End()
End() Ends the render pass and composites the whole surface, at native size and the origin, onto whatever's currently bound; throws if Begin() wasn't called first
EndWithoutDrawing() Same as End(), but skips the composite — use only when End()'s single whole-surface blit can't express what you need (drawing it more than once, at a different size/position, or sampling it in a shader). You then draw Texture yourself.
Dispose() Releases the underlying GPU resources; throws if called between Begin() and End()

Size is fixed for the object's lifetime, like RenderTarget2D — construct a new SkiaRenderTarget2D (and Dispose() the old one) if you need a different size. Set a render target before calling Begin() if you want End()'s composite to land somewhere other than the back buffer.

Tear the shared backend down (e.g. on exit, or before switching backends) with:

SkiaRenderer.Dispose();

Dispose your own SkiaRenderTarget2D instances first — this doesn't track or dispose them for you.

Performance

Skia is a general-purpose vector renderer. It is not a sprite batcher, so draw sprites with the engine's own SpriteBatch and use Skia for vectors, SVG and text. The two interleave freely.

  • If sprites must go through Skia, use SKCanvas.DrawAtlas. One DrawAtlas call per texture sheet costs about the same as SpriteBatch. One DrawImage per sprite is several times slower.
  • Tint through DrawAtlas's colors array with SKBlendMode.Modulate, the equivalent of SpriteBatch.Draw's color. It costs almost nothing extra. Tinting DrawImage with a per-sprite SKPaint.ColorFilter is the slowest option measured. SKColor is unpremultiplied, so the matching SpriteBatch color is Color.FromNonPremultiplied(r, g, b, a).
  • Upload sprite images once with SKImage.ToTextureImage(SkiaRenderer.CurrentBackend.GRContext), called between Begin() and End() when Skia's GPU context is current, and reuse the result.
  • Use few, large targets. Every Begin()/End() pair switches GPU context and flushes Skia. On WindowsDX and KNI WindowsDX, End() also calls glFinish, which stalls the CPU until the GPU finishes. One screen-sized target per frame beats one per element.

Measured with benchmarks/Benchmarks.ShapeRendering.DesktopGL (MonoGame DesktopGL, macOS, Apple Silicon), frame time in ms:

Sprites Skia DrawImage Skia DrawAtlas SpriteBatch
0 0.190 0.187 0.149
500 0.466 0.242 0.174
2k 1.017 0.284 0.218
10k 3.977 0.638 0.579
500 tinted 0.836 0.250 0.174
10k tinted 10.794 0.658 0.585

For whole-frame FPS of a plain MonoGame app against the same scene drawn through Skia, by platform and hardware, see docs/performance.md.

Sample Projects

  • samples/Sample.MonoGame.DesktopGL/ — DesktopGL sample (cross-platform: Windows, Linux, macOS)
  • samples/Sample.MonoGame.WindowsDX/ — WindowsDX sample (Windows only)
  • samples/Sample.Kni.DesktopGL/ — KNI DesktopGL sample (cross-platform: Windows, Linux, macOS)
  • samples/Sample.Kni.WindowsDX/ — KNI WindowsDX sample (Windows only)
  • samples/Sample.Kni.WebGL/ — KNI Blazor WebAssembly sample
  • samples/Sample.Gum.Kni.WebGL/ — KNI Blazor WebAssembly sample of Gum UI interleaved with SpriteBatch, render targets, shaders, and browser input
  • samples/Sample.Raylib.OGL/ — raylib sample (Windows, Linux, macOS)
  • samples/Sample.Fna.WindowsDX/: FNA sample (Windows, D3D11 only; builds against the external/FNA submodule and the vendored external/fnalibs)
  • samples/Sample.Fna.OGL/: FNA sample on FNA3D's OpenGL driver (same setup; the vendored fnalibs are Windows x64 only, so on Linux/macOS drop in your own)
  • samples/Sample.Stride.D3D11/ — Stride sample (Windows, D3D11 only)
  • samples/Sample.Stride.VK/ — Stride sample (Vulkan; builds on Windows via StrideGraphicsApi=Vulkan, runs on Windows/Linux/macOS)
  • samples/Sample.Stride.D3D12/: Stride sample (Windows, D3D12 via StrideGraphicsApi=Direct3D12)
  • samples/Sample.Godot/ — Godot 4.7 project (Vulkan, D3D12 or Compatibility via --rendering-driver; dotnet build it, then open or run it with a Godot .NET editor binary - not shipped here)
  • samples/Sample.Unity/: Unity 6 project (Windows D3D11 and D3D12, macOS Metal). Run eng/build-unity-package.ps1 first to fill the package's Plugins/; --smoke-test checks the rendered pixels and exits
  • samples/Test/ — More comprehensive test with dynamic add/remove, FPS counter, input handling

DesktopGL, WindowsDX, KNI WindowsDX, and both FNA samples share the same Game1.cs via a linked file include; KNI DesktopGL has its own copy.

Architecture

The library uses a backend abstraction (SkiaBackend base class) so each graphics API gets its own implementation. Core source files are shared across platform-specific library projects via linked includes:

  • src/SkiaGameRendering/ — DesktopGL library (core + SkiaGlBackend)
  • src/SkiaGameRendering.Core.OGL/ — engine-agnostic raw-GL/Skia FBO interop shared by GL-based backends
  • src/SkiaGameRendering.Core.ANGLE/ — engine-agnostic D3D11/ANGLE interop shared by ANGLE-based backends
  • src/SkiaGameRendering.WindowsDX/ — MonoGame WindowsDX library (shared core + SkiaAngleBackend, on Core.ANGLE)
  • src/SkiaGameRendering.Kni.DesktopGL/ — KNI DesktopGL library (shared core + SkiaKniGlBackend)
  • src/SkiaGameRendering.Kni.WindowsDX/ — KNI WindowsDX library (shared core + SkiaKniAngleBackend, on Core.ANGLE)
  • src/SkiaGameRendering.Kni.WebGL/ — KNI/Blazor library (shared core + SkiaWebGlBackend)
  • src/SkiaGameRendering.Fna.WindowsDX/: FNA library (shared core + SkiaFnaAngleBackend, on Core.ANGLE, Windows/D3D11 only)
  • src/SkiaGameRendering.Fna.OGL/: FNA library (shared core + SkiaFnaGlBackend, on Core.OGL; src/SkiaGameRendering.Fna/ holds the FNA3D binding both FNA packages link)
  • src/SkiaGameRendering.Raylib.OGL/ — raylib library (shared Core.OGL + SkiaRaylibRenderTarget2D)
  • src/SkiaGameRendering.Stride.D3D11/ — Stride library (shared Core.ANGLE + SkiaStrideRenderTarget2D, Windows/D3D11 only)
  • src/SkiaGameRendering.Core.VK/ — engine-agnostic Vulkan/Skia interop shared by Vulkan-based backends
  • src/SkiaGameRendering.Core.Metal/: engine-agnostic Metal/Skia interop shared by Metal-based backends (Godot on macOS today)
  • src/SkiaGameRendering.Stride.VK/ — Stride library (shared Core.VK + SkiaStrideVulkanRenderTarget2D, Windows/Linux/macOS)
  • src/SkiaGameRendering.Core.D3D12/: engine-agnostic D3D12/Skia interop shared by D3D12-based backends
  • src/SkiaGameRendering.Stride.D3D12/: Stride library (shared Core.D3D12 + SkiaStrideD3D12RenderTarget2D, Windows/D3D12 only)
  • unity/com.vchelaru.skiagamerendering/: Unity UPM package (Core.ANGLE's, Core.D3D12's and Core.Metal's netstandard2.1 builds + SkiaUnityRenderTarget, Windows/D3D11, Windows/D3D12 and macOS/Metal; the plugins' sources are unity/native/SkiaUnityD3D12/ and unity/native/SkiaUnityMetal/)
  • src/SkiaGameRendering.Godot/ — Godot library (Core.VK, Core.D3D12, Core.Metal and Core.OGL behind one SkiaGodotRenderTarget2D, backend chosen from the running driver; no reflection, all public Godot API)

How each backend works

Backend Technique
OpenGL (MonoGame/KNI DesktopGL, FNA OpenGL) Second SDL GL context shared with the engine's
OpenGL (raylib) Second WGL (Windows), GLX (Linux) or CGL (macOS) context shares rlgl's GL namespace
OpenGL 3.3 (Godot Compatibility) Second GL context sharing Godot's; Skia draws into an FBO around an ImageTexture's GL texture, zero-copy
D3D11 (MonoGame/KNI/FNA WindowsDX, Stride, Unity) ANGLE (GL ES → D3D11 translation) on the engine's shared device. FNA gets the device through FNA3D_GetSysRendererEXT; Unity uses the device its RenderTexture belongs to
D3D12 (MonoGame WindowsDX12, Stride, Godot, Unity) Skia's D3D12 backend on the engine's ID3D12Device/queue. MonoGame wraps the target with RenderTarget2D.FromNativeHandle(); Godot draws into a typed resource and does one GPU CopyResource per frame (no readback, not zero-copy); Unity goes through a small native plugin
Vulkan (MonoGame DesktopVK, Stride, Godot) Skia's Vulkan backend on the engine's VkDevice/VkQueue, no separate context. Godot's handles come from public RenderingDevice.GetDriverResource, no reflection
Metal (Godot, Unity) Skia's Metal backend on the engine's MTLDevice/queue, drawn straight into the engine's MTLTexture; Unity goes through a small native plugin
WebGL2 (KNI Blazor) Cross-context texSubImage2D(canvas) through KNI's stock public API

Unity records draws to an SKPicture on the main thread and plays them back on its render thread.

See SkiaGameRendering-Notes.md for detailed technical documentation on how each backend works, including the ANGLE integration and D3D11 state management.

FNA

FNA's graphics layer is the native FNA3D library, which has three drivers: SDL_GPU (the default on SDL3 builds), D3D11 (Windows) and OpenGL. Only D3D11 and OpenGL hand out their native device (FNA3D_GetSysRendererEXT); the SDL_GPU driver leaves that call unimplemented, and SDL3 itself exposes no native handles from an SDL_GPUDevice, so there is nothing for Skia to share. Until that changes upstream, the FNA backends need FNA3D's D3D11 or OpenGL driver, which you select with an SDL hint before the Game exists:

// Program.cs
System.Environment.SetEnvironmentVariable("FNA3D_FORCE_DRIVER", "D3D11"); // or "OpenGL" with SkiaGameRendering.Fna.OGL
using var game = new Game1();
game.Run();

SkiaRenderer.Initialize throws with that instruction if the package's driver isn't the active one. D3D11 goes through ANGLE like the MonoGame/KNI WindowsDX backends; OpenGL creates a second SDL GL context sharing FNA's, like MonoGame DesktopGL, and works wherever FNA3D's GL driver does. Everything inside Game is the same code as the MonoGame/KNI backends (samples/Sample.Fna.WindowsDX links the shared Game1.cs). FNA itself is referenced the FNA way, as a submodule plus a ProjectReference, so the package carries no FNA dependency and binds to whatever FNA your game builds against; ship fnalibs next to your exe as usual. Tested against FNA 26.09.

WebGL / WASM Status

The WebGL backend is implemented in SkiaGameRendering.Kni.WebGL. It creates a synchronous SkiaSharp WebGL2 source host, flushes the current Skia frame, and uploads that canvas directly into a preallocated KNI Texture2D with texSubImage2D. Production code has no readPixels, managed pixel buffer, or Texture2D.SetData(byte[]) path.

The browser backend consumes stock KNI from NuGet (no fork or patch); the one internal (the current WebGL rendering context) that KNI doesn't expose publicly is reached via reflection instead — see src/SkiaGameRendering.Kni.WebGL/WebGlCanvasUpload.cs.

dotnet workload install wasm-tools-net8
dotnet build samples\Sample.Kni.WebGL\Sample.Kni.WebGL.csproj -c Release
dotnet run --project samples\Sample.Kni.WebGL\Sample.Kni.WebGL.csproj -c Release --no-build

Sample.Kni.WebGL draws the shared scene like every other sample. Sample.Gum.Kni.WebGL (same commands, different project) covers SpriteBatch interleaving, render-target consumption, shader sampling, animated Gum content, pointer/touch/wheel/text input, fractional DPR handling, and backend recreation. See docs/webgl/quickstart.md, docs/webgl/validated-baseline.md, and docs/documentation/SkiaWebGlBackend.md for the exact contract and support status.

Firefox is not usable yet

Measured on real hardware (docs/webgl/performance-results.md), every upload path misses budget on Firefox by 60-300x (35-171ms per frame just for the upload, vs. a <1ms target), consistent with an internal CPU readback on cross-context canvas uploads. Chrome and Edge are unaffected (Tier 1). Fixing this needs a shared-GL-context redesign ("Option A" in docs/webgl/validated-baseline.md), which is unstarted.

Using SkiaSharp

Between Begin() and End(), SkiaRenderTarget2D.Canvas gives you a full GPU-accelerated SKCanvas. For example, drawing an anti-aliased circle:

canvas.Begin();
canvas.Canvas.DrawCircle(Radius, Radius, Radius, _paint);
canvas.End();

For more on SkiaSharp drawing, see the SkiaSharp documentation.

License

MIT License. See LICENSE.md.

Credits

Originally created by Miguel Anxo Figueirido. Multi-platform backend abstraction and WindowsDX/ANGLE support added by Victor Chelaru.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.17.3-preview 41 10/3/2026
0.17.2-preview 35 10/2/2026