dorkhub

ProGPU

Cross-platform GPU rendering, composition, vector, text, compute, and DirectX/LibreWPF/WinUI/Avalonia interop on WebGPU…

wieslawsoltes
C#1265 forksMITupdated 1 day ago
visit the demogit clone https://github.com/wieslawsoltes/ProGPU.gitwieslawsoltes/ProGPU

ProGPU Substrate Framework

Telegram Community

ProGPU is a high-performance, GPU-first UI framework and composition substrate for .NET, built on top of Silk.NET and WebGPU (wgpu-native). It provides a lightweight, low-allocation alternative to traditional heavyweight UI frameworks by routing all vector graphics, text layout, and composition operations directly to the GPU using native WebGPU draw pipelines.

NuGet Packages

ProGPU runtime packages are built from eng/progpu-package-list.sh by the Release GitHub Actions workflow. The same workflow builds the Avalonia renderer and Silk.NET windowing packages from the separate versioned integration lane in scripts/progpu-package-list.sh, publishes them after their ProGPU runtime dependencies, and attaches both lanes to the GitHub release. Samples, tests, diagnostics, and framework shim projects are intentionally not packed.

Package Purpose NuGet
ProGPU.Backend WebGPU device, swapchain, Silk.NET windowing, and platform backend services. NuGet
ProGPU.Backend.Dawn Typed Dawn native presentation, shared texture memory, and cross-queue fence services. NuGet
ProGPU.Media Framework-neutral media playback, diagnostics, audio processing, effects, and provider contracts. NuGet
ProGPU.Media.Editing Reusable non-destructive composition, project serialization, overlays, effects, and native export coordination. NuGet
ProGPU.Media.Scene Retained 2D/3D WebGPU media presentation and fused effect integration. NuGet
ProGPU.WinRT Platform-neutral WinRT-shaped foundation, storage, property-set, and media value contracts. NuGet
ProGPU.Windows.Media Native Windows Media Foundation, D3D11/DXGI, and platform-audio media provider. NuGet
ProGPU.Linux.Media Dependency-free Linux V4L2, DMA-BUF, Vulkan Video, and PipeWire media provider. NuGet
ProGPU.Text.Shaping AOT-safe OpenType shaping contracts and execution primitives. NuGet
ProGPU.Browser Batched .NET WebAssembly dispatcher and navigator.gpu browser host services. NuGet
ProGPU.DirectX DirectX-compatible facade and shader-oriented API surface implemented on ProGPU/WebGPU. NuGet
ProGPU.Transpiler Shader/source transformation helpers used by generated GPU pipelines. NuGet
ProGPU.Compute Compute pipeline helpers for GPU-side effects, acceleration, and future hit-test indexes. NuGet
ProGPU.Vector Vector primitives, paths, geometry, brushes, pens, and rasterization data models. NuGet
ProGPU.Text Text layout, glyph metrics, and GPU-ready text rendering helpers. NuGet
ProGPU.Fonts.Inter Official Inter font assets and typed accessors for deterministic UI typography. NuGet
ProGPU.Fonts.Noto Official Noto fallback assets and typed accessors for CJK and symbol coverage. NuGet
ProGPU.Scene Scene graph, compositor commands, retained visuals, effects, and presentation primitives. NuGet
ProGPU.Voxel Chunked voxel worlds, greedy meshing, collision, terrain generation, and grid ray casting. NuGet
ProGPU.Layout Measure/arrange layout substrate shared by higher-level UI adapters. NuGet
ProGPU.Virtualization Virtualization helpers for large retained visual and item surfaces. NuGet
ProGPU.WinUI WinUI-shaped controls and app model implemented on ProGPU. NuGet
ProGPU.Voxel.WinUI Playable WinUI voxel control with first-person input and retained ProGPU rendering. NuGet
ProGPU.WinUI.Themes.Fluent Source-generated unchanged WinUI Fluent theme resources and inspectable XAML content. NuGet
ProGPU.WinUI.Charts Chart controls and chart rendering primitives for the WinUI-shaped layer. NuGet
ProGPU.WinUI.Designer Designer/editor controls and diagnostics for ProGPU WinUI surfaces. NuGet
ProGPU.Xaml Framework-neutral XAML syntax, schema, diagnostics, and compiler contracts. NuGet
ProGPU.Xaml.Roslyn Roslyn symbol type system and structured C# emitter for the XAML compiler. NuGet
ProGPU.Xaml.SourceGenerator Incremental XAML source generator plus transitive MSBuild integration. NuGet
ProGPU.Xaml.Workspaces Roslyn Workspace editing, formatting, and bidirectional XAML services. NuGet
ProGPU.Xaml.Cli Standalone XAML compiler and Roslyn/MSBuild workspace command-line tool. NuGet
ProGPU.Avalonia Avalonia integration and compositor backend adapter. NuGet
ProGPU.Avalonia.Rendering GPU-first ProGPU/WebGPU rendering backend shipped as separate versioned packages for Avalonia 11 and 12. NuGet
ProGPU.Avalonia.SilkNet Cross-platform windowing, input, surfaces, and WebGPU integration shipped as separate versioned packages for Avalonia 11 and 12. NuGet
ProGPU.Uno Uno/WinUI integration and compositor backend adapter. NuGet
ProGPU.Dxf DXF import/rendering support for ProGPU vector scenes. NuGet
ProGPU.SkiaSharp ProGPU-backed portable SkiaSharp compatibility shim used by drawing and imaging adapters. NuGet
ProGPU.System.Drawing.Common ProGPU-backed portable System.Drawing.Common compatibility shim for LibreWinForms and GDI-style callers. NuGet
LibreWPF.Interop LibreWPF portable interop contracts consumed by the ProGPU/Silk.NET SDK lane. NuGet
ProGPU.Android Native Android SurfaceView host using WebGPU over Vulkan. NuGet
ProGPU.iOS Native UIKit/CAMetalLayer host using WebGPU over Metal. NuGet
ProGPU.Android.Media Android MediaCodec playback, AHardwareBuffer WebGPU interop, audio, composition export, and native composition thumbnails. NuGet
ProGPU.Apple.Media Shared macOS/iOS AVFoundation playback, IOSurface WebGPU interop, audio, and composition export. NuGet

Local package build:

PROGPU_PACKAGE_VERSION=0.1.0-preview.31 ./eng/progpu-pack.sh

Pack both Avalonia integration lanes after the portable ProGPU runtime packages:

PROGPU_PACKAGE_GROUP=portable ./eng/progpu-pack.sh
./scripts/progpu-pack.sh

See docs/progpu-packaging.md for package-only consumer validation, version overrides, and publishing.

The mobile packages contain the managed hosts and buildTransitive native-link contracts. Until native binaries are distributed independently, applications set ProGpuWgpuNativeAndroidRoot or ProGpuWgpuNativeXCFramework to outputs from the repository's pinned wgpu-native build scripts before deployment. Packing the complete set requires macOS with the Android and iOS workloads; PROGPU_PACKAGE_GROUP=portable packs the 31 cross-platform packages on Linux.

Native iPhone WebGPU sample

ProGPU.Samples.iOS is a thin native host for the same ProGPU.Samples gallery used by desktop and browser. It renders directly into a physical-pixel CAMetalLayer through a Metal-only wgpu-native XCFramework and the existing Silk.NET WebGPU API. It has no MAUI, Uno, WKWebView, or JavaScript rendering dependency.

./eng/build-wgpu-native-ios.sh
dotnet build src/ProGPU.Samples.iOS/ProGPU.Samples.iOS.csproj \
  -c Debug -r iossimulator-arm64

The iOS-specific solution, native build details, ABI pin, architecture research, simulator commands, physical-device guidance, and current limitations are in docs/ios.md.

Native Android WebGPU sample

ProGPU.Samples.Android is the corresponding thin .NET Android host for the shared gallery. It renders directly to an ANativeWindow-backed WebGPU surface through Vulkan, with no browser, Android Canvas, MAUI, Uno, or readback path.

export ANDROID_NDK_ROOT=/absolute/path/to/android-sdk/ndk/your-version
./eng/build-wgpu-native-android.sh arm64
dotnet build src/ProGPU.Samples.Android/ProGPU.Samples.Android.csproj \
  -c Debug -f net10.0-android

