WebGPUSharp 0.5.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package WebGPUSharp --version 0.5.0
                    
NuGet\Install-Package WebGPUSharp -Version 0.5.0
                    
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="WebGPUSharp" Version="0.5.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="WebGPUSharp" Version="0.5.0" />
                    
Directory.Packages.props
<PackageReference Include="WebGPUSharp" />
                    
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 WebGPUSharp --version 0.5.0
                    
#r "nuget: WebGPUSharp, 0.5.0"
                    
#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 WebGPUSharp@0.5.0
                    
#: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=WebGPUSharp&version=0.5.0
                    
Install as a Cake Addin
#tool nuget:?package=WebGPUSharp&version=0.5.0
                    
Install as a Cake Tool

<div align="center">

NuGet Downloads Stars License

</div>

WebGPUSharp

WebGPU native bindings with a safe C# wrapper API on top mirroring the JavaScript API in the browser.

Overview

WebGPUSharp provides low-level bindings to the native WebGPU API through P/Invoke without runtime marshalling. Along with a high-level C# wrapper that closely follows the WebGPU JavaScript API. This allows developers to write GPU-accelerated applications in C# with a safe managed API surface and to translate WebGPU JavaScript code to C# easily.

Installation

WebGPUSharp requires .NET 10 or later. You can install the package via NuGet:

dotnet add package WebGPUSharp

Platform Support

WebGPUSharp includes native binaries of Dawn for the following platforms:

  • Windows: x64, ARM64
  • macOS: x64 (Intel), ARM64 (Apple Silicon)
  • Linux: x64, ARM64

For any other platforms, you will need to build Dawn from source and provide the native binaries yourself. You can use https://github.com/EmilSV/webgpu-dawn-build to help with building dawn for other platforms.

AOT Support

WebGPUSharp fully supports AOT as it doesn't use dynamic code generation or reflection. You can also statically link the native Dawn binaries into your application but doing so requires building Dawn from source.

Getting Started

Here is a simple hello world triangle example using WebGPUSharp and SDL2(via ppy.SDL2-CS) for window creation and surface handling:

using static SDL2.SDL;
using WebGpuSharp;

const string TriangleVertShaderSource =
"""
    @vertex
    fn main(
    @builtin(vertex_index) VertexIndex : u32
    ) -> @builtin(position) vec4f {
    var pos = array<vec2f, 3>(
        vec2(0.0, 0.5),
        vec2(-0.5, -0.5),
        vec2(0.5, -0.5)
    );

    return vec4f(pos[VertexIndex], 0.0, 1.0);
    }
""";

const string RedFragShaderSource =
"""
    @fragment
    fn main() -> @location(0) vec4f {
    return vec4(1.0, 0.0, 0.0, 1.0);
    }
""";

const int WIDTH = 800;
const int HEIGHT = 600;

SDL_SetMainReady();
if (SDL_Init(SDL_INIT_EVERYTHING) < 0)
{
    Console.Error.WriteLine($"Could not initialize SDL! Error: {SDL_GetError()}");
    return 1;
}

var instance = WebGPU.CreateInstance()!;
var window = SDL_CreateWindow("Hello Triangle", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WIDTH, HEIGHT, (SDL_WindowFlags)0);
var surface = SDL_GetWGPUSurface(instance, window)!;

// SDL2 do not like async/await as it can switch threads, so we block here
var adapter = instance.RequestAdapterAsync(new()
{
    CompatibleSurface = surface
}).Result!;

var device = adapter.RequestDeviceAsync().Result!;
var queue = device.GetQueue()!;
var surfaceFormat = TextureFormat.BGRA8Unorm;

surface.Configure(new()
{
    Width = WIDTH,
    Height = HEIGHT,
    Usage = TextureUsage.RenderAttachment,
    Format = surfaceFormat,
    Device = device,
    PresentMode = PresentMode.Fifo,
    AlphaMode = CompositeAlphaMode.Auto,
});


