Game UI in HTML/CSS for Unreal Engine — rendered off the game thread.
RmlUi layout and styling, optional JavaScript, on a dedicated UI thread —
the game thread pays for a queue write and a snapshot read, nothing else.
Get started | Features | Performance | Website | Download | Fab
vacuus.RefHud — the reference HUD the plugin ships and measures itself against: 1,732 live nodes over an Unreal level, captured headless.
You author screens as .rml/.rcss documents — the web languages, not a UMG graph — and
the plugin runs them on its own UI thread: layout, styling and draw-command recording all
happen away from the game thread, which only enqueues input and reads back a snapshot.
Optional JavaScript (QuickJS) and a TypeScript/Preact workflow sit on top. Rendering goes
through the engine's RHI into a persistent render target that Slate composites, so a
document can be a full-screen HUD or a panel on a quad in the world.
Status: released, 1.3.0. Engines: UE 5.6, 5.7 and 5.8. The 5.6 and 5.8 packages compile from this one source tree; the 5.7 package is derived from the 5.6 build with its provenance stated in the release notes. The 295-test automation suite ships with the plugin and can be run locally (below). Supported platforms are Windows, macOS, Linux, Android and iOS; consoles on request. Packages are published on GitHub Releases, one archive per engine (
VaCuus-<version>-UE5.8.zipand its 5.6 and 5.7 siblings). Take the download that matches your engine (docs/buyer/setup.mdsays how to check). The API is stable — breaking changes are reserved for major versions and carry release notes.
SVG files, inline SVG, SVG decorators and Lottie animations are built in. See
vector assets for markup, resource paths and cost,
or run vacuus.VectorDemo in the editor.
One process-wide UI thread owns every RmlUi context — RmlUi's state is process-global, so this is the shape it has to have. Each UI frame drains the command queue, updates layout and style, publishes a snapshot of the interactive regions, and records draw commands without touching the GPU. The render thread replays them into a persistent render target that Slate composites. Slate needs a synchronous Handled / Unhandled answer about geometry that lives on another thread; the snapshot is that answer.
The workload is the plugin's own 1,732-node reference HUD, animating every frame on purpose — the worst case it ships. Cooked Shipping, Linux, Vulkan, 1920×1080, UE 5.8.1:
| Measured | Budget | Headroom | |
|---|---|---|---|
| Game thread, per frame | 0.008–0.009 ms | 0.10 ms | ~8–11× |
| Composite only, idle UI | 0.002 ms | 0.05 ms | ~12–25× |
| Added RAM, JS heap included | +14.3 MB | 32 MB | ~2.2× |
| Added disk, binary + cooked bundle | +3.22 MiB | 10 MB | ~3× |
A static HUD published 1 frame in 6,910 — publication is withheld while the content hash
is unchanged, so a static pause menu costs only its composite. Every number is re-runnable in your own
project (vacuus.RefHud + vacuus.M1HUD.PerfLog 1); methods, the Dev column and the rows
that are tighter than these are in docs/buyer/perf-guide.md.
The reference-HUD baseline above was measured before vector support. See vector assets for SVG/Lottie costs and their measurement.
| Page | What it answers |
|---|---|
docs/buyer/setup.md |
Start here. What is in the package, what your project needs, first document, and how UI ships in a cooked build. |
docs/buyer/gotchas.md |
Every recorded surprise, numbered. Read before your first authoring day — most of them cost someone a debugging session already. |
docs/buyer/rcss-matrix.md |
The exact supported RCSS surface, generated from the vendored RmlUi this plugin ships. RCSS is not CSS; this page is the difference. |
docs/buyer/vector-assets.md |
SVG files, inline SVG, SVG decorators and Lottie animation, with resource paths and cost. |
docs/buyer/perf-guide.md |
The budgets, the measured numbers, and the idioms that keep a HUD inside them. |
docs/buyer/ai-guide.md |
If an AI agent writes your UI. The whole interface is plain text, which suits agents well — but most mistakes here produce no error and a screen that renders, and an agent cannot see the screen. This page is what to brief it with. AGENTS.md beside this file is the short version an agent finds on its own. |
Using Claude Code, Codex, Cursor or Copilot? One line in your project's own
CLAUDE.md / AGENTS.md is enough to point it at the right pages:
UI is VaCuus (HTML/CSS off the game thread). Before touching anything under
Content/DevUI, read Plugins/VaCuus/docs/buyer/ai-guide.md and
Plugins/VaCuus/docs/buyer/rcss-matrix.md.From Fab: the launcher installs the plugin into
the engine, not into your project — Epic builds the binaries for code plugins, so
nothing compiles on your machine and a Blueprint-only project works. Enabling it is
the whole install. From source (a release archive
or this repository): your project must be a C++ project —
a Blueprint-only project has no toolchain to compile it with (converting takes two
minutes: Tools → New C++ Class → None). Drop the folder into
<YourProject>/Plugins/VaCuus, so that VaCuus.uplugin lands at
<YourProject>/Plugins/VaCuus/VaCuus.uplugin, and the first editor launch compiles the
modules once. Either way, enable it in Edit → Plugins or in your .uproject:
"Plugins": [
{
"Name": "VaCuus",
"Enabled": true
}
]The package root is the plugin folder — there is no extra directory level to strip.
setup.md §0 has the full version of this, including the two ways a shipped copy differs
from the repository.
Verify it works: the console knows vacuus.M2Demo — run it and an interactive demo
document appears. Automation RunTests VaCuus runs the suite; the test source and its
fixtures ship on purpose.
A symlink instead of a copy works at runtime but breaks the build: Unreal Build
Accelerator's file detours see the symlinked path and the real path as two different files
and abort renaming an object file (cross-process rename-while-open). To keep the plugin
outside the project, use AdditionalPluginDirectories in your .uproject instead — and
point it at a parent of the plugin directory, never at the plugin directory itself.
VaCuus.uplugin Plugin descriptor
Content/DevUI/ The RML/RCSS documents the plugin ships, demos and test fixtures
Resources/Icon128.png Icon shown in the editor plugin browser
Shaders/ The UI shaders, including the scene-colour backdrop path
Source/ThirdParty/RmlUi/ Vendored RmlUi 6.x — bit-exact upstream plus recorded patches
Source/ThirdParty/quickjs-ng/ Vendored QuickJS-ng, the JavaScript runtime
Source/VaCuusRml/ The vendored library, built as a module (Runtime, Default)
Source/VaCuus/ Runtime core: the UI thread, VFS, subsystem, views (Runtime, Default)
Source/VaCuusRender/ Record -> RHI replay -> Slate composite, UMG widget (Runtime, PostConfigInit)
Source/VaCuusJs/ The JavaScript host and the DOM facade (Runtime, Default)
Source/VaCuusEditor/ Live reload and the bundle factory (Editor, PostEngineInit)
Web/ Optional TypeScript/Preact workflow, source-only (npm-install it yourself)
docs/buyer/ The five pages above, plus localization.md
There is no Binaries/ directory in the package and that is Fab's rule for sellers of
UE-facing code: what we upload is source. The binaries a Fab install carries are Epic's
build of that source, and they land in the engine — a source install compiles them on
your machine instead.
Every RmlUi call happens on the UI thread; the game thread only enqueues commands and reads
a published interactive-region snapshot. Runtime code logs under the LogVaCuus category
declared in Source/VaCuus/Public/VaCuusDefines.h — that is the category to raise to
Verbose when something is quiet.
Both are vendored in-tree with their licenses beside them, and both are MIT:
- RmlUi 6.x —
Source/ThirdParty/RmlUi/LICENSE.txt. The document, layout and styling engine. Patches applied to it are recorded rather than silent; seeVENDORED_TAG.txt. - QuickJS-ng —
Source/ThirdParty/quickjs-ng/LICENSE. The JavaScript runtime, with its symbols deliberately kept out of the module's export table.
The plugin's own source is licensed under the Business Source License 1.1
(LICENSE.md): free to read, modify and use outside production, free in
production for noncommercial purposes, and each version converts to MIT four years after
release. Commercial production use needs a purchased license — on
Fab (under the Fab EULA) or directly from the author;
COMMERCIAL.md is the plain-language version. A copy bought on a store
is governed by that store's terms. Contributions require the CLA (CLA.md).
Issues and questions: https://github.com/ufna/VaCuus/issues.
Working on the plugin itself rather than with it? The host-project layout, the
two-working-trees hazard and the arrangements that do and do not remove the duplication are
in docs/dev/host-project.md, which is repo-only.