The Android architecture, exact native ABI pin, ARM64/x64 build lane, AOT publishing guidance, input/IME/inset/storage contracts, clean-room research, and device validation gates are in docs/android.md.

Browser WebGPU sample

The gallery is split into a shared ProGPU.Samples library and thin ProGPU.Samples.Desktop, ProGPU.Samples.Browser, ProGPU.Samples.iOS, and ProGPU.Samples.Android hosts. The browser host publishes with the .NET WebAssembly SDK, negotiates WebGPU capabilities, sends aligned binary command packets directly from WASM memory, and passes embedded WGSL unchanged to GPUDevice.createShaderModule.

Prerequisites

  • .NET 10 SDK.
  • The wasm-tools workload for native relinking and AOT publishing.
  • A browser with WebGPU enabled. Current Chromium-based browsers are the primary development target.
  • An HTTP or HTTPS origin. http://localhost and http://127.0.0.1 are suitable for local development; do not open the published index.html through a file:// URL.

Install or restore the WebAssembly workload once:

dotnet workload install wasm-tools
dotnet workload restore src/ProGPU.slnx

Use the browser host

ProGPU.Browser is the reusable browser backend. It supplies the navigator.gpu implementation of IWebGpuApi, the batched command protocol, browser input and storage adapters, and the external WinUI-shaped window host. ProGPU.Samples.Browser shows the complete startup wiring while reusing every page and shader from the ProGPU.Samples library.

To add a browser host to another ProGPU application:

  1. Create a Microsoft.NET.Sdk.WebAssembly executable targeting net10.0 with RuntimeIdentifier set to browser-wasm.
  2. Reference ProGPU.Browser and the project containing the shared application and views.
  3. Copy src/ProGPU.Browser/BrowserAssets/progpu-browser.js to the host's wwwroot as its JavaScript entry point, and provide an index.html containing the canvas selected by BrowserAppHostOptions.CanvasSelector.
  4. Call BrowserGpuRuntime.InitializeAsync, install its capabilities in a BrowserWindowHost, assign that host to WindowHostServices.Current, and run the shared application through AppBuilder<TApplication>.

The browser sample's ProGPU.Samples.Browser.csproj, Program.cs, and index.html are a minimal working template. The shared UI belongs in ProGPU.Samples; only startup, canvas/DOM assets, and browser platform wiring belong in the thin browser host. See docs/browser.md for the command protocol, transport modes, and deployment model.

Browser implementation architecture

The browser backend is an implementation of the same typed IWebGpuApi boundary used by desktop. Renderer, compositor, vector, text, compute, control, and sample code do not branch into a browser renderer. Only the host and WebGPU transport change:

flowchart LR
    App["Shared C# application and controls"] --> Scene["Retained scene, text, vector, and compute"]
    Scene --> Api["IWebGpuApi"]
    Api --> Desktop["SilkWebGpuApi<br/>wgpu-native"]
    Api --> BrowserApi["BrowserWebGpuApi<br/>browser-wasm"]
    BrowserApi --> Packet["Reusable aligned binary command arena"]
    Packet --> Interop["One coarse JS interop dispatch"]
    Interop --> Decoder["progpu-browser.js packet decoder"]
    Decoder --> WebGPU["navigator.gpu"]
    Shader["Embedded WGSL shader resources"] --> Scene
    Shader -->|"unchanged UTF-8 source"| WebGPU
Loading

The browser path is composed of these layers:

  • ProGPU.Samples contains the application, windows, pages, retained visuals, and every shader-using sample. It is a library shared unchanged by both hosts.
  • ProGPU.Samples.Desktop is a thin Silk.NET executable. ProGPU.Samples.Browser is a thin Microsoft.NET.Sdk.WebAssembly executable with the canvas, DOM shell, and JavaScript module.
  • BrowserGpuRuntime.InitializeAsync selects the canvas, requests an adapter/device/profile, chooses a transport, and returns a typed BrowserGpuCapabilities result. Unsupported capabilities fail during negotiation rather than rewriting application shaders.
  • BrowserWindowHost installs browser input, storage, clipboard, fallback font, and window services. Its requestAnimationFrame loop drains one fixed-width input batch, reads physical canvas size and DPI, then calls the ordinary Window.RenderExternalFrame path.
  • BrowserWebGpuApi implements renderer-facing WebGPU operations. It serializes resource descriptors, pass commands, copies, uploads, submission, mapping, and destruction into a versioned little-endian protocol. Packets have a 16-byte PGPU header, eight-byte-aligned commands, and generation-bearing handles that reject stale JavaScript resources.
  • progpu-browser.js reads packets directly from the current WebAssembly linear-memory view. It creates the corresponding GPUDevice, buffers, textures, bind groups, pipelines, render/compute passes, and queue operations without JSON or one JS call per draw. Upload payloads and mapped-buffer completion use separate coarse asynchronous seams where WebGPU requires them.
  • MainThread, Worker, and IsolatedWorker transports use the same packet format. Auto prefers a cross-origin-isolated shared-memory worker, then an OffscreenCanvas worker, and finally the main thread. All packets produced by one managed frame are handed to a worker as one ordered batch so the acquired surface texture remains valid for the complete frame.
  • Production WGSL remains in each owning project's Shaders/ directory and is embedded by ShaderResource. The exact source used by desktop is carried through ShaderModuleWGSLDescriptor and passed directly to GPUDevice.createShaderModule; the browser does not transpile, fork, or maintain copies of those shaders.

The standard browser canvas host currently supports one top-level Window. Popups remain ordinary compositor layers, so menus, flyouts, tooltips, and dialogs still share the same frame, input, and hot-reload tree.

Hot Reload architecture

Desktop and browser Debug hosts use the same framework-level metadata update pipeline. ProGPU.WinUI registers HotReloadManager with the runtime through MetadataUpdateHandlerAttribute; the browser project additionally enables the .NET WebAssembly Hot Reload component in Debug.

flowchart LR
    Edit["C# edit from dotnet watch or IDE"] --> Delta[".NET metadata/IL delta"]
    Delta --> Clear["HotReloadManager.ClearCache"]
    Delta --> Update["HotReloadManager.UpdateApplication"]
    Clear --> Queue["Coalesced UIThread generation"]
    Update --> Queue
    Queue --> Maps["Original/replacement type mapping"]
    Maps --> Hooks["Application and registered update hooks"]
    Hooks --> Roots["Active windows, popups, and registered roots"]
    Roots --> Choice{"Reload strategy"}
    Choice --> InPlace["IHotReloadable.Reload"]
    Choice --> Factory["Typed factory or development activator"]
    Choice --> Page["Static NavigationView page factory refresh"]
    Factory --> State["Capture and restore interaction state"]
    Page --> State
    InPlace --> Done["Invalidate layout/render and report result"]
    State --> Done
Loading

Each runtime callback only queues work; reconciliation runs on UIThread before rendering. Multiple deltas arriving before the next UI turn become one generation. The manager clears reusable theme/style factory caches, maps replacement types back to their original live types, invokes application hooks, and walks active Window content, popup roots, plus roots registered by embedded hosts. Recursive theme-resource reevaluation is reserved for ThemeManager, Style, ResourceDictionary, ThemeResource, and conservative all-types updates; unrelated method-body edits leave the retained scene and unaffected controls intact.

For an updated element, the manager uses these strategies in order:

  1. An IHotReloadable element rebuilds itself in place. This is the preferred choice for controls with explicit construction state or an existing Build method.
  2. A factory registered with HotReloadManager.RegisterFactory<TElement> recreates the element without reflection and is suitable for required constructor arguments.
  3. In an untrimmed modifiable development runtime, a parameterless element can be activated automatically. This reflection fallback is gated off when runtime metadata updates are unavailable, so Release AOT remains trim-safe.
  4. Cached NavigationViewItem pages created by static page factories are recreated when their factory owner type changes. Inactive cached pages are cleared and will use the updated factory when selected.
  5. If recreation is unavailable or fails, the old element stays live, is invalidated, and a diagnostic is reported; failure in one subtree does not stop the remaining roots.

