Developer Guide

C# Developer Guide

Best practices for building a fast, robust and maintainable real-time application in C# / .NET — and the kind of libraries worth reaching for along the way.

This is guidance, not a rulebook. The principles below apply to most real-time, media-rich desktop apps; adapt them to your project. C# / .NET has a huge ecosystem, so treat the libraries listed here as a useful starting point — there are many alternatives you can integrate to suit your needs.

Start on solid foundations

  • Target a current runtime (.NET 9) and pin your dependencies so builds are reproducible.
  • Pick one UI framework and commit to it — a cross-platform toolkit like Avalonia keeps your options open, though native hardware interop will usually tie the shipping build to Windows.
  • Separate intent from work: the UI captures what the user wants; a dedicated loop does the heavy lifting. A clear, one-directional data flow is worth more than any clever abstraction.
  • Keep the hot path boring. Prefer plain data and simple functions over deep inheritance and per-frame allocations.

Design around your data

  • For anything you touch every frame, favour a data-oriented layout: parallel arrays with a live count, rather than arrays of rich objects. It's cache-friendly, vectorises well, and maps straight onto GPU buffers.
  • Have one canonical "working set" that everything feeds into — models, text, particles, video and live streams should all synthesise into the same representation and flow through the same effect and lighting passes.
  • Allocate once and reuse. Grow buffers on demand, never shrink-and-reallocate mid-loop.

Keep the UI responsive

  • Never block the UI thread. Do rendering and processing on a worker/render thread and let the UI stay interactive.
  • Share state through one object that the UI writes and the worker reads, and avoid locks on the hot path — a mix of volatile scalars (for values that can lag a frame), atomic reference swaps (so readers never see a half-updated array), and one-shot command flags covers most needs.
  • Drive continuous input (held keys, sliders) from a polling timer rather than OS key-repeat, so motion feels smooth.

Make it run anywhere

  • Pair every GPU path with an equivalent CPU fallback so the app still runs with no capable GPU, on a CI machine, or in a headless test. Detect capability once and latch failures off so you don't retry a broken device every frame.
  • Bind to native SDKs at runtime and treat missing entry points as optional — probe what's available and degrade gracefully rather than crashing.
  • Always support a "no hardware" mode. An on-screen simulator lets people develop, test and demo without a display attached.

Design for the volume

  • A volumetric display is comparatively low-resolution. Lean into that — bold shapes, strong colour and clear motion read far better than fine texture detail, and let the viewer's eye fill in the rest.
  • Show only what should be seen: cull interior points that sit behind the outer surface, so a solid model doesn't reveal its hollow insides.
  • Keep visual density stable as the subject is scaled or moved, so content doesn't flicker or thin out when the viewer zooms.
  • Use the device's real-world bounds to size content, so the same scene looks right on both a VX2 and a VX2-XL.

Be disciplined about performance

  • Don't re-upload or recompute what hasn't changed — use dirty flags and cache results keyed by their inputs.
  • Batch work. On the GPU, per-call overhead is often the real cost, so pack many arrays into one transfer instead of many small ones.
  • Know your crossover point: below a certain size, the latency of dispatching to the GPU and reading back is slower than just doing it on the CPU.
  • Measure before optimising, and keep a lightweight on-screen profiler so regressions are obvious.

Robust interop & I/O

  • When you shell out to an external tool (for example ffmpeg for encoding), always drain its output streams — an un-read pipe will deadlock the process.
  • For cross-process/live input, choose one transport (named pipe, memory-mapped file) and make both ends agree on it; a silent mismatch looks like "nothing connects".
  • Make native structs blittable and explicit about layout, and locate native DLLs via a predictable search chain (app directory → PATH → registry).

Persist data that survives change

  • Design your save format to be forward- and backward-compatible from day one. A flat key/value document (JSON) is easy to evolve.
  • Persist enums by a stable ordinal and only ever append — never renumber existing values.
  • Dual-read renamed keys, tolerate type drift (int vs float), and never overwrite keys owned by another subsystem on save.
  • Keep user preferences separate from documents, and load them first so a document can override them.

Test without hardware

  • Make the app testable with no display and no GPU: feed synthetic inputs directly and force the CPU fallback path in tests.
  • Assert that GPU and CPU paths produce the same result — that's your safety net for the fallback actually working.
  • Expose internals to your test project (InternalsVisibleTo) rather than loosening real APIs.

Ship a clean build

  • Prefer a self-contained publish so users don't need a matching runtime installed.
  • If you trim, watch for anything loaded reflectively or via P/Invoke — reflection-based JSON, COM interop for some audio codecs, and libraries with native glue often need to be preserved explicitly, or they'll fail only in the published build.
  • Avoid spaces and surprises in your product name and output paths where downstream tooling has to parse them.

Libraries worth knowing

A starting toolkit for real-time, media-rich C# apps. All are optional — pick what fits, and swap in alternatives freely.

UseLibrary
Cross-platform UIAvalonia
Audio playback, capture & analysisNAudio
3D mesh import (FBX, GLB/glTF, OBJ, STL, PLY…)AssimpNet
GPU compute from C# (DirectX 12)ComputeSharp
Bitmaps & texturesSystem.Drawing.Common
Device / USB probing (WMI)System.Management
Video decode & computer visionOpenCvSharp4
Video encode / transcode (external tool)ffmpeg
6-axis input (SpaceMouse)3Dconnexion SDK

And, of course, the Voxel SDK itself for drawing straight to the volume — see the SDK overview to get started.

Get started

Bring your content to the volume

Explore the VX2 and VX2-XL, try the live simulator, or talk to us about your use case.