Skip to content

Massive Parallelism in Blazor Wasm: Running C# ILGPU kernels on WebGPU, WebGL, and Wasm.

License

Notifications You must be signed in to change notification settings

LostBeard/SpawnDev.ILGPU

Repository files navigation

SpawnDev.ILGPU

NuGet

Massive Parallelism in Blazor Wasm — Run ILGPU C# kernels on WebGPU, WebGL, and Wasm.
Write parallel compute code in C# and let the library pick the best available backend automatically. Two GPU backends bring GPU-accelerated compute to virtually every modern browser and device, while the Wasm backend provides near-native multi-threaded execution on any browser that supports Blazor WebAssembly.

Architecture

┌────────────────────────────────────────────────────────────────┐
│                     Your C# ILGPU Kernel                       │
├──────────────────┬──────────────────┬──────────────────────────┤
│     WebGPU       │     WebGL        │          Wasm            │
│     Backend      │     Backend      │        Backend           │
├──────────────────┼──────────────────┼──────────────────────────┤
│ WGSL             │ GLSL ES 3.0      │ WebAssembly binary       │
│ transpile → GPU  │ transpile → GPU  │ compile → Web Workers    │
└──────────────────┴──────────────────┴──────────────────────────┘
  Also includes CPU backend for debugging and comparison.

Demo Application

The Live Demo source is located in SpawnDev.ILGPU.Demo and showcases:

Benchmarks Screenshot
Fractal Explorer Screenshot

Backends at a Glance

🎮 WebGPU 🖼️ WebGL 🧊 Wasm CPU (Debug)
Executes on GPU GPU Web Workers Main (UI) thread
Transpiles to WGSL GLSL ES 3.0 WebAssembly binary — (interpreted)
Technique Compute shader Transform Feedback Multi-worker Single-threaded
Blocking Non-blocking Non-blocking Non-blocking ⚠️ Blocks UI thread
SharedArrayBuffer Not required Not required Required for multi-worker Not required
Performance ⚡⚡⚡ Fastest ⚡⚡ Fast ⚡⚡ Fast 🐢 Slowest
Shared Memory ⚠️ Barriers broken in WASM
Atomics ⚠️ Crashes in WASM
64-bit (f64/i64) ✅ Emulated ✅ Emulated ✅ Native ✅ Native
Browser support Chrome/Edge 113+ All modern browsers All modern browsers All modern browsers
Best for GPU compute (modern) GPU compute (universal) General compute Debugging / comparison

Auto-selection priority: WebGPU → WebGL → Wasm

Features

  • Three parallel backends — WebGPU (GPU compute via WGSL), WebGL (GPU via Transform Feedback), and Wasm (native WebAssembly on Web Workers)
  • CPU backend — Standard ILGPU CPU accelerator included for debugging and performance comparison
  • Two GPU backends — WebGPU for modern browsers, WebGL for universal GPU access on virtually every device
  • Automatic backend selectionCreatePreferredAcceleratorAsync() picks the best available
  • ILGPU-compatible — Use familiar APIs (ArrayView, Index1D/2D/3D, math intrinsics, etc.)
  • WGSL transpilation — C# kernels automatically compiled to WebGPU Shading Language
  • GLSL transpilation — C# kernels compiled to GLSL ES 3.0 vertex shaders with Transform Feedback for GPU compute
  • Wasm compilation — C# kernels compiled to native WebAssembly binary modules
  • 64-bit emulation — Support for double (f64) and long (i64) via software emulation on both GPU backends
  • WebGPU extension auto-detection — Probes adapter for shader-f16, subgroups, timestamp-query, and other features; conditionally enables them on the device
  • Subgroup operationsGroup.Broadcast and Warp.Shuffle are supported on the WebGPU backend when the browser supports the subgroups extension
  • Multi-worker dispatch — Wasm backend distributes work across all available CPU cores via SharedArrayBuffer; falls back to a single off-thread worker when SAB is unavailable
  • Blazor WebAssembly — Seamless integration via SpawnDev.BlazorJS
  • Shared memory & atomics — Supports workgroup memory, barriers, and atomic operations (WebGPU, Wasm)
  • No native dependencies — Entirely written in C#