Replacement captures state recursively by stable Name with structural-path fallback. The built-in state store preserves local DataContext, text/password and caret/selection, check/toggle state, selector and pivot selection, navigation selection and pane state, data-grid selection, scroll/virtualization offsets, attached layout properties such as Grid.Row, focus, and custom IHotReloadStateful values. Loading/unloaded lifecycle events are isolated, immediate state is restored before the next draw, and layout-dependent scrolling/focus is restored on the following UI turn.

Application authors can opt into the pipeline with small typed contracts:

using Microsoft.UI.Xaml.HotReload;

public sealed class LiveChart : Grid, IHotReloadable, IHotReloadStateful
{
    public void Reload(HotReloadContext context)
    {
        ClearChildren();
        BuildChart();
    }

    public object? CaptureHotReloadState() => SelectedSeries;
    public void RestoreHotReloadState(object? state) => SelectedSeries = state as string;
}

// Retain this IDisposable for as long as the factory should be active.
var registration = HotReloadManager.RegisterFactory(
    () => new ConstructorBoundControl(applicationService));

An Application may implement IHotReloadable to reapply window- or application-level configuration. Static shell builders can register an update callback and call HotReloadManager.ReloadWindowContent; custom containers whose backing collection must change with a visual child can implement IHotReloadChildReplacer. Embedded/non-window hosts register stable visual roots with HotReloadManager.RegisterRoot(root). When the registered root itself can be recreated, use HotReloadManager.RegisterRoot(root, replacement => host.Root = replacement) so the framework can replace it atomically while preserving state.

HotReloadManager.UpdateStarted, UpdateCompleted, LastResult, and Diagnostic expose generation, duration, replacement/reload/invalidation/failure counts, and isolated exceptions. Set PROGPU_HOT_RELOAD=0 to disable framework reconciliation. Live metadata updates are a Debug development feature: Release AOT artifacts retain the same UI/browser architecture but do not activate metadata-update handlers.

Quick start

From the repository root, restore and run the normal Debug build:

dotnet restore src/ProGPU.Samples.Browser/ProGPU.Samples.Browser.csproj
dotnet run --project src/ProGPU.Samples.Browser/ProGPU.Samples.Browser.csproj -c Debug

Open the HTTP URL printed by the command. The sample itself is used like the desktop gallery: select pages from the navigation pane, interact with the controls and GPU samples, and use Settings → Show Browser WebGPU Diagnostics when transport or adapter details are needed.

Normal development mode (without AOT)

Debug configuration uses the managed WebAssembly interpreter and keeps browser Hot Reload enabled. Build it without starting a server with:

dotnet build src/ProGPU.Samples.Browser/ProGPU.Samples.Browser.csproj -c Debug

For the normal edit/run loop, start the local server with dotnet watch:

dotnet watch --project src/ProGPU.Samples.Browser/ProGPU.Samples.Browser.csproj run -c Debug

Open the printed URL and leave dotnet watch running. Supported C# method-body edits are applied to the running browser application; use Ctrl+R in the watch terminal when an edit requires a restart and Ctrl+C to stop it. dotnet run can be used instead when Hot Reload is not needed.

To create a deployable interpreter artifact—useful for fast deployment iterations or comparison with AOT—publish with AOT disabled explicitly:

dotnet publish src/ProGPU.Samples.Browser/ProGPU.Samples.Browser.csproj \
  -c Release \
  -p:RunAOTCompilation=false \
  -o artifacts/browser-interpreter

python3 -m http.server 8080 --directory artifacts/browser-interpreter/wwwroot

Then browse to http://localhost:8080/.

AOT mode: publish and run

Release configuration enables managed WebAssembly AOT compilation, trimming, SIMD, and native relinking by default. AOT is a publish-time mode, so use dotnet publish rather than dotnet run:

dotnet publish src/ProGPU.Samples.Browser/ProGPU.Samples.Browser.csproj \
  -c Release \
  -o artifacts/browser-aot

python3 -m http.server 8080 --directory artifacts/browser-aot/wwwroot

Open http://localhost:8080/. All ProGPU and sample assemblies are AOT compiled. netDxf.netstandard is intentionally retained in supported mixed interpreter/AOT mode because the .NET 10 Mono AOT compiler currently asserts while compiling that upstream assembly.

The deployable application is the complete contents of artifacts/browser-aot/wwwroot; upload that directory to any static HTTP(S) host. Do not deploy its parent directory and do not open index.html with a file:// URL.

To confirm that a publish actually used AOT, inspect the publish log for AOT'ing and native WebAssembly linking steps. Browser Hot Reload is a Debug development feature and is not active in the trimmed Release AOT artifact. For a clean comparison between modes, remove the selected output directory before republishing so stale fingerprinted framework assets cannot be served.

The two local deployment modes can use different ports when comparing them side by side:

python3 -m http.server 8080 --directory artifacts/browser-interpreter/wwwroot
python3 -m http.server 8081 --directory artifacts/browser-aot/wwwroot

GitHub Pages AOT deployment

The Browser Pages workflow publishes the Release browser host with WebAssembly AOT and deploys artifacts/browser-aot/wwwroot whenever main changes. It can also be started manually from Actions → Browser Pages → Run workflow. The public gallery is available at https://wieslawsoltes.github.io/ProGPU/ after the first successful deployment.

The browser page uses relative asset URLs so the .NET runtime, worker module, styles, and fingerprinted framework files resolve beneath the /ProGPU/ repository path. The published .nojekyll marker ensures GitHub Pages serves the _framework directory unchanged.

Runtime and diagnostics

The browser host runs the shared retained gallery through the complete IWebGpuApi dispatcher. It supports main-thread, OffscreenCanvas worker, and cross-origin-isolated worker transports; batches DOM input once per frame; preserves asynchronous mapped-buffer read/write behavior; and sends the same embedded WGSL used by desktop directly to GPUDevice.createShaderModule.

The status bar at the bottom of the gallery remains visible. The larger browser WebGPU diagnostics overlay reports the active transport, adapter/profile, frame count, dispatch count, and command bytes; it starts hidden and can be enabled from Settings → Show Browser WebGPU Diagnostics. Startup and WebGPU errors reveal it automatically.

For cross-origin-isolated worker mode, configure the server to send:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Without these headers, Auto uses the ordinary OffscreenCanvas worker when available and falls back to main-thread rendering when canvas transfer is unavailable. See the browser backend guide for the full protocol, hosting, and capability details.

Local publishing reads the API key from NUGET_API_KEY without storing it in the repository:

PROGPU_PACKAGE_VERSION=0.1.0-preview.31 ./eng/progpu-publish.sh

The release workflow validates docs, restores, builds, tests, packs .nupkg/.snupkg artifacts, and can publish to NuGet.org when NUGET_API_KEY is configured. See docs/release.md.

Projects Using ProGPU

LibreWPF

LibreWPF ports the managed WPF runtime and SDK to the ProGPU/Silk.NET platform. Applications can switch to the custom SDK while retaining familiar WPF source, XAML, controls, and Windows-shaped APIs on supported non-Windows hosts.

Package Purpose NuGet
LibreWPF.Sdk MSBuild SDK that redirects WPF applications to the portable ProGPU/Silk.NET platform. NuGet
LibreWPF.ProGPU WPF host, retained/source replay bridge, input integration, and ProGPU compositor adapter. NuGet
LibreWPF.Transport Managed WPF assemblies, reference assemblies, themes, XAML build tasks, and runtime metadata. NuGet

LibreWinForms

LibreWinForms provides portable WinForms-shaped APIs hosted by the ProGPU/LibreWPF stack. It preserves the common System.Windows.Forms development model while replacing native GDI rendering with the ProGPU-backed compatibility layer.

Package Purpose NuGet
LibreWinForms.Sdk MSBuild SDK that configures applications for the portable LibreWinForms package set. NuGet
LibreWinForms.System.Windows.Forms Portable System.Windows.Forms API and runtime surface. NuGet
LibreWinForms.WindowsFormsIntegration Portable bridge for hosting WinForms content in LibreWPF applications. NuGet

Avalonia ProGPU and Silk.NET Backends

