Note
Bug reports and pull requests are welcome, but please understand that development happens in my free time and progress may be slow at times. The project is still maintained even if the last commit was made a while ago.
My AMD Adrenaline / NVIDIA Settings Linux Alternative
volt-gui is a graphical control panel for Vulkan games on Linux. Settings are applied by volt, a Vulkan implicit layer written in Rust, so they work on every Vulkan driver: RADV, ANV, NVK, AMDVLK, the NVIDIA proprietary driver.
The floor is Vulkan 1.0, and so is the ceiling: the layer requests no
extension beyond VK_KHR_swapchain, so every setting behaves the same on
every conformant driver. VOLT_LOG=info shows what was applied.
- What you can do?
- How It Works
- Requirements
- Installation
- Building Releases
- Usage
- Flatpak
- Profiles & Presets
- What volt will never do
- Contributing
Every setting defaults to default, meaning the layer does not touch that value and the game keeps its own choice. A profile with everything on default does nothing.
Every setting is one value: the value volt forces, or default. There is no
range, no ordering, and nothing to get backwards.
The values each setting offers come from your own hardware, not from a list
built into volt-gui. Present modes, colour depths, colour spaces, transfer
functions and alpha modes come from what the surface reports, the GPU list
from what the driver enumerates, and mip levels and LOD bias run up to the
limits the device gives. A setting whose feature the device lacks holds
nothing but default. Only the three Framerate settings have a fixed
list, since they are volt's own.
Settings are read once, when the game starts, and never change while it runs. Press Apply, then start the game again.
volt-gui learns this by keeping a small vkgears window running under the
profile you are editing, which also serves as a live preview. Close it and
volt-gui carries on with what it learned last time.
volt --probe myprofile -- vkgears
A value volt has no name for still appears in the list, still saves to a profile, and still applies. Where the specification admits only what a query returned, a value the device did not report is not forced: volt keeps the game's own value and logs a warning.
Where the specification bounds a value — LOD bias against the device limit, image count against what the surface allows — volt clamps what it passes down. That clamp is correctness, not a setting.
- Physical Device: pick which GPU the game sees, listed by name. The layer hides the rest during device enumeration, so it works on every Vulkan driver. If nothing matches, the full list comes back and a warning is logged.
-
VSync / Present Mode: whatever the surface supports.
immediateturns vsync off,mailboxis low latency vsync,fifois classic vsync,fifo_relaxedtears only below refresh. Every other mode is hidden from the list the game is shown, so a game's own vsync menu cannot offer one you ruled out. That filtering is what makes the setting hold wherever the game asks Vulkan what it may use: a swapchain may only switch between modes the surface reported, and a present may only name one the swapchain was built with. A mode the surface does not support falls back to the game's own choice with a warning.A game that enables
VK_KHR_swapchain_maintenance1can switch present mode without rebuilding the swapchain. It can only switch among the modes the surface offered it, which volt has already filtered, so the setting still holds. -
Swapchain Images: how many images the swapchain holds. Fewer images lower display latency, more images smooth frame delivery. The list is what the surface allows, and the choice is reported back to the game, so a game that derives its count from the surface honours it on its own.
-
Color Depth: the bits per colour channel this surface offers, usually 8-bit and 10-bit. The layer hides the formats you did not pick, so a game that takes the first supported format ends up with yours. If nothing matches, the full list comes back and a warning is logged.
-
Color Space: filtered out of the same list. Everything past
srgb_nonlinearneeds the stack around the game to have enabled it, through DXVK_HDR, PROTON_ENABLE_HDR or gamescope, so on most setups this card holds one entry. -
Transfer Function: whether the game is shown
srgbformats, plainunormones, or float ones, filtered out of the same list again. Getting it wrong looks washed out or crushed rather than broken, so set it back to default if the image looks off. No preset touches it. -
Composite Alpha: how the compositor treats the finished image's alpha. Forcing
opaqueskips compositor blending on Wayland. A value the surface does not report falls back to the game's own choice with a warning. -
Clipped Presentation: whether the driver may skip pixels another window covers. On or off.
- Texture Filtering: retro (sharp pixels), bilinear, trilinear. A sampler that matches none of the three exactly counts as the closest one below it.
- Mipmap Mode: a hard cut between mip levels, or a blend across them, independently of the filter choice.
- Anisotropic Filtering: off up to whatever your GPU reports. Needs the
samplerAnisotropyfeature; volt never enables one, so where a game left it off this card holds nothing butdefault. Nearly every game enables it. - LOD Bias: shift mipmap selection, sharper or blurrier, across the range the device allows.
- Mip Floor and Mip Ceiling: the lowest and highest mip levels samplers may use, called minimum and maximum LOD in Vulkan.
- Sample Shading: shade at sample rate inside MSAA targets to reduce
shimmer. Needs the
sampleRateShadingfeature, which most deferred renderers never enable, so expect this card to be empty more often than not. - Alpha To Coverage: turn fragment alpha into coverage. Softens cutout edges on foliage and fences, and only does anything where the game already renders to an MSAA target.
- Frame Limit: cap the frame rate at present time, from a fixed list of common caps. Deadlines follow a fixed target timeline rather than the last present, unless the method is reactive, so scheduler jitter does not build up into a drift below the rate you asked for.
- Frame Limit Method: early holds the frame back so presents leave on a fixed cadence; late lets the present through and waits afterwards, so the game starts its next frame later and samples input closer to display time; reactive waits where early does but measures from the frame just shown instead of a fixed timeline, so a slow frame is never chased with a fast one.
- Frame Pacing: how the limiter kills time, from cheapest to tightest.
sleephands the whole wait to the kernel.slicedsleeps in short steps and rechecks the clock, correcting for the kernel waking late.precisesleeps most of the interval then busy waits half a millisecond.spinbusy waits the whole interval, the steadiest option and the only one that keeps a core awake.
volt registers as an implicit Vulkan layer, gated by VOLT_ENABLE=1 which the
volt launcher sets on the target process only. The layer reads the selected
profile from ~/.config/volt-gui/<profile>.toml once at startup and rewrites
the Vulkan calls the game makes, and the tabs run in the order the layer acts:
device enumeration for GPU selection, the surface queries and swapchain
creation for the display settings, vkCreateSampler for texture settings,
vkCreateGraphicsPipelines for the rendering toggles, and vkQueuePresentKHR
for the frame limiter. Device creation is read and never modified: volt learns
which features the game enabled so a setting that needs one applies only where
the game asked for it, and it enables nothing the game left off.
Every setting is hooked on each path that reaches it, not just the obvious
one. A game that queries formats through
vkGetPhysicalDeviceSurfaceFormats2KHR, enumerates through
vkEnumeratePhysicalDeviceGroups, creates samplers inline through
vkWriteSamplerDescriptorsEXT or sets alpha to coverage as dynamic state gets
the same treatment as one that takes the core path.
Settings are frozen for the life of the process. Press Apply, then start the game again.
volt-gui is the PySide6 front end. It edits the same profile files the layer reads Apply just saves the profile, no elevated permissions, no scripts.
| Component | Requirement |
|---|---|
| Layer | Vulkan 1.0+ with VK_KHR_swapchain, Linux x86_64 (optionally i686 for 32-bit games) |
| Build | Rust 1.85.1+ with rustup, GNU make 4.3+ |
| GUI | Python 3.10+, PySide6 (a venv is created under build/ by the make targets) |
| Flatpak bundles | flatpak, ostree |
| Container release | podman or docker |
| Preview | vkgears from mesa-demos, optional: without it the option lists fall back to defaults |
Every build target is a file, so make only rebuilds what actually changed.
Everything lands under build/; make clean is a single rm -rf.
| Command | What it builds |
|---|---|
make |
64-bit layer, 32-bit layer, the volt launcher, and the GUI |
make layer-64 |
build/target/x86_64-unknown-linux-gnu/release/{libvolt.so,volt} |
make layer-32 |
build/target/i686-unknown-linux-gnu/release/libvolt.so |
make gui-pyinstaller |
build/bin/volt-gui-pyinstaller |
make gui-nuitka |
build/bin/volt-gui-nuitka |
make flatpak |
build/bundles/*.flatpak |
make release |
the full release matrix in releases/, host toolchain |
make release-container |
the same, built inside a pinned container image |
sudo make install |
launcher, GUI, both layers and the manifest |
sudo make flatpak-install |
the built extension bundles, for the invoking user |
sudo make uninstall |
everything, including the user Flatpak extension and ~/.config/volt-gui |
make clean |
rm -rf build releases |
git clone https://github.com/pythonlover02/volt-gui.git
cd volt-gui
make
sudo make install
make picks PyInstaller for the GUI. For Nuitka, set GUI on both halves so
install picks up the binary you built:
make GUI=nuitka
sudo make install GUI=nuitka
Building with sudo is refused: the build targets stop with an error rather
than leaving a root-owned build/. Build as your user, install as root.
Packagers can skip root entirely — with DESTDIR set, make install stages
into the given prefix without needing it:
make
make install DESTDIR="$PWD/pkg" PREFIX=/usr
Both release targets produce the same four files in releases/:
volt-gui-<version>-pyinstaller-x86_64.AppImage
volt-gui-<version>-nuitka-x86_64.AppImage
volt-gui-<version>-pyinstaller.tar.gz
volt-gui-<version>-nuitka.tar.gz
Each archive is a ready-to-install tree: the compiled layers sit at the paths
the Makefile expects, so unpacking and running sudo make install installs
without compiling anything. The sources, the Flatpak bundles and the Makefile
ride along. The Nuitka archive installs with sudo make install GUI=nuitka.
make release builds against your own toolchain and glibc. Fast, and
right for a local build, but the binaries inherit your system's glibc floor.
make release-container builds everything inside rust:1.82-bookworm
(Debian 12, glibc 2.36, Python 3.11) from container/Containerfile, so the
floor is fixed regardless of what you run. It builds into build/container/,
so it never collides with your host build and neither invalidates the other.
The container runs as your own uid, so nothing it writes is root-owned.
Override the base if you want a different floor:
make release-container CONTAINER_BASE=rust:1.82-bullseye
make release-container CONTAINER=docker
Bullseye drops the floor to glibc 2.31 but ships Python 3.9, which is below
what the GUI needs — use it only for make layer-64 layer-32 inside the
container, not for a full release.
Prepend the launcher to your game command:
volt -- %command% # Steam, default profile
volt myprofile -- %command% # Steam, named profile
volt -- ./game # terminal
volt -- flatpak run com.example.Game
The launch command for the selected profile is always shown next to the Apply button in volt-gui, ready to copy.
Flatpak games run sandboxed, so the layer ships as a Flatpak runtime
extension for org.freedesktop.Platform 23.08, 24.08 and 25.08. Build with
make flatpak, which writes to build/bundles/, then install with
sudo make flatpak-install (or grab the bundles from a release archive). The
volt launcher detects flatpak run commands and routes activation through
the in-sandbox wrapper automatically.
There is no Flatpak build of volt-gui itself, only the layer extension.
Profiles are TOML files in ~/.config/volt-gui/, one per configuration.
Create and switch them from the GUI or the system tray; select one at launch
with volt <name> -- ....
Presets populate the active profile with curated values, from Quality (trilinear, 16x anisotropy, blended mips, 10 bit colour, classic vsync) down to Potato Low Latency (bilinear, anisotropy off, hard mip cuts, immediate present, 2 image swapchain). A preset writes every value in the profile, so anything it does not set goes back to default. That includes the frame limit and the colour space, transfer function, composite alpha and clipped settings: those depend on your display and your compositor, so those choices stay yours.
A preset can name something your hardware does not offer. That setting resets to default and volt-gui says which ones, so the rest of the preset still lands.
volt changes what the game asks Vulkan for; it never draws. Anything that requires injecting shaders or processing the image is out of scope:
- Sharpening, FSR or any upscaling, frame generation, post processing.
- Forced MSAA or SSAA. Render passes and sample counts belong to the game.
- Overlays and HUDs. Use MangoHud for that.
- Overclocking, fan curves, power limits. That is sysfs territory, not the Vulkan API. Use CoreCtrl for that.
- OpenGL. The per driver environment variable maze that OpenGL support requires is exactly what this rewrite retired.
- Enable a Vulkan device feature or extension the game did not request. Where a setting needs a feature, volt reads what the game enabled and applies the setting only where the game enabled it.
- Require a Vulkan extension. Core 1.0 and
VK_KHR_swapchainare the whole surface volt asks for, so behaviour never splits between drivers. volt may intercept an extension command the game itself calls, so a setting keeps applying where the game moved that state elsewhere; a hook for an extension the game never enabled is unreachable and its entry point is never handed out. - Change a setting under a running game. The profile is read once at startup.
- Write into memory the game owns. volt patches the structures it passes on
and fills the arrays a query asks it to fill; a
pNextchain the game built is read and never written. A setting reaches the game through the lists volt hands it, not by reaching into what the game already built.
Contributions are welcome. The layer is plain Rust with no build scripts; the GUI is PySide6 only. Please keep changes working on core Vulkan 1.0 with no extensions that floor is the point of the project.