Installation

dotnet add package SpawnDev.ILGPU

1. Configure Program.cs

SpawnDev.ILGPU requires SpawnDev.BlazorJS for browser interop.

using SpawnDev.BlazorJS;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

// Add BlazorJS services
builder.Services.AddBlazorJSRuntime();

await builder.Build().BlazorJSRunAsync();

2. Quick Start — Automatic Backend Selection

The simplest way to use SpawnDev.ILGPU is with automatic backend selection. The library discovers all available backends and picks the best one (WebGPU → WebGL → Wasm):

using ILGPU;
using ILGPU.Runtime;
using SpawnDev.ILGPU;

// Initialize context with all available backends
using var context = await Context.CreateAsync(builder => builder.AllAcceleratorsAsync());

// Create the best available accelerator (WebGPU > WebGL > Wasm)
using var accelerator = await context.CreatePreferredAcceleratorAsync();

// Allocate buffers and run a kernel — same API regardless of backend
int length = 256;
using var bufA = accelerator.Allocate1D(Enumerable.Range(0, length).Select(i => (float)i).ToArray());
using var bufB = accelerator.Allocate1D(Enumerable.Range(0, length).Select(i => (float)i * 2f).ToArray());
using var bufC = accelerator.Allocate1D<float>(length);

var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<float>, ArrayView<float>, ArrayView<float>>(VectorAddKernel);
kernel((Index1D)length, bufA.View, bufB.View, bufC.View);

await accelerator.SynchronizeAsync();
var results = await bufC.CopyToHostAsync<float>();

// The kernel — runs on GPU or Wasm transparently
static void VectorAddKernel(Index1D index, ArrayView<float> a, ArrayView<float> b, ArrayView<float> c)
{
    c[index] = a[index] + b[index];
}

3. Using a Specific Backend

You can also target a specific backend directly using Context.CreateAsync:

// WebGPU — GPU compute via WGSL
using var context = await Context.CreateAsync(builder => builder.WebGPU());
var device = context.GetWebGPUDevices()[0];
using var accelerator = await device.CreateAcceleratorAsync(context);
// WebGL — GPU compute via GLSL ES 3.0 + Transform Feedback (works on virtually all browsers)
using var context = await Context.CreateAsync(builder => builder.WebGL());
var device = context.GetWebGLDevices()[0];
using var accelerator = await device.CreateAcceleratorAsync(context);
// Wasm — native WebAssembly binary
using var context = await Context.CreateAsync(builder => builder.Wasm());
var device = context.GetDevices<WasmILGPUDevice>()[0];
using var accelerator = await device.CreateAcceleratorAsync(context);
// CPU — single-threaded fallback for debugging and comparison (runs on main thread)
using var context = Context.Create().CPU().ToContext();
using var accelerator = context.CreateCPUAccelerator(0);

Testing

Browser Tests

Start the demo app and navigate to /tests to run the unit test suite.

Automated Tests (Playwright)

# Windows
_test.bat

# Linux/macOS
./_test.sh

Test Coverage

400+ tests across four test suites covering all core features.

Test Suites

Suite Backend What's Tested
WebGPUTests WebGPU Full ILGPU feature set on GPU via WGSL
WebGLTests WebGL GPU compute via GLSL ES 3.0, f64/i64 emulation
WasmTests Wasm Native WebAssembly binary dispatch to workers
CPUTests CPU ILGPU CPU accelerator as reference (barriers/atomics excluded)
DefaultTests Auto Device enumeration, preferred backend, kernel execution

Coverage by Area

Area What's Tested Status
Memory Allocation, transfer, copy, views
Indexing 1D, 2D, 3D kernels, boundary conditions
Arithmetic +, -, *, /, %, negation, complex expressions
Bitwise AND, OR, XOR, NOT, shifts (<<, >>)
Math Functions sin, cos, tan, exp, log, sqrt, pow, abs, min, max
Atomics Add, Min, Max, CompareExchange, Xor
Control Flow if/else, loops, nested, short-circuit
Structs Simple, nested, with arrays
Type Casting float↔int, uint, mixed precision
64-bit Emulation double and long via software emulation (WebGPU, WebGL)
GPU Patterns Stencil, reduction, matrix multiply, lerp, smoothstep
Shared Memory Static and dynamic workgroup memory
Broadcast & Subgroups Group.Broadcast, Warp.Shuffle (WebGPU with subgroups extension)
Special Values NaN, Infinity detection
Backend Selection Auto-discovery, priority, cross-backend kernel execution