The Avalonia ProGPU renderer replaces the Skia renderer with a GPU-first WebGPU implementation while preserving Avalonia's rendering contracts. It also exposes an API lease for issuing custom ProGPU vector operations and WebGPU shaders inside an Avalonia frame.

The Silk.NET Avalonia backend supplies cross-platform desktop windowing, input, surfaces, and WebGPU integration. It is designed to pair with the ProGPU renderer but can host another compatible Avalonia renderer.

Package Purpose NuGet
ProGPU.Avalonia.Rendering ProGPU and WebGPU rendering platform for Avalonia. NuGet
ProGPU.Avalonia.SilkNet Cross-platform Silk.NET windowing platform for Avalonia. NuGet

Each compatibility lane produces its own .nupkg and .snupkg artifacts from separate project files. The v11 and v12 artifacts intentionally share the same two NuGet package IDs and are distinguished by their package versions:

Avalonia Rendering package Silk.NET package
12.0.5 ProGPU.Avalonia.Rendering 12.0.5-preview.31 ProGPU.Avalonia.SilkNet 12.0.5-preview.31
11.3.18 ProGPU.Avalonia.Rendering 11.3.18-preview.31 ProGPU.Avalonia.SilkNet 11.3.18-preview.31

The Avalonia 12 artifacts are built from src/ProGPU.Avalonia.Rendering and src/ProGPU.Avalonia.SilkNet. The Avalonia 11 artifacts are built separately from the thin src/ProGPU.Avalonia.Rendering.V11 and src/ProGPU.Avalonia.SilkNet.V11 projects, which source-link the shared implementation and define AVALONIA11.

Install the Avalonia 12 packages with:

dotnet add package ProGPU.Avalonia.Rendering --version 12.0.5-preview.31
dotnet add package ProGPU.Avalonia.SilkNet --version 12.0.5-preview.31

For Avalonia 11, use the same package IDs with 11.3.18-preview.31 and pin all Avalonia packages to 11.3.18. Configure the application with UseSilkNet().UseProGpu() before starting the desktop lifetime. Complete startup, API-lease, local packaging, and package-only validation instructions are in docs/progpu-packaging.md.

SkiaSharp Compatibility Shim

The shim provides a managed SkiaSharp 4.148-shaped API over ProGPU vector, text, imaging, path, and compositing primitives without loading native Skia binaries. Compatibility consumers such as Svg.Skia can use the ProGPU renderer while CPU-only metadata and geometry operations remain independent of WebGPU initialization.

Detailed API coverage, rendering behavior, algorithms, and complexity guarantees are maintained in the SkiaSharp compatibility log.

Package Purpose NuGet
ProGPU.SkiaSharp ProGPU-backed SkiaSharp API compatibility layer. NuGet

Svg.Skia

Svg.Skia renders SVG 1.1 documents and its supported static SVG 2 subset through a SkiaSharp-shaped canvas. Its W3C and resvg test lanes also exercise ProGPU.SkiaSharp, providing broad compatibility and rendering-parity coverage for the shim.

The Svg.Skia parity workflow pins the exact Svg.Skia source commit and runs separate native-SkiaSharp and ProGPU-shim checkouts on macOS. The native W3C lane must remain 530 passes with 3 skips; resvg and the non-W3C remainder must stay fully green through the shim. The ProGPU W3C lane validates its exact reviewed difference inventory and uploads both TRX files plus actual PNGs. A resolved row, a new failure, or a changed failure set intentionally fails the inventory gate until the images are reviewed and eng/svg-skia-w3c-known-differences.txt is updated in the same change.

Package Purpose NuGet
Svg.Skia Core SVG-to-SkiaSharp renderer. NuGet
ShimSkiaSharp Backend-neutral SkiaSharp API abstraction used by Svg.Skia integrations. NuGet
Svg.Skia.JavaScript Optional JavaScript execution support for SVG documents. NuGet
Svg.Controls.Skia.Avalonia Avalonia control integration for the Svg.Skia renderer. NuGet
Svg.SourceGenerator.Skia Source generator for compiling SVG resources into SkiaSharp drawing code. NuGet

Architectural Hierarchy

The ProGPU framework is built in a modular, layered stack that bridges native graphics APIs and system windowing up to a modern, declarative WinUI-compatible user interface layer.

graph TD
    subgraph L6 ["Layer 6: Application Layer"]
        App["Gallery Dashboard / LOL/s & MotionMark Benchmarks"]
    end

    subgraph L5 ["Layer 5: WinUI Framework Layer"]
        Controls["Grid, StackPanel, ScrollViewer, Border, Pivot, RichTextBlock"]
        FE["FrameworkElement"]
        LN["LayoutNode - Measure & Arrange Sizing Negotiation"]
    end

    subgraph L4 ["Layer 4: Scene Graph & Effects Layer"]
        CV["ContainerVisual / DrawingVisual / Visual with ChangeVersion"]
        ILN["ILayoutNode Interface - Layout and Scene Invalidation"]
        FX["GPGPU Multi-Pass Effects Pipeline - Blur & DropShadow"]
    end

    subgraph L3 ["Layer 3: Compositor, Text & GPGPU Rasterizer"]
        Cache["Compiled Scene Cache - Versions, Targets, Atlases, Layers"]
        Comp["Compositor - Z-Ordered Draw Lists and GPU Buffer Compiler"]
        Text["TTF Layout, Rich Text Command Cache, Glyph Atlas"]
        Rast["Compute-Bound 4x SSAA Glyph and Path Rasterizers"]
    end

    subgraph L2 ["Layer 2: Graphics Infrastructure"]
        Wgpu["WgpuContext - WebGPU Adapter/Device & Swapchain Management"]
    end

    subgraph L1 ["Layer 1: System & Windowing"]
        Silk["Silk.NET Windowing & GLFW OS Event Loop"]
    end

    App --> Controls
    Controls --> FE
    FE --> LN
    LN --> CV
    CV --> ILN
    CV --> FX
    ILN --> Cache
    Cache --> Comp
    FX --> Rast
    Comp --> Rast
    Rast --> Wgpu
    Wgpu --> Silk
Loading

Layer Description

  1. System & Windowing (Layer 1): Interacts with the operating system event queue and monitors display boundaries via Silk.NET and GLFW. It handles window load, resize, rendering loops, and low-level mouse and keyboard input events.
  2. Graphics Infrastructure (Layer 2): Manages physical GPU adapter querying, logical device creation, graphics command queues, and swapchain surface configuration.
  3. Compositor, Text & GPGPU Rasterizer (Layer 3): Validates and reuses compiled scenes when their visual versions, target configuration, atlas generations, overlays, and cached layers are unchanged. Cache misses compile high-level commands into ordered draw lists and reusable GPU buffers. Framework adapters retain shaped glyph indices and positions as one glyph-run command; the compositor caches font feature availability and rasterizes glyph and vector outlines analytically in WGSL at physical-pixel resolution.
  4. Scene Graph & Effects Layer (Layer 4): Establishes the retained ContainerVisual, DrawingVisual, and Visual hierarchy. Mutations propagate ChangeVersion and dirty state so layout, compiled-scene, and CacheAsLayer reuse remain correct. Mask and effect passes use offscreen textures and intentionally stay on the dynamic compilation path.
  5. WinUI Framework Layer (Layer 5): Implements cached Measure and Arrange, controls, input, and CPU visual-tree hit testing. The WinUI host disables the compositor's duplicate GPU hit-test index while direct compositor consumers retain it by default.
  6. Application Layer (Layer 6): Hosts gallery pages, diagnostics, and opt-in performance workloads. Sample animation and status updates invalidate only the visuals that actually changed.

Shader Source and Startup Contract

Fixed GPU programs live as individual .wgsl, .glsl, or .hlsl files under the owning project's Shaders/ directory. Directory.Build.props embeds these resources into each assembly, and ShaderResource decodes each source once into a process-wide cache. Pipeline owners retain the returned reference in static readonly fields, so rendering and compute hot paths perform no shader file I/O, manifest lookup, UTF-8 decoding, helper concatenation, or per-frame source allocation.