var pipeline = device.CreateRenderPipeline(new()
{
    Layout = null, // Auto-layout
    Vertex = new()
    {
        Module = device.CreateShaderModuleWGSL(new()
        {
            Code = TriangleVertShaderSource
        }),
    },
    Fragment = new()
    {
        Module = device.CreateShaderModuleWGSL(new()
        {
            Code = RedFragShaderSource
        }),
        Targets = [
            new()
            {
                Format = surfaceFormat
            }
      ]
    },
    Primitive = new()
    {
        Topology = PrimitiveTopology.TriangleList,
    },
})!;

while (true)
{
    while (SDL_PollEvent(out var @event) != 0)
    {
        switch (@event.type)
        {
            case SDL_EventType.SDL_QUIT:
                return 0;
            default:
                break;
        }
    }
    var commandEncoder = device.CreateCommandEncoder(new());
    var texture = surface.GetCurrentTexture().Texture!;
    var textureView = texture.CreateView()!;

    var passEncoder = commandEncoder.BeginRenderPass(new()
    {
        ColorAttachments = [
            new()
                {
                    View = textureView,
                    LoadOp = LoadOp.Clear,
                    StoreOp = StoreOp.Store,
                    ClearValue = new(0, 0, 0, 0),
                }
        ],
    });

    passEncoder.SetPipeline(pipeline);
    passEncoder.Draw(3);
    passEncoder.End();

    queue.Submit([commandEncoder.Finish()]);
    surface.Present();
}

static unsafe Surface? SDL_GetWGPUSurface(Instance instance, nint window)
{
    SDL_SysWMinfo windowWMInfo = new();
    SDL_VERSION(out windowWMInfo.version);
    SDL_GetWindowWMInfo(window, ref windowWMInfo);

    if (windowWMInfo.subsystem == SDL_SYSWM_TYPE.SDL_SYSWM_WINDOWS)
    {
        var wsDescriptor = new WebGpuSharp.FFI.SurfaceSourceWindowsHWNDFFI()
        {
            Hinstance = (void*)windowWMInfo.info.win.hinstance,
            Hwnd = (void*)windowWMInfo.info.win.window,
            Chain = new()
            {
                SType = SType.SurfaceSourceWindowsHWND
            }
        };

        SurfaceDescriptor descriptor_surface = new(ref wsDescriptor);
        return instance.CreateSurface(descriptor_surface);
    }
    else if (windowWMInfo.info.wl.surface != 0 && windowWMInfo.subsystem == SDL_SYSWM_TYPE.SDL_SYSWM_WAYLAND)
    {
        var wlDescriptor = new WebGpuSharp.FFI.SurfaceSourceWaylandSurfaceFFI
        {
            Chain = new ChainedStruct
            {
                Next = null,
                SType = SType.SurfaceSourceWaylandSurface
            },
            Display = (void*)windowWMInfo.info.wl.display,
            Surface = (void*)windowWMInfo.info.wl.surface
        };
        SurfaceDescriptor descriptor_surface = new(ref wlDescriptor);
        return instance.CreateSurface(descriptor_surface);
    }
    else if (windowWMInfo.info.x11.window != 0 && windowWMInfo.subsystem == SDL_SYSWM_TYPE.SDL_SYSWM_X11)
    {
        var xlibDescriptor = new WebGpuSharp.FFI.SurfaceSourceXlibWindowFFI
        {
            Chain = new ChainedStruct
            {
                Next = null,
                SType = SType.SurfaceSourceXlibWindow
            },
            Display = (void*)windowWMInfo.info.x11.display,
            Window = (uint)windowWMInfo.info.x11.window
        };
        SurfaceDescriptor descriptor_surface = new(ref xlibDescriptor);
        return instance.CreateSurface(descriptor_surface);
    } 
    else if (windowWMInfo.subsystem == SDL_SYSWM_TYPE.SDL_SYSWM_COCOA)
    {
        // Sadly macOS is too long to show in this snippet. See:
        // https://github.com/EmilSV/Webgpusharp-examples/blob/main/Setup/SDLWebgpu.cs
    }

    throw new Exception("Platform not supported");
}

