SdlVulkan.Renderer.Inspector 6.0.1071

This package has a SemVer 2.0.0 package version: 6.0.1071+c37b308eeee90bc2899deb2a37ff1460482cd580.
{
  "inputs": [
    {
      "type": "promptString",
      "id": "SDLVK_INSPECTOR_GROUP",
      "description": "Discovery multicast group override."
    },
    {
      "type": "promptString",
      "id": "SDLVK_INSPECTOR_PORT",
      "description": "Discovery multicast port override."
    },
    {
      "type": "promptString",
      "id": "group",
      "description": "Discovery multicast group (default 239.255.77.90)."
    },
    {
      "type": "promptString",
      "id": "port",
      "description": "Discovery multicast port (default 47891)."
    }
  ],
  "servers": {
    "SdlVulkan.Renderer.Inspector": {
      "type": "stdio",
      "command": "dnx",
      "args": ["SdlVulkan.Renderer.Inspector@6.0.1071+c37b308eeee90bc2899deb2a37ff1460482cd580", "--yes", "--", "--group", "${input:group}", "--port", "${input:port}"],
      "env": {
        "SDLVK_INSPECTOR_GROUP": "${input:SDLVK_INSPECTOR_GROUP}",
        "SDLVK_INSPECTOR_PORT": "${input:SDLVK_INSPECTOR_PORT}"
      }
    }
  }
}
                    
This package contains an MCP Server. The server can be used in VS Code by copying the generated JSON to your VS Code workspace's .vscode/mcp.json settings file.
dotnet tool install --global SdlVulkan.Renderer.Inspector --version 6.0.1071
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local SdlVulkan.Renderer.Inspector --version 6.0.1071
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=SdlVulkan.Renderer.Inspector&version=6.0.1071
                    
nuke :add-package SdlVulkan.Renderer.Inspector --version 6.0.1071
                    

SdlVulkan.Renderer

SDL3 + Vortice.Vulkan rendering library built on DIR.Lib primitives.

Types

  • SdlVulkanWindow — SDL3 window with Vulkan instance and surface lifecycle. Creates maximized, resizable windows with Vulkan surface.
  • VulkanContext — Vulkan device, command buffers, sync management. MaxFramesInFlight = 2 with per-frame vertex buffers. Two construction modes:
    • VulkanContext.Create(instance, surface, w, h, ...) — on-screen path with a swapchain tied to an SdlVulkanWindow.
    • VulkanContext.CreateOffscreen(instance, w, h, ...) — headless path rendering to a standalone VkImage; no surface, no swapchain, no SDL window. See Headless / offscreen rendering below.
  • VkRendererRenderer<VulkanContext> implementation with FillRectangle, DrawRectangle, FillEllipse, DrawText, plus batched glyph and persistent-vertex-buffer draw APIs. Exposes FontAtlasDirty so callers can trigger redraws after glyph rasterization. Has BeginFrame / BeginOffscreenFrame variants that match the two VulkanContext modes.
  • VkPipelineSet — GLSL 450 shader compilation and Vulkan pipeline creation (flat, textured, ellipse, stroke, SDF, blend variants).
  • VkFontAtlas — Dynamic bitmap glyph atlas with ManagedFontRasterizer (from DIR.Lib) rasterization and Vulkan texture upload. Supports grow (512→4096), deferred eviction, and skipUnflushed to prevent sampling stale GPU texture data.
  • VkSdfFontAtlas — Signed-distance-field glyph atlas side-car for resolution-independent text. SdfRasterSize = 128, fwidth-driven AA in the fragment shader auto-tunes to ±0.5 screen pixels at any zoom. Single-channel R8_Unorm texture, keyed on (font, size, character, charCode) so CID subset fonts don't collide.

Font Atlas Lifecycle