Each resource documents its algorithm, time complexity, and storage or bandwidth complexity at the top of the file. Final render and compute modules are self-contained, including analytic curve helpers that were previously concatenated from C# strings. Dynamic systems keep only input-dependent generation in C#: the DirectX HLSL translator emits WGSL from caller programs, WPF effects generate active sampler declarations, and ShaderToy appends user code. Their fixed headers and fragment wrappers are still resource-backed and cached.

ShaderResourceTests verifies that every source file is present in its assembly, loaded text matches the checked-in resource, required cost-model documentation exists, and fixed production stage modules do not reappear as C# literals. This keeps shader reuse and performance properties enforceable as the renderer evolves.


Current Frame Architecture and Performance Baseline

Microsoft.UI.Xaml.Window.RenderFrameCore records each host phase independently: dispatcher work, rendering callbacks, framebuffer/DPI setup, animation, layout, surface acquisition, compositor work, and presentation. Compositor.RenderScene then chooses between a retained fast path and a dynamic compile path:

flowchart TD
    Frame["Window frame"] --> Dispatch["Drain bounded UI work"]
    Dispatch --> Update["Rendering callbacks, animation, cached layout"]
    Update --> Acquire["Acquire physical-pixel surface"]
    Acquire --> Validate{"Compiled scene still valid?"}
    Validate -- Yes --> Reuse["Reuse draw lists, GPU buffers, brushes, hit index, and atlas entries"]
    Validate -- No --> Compile["Compile visual tree and external layers"]
    Compile --> AtlasStable{"PathAtlas coordinates stable?"}
    AtlasStable -- Yes --> Upload["Upload changed geometry, brushes, and uniforms"]
    AtlasStable -- "No, first failure" --> ResetAtlas["Reset once and discard CPU compilation"]
    ResetAtlas --> Compile
    AtlasStable -- "No after retry" --> FailFrame["Fail explicitly; live paths exceed capacity"]
    Upload --> Raster["Batch pending glyph and path rasterization"]
    Raster --> Capture{"Scene safe to retain?"}
    Capture -- Yes --> Remember["Capture versions, target, atlas generations, and layer textures"]
    Capture -- No --> Dynamic["Keep dynamic path for effects, masks, diagnostics, or DrawingVisual"]
    Remember --> Render["Execute ordered WebGPU render pass"]
    Dynamic --> Render
    Reuse --> Render
    Render --> Present["Present"]
Loading

The compiled scene cache is enabled by default with CompositorOptions.EnableCompiledSceneCache. A hit requires the same root identity and ChangeVersion, logical and physical target, viewport, DPI scale, glyph/path atlas generations, tooltip, external layer versions, and valid CacheAsLayer textures. Dynamic diagnostics force a miss. Mutable DrawingVisual content, masks, and effects are deliberately not retained because their output can change without a stable immutable command contract.

CacheAsLayer and compiled-scene reuse solve different costs. CacheAsLayer turns a stable subtree into one texture draw while the rest of a scene may still compile. Whole-scene reuse preserves the already compiled draw lists and GPU buffers for a stable frame. Atlas Generation values make both paths safe when a glyph/path atlas is cleared or repacked.

The WinUI host sets EnableGpuHitTesting = false because InputSystem already performs CPU visual-tree hit testing. Direct scene consumers keep the compositor GPU hit-test index enabled by default. This avoids building two indexes for every WinUI frame without changing input behavior.

Reference Performance

The opt-in sample harness reports wall-clock FPS, per-phase timings, allocation rate, scene-cache decisions, draw counts, and workload throughput. A July 2026 macOS 120 Hz reference run of the current architecture produced the following results; hardware, window size, and page state affect absolute values.

Workload VSync Wall FPS Workload throughput Scene cache
LOL/s Benchmark On 120.21 11,996 LOL/s Dynamic, 0/480 hits
LOL/s Benchmark Off 245.04 48,905 LOL/s Dynamic, 0/480 hits
Markdown Playground Off 519.02 Static after warmup 299/300 hits
DXF CAD Viewer Off 535.55 Static after warmup 299/300 hits

Run the same deterministic workload from the repository root:

dotnet build src/ProGPU.Samples.Desktop/ProGPU.Samples.Desktop.csproj -c Release

PROGPU_SAMPLE_BENCHMARK_PAGE='LOL/s Benchmark' \
PROGPU_SAMPLE_BENCHMARK_WARMUP_FRAMES=240 \
PROGPU_SAMPLE_BENCHMARK_MEASURE_FRAMES=480 \
PROGPU_SAMPLE_BENCHMARK_VSYNC=true \
dotnet run --project src/ProGPU.Samples.Desktop/ProGPU.Samples.Desktop.csproj -c Release --no-build

Set PROGPU_SAMPLE_BENCHMARK_VSYNC=false for uncapped throughput, or change the page to Markdown Playground, Text & Documents, or DXF CAD Viewer. The Text & Documents scroll workload builds a 20,000-paragraph mixed-script RichEditBox and reports realized paragraphs and visible characters alongside frame, layout, compositor, allocation, and scene-cache metrics. The first measured static frame may populate the cache; subsequent frames should report hits unless the page intentionally animates or invalidates.

Rendering quality remains part of the performance contract. The optimized text path retains the glyph index chosen during layout, hoists transform/raster invariants out of glyph loops, and skips color/bitmap table probes only when the parsed font has no such tables. Avalonia solid outline text records one retained glyph run instead of one path per glyph: shaped indices are retained, Vector2 positions are converted once when the platform glyph run is created, and redraws reuse both arrays. Recording is O(1) with no glyph-count-dependent allocation; compositor compilation is O(G) for G glyphs. Gradient brushes and color/bitmap fonts keep their path or texture fallbacks.

Vector glyphs keep 8x8 path-atlas coverage and use a device-pixel-size transfer calibrated against native Skia: small axis-aligned text preserves fine edge detail, large text receives the slightly stronger coverage needed to match Skia's visual weight, and rotated/reflected text keeps its separately calibrated branch. The physical-size classification includes display DPI, transform scale, and static-buffer zoom, and is computed once per text command for reuse by every glyph. Glyph geometry, subpixel placement, physical DPI rasterization, winding rules, brush opacity, and blend behavior remain unchanged.

Texture resampling follows the same contract. SKCubicResampler is an immutable two-coefficient SkiaSharp value with native float equality, hashing, and named Mitchell (1/3, 1/3) and Catmull-Rom (0, 1/2) kernels. SKSamplingOptions is the matching immutable discriminated value for nearest/linear filtering, mipmapping, cubic coefficients, and requested anisotropy. Cubic draws retain B/C through the recorded command and texture vertices, and the WGSL texture shader evaluates the full Mitchell-Netravali kernel. Named and custom kernels therefore remain distinct. Anisotropic draws retain their requested value through recording, clamp only at the portable WebGPU boundary to [1, 16], generate mipmaps, and reuse one lazily created sampler and persistent texture bind group per effective value; ordinary draws do not enter this cache. Value construction, reads, comparison, and hashing are allocation-free O(1) CPU operations. Sampler lookup is amortized O(1) with at most 15 anisotropic sampler objects, and the common Catmull-Rom render path keeps its original compact polynomial and pixel output.


Technical Specifications: Performance Optimizations

The sections below describe the cooperating layout, scene, text, atlas, batching, effects, and platform optimizations. They share one invariant: cached work is reused only while every input that can affect pixels remains valid.

1. WinUI-Compatible High-Performance Layout Caching & Invalidation

Sizing Negotiation Lifecycle

Traditional layout systems recursively traverse the entire scene graph every frame to negotiate sizing, causing massive $O(N)$ CPU overhead on complex visual trees even when the UI is static.

ProGPU introduces a cached sizing negotiation model that short-circuits measurements using layout dirty flags and cached input boundaries:

