How Rubylight works #
Docs · Configuration · Performance · Developer guide
Rubylight turns a Windows display into a Moonlight stream. Its Radeon path keeps captured pixels on the GPU, prepares them on D3D12 compute queues and passes them to AMD's native encoder. The host limits outstanding work so a busy encoder or network does not become a growing queue of old pictures.
This guide follows a frame from capture to delivery, then covers the process boundaries, colour paths and recovery that keep the stream running. Performance separates measured host processing from the complete render-to-client journey.
Written in Rust #
The Windows host, Moonlight protocol implementation, native helpers, service and installer are written in Rust. The released executables do not link the old C++ host. The browser console uses Svelte; codec libraries, GPU SDKs and Windows drivers are external components.
| Component | Responsibility |
|---|---|
| Core | Pairing, encryption, RTSP/SDP, error correction, input parsing, permissions, configuration and durable state, and the encoder, pacing and display policies the host applies. Builds and is tested on any OS. |
| Windows integration | Capture, colour conversion, native encoders, audio, input, displays, frame limiting and Windows service integration. |
| Host | HTTP/TLS, Moonlight endpoints, the console API, control and media transport, scheduling and session lifecycle. |
| Setup | Installation, upgrades and package recovery. |
| Web console | Devices, games, settings, stream statistics, logs and maintenance. |
| TrueHDR runtime and Vulkan layer | Native HDR integration for their supported paths. |
The earlier C++ host was removed after 2.0.0-rc.23; that tag keeps its sources. The Rust build uses the workspace at the repository root.
The Radeon frame path #
For standard H.264, HEVC and AV1 streams on supported AMD GPUs:
| Stage | What happens | Source |
|---|---|---|
| Capture | Windows Graphics Capture (WGC) supplies a display texture. Automatic selection prefers WGC; Desktop Duplication (DDX) is an explicit choice and fallback. | Capture backends |
| Take ownership | A GPU copy moves the captured image into a texture the host owns. The capture frame stays alive until the copy finishes. Consumers share the latest published frame. | Frame publication, compute handoff |
| Prepare | D3D12 compute converts RGB into the encoder's YUV format, including resizing and HDR conversion where required. | Colour conversion |
| Encode | Native AMD AMF consumes the D3D12 surface and produces an H.264, HEVC or AV1 bitstream. | AMF GPU integration |
| Deliver | The host packetizes encoded bytes, adds forward error correction and applies negotiated encryption, then sends them to Moonlight. | Video sender, packet formats |
The raw picture stays in GPU textures through preparation and encoding; compressed output returns to the CPU for transport. Moonlight receives, decodes and presents it on the client. Those client stages and network transit are separate from host-processing time.
Before claiming another picture, the stream loop checks both the encoder and the conventional video sender. Two outstanding frames stop another claim; keyframes reserve additional sending capacity. Once work drains, the host takes the newest available picture. This bounds picture age under pressure without promising a frame rate that the hardware or link cannot sustain.
The reviewed Sunshine-derived D3D11 path and Rubylight both use GPU textures and native AMF. Rubylight changes where frame preparation runs: compute work can overlap graphics work instead of joining the same graphics queue. The diagram illustrates scheduling; measured timings come from separate fixtures. The reviewed baseline is Vibepollo 2.0.
Compute and graphics still share GPU resources. Copy/conversion efficiency helps the host's part of the frame journey; game rendering and desktop composition also determine when a fresh picture exists. The saturation investigation records that distinction.
Synchronization and ownership #
A captured texture can arrive before its producer has finished writing it. Rubylight waits for producer readiness on the GPU, copies it into an owned texture and holds the captured frame until that copy is safe. Owning the copy lets capture move on while the encoder still uses the previous picture. Each converted output texture has its own fence, so AMF's completion signal cannot release a different frame early.
Texture pools and native encoder queues are bounded. Texture ownership and per-frame HDR metadata survive until native codec references release them. Capture textures that cannot be shared use the D3D11 fallback. The compute implementation and synchronization measurements contain the details.
WGC through the Windows service #
The service starts a hidden capture worker as the signed-in user, giving WGC access to the user's Windows capture broker. Three shared GPU textures transfer frames to the host; a local pipe carries bounded metadata and checks the participating process identities. The worker belongs to the capture session and closes with it.
WGC startup failures select Desktop Duplication. The implementation also falls back for lock/UAC desktops and retries WGC on return to the normal desktop. While Windows is locked, the stream's display is set up, kept and restored from the lock screen's desktop, so you can stream the sign-in screen and unlock the PC remotely.
WGC requests an explicit zero minimum update interval where Windows supports it. Guarded source-phase pacing waits briefly for a predicted fresh update when capture history is stable and faster than the stream target. Irregular or slower sources use ordinary pacing. Capture settings expose the diagnostic overrides.
Native HDR and full-resolution colour #
The native HDR path captures FP16 scRGB, resizes in linear light and converts to ten-bit BT.2020/PQ. Shader math preserves absolute ST.2084 luminance. Windows HDR detection distinguishes an active HDR output from wide-gamut SDR colour management. Independent decoding and reference-pixel checks verify the tested pixel path.
PyroWave provides full 10-bit HDR 4:4:4 through a separate encoder and shared D3D11/Vulkan path. D3D12 compute prepares its three planar textures; a shared fence orders conversion and Vulkan access, and the encoded bitstream returns to the CPU. If compute conversion is unavailable, PyroWave uses graphics-queue conversion. The PyroWave integration owns this handoff independently of AMF.
Full-resolution chroma gives each pixel its own colour samples. Use Rubylight Android, our own client, or on a PC Nonary's compatible Moonlight client, over a fast local network; client selection explains the options.
Encoding beyond Radeon #
The code also includes native NVIDIA NVENC, Intel Quick Sync imports and software compatibility paths. Native NVENC supports capability-gated reference recovery and GPU-only CUDA interop for ten-bit 4:4:4; Quick Sync imports D3D11 frames. Rubylight's tuning and measurements are for AMD hardware.
Session, audio and input #
Pairing, app discovery and launch requests enter through the Moonlight HTTP endpoints. An authorized launch lets the RTSP server negotiate stream settings; the stream host then manages display preparation, capture, encoding and media workers. The browser console has a separate administration API.
Audio uses WASAPI loopback capture and Opus encoding on its own worker. Client input follows the control path back to Windows. Virtual controllers have a separate worker, so a blocking controller-driver call does not hold up keyboard, mouse or touch input. Control messages and microphone transport describe Rubylight's client extensions.
Recovery and updates #
An encoder that returns no frame is recreated with the same encoder settings after 250 ms, then 500 ms, 1 s and 2 s; the session ends after 20 s without output. After a second encoder failure in one session, colour conversion moves to the graphics queue for the rest of it. A session that hears nothing from its client for the ping timeout (10 s by default) ends as a disconnect does.
A GPU device loss triggers a broader rebuild: capture consumers first release their encoders, images and filters, then capture and encoding restart together. This has a separate 30-second recovery deadline; repeated failures do not extend it. If the GPU stays unavailable, the stream ends while the host console remains available.
Display recovery journals host-owned changes and preserves later user changes. Virtual-display startup uses a short guard for unexpectedly reactivated displays, ending after startup or an observed competing layout change.
Updates notify first. The installed service verifies the official installer, waits for an idle host (or, on Install now, ends the stream) and upgrades with package recovery for copying or startup failures. See configuration for the update controls and getting started for installation and migration.