Per frame:

  1. BeginFrame() / BeginOffscreenFrame() — handles deferred eviction, runs OnPreFlush (pre-warm callback), calls Flush(cmd) on both atlases, runs OnPreRenderPass (texture uploads), then BeginRenderPass.
  2. Flush(cmd) — uploads dirty staging region to GPU via vkCmdCopyBufferToImage.
  3. DrawText(...)GetGlyph(...) — cache hit returns UV coords; miss rasterizes into staging.
  4. GetGlyph(..., skipUnflushed: true) — in draw loops, returns zero-width for glyphs not yet uploaded. Pair with PreWarmGlyph in OnPreFlush if drawing a glyph that wasn't shown last frame (first-frame glyph flicker). For SDF text, PreWarmSdfGlyph is the equivalent; PreWarmSdfGlyphBatch warms many glyphs in one call with parallel rasterization — preferred when a page introduces tens-to-hundreds of unique glyphs.

Thread safety: vkDeviceWaitIdle() before reusing the shared upload buffer (prevents race with MaxFramesInFlight = 2).

Diagnostic logging (Console.Error): [FontAtlas] / [VkRenderer] prefixed lines for Flush, Grow, EvictAll, cache miss, Resize. Capture with 2>stderr.log.

Usage

using SdlVulkan.Renderer;

using var window = SdlVulkanWindow.Create("My App", 800, 600);
window.GetSizeInPixels(out var w, out var h);

var ctx = VulkanContext.Create(window.Instance, window.Surface, (uint)w, (uint)h);
var renderer = new VkRenderer(ctx, (uint)w, (uint)h);

while (running)
{
    if (!renderer.BeginFrame(bgColor)) { renderer.Resize(w, h); continue; }
    renderer.FillRectangle(rect, color);
    renderer.DrawText("Hello", fontPath, 14f, white, layout);
    renderer.EndFrame();
    if (renderer.FontAtlasDirty) needsRedraw = true;
}

Headless / offscreen rendering

VulkanContext.CreateOffscreen builds a context that renders to a single VkImage instead of a swapchain. No VkSurfaceKHR, no SDL window, no VK_KHR_swapchain device extension requested — useful for tests, thumbnail / raster workers, CI without a display server, and server-side rendering.

using SdlVulkan.Renderer;
using Vortice.Vulkan;
using static Vortice.Vulkan.Vulkan;

// Minimal instance — no windowing extensions needed.
vkInitialize().CheckResult();
VkInstanceCreateInfo ici = new();
vkCreateInstance(&ici, null, out var instance).CheckResult();

const uint W = 1920, H = 1080;
using var ctx = VulkanContext.CreateOffscreen(instance, W, H);
using var renderer = new VkRenderer(ctx, W, H);

renderer.BeginOffscreenFrame(new DIR.Lib.RGBAColor32(255, 255, 255, 255));
renderer.FillRectangle(new RectInt(new(100, 100), new(400, 300)), red);
renderer.DrawText("Hello", fontPath, 14f, black, layout);
renderer.EndOffscreenFrame();
ctx.WaitOffscreenFrameComplete();

byte[] rgba = ctx.ReadbackOffscreenRgba(); // top-down, 4 bytes per pixel
// Pipe rgba into PNG encoder / image library of choice.

The offscreen path reuses all existing pipelines, MSAA slots, sync objects, and vertex ring buffers. Only the swapchain acquire/present is replaced by vkCmdCopyImageToBuffer into a host-visible staging buffer.

Runtime requirements (native)

At runtime you need the Vulkan loader plus an ICD (driver). Nothing else: no X11 / Wayland, no SDL native, no DirectX.

  • Windows: vulkan-1.dll (shipped with any modern GPU driver; also installable via the Vulkan SDK).
  • Linux: libvulkan.so.1 + an ICD. Options in increasing order of portability:
    • GPU driver (Mesa radv / intel_anv / NVIDIA proprietary) — fastest.
    • Mesa lavapipe / llvmpipe — software rasterizer. 5–20× slower but fully headless. Apt: mesa-vulkan-drivers.
    • Google SwiftShader — alternative software ICD.
  • macOS / iOS: MoltenVK (Vulkan on Metal). Untested for offscreen here but no reason it shouldn't work.

SDL3-CS remains a package reference because the on-screen path uses it. Its P/Invokes are lazy — libSDL3.so / SDL3.dll is never loaded if SdlVulkanWindow is not instantiated, so the offscreen path has no SDL runtime dependency.

CI setup

On a fresh Ubuntu runner (GitHub Actions ubuntu-latest, Azure Pipelines, GitLab):