flowchart TD
    Start["Measure Pass availableSize"] --> Cached{"_isMeasureValid and availableSize == _previousAvailableSize?"}
    Cached -- Yes --> O1Exit["O1 Early Exit - Return Cached DesiredSize"]
    Cached -- No --> Calc["Calculate Margin Insets & Bounds Constraints"]
    Calc --> Override["Execute MeasureOverride child passes recursively"]
    Override --> CacheResult["Store DesiredSize, _previousAvailableSize & set _isMeasureValid = true"]
    
    CacheResult --> ArrangeStart["Arrange Pass finalRect"]
    ArrangeStart --> CachedArr{"_isArrangeValid and _isMeasureValid and finalRect == _previousFinalRect?"}
    CachedArr -- Yes --> O1ExitArr["O1 Early Exit - Return Immediately"]
    CachedArr -- No --> Align["Calculate Offset Coordinates & Horizontal/Vertical Alignments"]
    Align --> OverrideArr["Execute ArrangeOverride child placements recursively"]
    OverrideArr --> CacheResultArr["Store Offset/Size, _previousFinalRect & set _isArrangeValid = true"]
Loading
  • Measure Cache: Inside LayoutNode.Measure(), if _isMeasureValid is true and the incoming availableSize matches _previousAvailableSize, the pass returns immediately. MeasureOverride and recursive child traversals are fully bypassed in $O(1)$ time.
  • Arrange Cache: Inside LayoutNode.Arrange(), if _isArrangeValid and _isMeasureValid are true and the incoming finalRect matches _previousFinalRect, the pass short-circuits. Children offsets are not recalculated, and recursive child arrangements are bypassed.
  • Parent Bubble-Up Invalidation: When layout-affecting properties (such as Margin, Padding, WidthConstraint, HeightConstraint, alignments, or child mutations) are changed, they invoke InvalidateMeasure() or InvalidateArrange(). These clear local flags and bubble up the invalidation recursively to parent nodes, forcing only the dirty subtrees to be re-evaluated during the next frame's deferred layout pass.

Decoupled Visual Invalidation

To prevent circular dependencies between the ProGPU.Scene assembly (base visual layer) and the ProGPU.Layout assembly (WinUI framework layer), the ILayoutNode interface is defined in ProGPU.Scene:

public interface ILayoutNode
{
    void InvalidateMeasure();
}

Visual tree mutation methods (ContainerVisual.AddChild, RemoveChild, ClearChildren) check if this implements ILayoutNode. If so, they invoke InvalidateMeasure(), ensuring that any changes in visual tree structure automatically mark the layout path dirty without explicit parent layout references.

Retained-Scene Invalidation and Text-Page Responsiveness

Layout validity and pixel validity are separate. A layout property can alter clipping, alignment, padding, or descendant placement even when the final Size and Offset happen to compare equal. The LayoutNode setters therefore invalidate measure or arrange and call visual Invalidate(), which advances ChangeVersion to the compiled-scene root. This prevents a retained frame from displaying stale text or geometry after a layout-only mutation.

Text interaction uses the narrowest valid invalidation path. RichTextBlock observes every retained TextElement in its inline tree, so changing Run.Text, font, size, or foreground advances the owning visual version and invalidates layout without waiting for pointer activity. Hyperlink hover, caret movement, and selection changes dirty only render commands and pixels; they retain positioned characters and do not discard document layout. Markdown uses one immutable process-wide Markdig pipeline, warmed asynchronously during application startup. Single-column Markdown measured with infinite height performs one engine layout instead of a measurement layout followed by an identical second pass, while multi-column measurement reuses scratch lists. Width and multi-column-height changes explicitly invalidate layout, and empty content transactionally clears positioned characters, embedded children, and retained commands.

Page navigation keeps stable ownership boundaries:

  • Replacing NavigationView.Content changes only the SplitView content child. The pane and all menu-item visuals remain parented, preserving their layout, theme resources, and cached layer.
  • SplitView removes and inserts only the child that changed instead of clearing and rebuilding both children.
  • Reparenting a dependency-object subtree recursively reapplies theme state only when the resolved theme or theme family actually changes. Moving a cached page between parents with the same theme therefore performs an O(H) ancestor-context comparison for tree height H instead of O(N) invalidation over the page subtree.
  • System-font discovery and TextMate grammar loading use one shared background warm-up. Font menu controls are created only when the dropdown is opened. A code editor renders the same source as plain themed runs while grammar loading is pending, then retokenizes on the UI dispatcher with the shared theme grammar. Page activation therefore performs neither a synchronous filesystem font scan nor per-editor grammar initialization.

Large indexed pages remain virtual from source to pixels. ItemsControl retains an IList source rather than eagerly copying and boxing every item. Attaching a 65,535-glyph source is O(1) time and storage; UniformVirtualizingGridPanel realizes and binds only V visible/overscan items in O(V), reuses its recycler-index buffer, and does not allocate a binding closure on each viewport update. Selection and syntax-highlight changes rebind the V active containers in place instead of recycling, reparenting, and remeasuring them. Scrollbar z-order uses an in-parent reorder that invalidates pixels but not layout. The glyph browser represents indices through a read-only computed list, so it allocates neither a 65,535-entry source array nor an internal duplicate. FontIcon records the cached raw outline with a public DrawPath transform, preserving line, quadratic, cubic, and arc segments without allocating a transformed path per cell or render.


2. High-Performance Struct Equality and Comparison

Layout caching relies heavily on comparing boundary structs (Thickness and Rect) on every node. Standard C# struct comparison utilizes generic ValueType.Equals, which triggers CPU reflection, runtime boxing, and high memory allocations.

To eliminate this bottleneck, we implemented type-safe, non-boxing, custom equality overloads for both structs:

  • Thickness (Margins and Paddings)
  • Rect (Layout arrangements and clipping boundaries)

Each struct now overrides Equals(Thickness/Rect other), Equals(object? obj), GetHashCode(), and provides high-speed operators:

public bool Equals(Rect other)
{
    return X == other.X && Y == other.Y && Width == other.Width && Height == other.Height;
}

public static bool operator ==(Rect left, Rect right)
{
    return left.Equals(right);
}

public static bool operator !=(Rect left, Rect right)
{
    return !left.Equals(right);
}

These overloads compile down to direct float comparison instructions, achieving zero-allocation, ultra-fast boundary checks.


3. VSync-Off Graphics Pipeline Swapchain

To allow graphics and layout benchmarks to be evaluated at their true physical limit, we disabled vertical synchronization (VSync) throttling across all layers of the GPU pipeline:

  • Windowing Layer: Window options in the main, developer tools, and dynamic window controllers explicitly configure VSync to be disabled:
    options.VSync = false;
  • WebGPU Swapchain: Inside WgpuContext.ConfigureSwapChain(), the surface capabilities of the GPU adapter are queried. If PresentMode.Immediate is supported, the swapchain present configuration bypasses synchronization lockups:
    PresentMode presentMode = PresentMode.Fifo; // Fallback VSync
    for (uint i = 0; i < capabilities.PresentModeCount; i++)
    {
        if (capabilities.PresentModes[i] == PresentMode.Immediate)
        {
            presentMode = PresentMode.Immediate; // VSync Off
            break;
        }
    }

This enables the graphics swapchain to present frames as quickly as the GPU queue is filled, releasing the 60 FPS constraint and allowing framerates to soar into the hundreds or thousands of FPS.


4. Dynamic Backpressure-Throttled Event Dispatcher

The LOL/s benchmark stresses the visual framework by constantly removing and adding hundreds of poolable text controls to a canvas using a background thread loop.

  • The Livelock Risk: If a background thread pushes UI events (like AddChild or property changes) to the main thread's dispatcher loop as fast as possible without throttling, it will quickly overflow the main thread's event queue. The main thread then spends entire frame cycles acquiring queue locks to process actions, creating massive lock contention that completely starves the UI thread and freezes the application.
  • The Backpressure Solution: We introduced a thread-safe PendingCount property to the main UIThread queue. The background benchmark thread loops continuously without fixed sleep periods, but monitors queue occupancy:
    flowchart TD
        Start["Background Task Loop"] --> CheckBackpressure{"UIThread.PendingCount > 100?"}
        CheckBackpressure -- Yes --> Sleep["Thread.Sleep 1ms / Release Monitor Locks"]
        Sleep --> Start
        CheckBackpressure -- No --> Post["Post Action immediately / No Sleep"]
        Post --> UIThread["UIThread.RunPending - Main Thread drains queue"]
        UIThread --> AddChild["AddChild/RemoveChild visual tree mutation"]
    
    Loading
    • Backpressure Active (>100): The background thread sleeps for exactly 1ms. This releases the queue monitor lock completely and relinquishes the CPU slice, allowing the main UI thread to drain the event queue with zero lock contention. The application remains 100% responsive and immune to livelocks.
    • Backpressure Inactive (<=100): The background thread runs with zero sleep, dispatching new visual mutations to the UI thread continuously to maximize throughput.