This code will create a window and render a red triangle to it using WebGPU. A image of the triangle rendered by the above code

Examples

You can find more examples in the WebGPUSharp-Examples repository they are all ports of the official WebGPU samples to C# using WebGPUSharp.

A fractal cube example deferred rendering example lightmap generated using software ray-tracing. normal map example gpu particles example render bundles example

Buffers

WebGPUSharp's buffers are different from their JavaScript counterparts in two ways:

  • When you get the mapped data, you get it through a callback via a span. This is for safety reasons: if you just got a span, you could unmap the buffer while still holding a reference to the span, causing undefined behavior.

var buffer = device.CreateBuffer(new()
{
    Size = 1024,
    Usage = BufferUsage.Vertex | BufferUsage.CopyDst,
    MappedAtCreation = true // Create the buffer in a mapped state
});

// If it was not using callbacks it would look like this unsafe code:

var span = buffer.GetMappedRange<float>()

buffer.Unmap();

span[0] = 1.0f; // This would be undefined behavior as the buffer is unmapped

// instead we have to do it like this:
buffer.GetMappedRange<float>(data =>
{
    data[0] = 1.0f; // Safe to use data here

    // If we unmap here it would just throw as the buffer is being used.
    // This would not be possible without callbacks as the callback is the way we know the buffer is being used.
    // buffer.Unmap(); // This would throw

});
buffer.Unmap();

  • C# has structs, so when you read and write to buffers, you can use structs to represent the data layout in the buffer instead of manually calculating offsets. Here's an example of writing an array of structs to a buffer:

// A vertex struct with padding to ensure 16-byte alignment
// You can use https://eliemichel.github.io/WebGPU-AutoLayout/ to help with calculating padding
[StructLayout(LayoutKind.Sequential)]
public struct Vertex
{
    public Vector3 Position;
    private float _pad1; // Padding to align to 16 bytes
    public Vector3 Normal;
    private float _pad2; // Padding to align to 16 bytes
    public Vector2 Uv;
    private float _pad3; 
    private float _pad4; // Padding to align to 16 bytes
}

Vertex[] vertices =
[
    new Vertex { Position = new(0, 1, 0), Normal = new(0, 0, 1), Uv = new(0.5f, 1) },
    new Vertex { Position = new(-1, -1, 0), Normal = new(0, 0, 1), Uv = new(0, 0) },
    new Vertex { Position = new(1, -1, 0), Normal = new(0, 0, 1), Uv = new(1, 0) },
];

var vertexBuffer = device.CreateBuffer(new()
{
    Size = (ulong)(Unsafe.SizeOf<Vertex>() * vertices.Length),
    Usage = BufferUsage.Vertex | BufferUsage.CopyDst,
    MappedAtCreation = true
});

vertexBuffer.GetMappedRange<Vertex>(data => ((ReadOnlySpan<Vertex>)vertices).CopyTo(data));
vertexBuffer.Unmap();

Reading/Writing from Multiple Buffers

When reading/writing from multiple buffers, GetMappedRange can be rather cumbersome as you have to nest the callbacks, which do not work well with spans. To solve this, WebGPUSharp provides the Buffer.DoReadWriteOperation methods, which allow you to read/write from multiple buffers in a single callback:


var bufferA = device.CreateBuffer(new()
{
    Size = 1024,
    Usage = BufferUsage.MapRead | BufferUsage.CopyDst,
    MappedAtCreation = true
});

var bufferB = device.CreateBuffer(new()
{
    Size = 1024,
    Usage = BufferUsage.MapWrite | BufferUsage.CopyDst,
    MappedAtCreation = true
});