- run: sudo apt-get update && sudo apt-get install -y libvulkan1 mesa-vulkan-drivers vulkan-tools
- run: dotnet test

vulkan-tools is optional but gives you vulkaninfo --summary for sanity-checking which ICD the runner loaded. For deterministic behaviour across runners, pin to lavapipe:

- run: echo "VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.x86_64.json" >> $GITHUB_ENV

Containers: add the same two packages to your image. The offscreen path doesn't require a tty, display variable, or privileged mode.

Native WebView (optional)

Host a native browser view inside an SdlVulkanWindow. The window system composites it over the Vulkan swapchain as a child surface — it does not go through the Vulkan render pass. Shipped as separate packages so core renderer consumers pull no webview dependency:

Package Purpose
SdlVulkan.Renderer.WebView The INativeWebView abstraction + platform backends. Multi-targets net10.0 (interface, factory + the Linux/WebKitGTK backend) and net10.0-windows (adds the Windows/WebView2 backend + its native dependency).
SdlVulkan.Renderer.WebView.Native WebView2Loader.dll native assets per Windows RID (x64 / arm64 / x86). Pulled in transitively on Windows — no need to reference it directly.
dotnet add package SdlVulkan.Renderer.WebView

Backends:

  • WindowsWin32WebView, WebView2 (Edge/Chromium) via the WebView2Aot Native-AOT bindings. Needs the Microsoft Edge WebView2 Runtime installed: that runtime is a system component and is not redistributed here; only the loader DLL ships (in the .Native package).
  • LinuxGtkWebView, WebKitGTK 4.1 embedded via X11 (XReparentWindow into SDL's window; GTK runs its own loop on a dedicated thread). Requires the SDL window on the X11 driver — run with SDL_VIDEODRIVER=x11 (works under Wayland sessions via XWayland) — and the WebKitGTK 4.1 + GTK3 runtime (apt install libwebkit2gtk-4.1-0 on Debian/Ubuntu; present on most desktops).
  • macOSWKWebView; backend is present but not yet implemented.
using SdlVulkan.Renderer;
using SdlVulkan.Renderer.WebView;
using DIR.Lib;

using var window = SdlVulkanWindow.Create("App", 1280, 800);
using var web = NativeWebView.Create();            // platform backend via the factory

web.NavigationCompleted += url => Console.WriteLine($"loaded {url}");
web.ConsoleMessage += (level, text) => Console.WriteLine($"[{level}] {text}");
web.PageError += err => Console.WriteLine($"JS error: {err}");

web.AttachToWindow(window);                         // parents the webview into the window's HWND
web.Navigate("https://example.com");
window.GetSizeInPixels(out var w, out var h);
web.SetBounds(new RectInt(new PointInt(w, h), new PointInt(0, 0)));   // window pixel coords

string href = await web.ExecuteScriptAsync("location.href");   // JSON-encoded result

For two-way native↔web interaction, MessageReceived surfaces the page's window.chrome.webview.postMessage(...) calls (as raw JSON) and PostMessage(json) sends the other way (the page receives it on chrome.webview's message event) — build whatever request/response protocol you want on top. The Linux backend injects a small window.chrome.webview shim at document-start (mapping to WebKit's messageHandlers), so the same page JS API works on both backends.

Fit-to-content sizing

SetBounds is host→browser (you position the webview). For the reverse direction — letting the page's content drive the webview's size — wrap it in a WebViewContentSizer. It injects a small ResizeObserver reporter after each navigation, surfaces the page's content size (in device pixels, ready for SetBounds) as ContentSizeChanged, and can drive the bounds for you:

using var web = NativeWebView.Create();
using var sizer = new WebViewContentSizer(web);     // construct before navigating

// Grow/shrink the webview's height to fit the page; width stays fixed at `w`, top-left at (0,0).
sizer.EnableAutoSize(origin: new PointInt(0, 0), fixedExtent: new PointInt(w, 0), axis: AutoSizeAxis.Height);
sizer.ContentSizeChanged += size => Console.WriteLine($"content is {size.X}x{size.Y} device px");

web.AttachToWindow(window);
web.NavigateToString("<h1>I size myself</h1>");

AutoSizeAxis.Height is the default and the safe choice: a fixed width never reflows the page, so it can't oscillate. Width/Both track the other axis but are prone to reflow feedback. The __sdlLayout message key is reserved for this protocol — layout envelopes are consumed by the sizer and never reach its MessageReceived, so subscribe to sizer.MessageReceived to get only your app's messages. No INativeWebView change is involved; the sizer is built entirely on the existing message bridge.

Beyond navigation, both backends surface diagnostics: Trace (redirect/load chain), ConsoleMessage (console.*), and PageError (uncaught JS exceptions, with stack). On Windows these come from the WebView2 DevTools Protocol; on Linux from in-page hooks forwarded over dedicated script-message channels.

Backend notes:

  • Windows — WebView2's async creation completes on the Win32 message pump, which SDL's event loop drives, so it composes with the standard render/event loop. Requires an STA thread. Events are raised on the UI thread.
  • Linux — GTK runs its own main loop on a dedicated thread; public methods are safe to call from the SDL event-loop thread (they marshal across via g_idle_add). Events (MessageReceived, TitleChanged, …) are raised on the GTK thread, so handlers must be thread-safe or marshal back themselves.

Dependencies

License

MIT

Rationale: Why SDL3 + Vortice.Vulkan

This library exists because the TianWen project needed a path to HDR display output (HDR10, scRGB, wide color gamut) that its previous Silk.NET + OpenGL stack could not deliver. The investigation below documents the alternatives considered and why SDL3 (via edwardgushchin/SDL3-CS) + Vortice.Vulkan won. This is decision rationale, not documentation of current features — check the types list above for what the library actually does today.

Why OpenGL was a dead end for HDR

  • GPU vendors (NVIDIA, AMD) block 10-bit and floating-point pixel formats for OpenGL in windowed mode.
  • The Windows HDR compositor (DWM) requires a DXGI swapchain, which only DirectX can drive natively.
  • GLFW has no HDR support — glfw issue #890 open since 2016, never implemented.
  • GLFW 3.4 (Feb 2024) shipped without it; a proposed GLFW_FLOAT_PIXEL_TYPE patch was never merged.
  • Silk.NET's WindowHintBool ends at SrgbCapable / DoubleBuffer — no float pixel type or HDR color space.

Why Vulkan

Vulkan supports HDR output via VK_EXT_swapchain_colorspace + HDR10 surface formats.

Platform OpenGL Vulkan HDR possible?
Windows Native Native Yes (Vulkan HDR swapchain)
Linux Native Native Yes (if compositor supports)
macOS Deprecated (frozen at 4.1) MoltenVK No (Metal HDR needs separate path)
Android OpenGL ES Native Yes (Android 10+)
iOS OpenGL ES (deprecated) MoltenVK No (same as macOS)
Web/WASM WebGL No No

Shader migration effort: low. GLSL shaders compile to SPIR-V with minimal mechanical changes (#version 330 core to #version 450, uniforms packed into UBOs, explicit layout(binding=N), build-time compile via glslc / glslangValidator or runtime via Vortice.ShaderCompiler). Shader math (MTF stretch, Hermite soft-knee, WCS deprojection, histogram) stays identical.

API migration effort: high. The real work was replacing ~2000 lines of OpenGL API calls with swapchain setup, descriptor sets, pipeline objects, command buffers, and synchronization.

Silk.NET status at the time of the decision

  • v2.23.0 (Jan 2026) — stable, quarterly maintenance releases.
  • 3.0: develop/3.0 branch exists, tracking issue #209 open since June 2020 (5.5+ years). Complete rewrite of bindings generation, no release date, lead developer (Perksey) less active, WebGPU bindings planned.
  • Silk.NET's prior usage in TianWen was well-contained: 4 source files, 3 NuGet packages, AOT with trimmer warning suppressions.
  • Verdict: not dead, but 3.0 had been in development for years; 2.x worked fine for OpenGL but was a dead end for Vulkan/HDR on any near-term horizon.

Known Silk.NET-side blockers

  • macOS regression: Silk.NET 2.21+ cannot create GLFW Vulkan windows on macOS (#2440); 2.20 worked.
  • MoltenVK not fully conformant: translates Vulkan to Metal, supports Vulkan 1.4 but some features missing; HDR swapchain extensions may not be implemented.
  • Web target lost: Vulkan has no browser support (WebGPU would be the path forward).

Alternatives evaluated (March 2026)

Veldrid — avoid (dead project)
  • Last commit: March 2024. Latest NuGet: v4.9.0 (Feb 2023). 159 open issues.
  • Clean abstraction (Vulkan, D3D11, Metal, OpenGL) but author (mellinoe) has moved on.
  • Targets .NET 6 / netstandard2.0, not .NET 10. No AOT testing. No HDR.
Avalonia + GPU interop — consider only if full UI rewrite desired
  • 30K+ stars, extremely active. .NET 10 supported.
  • Has GpuInterop sample with Vulkan demo via CompositionDrawingSurface.
  • Gives a proper UI framework (menus, panels, dialogs) — could replace hand-built text/panel rendering.
  • But: GPU interop is low-level (manage your own Vulkan context inside a compositor callback). HDR depends on the SkiaSharp compositor pipeline (no HDR). Very high migration effort, only worth it if replacing the hand-built UI.
SDL3 (.NET bindings)
  • SDL3 itself: 15K stars, actively developed, battle-tested.
  • Three competing .NET bindings: ppy/SDL3-CS (osu! team, most production-tested), edwardgushchin/SDL3-CS, flibitijibibo/SDL3-CS.
  • SDL3 has native Vulkan surface creation + SDL_GPU abstraction (Vulkan/D3D12/Metal with automatic shader cross-compilation).
  • HDR output support at the windowing level.
  • SDL3 + keep OpenGL: replaces only GLFW windowing/input. Medium effort. But HDR is still blocked because SDL3's OpenGL renderer hardcodes SDL_COLORSPACE_SRGB.
  • SDL3 + SDL_GPU: higher-level Vulkan-like API with shader translation. Medium-high effort.
Evergine Vulkan.NET — best raw Vulkan bindings, no ecosystem
  • 284 stars. Source-generated from Vulkan headers (always up-to-date).
  • Targets .NET 8+. Full HDR access via raw swapchain formats.
  • Raw bindings only — no windowing, no VMA, no shader compiler. Very high migration effort; 5-10x more code than OpenGL for the same result.
Vortice.Vulkan — best raw Vulkan ecosystem (chosen)
  • 371 stars (Vulkan), 1.1K stars (Windows/D3D). Last commit: Feb 2026. Only 2 open issues.
  • Explicitly targets net9.0 + net10.0. Pure managed C# bindings (delegate* unmanaged function pointers, no P/Invoke). IsAotCompatible = true.
  • Bundles VMA (Vulkan Memory Allocator), SPIRV-Cross, and shaderc as companion packages.
  • Caveat: single maintainer (bus factor of 1).
WebGPU via wgpu-native — future option, not ready
  • wgpu-native: 1.2K stars. Translates to Vulkan/D3D12/Metal.
  • .NET bindings immature (Evergine WebGPU.NET Nov 2025, WebGPUSharp 14 stars).
  • Shader language is WGSL (GLSL would need porting). HDR not yet in WebGPU spec.
  • Revisit when .NET bindings mature.

Why Vortice.Vulkan + edwardgushchin/SDL3-CS

edwardgushchin/SDL3-CS uses LibraryImport (source-generated, AOT-safe) — preferred over ppy/SDL3-CS which uses old DllImport. SDL3-CS.Native NuGet ships desktop natives; Android works but needs manual lib bundling.

Platform Vulkan SDL3 native AOT HDR
Windows x64 Native NuGet Yes Yes (Vulkan HDR swapchain)
Windows ARM64 Native NuGet Yes Yes
Linux x64 Native (Mesa/NVIDIA) NuGet Yes Possible (Wayland + Vulkan)
Linux ARM64 Native (Mesa) NuGet Yes Limited
macOS x64 MoltenVK NuGet Yes MoltenVK limitations
macOS ARM64 MoltenVK NuGet Yes MoltenVK limitations
Android Native Manual bundling Partial Yes
iOS MoltenVK (must bundle) Not shipped Yes Limited

SDL3 HDR support: SDL.window.HDR_enabled, SDL.window.SDR_white_level, SDL.window.HDR_headroom display properties, plus PQ (ST 2084) and HLG transfer characteristics. Combined with Vulkan VK_COLOR_SPACE_HDR10_ST2084_EXT swapchain, full HDR output is achievable.

SDL3 Vulkan surface creation: SDL.VulkanLoadLibrary() auto-finds MoltenVK on macOS; SDL.VulkanCreateSurface() returns a VkSurfaceKHR that pairs directly with Vortice.Vulkan.

Could we have stayed on Silk.NET by fixing it upstream?

macOS Vulkan regression (#2440) — small PR, uncertain merge timeline

GLFW 3.4 changed Vulkan detection on macOS; glfwVulkanSupported() can't find the Vulkan loader even though Silk.NET ships it (Silk.NET.Vulkan.Loader.Native). GLFW 3.4 added glfwInitVulkanLoader() which could solve this.

Possible fixes: call glfwInitVulkanLoader() with a custom vkGetInstanceProcAddr before glfwInit(); set VK_ICD_FILENAMES at the bundled MoltenVK ICD; ensure the Vulkan loader is on DYLD_LIBRARY_PATH.

Status: no PRs submitted, zero maintainer engagement on the issue. Silk.NET 2.x is in maintenance mode (14-month gap between 2.22 and 2.23), team focused on 3.0. Trivial PRs merge in 0-11 days; no evidence of substantive external feature PRs merging recently.

HDR - not feasible within Silk.NET's GLFW-based windowing
  • GLFW has no API for HDR pixel formats, transfer functions, or color spaces.
  • GLFW's own HDR issue (#890) open since 2016.
  • Silk.NET's Vulkan bindings already cover all HDR swapchain extensions — the blocker is purely windowing.
  • Would require replacing GLFW with SDL3 as windowing backend (huge change) or platform-specific code.
Path macOS fix HDR Effort Risk
Fix Silk.NET upstream Small PR, may wait months Blocked by GLFW Low for macOS, impossible for HDR PR rot
Vortice.Vulkan + SDL3-CS SDL3 auto-detects MoltenVK Full HDR built into SDL3 High (rewrite renderer) Two active projects

Comparison matrix

Option Maintenance Vulkan HDR AOT Migration Shaders kept?
Silk.NET 2.x (stay) Moderate Via 3.0 someday No Yes None Yes
Silk.NET 2.x + macOS PR Moderate Yes (with fix) No Yes None Yes
SDL3 + OpenGL Excellent Surface only No Yes Medium Yes
SDL3 + SDL_GPU Excellent Under the hood Possible Yes Medium-high Rewrite to SDL_GPU
Vortice.Vulkan + SDL3 (chosen) Good Full Yes Yes Very high GLSL to SPIR-V
Evergine Vulkan.NET + SDL3 Excellent Full Yes Yes Very high GLSL to SPIR-V
Avalonia + Vulkan interop Excellent Yes (interop) No Improving Very high Rewrite
WebGPU/wgpu Weak (.NET) Under the hood Not yet Possible High GLSL to WGSL

SDL3 + OpenGL HDR was corrected to No during evaluation: SDL3's OpenGL renderer hardcodes SDL_COLORSPACE_SRGB as the only accepted output. No float pixel formats, no scRGB, no HDR10 via OpenGL on any platform.

What to watch

  • SDL3_GPU maturity — could simplify Vulkan over time.
  • WebGPU .NET bindings — if they mature, a future browser target becomes possible.
  • Silk.NET 3.0 — if it ever ships with working Vulkan + a non-GLFW windowing path, the comparison may change.
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.

This package has no dependencies.

Version Downloads Last Updated
6.0.1071 0 6/11/2026
6.0.1051 0 6/11/2026
6.0.1031 0 6/11/2026
6.0.1011 20 6/10/2026
6.0.991 29 6/9/2026
6.0.971 26 6/9/2026
6.0.951 29 6/8/2026
6.0.931 29 6/8/2026
6.0.911 29 6/8/2026
6.0.891 26 6/8/2026