5. Compositor Mesh Compilation via Span-Based Direct Writes

In real-time GPU-based vector rendering, compiling high-level primitives (such as Rectangles, Ellipses, Rounded Rectangles, Paths, Lines, and Bezier curves) into dynamic vertex and index buffers is a major CPU bottleneck. Standard implementation using sequential .Add(...) calls on List<T> invokes continuous bounds checks, potential array resizing/reallocations, and element copying overhead.

To maximize throughput, the Compositor is optimized using high-performance Span<T> memory writes:

  • Pre-Allocation Throttling: Instead of building meshes incrementally, the compositor determines the exact number of vertices and indices required for a primitive beforehand.
  • Backing Buffer SetCount: The internal list count is directly resized using CollectionsMarshal.SetCount(list, newCount) to avoid iterative dynamic reallocation/growth logic inside .NET's List<T>.
  • Direct Memory Access: The internal backing array is extracted as a type-safe memory slice via CollectionsMarshal.AsSpan(list).Slice(offset, count).
  • Fast Assembly Assignment: Vertices and indices are written directly to indices in the returned Span<T> or pre-filled using Span.Fill(defaultValue) for uniform values.
  • Bulk Memory Clipping: Clamping vector coordinates to active clipping boundaries is performed in a single linear pass over the direct Span<VectorVertex> reference, bypassing indexed list getters.
int originalVertexCount = _vectorVerticesList.Count;
int vertexToAdd = 2 * (N + 1);
CollectionsMarshal.SetCount(_vectorVerticesList, originalVertexCount + vertexToAdd);
var vertexSpan = CollectionsMarshal.AsSpan(_vectorVerticesList).Slice(originalVertexCount, vertexToAdd);
vertexSpan.Fill(baseVertex);

This ensures that the mesh compiler achieves zero-allocation dynamic buffer construction, minimal instruction-level overhead, and runs at near-native C-speed.


6. Retained MotionMark Geometry and Frame Scheduling

In traditional UI and vector engines, every active visual element in an animation loop is modeled as a heap-allocated class object. During high-count stress tests (such as the MotionMark benchmark rendering thousands of dynamically moving curves), these allocations put immense pressure on the .NET Garbage Collector (GC), leading to periodic micro-stutters and frame drops.

ProGPU eliminates this overhead using densely stored value types, retained geometry, and explicit pre-render scheduling:

  • Dense Elements: Animated shapes are modeled using compact Element and GridPoint value types, avoiding one managed object allocation per segment:
    public struct Element
    {
        public SegmentKind Kind;
        public GridPoint Start;
        public GridPoint Control1;
        public GridPoint Control2;
        public GridPoint End;
        public Vector4 Color;
        public float Width;
        public bool Split;
        public SolidColorBrush CachedBrush;
        public Pen CachedPen;
        public PathGeometry CachedPath;
    }
  • Retained Official Paths: Logical 80x40 grid points are mapped when elements are generated or the viewport changes. Each segment owns one retained PathGeometry; steady frames submit the same geometry references through the public DrawingContext.DrawPath API instead of allocating paths and segment objects in OnRender.
  • Two Public-API Modes: Individual mode emits one retained path command per segment. The default grouped mode reuses pooled PathGeometry/PathFigure containers and retained segment references for each split-delimited group, reducing command compilation without bypassing the renderer.
  • Pre-Render Animation Scheduling: The visual implements IAnimatedElement, so the normal sample-tree update traversal advances it every frame while it is attached. Update(delta) mutates split state and invalidates before compositor compilation; OnRender is side-effect free. Pointer movement is no longer needed to keep the benchmark active.
  • Time-Normalized Split Work: The original 0.5% split probability at 60 Hz is accumulated as 0.3 * N * delta toggles. Selecting K due toggles is O(K); rebuilding split-delimited retained groups before rendering is O(N + G) for N segments and G groups, and OnRender then records only O(G) grouped commands without mutating geometry. Persistent geometry storage is O(N); the retained group pool and end-index table are O(Gmax), bounded by N, after warmup. Pens, brushes, HUD strings, and path containers are refreshed only when their owning settings, theme, geometry, or viewport change.
  • Measured Result: On the 120 Hz reference macOS machine, the 1,000-element uncapped benchmark improved from 146.9 to 190.4 wall FPS and reduced visual compilation from 3.71 to 2.24 ms per frame. With VSync enabled it sustains 120 FPS with a deliberate Root version changed cache miss on every animated frame.

7. GPGPU Real-Time Multi-Pass Effects Pipeline

Standard graphics engines struggle to apply dynamic blurred effects (such as Gaussian backdrop blurs, soft ambient drop shadows, and neon glowing halos) to standard layout elements in real-time due to high composition and memory transfer overhead. ProGPU overcomes this with a multi-pass offscreen composition and compute processing system.

graph TD
    Subtree["Subtree Render Pass"] -->|Draw Elements 1x MSAA| Src["Source Offscreen Texture"]
    Src -->|Horiz. Dispatch| HCompute["Gaussian Blur Compute Shader Pass 1"]
    HCompute -->|Vert. Dispatch| VCompute["Gaussian Blur Compute Shader Pass 2"]
    VCompute -->|Output Framebuffer| Dest["Destination Blurs/Shadows Texture"]
    Dest -->|Matrix Align and Z-Order Bind| Framebuffer["Primary Swapchain Framebuffer"]
Loading
  • Dynamic Texture Caching: Textures (Source, Temp, and Destination buffers) are cached per-element in a specialized dictionary (_effectTextures). They are dynamically resized only when the element's actual visual bounds mutate, eliminating frame-by-frame allocation/deallocation thrashing.
  • Offscreen Redirection: Standard scene-graph rendering in ProGPU uses 4x MSAA for vector geometry. Since WebGPU compute shaders cannot directly read or sample multisampled textures, ProGPU compiles a specialized 1x MSAA offscreen rendering pipeline (_vectorPipelineOffscreen, _textPipelineOffscreen, _texturePipelineOffscreen). When an element has an active effect:
    1. The compositor preserves the active vector batch state and clips.
    2. It redirects all rendering of the element and its entire visual child subtree into the 1x MSAA offscreen Source texture using an isolated orthographic projection matrix.
    3. Restores the main batch state after capture.
  • Two-Pass Compute Acceleration: The compute pass binds the Source texture and executes a horizontal-pass WGSL compute shader, writing intermediate results to the Temp texture. It then binds the Temp texture to execute a vertical-pass compute shader, outputting the final blurred mask to the Destination texture.
  • High-Performance Compositing: The final blurred texture is drawn back onto the main screen swapchain as a textured quad. For drop shadows, the texture is drawn with configurable offsets, blending colors, and alpha multipliers, and the original Source texture is composited cleanly on top, maintaining crisp bounds.

8. GPU-Bound Analytical Vector Path Rasterization

To bypass CPU bottlenecks (e.g. flattening Bezier curves into thousands of lines and performing heavy triangulation), ProGPU integrates a pure GPU-bound vector path rasterizer. The engine computes vector fills analytically directly inside custom WebGPU WGSL compute shaders.

Sequential 16-Byte Aligned Struct Layouts

To satisfy WebGPU/WGSL uniform and storage buffer packing requirements, layout metrics are organized into sequentially packed structs matching exact 16-byte memory alignments:

[StructLayout(LayoutKind.Sequential, Pack = 16)]
public struct PathUniforms
{
    public float XStart;   public float YStart;
    public float Scale;    public uint PathIndex;
    public uint AtlasX;    public uint AtlasY;
    public uint Width;     public uint Height;
}