Browser Requirements

Backend Browser Support
WebGPU Chrome/Edge 113+, Firefox Nightly (dom.webgpu.enabled)
WebGL ✅ All modern browsers (Chrome, Edge, Firefox, Safari, mobile browsers)
Wasm All modern browsers (compatible with every browser that supports Blazor WASM)
CPU All modern browsers

GPU on every device: WebGL support means GPU-accelerated compute works on virtually every browser and device — including mobile phones, tablets, and older desktops without WebGPU support.

Note: For multi-worker SharedArrayBuffer support (used by the Wasm backend for parallel dispatch), the page must be cross-origin isolated (COOP/COEP headers). The demo includes a service worker (coi-serviceworker.js) that handles this automatically. Without SharedArrayBuffer, the Wasm backend falls back to single-worker mode — still running off the main thread to keep the UI responsive.

GPU Backend Configuration

64-bit Emulation

GPU hardware typically only supports 32-bit operations. Both GPU backends (WebGPU and WebGL) provide software emulation for 64-bit types (double/f64 and long/i64), enabled by default for full precision parity with the Wasm and CPU backends.

To disable emulation for better performance (at the cost of precision):

// WebGPU
using SpawnDev.ILGPU.WebGPU.Backend;
var options = new WebGPUBackendOptions { EnableF64Emulation = false, EnableI64Emulation = false };
using var accelerator = await device.CreateAcceleratorAsync(context, options);

// WebGL
using SpawnDev.ILGPU.WebGL.Backend;
var options = new WebGLBackendOptions { EnableF64Emulation = false, EnableI64Emulation = false };
using var accelerator = await device.CreateAcceleratorAsync(context, options);
Option Default Description
EnableF64Emulation true 64-bit float (double) emulation via double-float technique
EnableI64Emulation true 64-bit integer (long) emulation via double-word technique

Wasm Backend

The Wasm backend compiles ILGPU kernels to native WebAssembly binary modules and dispatches them to Web Workers for parallel execution. This provides near-native performance for compute-intensive workloads.

  • Kernels are compiled to .wasm binary format (not text)
  • Compiled modules are cached and reused across dispatches
  • Shared memory uses SharedArrayBuffer for zero-copy data sharing

Async Synchronization

In Blazor WebAssembly, the main thread cannot block. Use SynchronizeAsync() instead of Synchronize():

// ❌ Don't use — causes deadlock in Blazor WASM
accelerator.Synchronize();

// ✅ Use async version — works with all backends
await accelerator.SynchronizeAsync();

Verbose Logging

All backends include verbose debug logging, disabled by default. Enable per-backend when needed:

using SpawnDev.ILGPU.WebGPU.Backend;
using SpawnDev.ILGPU.WebGL.Backend;
using SpawnDev.ILGPU.Wasm.Backend;

WebGPUBackend.VerboseLogging = true;   // WebGPU backend
WebGLBackend.VerboseLogging = true;    // WebGL backend
WasmBackend.VerboseLogging = true;     // Wasm backend

Blazor WebAssembly Configuration

When publishing, specific MSBuild properties are required:

<PropertyGroup>
  <!-- Disable IL trimming to preserve ILGPU kernel methods and reflection metadata -->
  <PublishTrimmed>false</PublishTrimmed>
  <!-- Disable AOT compilation - ILGPU requires IL reflection -->
  <RunAOTCompilation>false</RunAOTCompilation>
</PropertyGroup>

License

This project is licensed under the same terms as ILGPU. See LICENSE for details.

Credits

SpawnDev.ILGPU is built upon the excellent ILGPU library. We would like to thank the original authors and contributors of ILGPU for their hard work in providing a high-performance, robust IL-to-GPU compiler for the .NET ecosystem.

Resources