Buffer.DoReadWriteOperation([bufferA, bufferB] /* The buffers to operate on */, context =>
{
    var spanA = context.GetConstMappedRange<float>(bufferA);
    var spanB = context.GetMappedRange<float>(bufferB);

    // Read from bufferA and write to bufferB
    for (int i = 0; i < spanA.Length; i++)
    {
        spanB[i] = spanA[i] * 2.0f;
    }
});

Managed vs Unmanaged/Unsafe API

There are two API levels in WebGPUSharp: the unmanaged unsafe API and a managed safe API built on top of the unmanaged one. You can tell them apart as the unmanaged API lives in the WebGpuSharp.FFI namespace and uses raw handles for resources, while the managed API lives in the WebGpuSharp namespace and uses classes for resources. For example, here is how to create a buffer using the unmanaged API:

using WebGpuSharp.FFI;

DeviceHandle device = /* Get device handle from somewhere */;

BufferDescriptorFFI bufferDescriptor = new()
{
    Size = 1024,
    Usage = BufferUsage.Vertex | BufferUsage.CopyDst,
    MappedAtCreation = true
};

using BufferHandle buffer = device.CreateBuffer(&bufferDescriptor);
float* data = (float*)buffer.GetMappedRange(0, 1024);
for (int i = 0; i < 256; i++)
{
    data[i] = i * 1.0f;
}
buffer.Unmap();

You can convert a managed object to an unmanaged handle using the WebGPUMarshal.GetBorrowHandle or WebGPUMarshal.GetOwnedHandle methods. You can convert an unmanaged handle to a managed object using the WebGPUMarshal.ToSafeHandle and WebGPUMarshal.ToSafeHandleNoRefIncrement methods. The difference between Borrow and Owned is that an owned handle will increment the reference count of the unmanaged resource, while a borrowed handle will not. An owned handle should be used when you want to keep a reference to the resource, while a borrowed handle should be used when you just need to use the resource temporarily.

using WebGpuSharp;
Buffer buffer = device.CreateBuffer(new()
{
    Size = 1024,
    Usage = BufferUsage.Vertex | BufferUsage.CopyDst,
    MappedAtCreation = true
});

// Convert to unmanaged owned handle
BufferHandle ownedHandle = WebGPUMarshal.GetOwnedHandle(buffer);
// Convert back to managed object
Buffer managedBuffer = WebGPUMarshal.ToSafeHandle<Buffer, BufferHandle>(ownedHandle);

Generated Code

Most of the low-level unmanaged API in the WebGpuSharp.FFI namespace is generated using https://github.com/EmilSV/webgpu-dawn-build, which generates C# P/Invoke bindings from the C headers from Dawn and WebGPU Headers. All generated code is in the gen folder.

Contributing

You are welcome to contribute to WebGPUSharp! Please see the CONTRIBUTING file for more information on how to get started.

License

WebGPUSharp is licensed under the MIT License. See the LICENSE file for more information. WebGPUSharp uses Dawn as the native WebGPU implementation, which is licensed under the BSD 3-Clause License. See the Dawn LICENSE for more information.

Credit

WebGPUSharp has used code and take inspiration from the following projects:

  • Dawn - The native WebGPU implementation used by WebGPUSharp.
  • WebGPU Headers - The C headers for the WebGPU API used to generate bindings.
  • wgpu - A Rust implementation of the WebGPU API, used as bases for the documentation.
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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.
  • net10.0

    • No dependencies.

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.5.2 100 2/16/2026
0.5.1 102 1/17/2026
0.5.0 106 1/14/2026
0.4.0 272 12/6/2025
0.3.0 142 9/12/2025
0.2.1 206 8/20/2025
0.2.0 200 8/18/2025
0.1.5 161 6/6/2025
0.1.4 169 6/6/2025
0.1.3 167 6/6/2025
0.1.1 208 6/5/2025
0.1.0 200 6/5/2025