[StructLayout(LayoutKind.Sequential, Pack = 16)]
public struct GpuPathRecord
{
    public uint StartSegment;  public uint SegmentCount;
    public float MinX;         public float MinY;
    public float MaxX;         public float MaxY;
    public uint Pad0;          public uint Pad1;
}

[StructLayout(LayoutKind.Sequential, Pack = 16)]
public struct GpuPathSegment
{
    public Vector2 P0;         public Vector2 P1;
    public Vector2 P2;         public Vector2 P3;
    public uint SegmentType;   public uint Pad0;
    public uint Pad1;          public uint Pad2;
}

Analytical Non-Zero Winding Number WGSL Shaders

The rasterizer counts curve intersections analytically using a horizontal ray casting winding-number algorithm directly in WGSL:

  • Line Intersection: Evaluates linear roots analytically: $$t = \frac{p_y - A_y}{B_y - A_y}$$
  • Quadratic Bezier Intersection: Solves quadratic equation $(1-t)^2 A_y + 2(1-t)t B_y + t^2 C_y - p_y = 0$ for $t \in [0, 1]$. Valid intersections are checked against the ray, and winding adjustments are updated based on the tangent derivative: $$P'_y(t) = 2(1-t)(B_y - A_y) + 2t(C_y - B_y)$$
  • Cubic Bezier Intersection: Expands the cubic Bezier equation into $a t^3 + b t^2 + c t + d = 0$. The compute shader executes Cardano's formula (solve_cubic helper in WGSL) to find up to 3 real roots, updating the winding number according to the cubic tangent derivative: $$P'_y(t) = 3 a t^2 + 2 b t + c$$

Performance Enhancements & Quality Correctness

  • CPU Path Cache (_pathGeometryCache): Compiled segment arrays and pre-calculated local bounds are cached for each unique PathGeometry. Dynamic frames skip CPU figures traversal, and copy segment spans directly, reducing CPU path compilation times to 0.30ms for 100,000 shapes.
  • Pixel-Level Bounding Box Shader Skip: To eliminate GPU rasterization bottlenecks, the fine-rasterization pixel loop performs a screen-space bounding box check:
    if (px < inst.screenMinX || px > inst.screenMaxX || py < inst.screenMinY || py > inst.screenMaxY) {
        continue;
    }
    Pixels outside the shape boundaries immediately bypass local coordinate transforms, 4-sample subpixel loops, and expensive winding calculations. This discards ~95% of active operations per pixel, resulting in a 15x rendering speedup.
  • 4x SSAA Quality Correctness: Replaced screen coordinates with transformed local coordinates in the Sample 2 containment checks of the PathRasterizerShader. This ensures that under high multisampling/supersampling, anti-aliased edge pixels align perfectly, delivering sharp, hardware-accurate vector strokes and fills.

9. High-Quality Anti-Aliasing & Expanded-Quad Render Padding

Standard Signed Distance Field (SDF) rendering often clips the outer half of strokes or the edges of anti-aliasing gradients because the generated quad boundaries are drawn exactly at the shape's mathematical dimensions. This limits pixel operations outside the bounding box, resulting in a rough, aliased border.

To achieve state-of-the-art vector quality with zero performance degradation, we implemented a dual-stage quad inflation and pixel-distance anti-aliasing framework:

  • Separated-Pass Quad Expansion: During shape compilation in Compositor.cs, drawing of Rectangles, Ellipses, and Rounded Rectangles is divided into independent Brush (fill) and Pen (stroke) passes.
    • Fill Pass (Brush): Inflates bounding quad vertices and texCoord offset variables outwards by a padding of 1.5 pixels.
    • Stroke Pass (Pen): Inflates bounding quad vertices and texCoord offsets by thickness / 2.0 + 1.5 pixels. This expansion guarantees that the outer half of a stroke of width $T$, as well as its smooth anti-aliasing gradient, are fully rendered without quad boundary clipping.
  • Pixel-Distance WGSL Stroke Anti-Aliasing: For GPU-expanded Lines, Quadratic Beziers, Cubic Beziers, and elliptical Arcs, the vertex shader computes the exact signed pixel distance from the center spline to the expanded vertex boundaries, passing it to the fragment shader via gridIndex. The fragment shader evaluates anti-aliasing dynamically using:
    let d_pixels = abs(input.gridIndex);
    let d_shape = d_pixels - input.strokeThickness * 0.5;
    shapeAlpha = 1.0 - smoothstep(-0.5, 0.5, d_shape);
    This calculates a crisp, subpixel-accurate smoothstep edge transition directly in screen-space pixel coordinates, eliminating aliased jagged edges on all lines and splines.

10. High-Performance Theming, Styling & Templating Engine

ProGPU implements a lightweight, high-performance, and memory-safe theming, styling, and templating engine designed to emulate the logical capabilities of WinUI 3 but operating with minimal CPU and memory overhead.

flowchart TD
    Reg["DependencyProperty.Register"] -->|Sequential Indexing| DP["Index-Based Property Mapped Arrays"]
    DP -->|Precedence Resolution| GetVal["O1 GetValue Precedence Sweep"]
    Theme["ThemeManager.ThemeChanged"] -->|Lazy Invalidation| Dirty["Set IsThemeDirty = true"]
    Dirty -->|On-Demand Query| GetVal
    
    subgraph Storage ["O(1) Parallel Contiguous Value and Theme Arrays"]
        Local["_localValues"]
        Style["_styleValues"]
        DStyle["_defaultStyleValues"]
        LocalTheme["_localThemeResources"]
        StyleTheme["_styleThemeResources"]
        DStyleTheme["_defaultStyleThemeResources"]
    end
Loading

$O(1)$ Sequential Flat-Array Property Storage

Traditional XAML frameworks store DependencyObject property values in heavy dictionaries (Dictionary<DependencyProperty, object>), which trigger expensive hash calculation, collisions, and lookup overhead inside tight render or layout loops. ProGPU bypasses dictionaries entirely by introducing sequential indexing:

  • Sequential Indexing: Every registered DependencyProperty is assigned a unique, sequential, zero-based Index from a thread-safe static list during bootstrap.
  • Direct Array Access: DependencyObject stores properties in a set of parallel contiguous flat arrays (_localValues, _styleValues, _defaultStyleValues, _effectiveValues, and _valueSources) matching the index sizes.
  • Precedence Resolution: Property value resolution (GetValue(dp)) is simplified to direct index checks on these arrays in $O(1)$ time, resolving values via native priority precedence: $$\text{Local} \succ \text{Style} \succ \text{Default Style} \succ \text{Inherited} \succ \text{Default}$$

Lazy, Invalidation-Tracked Dynamic Theming

Eagerly traversing and updating dynamic brushes across the entire visual tree on every theme change triggers substantial CPU frame stutters. ProGPU bypasses this via a lazy evaluation pipeline:

  • Visual Tree Invalidation: When a theme toggle is triggered, ThemeManager.ThemeChanged fires. The system recursively propagates a cheap IsThemeDirty = true flag down the scene graph (NotifyThemeChanged), avoiding immediate value updates.
  • Parallel Flat Theme Mappings: Dynamic references are stored in parallel arrays (_localThemeResources, _styleThemeResources, and _defaultStyleThemeResources). During subsequent property reads (GetValue(dp)), if the dirty flag is set, the system sweeps these parallel arrays, re-evaluates active key lookups against the theme palette, and rebuilds only the affected elements' effective values in a single sequential linear pass.

Reflection-Free, Weak Callback Template Bindings

To support lightweight control customization without the heavy reflection, expression compilation, or string-matching of traditional bindings:

  • Index-Based Callbacks: DependencyObject maintains an index-sequential list of callbacks registered via RegisterPropertyChangedCallback(dp, callback).
  • WinUI-Compliant Tokens: Registration returns a unique long token, allowing surgical unregistration via UnregisterPropertyChangedCallback(dp, token).
  • Weak, Self-Cleaning Template Binding: Templat

more like this

Scythe

SCYTHE is a lightweight, C#-based game engine focused on modifiability and rapid iteration using Raylib.

C#50

RaylibWasm

.Net 10 (.Net 8+) webassembly starter project using raylib-cs nuget 7.0.2 and raylib 5.5

C#50

search

search projects, people, and tags