Rubylight 2.2.10 for Windows #
Documentation · Install and migrate · Configuration · Features
Release history: 2.2.10 · 2.2.9 · 2.2.8 · 2.2.7 · 2.2.6 · 2.2.5 · 2.2.4 · 2.2.3 · 2.2.2 · 2.2.1 · 2.2.0 · 2.1.0 · 2.0.1 · 2.0.0 · rc.30 · rc.29 · rc.28 · rc.27 · rc.26 · rc.25 · rc.24 · rc.23 · rc.22 · rc.21 · rc.20 · rc.19 · rc.18 · rc.17 · rc.16 · rc.15 · rc.14 · rc.13 · rc.12 · rc.11 · rc.10 · rc.9 · rc.8 · rc.7 · rc.6 · rc.5 · rc.4 · rc.3 · rc.2
Rubylight, called Butterpollo until rc.29, is written in Rust: host, native helpers, service and setup, with a Svelte web console. The 2.2.10 installer is named rubylight-setup-2.2.10.exe and upgrades an existing Vibepollo or Butterpollo installation in place, keeping settings, paired devices, the app library and covers. Codec SDKs and Windows drivers remain external components; the setup installs the drivers.
New in 2.2.10 #
- Headless encoder startup (#18). Encoder probing skips Microsoft Basic Render Driver even when Windows omits its software-adapter flag. When the desktop uses software rendering, the probe looks for a hardware GPU before the stream creates its virtual display. The configured GPU choice is respected, and capture still opens its selected display.
- Consistent GPU detection. The GPU list and monitorless device selection use the same check, so Basic Render Driver is no longer offered or selected as a hardware GPU.
The regression test reproduces the unflagged-adapter failure and passes with this fix. A native RX 7900 XT check encoded H.264, HEVC and AV1 using a device without a display output. Full reboot and RDP-disconnect recovery on the affected headless hosts still needs confirmation.
New in 2.2.9 #
- Faster input and feedback after reconnects or stalls (#24). Reusing a capture re-applies the configured GPU scheduling priority. Control immediately continues when its 512-event pass fills, and gamepad feedback and changed HDR metadata wake it directly.
- More current pictures on startup and recovery. A static capture helper asks for its first repaint after 33 ms instead of 250 ms. Encoder rebuilds fetch the newest compatible picture, and the last millisecond before a frame claim uses the existing precise wait.
- Keep capture responsive under CPU contention. The WGC helper now uses HIGH process priority. On the RX 7900 XT test host with competing highest-priority game-like threads, it sustained 59.92–60.00 fresh FPS versus 40.45–41.37 at NORMAL. A continuous-stream idle comparison differed by −0.003 ms in mean picture age; this is a controlled fixture result, not a promise for every game.
- Reduce startup and background delays. Client last-seen persistence runs in the background. Active display leases check every 100 ms while idle leases retain 250 ms checks. Display-watcher shutdown waits are bounded outside the journal lock. Audio waits on its actual silence deadline between capture events, and discarded capture images release their resources outside the publication lock.
- Recovery diagnostics. Debug logs record AMF initialization/termination and capture-helper connection/readiness times; a negotiated display mode that requires preparation again is logged explicitly.
The evaluation and validation record distinguishes implemented fixes from experiments that still need measurements. Wi-Fi pacing remains at 2×; in-flight delta cancellation, microphone backlog draining, capture notification changes and encoder queue-depth changes are deferred. The issue's estimated latency gains are not measured release claims.
New in 2.2.8 #
- Single-pass 4:2:0 GPU conversion. The D3D11 fallback converts NV12/P010 video and PyroWave 4:2:0 with one compute dispatch, reusing the existing shaders. Unsupported devices retain the pixel-pass fallback. The separate D3D12 compute path and PyroWave 4:4:4 remain unchanged.
- Measured and checked on the RX 7900 XT. At 4K while otherwise idle, mean conversion GPU time fell from 0.235 to 0.123 ms for NV12 and from 0.307 to 0.124 ms for PyroWave HDR 4:2:0. Exact pixel comparisons covered 960 cases, and real HEVC/PyroWave streams covered SDR and HDR. These are conversion measurements, not an end-to-end latency claim; the evidence and limits include the loaded results and stream checks.
The pacing experiments from #23 did not meet the latency/loss checks, so packet pacing retains its previous behavior. The work log records why.
New in 2.2.7 #
- Lower idle CPU and memory use (#23). Audio/video ping workers wait for packets; control waits for input or its next feedback, key-repeat or mouse-release deadline. Inactive telemetry and stall monitoring park, the service waits for events, and idle display leases check less often while recovery retains its fast retries. Helpers dispatch before the Tokio runtime starts, completed display watchers exit, and host/service log queues hold 8192 lines.
- Less work before encrypted video leaves the host. Video AES-GCM uses
ringwith identical ciphertext and tags. Conventional video sends each FEC block as it is built and reuses packet buffers. Packet numbering, encryption nonces and loss recovery remain covered by regression tests. - Release GPU resources when their work finishes. Cursor uploads no longer wait on the CPU; upload resources and captured images remain alive through their GPU fences. Capture and PyroWave owners release cached texture references, copy reuse waits for that texture's readers, and the last stream restores prior process power and GPU scheduling settings. The graphics fallback converts PyroWave 4:4:4 planes in one draw.
- Cheaper background queries. Startup skips unsupported AMF 4:4:4 probes on confirmed AMD-only systems. Server information caches hardware/network discovery for ten seconds while keeping session and authorization fields live; mDNS checks addresses every thirty seconds. Streams rebuild effective settings when their revision changes, selected ICC calibration still refreshes, update metadata retains only consumed fields, and update-panel polling pauses in hidden tabs.
The performance work log records validation and remaining experiments. Pacing waits and the four capture-helper slots retain their existing behavior. Persistent codec-probe caching, a single-pass 4:2:0 graphics fallback and new resource telemetry remain follow-up work; this release does not claim measured end-to-end latency or idle-power gains for them.
New in 2.2.6 #
- Upgrades from older profiles work without the ownership error (#20). Verified in-place service upgrades repair legacy user ownership and profile permissions while preserving settings, paired devices and credentials. Parent-folder ownership and reparse-point checks remain in place. See installer troubleshooting for the repair's scope.
- Separate profiles for copied portable packages (#17). An existing
configfolder beside the executable takes precedence over the per-user default. Explicit--config-dirpaths and the installed service's profile keep their precedence. Each instance needs its own base port; multiple instances shows the commands and multiseat session requirements. - Native touch and pen corrections. Touch transitions are retained when a client sends unknown or non-finite pressure, while invalid coordinates are still rejected. Touch and pen coordinates account for the virtual desktop origin, including monitors above or to the left of the primary display.
- Mouse clicks stay balanced when input modes change. Switching from absolute to relative mouse input releases any deferred left click before moving or pressing again, preventing a retained left-button press.
- Keyboard compatibility. Media and browser keys use the correct extended scan codes, and Print Screen works with clients that mark it extended. A remapped key stays held until all source keys are released. Keyboard repeat settings that would overflow the timer are rejected.
- Idle microphone handling no longer busy-spins (#22). The microphone receiver waits for incoming packets with a bounded timeout instead of repeatedly polling an idle socket.
- Optional hardware readings for client overlays (#16). Enable Settings → General → Hardware metrics → Send hardware metrics to client to let compatible clients request host CPU usage and available AMD GPU readings over their paired HTTPS connection. Collection is off by default and only happens on request during that client's video stream. Select readings and a sampling interval; GPU readings require ADLX support. The client protocol documents discovery, authentication, fields and errors.
Regression coverage includes profile selection, input handling, idle microphone waits and hardware-metrics access policy. A two-host startup smoke test confirmed separate profiles, identities, certificates and ports, with clean shutdowns. Elevated ownership repair, simultaneous multiseat streaming and native input behavior on the reporting clients still need confirmation. Live latency and smoothness were not benchmarked for this release.
New in 2.2.5 #
- Changing the virtual display layout in host, device or app settings now takes effect when reconnecting to a paused stream. Explicit layouts take precedence over the legacy app primary-display flag, and resuming reapplies the selected layout before capture starts. This addresses reproducible reconnect and selection failures related to issue #19.
- Startup codec probing follows the active Windows desktop and keeps retrying before sign-in. Headless display enumeration handles an empty topology, and encoder/display errors identify the selected adapter and possible console or RDP session problems (issue #18).
- The WGC capture helper retains the latest unsent update while its shared slots are busy, so the final update still arrives if the desktop becomes static.
- Paired clients can read the applied and initial encoder bitrate budgets. Rubylight Android 0.4.3 uses these values to initialize automatic bitrate without an immediate bitrate increase.
Regression tests cover the corrected paths. Headless reboot/RDP recovery and the display-layout behavior still need confirmation on the reporting users' machines. Live latency and smoothness were not benchmarked for this release.
New in 2.2.4 #
- Correct WGC picture selection for VRR streams when Windows publishes extra compositions between source updates. Source-phase tracking now operates independently of generic prediction, tolerates capture jitter, and waits for the expected update before an old composition consumes its frame slot. Additional compositions no longer dilute a learned phase or count as separate missed source updates.
- Bound the wait from the previous frame delivery so an early new picture is not held for another composition. Slow and irregular sources retain ordinary arrival pacing.
New in 2.2.3 #
- Capture recovery tries reopening immediately after releasing the old device. The video sender sleeps while its queue is full and waits to claim the next picture until a queued keyframe has left.
- AMF coalesces delayed reference-invalidation feedback already covered by an IDR or long-term-reference repair. Minimum-frame-rate repeats give imminent game frames room to arrive with Desktop Duplication, VRR and slower source rates. Pacing warnings include packet headers, encryption, wire overhead and FEC rounding.
- Saving configuration no longer holds the input thread's configuration lock during disk writes. Gamepad teardown and host command creation run off the control thread, held inputs are released after a second of control silence, and audio retains its Windows multimedia scheduling priority.
- Paused streams no longer reapply their exclusive display layout over local display changes. Disconnect clients releases retained displays and audio; an unused app audio route is restored. Saved display baselines are now opt-in for configurations without an explicit choice.
- Launches fail before changing displays when no encoder is available, report underlying causes and missing executables, and keep tracking slow Steam starts. Application cleanup is serialized with launches. Discovery no longer waits for the encoder probe, RTSP negotiation failures return an error status, and routine stream-state tray balloons are suppressed.
- JPEG covers are accepted, and virtual-display capabilities reflect host configuration. Existing content-based artwork cache invalidation remains in place.
- Console drafts survive canceled Back/Forward, link and pairing navigation. The console recovers after an unavailable initial session check; pairing-disabled and automatic-install status are accurate; installing an update asks before ending streams. Encoder guidance, fallback-language labeling, pairing feedback and setup/FEC/frame-rate wording are corrected.
The review's larger capture-transition, pointer-phase, Android phase-lock, in-place virtual-display resize and cursor-upload changes remain unverified and are not included. AV1's one-second recovery confirmation keyframe remains as a decoder compatibility safeguard. Paused games keep their virtual display; automatic physical-topology restoration while retaining it is unchanged. A failed explicitly requested virtual display is still reported instead of silently switching to a physical monitor.
New in 2.2.2 #
- Safer installation and network input. Setup replaces the permissions and owner of service folders so a previously owning user cannot change the SYSTEM service's files. Router descriptions have size and nesting limits. Device names and launch values are checked before use in preparation commands, Playnite cover IDs must be UUIDs, and the fallback console escapes dynamic labels.
- Streaming fixes. Sub-1-fps requests are rejected without stopping the host. Adaptive error correction now receives frame reports when video uses its separate send thread, including wireless routes. Refused resolution changes no longer leave a redundant rebuild queued, oversized frames keep the recovery counter in sync, and unknown
hostDisplayvalues leave the host in control. Stopping a stream during display setup no longer retains its display, and shutdown waits for stream restoration. - Keep your sound and settings. A Steam microphone you already chose as the recording default stays selected after streaming. Audio restoration handles Steam's playback endpoints and late driver installation. A sign-in changed with
--credssurvives paired-device saves and takes effect without restarting. Configuration imports and saves preserve unknown preparation-command fields and recover valid entries around malformed records. - More useful diagnostics. The default Information level includes encoder settings, display setup and the 5-second stream timings. Debug adds discovery, GPU scheduling and probe details, and changing the level applies to library logs too. Launch and resume parameters are logged without stream secrets or injected line breaks, slow sends identify where they waited, and recurring UPnP conflicts are reported once.
- Consistent releases and downloads. Releases use
v-prefixed tags with matching download links and package versions. This release carries only the Rubylight installer and portable ZIP; hosts on 2.0.1 or older should update to 2.1.0 first. The settings console identifies unsupported packet sizes and update intervals below one hour.
New in 2.2.1 #
- Your sound devices come back after a stream. The first time a phone sends its microphone, the host installs Steam's microphone driver, and Windows can make its speaker side, Lautsprecher (Steam Streaming Microphone), a default playback device. The host now treats Steam Streaming Speakers and both sides of Steam Streaming Microphone as its own devices: they are never remembered as your choice, and at the end of a stream every playback and recording role that still points at the stream's device or a Steam endpoint returns to your own device, while a role you changed yourself is left as you set it. A PC whose communications device was left on the microphone's speakers by 2.2.0 is put right after its next stream. The log says
audio default restoredfor each role it switched back andaudio defaults after the streamonce per stream. The rules have unit tests. - Choose the PC's display from the phone. A client can now pick, for each stream, how the PC shows it: only the virtual display, the virtual display added beside your monitors as the main display or next to the main display, or your physical display without a virtual one. Rubylight Android has the choice under This PC > Display on the PC. The client sends it as
hostDisplayon launch and resume, and it takes precedence over the device, app and global layout for that stream; without it the host's own settings decide, as before. Your previous display layout comes back when the stream ends. Configuration lists the values. The parsing and precedence have unit tests. - HDR highlights stay bright on phones that report a low peak. When a client reports a peak brightness below the host display's, the host keeps the display's HDR metadata, because the game renders for that display. Phones such as the Galaxy Z Fold 7 report Android's 400-nit content hint here rather than their panel's peak.
New in 2.2.0 #
- Frames arrive just before the phone's screen refreshes. A Rubylight client can report, about every half second, how early each frame is ready before its display refreshes (control message 0x5530). The host then times its captures to the client's exact refresh rate, at a phase that gets each frame there with a small margin before the refresh, instead of letting two independent clocks drift: without it a 120 Hz phone waits anywhere from nothing to a whole refresh (8.3 ms) for its screen, and the wait swings from minute to minute. A 60 fps stream to a 120 Hz screen uses two refreshes per frame, and a frame rate that is no whole fraction of the screen's keeps the host's own pacing. Clients that never report, and VRR streams, are paced exactly as before, and after 3 s without a report the host returns to its own pacing. Unit tests check the lock and that pacing without reports is identical to 2.1.0; the host log says
phase lock appliedabout every ten seconds while it is active. - Error correction follows the network. Moonlight clients can report each frame they had to repair with error correction or could not rebuild; until now the host only counted those reports. Now it uses them: a repaired frame raises the share of recovery packets at once, a frame that could not be rebuilt puts it straight back to the configured Error correction percentage (
fec_percentage, 20 % by default), and after 5 seconds without either it falls by one point every 2 seconds, down to 5 %. The configured percentage is never exceeded, the client's minimum number of recovery packets per block still applies, and a client that sends no reports keeps the configured percentage for the whole stream. The encoder bitrate is not changed. Adapt error correction (adaptive_fec, on by default, also per device or app) turns it off, and the stream is then sent exactly as in 2.1.0. The log saysadaptive fecwith the old and new percentage, at most once a second, and the session API shows the currentfec_percent. The rules are checked by unit tests: no reports, loss, a clean link, turned off, and the limits. - HDR metadata for the client's own screen. A client can now tell the host its screen's peak, frame-average and black luminance (control message 0x5531). In an HDR stream the host then describes that screen in the HDR metadata it sends the client and writes into the video: the peak as mastering display maximum and MaxCLL, the frame-average as MaxFALL and the black level as mastering display minimum, so the client tone-maps for its real panel. An HDR profile selected for the device on the host, or a peak brightness set for the device or app, takes precedence. When the screen changes during a stream, for example on a foldable, the HDR message is sent once more, and only if the values changed; clients that never send 0x5531, such as Moonlight for Xbox, get the single message they get today. The log says
display capswith the values and what was applied. The game still renders for the host display's peak, which a virtual display gets before the client connects. Checked by unit tests. - The host lists the control messages it takes. The RTSP
DESCRIBEanswer now carriesa=x-rl-control:0x5530,0x5531,0x5532, so a client sends Rubylight's own control messages only to a host that takes them. Control messages describes them for client authors. - The stream follows the client's screen. A Rubylight client can ask for another resolution or frame rate during a stream (control message
0x5532), for example when a foldable phone opens its inner screen, and the host switches without reconnecting. It applies the latest request once the client has stopped asking for 200 ms, at most once a second, recreates the encoder at the new size and sends a keyframe at that size; pacing and the phase lock follow the new rate. The PC's display keeps the mode it got at the start of the stream, and the picture is scaled to the new size between black bars where the shapes differ. A request for the current mode is ignored, an encoder that cannot make the new size keeps the old one, and PyroWave streams keep their mode.stream_reconfigure(default on) turns it off for the host, a device or an app; clients that never send the message see no change. Control messages describes it for client authors. The request handling, coalescing and rate calculation have unit tests.
New in 2.1.0 #
- Your microphone on the PC. A client that sends its microphone in the format of Apollo's microphone passthrough is now heard on the PC as Microphone (Steam Streaming Microphone), a recording device Steam provides; Sunshine and Vibepollo have no microphone. Rubylight installs Steam's microphone driver the first time a device sends one (Steam must be installed) and puts your default audio devices back. It is on by default (
stream_mic) and can be turned off per device or app; it uses UDP port 48001, next to the other stream ports. Lost packets are concealed or repaired by Opus, and at most 60 ms are queued, so the microphone does not lag behind. The packet format matches moonlight-common-c byte for byte, checked against its own test vectors, and Microphone describes the protocol for client authors. Checked through the real RTSP and microphone socket path and with a host-side probe on a PC. - Choose the install folder, and install or update while you stream. The setup window has Change folder... for a new installation; an installed Rubylight is updated in its own folder. A folder that already holds other files gets a Rubylight folder inside it, network and removable drives are refused, and a folder outside Program Files is locked to SYSTEM and Administrators because the service runs from it. Setup no longer refuses while Rubylight streams: it says the stream will end and games started from Moonlight close, and Install goes ahead; Maintenance > Updates > Install now does the same from the console, so you can update from another room and connect again. Install when idle and automatic updates still wait for an idle host, and a quiet setup still refuses unless it gets
--end-streams, so scripts never end a stream by accident. On the RX 7900 XT host an update during a stream took 16 seconds from starting setup to the host answering with all its codecs, and settings, paired devices and apps were unchanged. Setup stops and starts the service once per update, so a client reconnecting right after an update stays connected, and a reinstall over an installed Rubylight checks for a stream before it touches the service. - PyroWave SDK 1.0. PyroWave moves to the SDK version upstream froze as 1.0, which brings a fix for an out-of-bounds read in its analysis shader. The stream format is unchanged, so Rubylight Android and Vibepollo 2.0 clients decode it as before. The encoder now asks Vulkan for a high-priority queue, like the copy and conversion queue. On the RX 7900 XT at 1968x2184 HDR 4:4:4 120 fps, picture age was 18.7 ms on average before and after (95th percentile 27.8 and 27.2 ms), PyroWave encoding took 1.05 instead of 1.16 ms, and every frame decoded in all runs (0 decode errors). At 1080p120 SDR it was 14.8 against 14.7 ms. Runs beside a game-like GPU load matched the old build, and the GPU watchdog stayed quiet.
- Steam covers for newer games. Newer Steam apps keep their portrait cover under a content-hash folder, and Steam's CDN publishes the large image only under that hash. Rubylight looked for the fixed path, got a 404 and fell back to the small landscape header, which came out cropped. It now finds the portrait cover in the hashed folders and retries the hashed download address.
- A stray mDNS packet can't stop the host. The mDNS library used for discovery could index past the end of a malformed record, and with the host built to abort on a panic, one 32-byte multicast packet from any device on the LAN took the whole service down in a local reproduction. Rubylight now uses mdns-sd 0.21.5, which drops such records and keeps announcing; it also keeps its timers on a monotonic clock and expires cached records across system sleep.
- One product, one download. Update checks accept the Rubylight asset names (
rubylight-setup-<version>.exe,rubylight-<version>-windows-x64.zip). The portable launcher is now Start Rubylight.exe; setup and in-app updates remove the old Start Butterpollo.exe and refresh the Start menu entry. Hosts up to 2.0.1 only offer an update that carries abutterpollo-setupfile, so 2.1.0 is the last release that also publishes that copy; from the next release only the Rubylight names are published, and a host on 2.1.0 or later finds them. If your host is on 2.0.1 or older, update to 2.1.0 once and updates continue from there. - Documentation rewritten. The README and docs now say what Rubylight does, which hardware it has been tested on (including the owner's Kubernetes pods with several GPU configurations and the secure desktop) and its known issues, with the Wi-Fi results from a real client.
New in 2.0.1 #
-
HDR works on Moonlight for Xbox. At the start of every stream the host told the client about HDR twice: first with placeholder values as soon as the display was picked, then with the display's real ones. Moonlight for Xbox sets the TV's HDMI mode on each such message, so it switched the TV twice in a row, and the picture came out washed out and too bright with no HDR badge on the TV (#11). The host now sends that message once, after the encoder has the display's values, as Sunshine does, and again only if they change. Checked on an Xbox streaming 3840×2160 at 120 Hz in HEVC HDR: HDR now works. The test client can now require an exact number of these messages.
-
AV1 recovery without a keyframe is now on for upgraded hosts too. 2.0.0 turned it on by default (
amd_ltr_frames4), but a host whose settings had been saved by an earlier console could carry the old default,amd_ltr_frames = 0, which kept it off. On the first start of 2.0.1, a saved 0 is taken for that old default and removed fromsunshine.conf, so the new default applies; the log says so. Setting it to 0 again turns the recovery off for good. Found on the test host, where the upgraded settings kept a 0 from October 8 and a real AV1 stream logged no long-term reference recovery. Today's console saves only the settings you change, never defaults. -
One copy less per frame on the service host. Windows Graphics Capture runs in a helper in your user session, which hands each frame over in a shared texture; the host copied that into a texture of its own before converting it for the encoder. On AMD the conversion now reads the helper's texture directly, and the helper gets it back once the conversion is done. On the RX 7900 XT host at 1440p120 AV1, picture age fell from 10.07 to 9.84 ms and host time from 2.84 to 2.77 ms on average over three pairs of runs, with every picture complete and decoded; reconnecting, killing the client and a 2-minute stream were clean.
wgc_helper_zero_copy = falsebrings the copy back. -
No more "amd_ltr_frames applies to AV1 only" warning at every start. The startup encoder check asked H.264 and HEVC for long-term references and warned each time. The warning now appears only when you set
amd_ltr_framesyourself and stream H.264 or HEVC.Both fixes have unit tests.
New in 2.0.0 #
Rubylight 2.0.0 is the first stable release, after 30 release candidates. It runs on a Windows PC with a Radeon and imports a Vibepollo or Apollo installation in place, keeping its settings and paired devices (Features lists everything it does). On the RX 7900 XT test host at the native 1968×2184 HDR 120 fps, the picture reached a loopback client 1.5 to 2.2 ms sooner than with Vibepollo 2.0 at every percentile (HEVC 17.8 against 20.0 ms, AV1 17.4 against 18.9 ms on average, rc.24), and it reached a Radeon 780M laptop over 5 GHz Wi-Fi in 13.5 ms on average (95th percentile 14.3 ms) with AV1, 120 fresh pictures a second; Performance has the method and the runs. Changes since rc.30:
-
AV1 recovers from a lost frame without a keyframe. When a client loses a frame that error correction cannot repair, it asks the host to start over. Until now that was always a keyframe, the largest frame there is, sent just when the link is struggling. Rubylight now keeps four older AV1 frames as long-term references, and when a client that supports it (Moonlight for Windows does) reports the loss, the next frame is predicted from one the client still has. On the RX 7900 XT host at 1440p120 AV1, 80 Mb/s, with an 80 ms outage every 1.5 s, that recovery frame was 15.5 to 24.6 KB instead of a 98 to 127 KB keyframe, and the picture came back 4 to 7 ms sooner (stalls of 92 to 100 ms against 96 to 106 ms) in three sets of runs, with every frame decoded. Without loss the picture and frame sizes were the same. A keyframe still follows a second after such a recovery, in case a decoder could not follow it. HEVC and H.264 keep recovering with keyframes: on a test decoder both froze after this kind of recovery.
amd_ltr_framesnow defaults to 4 and applies to AV1 only; 0 turns it off. Since the release it has also been tried with a real client over Wi-Fi: Moonlight 6.2 on a laptop streamed AV1 1440p120 at 50 Mb/s for 90 s while the host dropped 20 ms of video every 1.5 s. All 59 lost frames were recovered this way, with no keyframe requests after the start and no frozen picture, at 117.7 fps and 2.7 ms host processing. -
Faster recovery when the host itself is on Wi-Fi. On a host whose Wi-Fi briefly stops taking packets, as in a user's Legion Go log, Windows refuses the frame the host is sending. The client noticed only a frame later and then asked for a keyframe. Now, when more of a frame is refused than error correction can repair, the host stops sending it and makes the next frame a keyframe at once. With a simulated 80 ms outage every 1.5 s on the host (HEVC 1440p120, 50 Mb/s), the picture came back after 88 ms instead of 98 ms and the client's keyframe requests fell from 17 to 1 per run. Without outages nothing changed. Tested with a simulated outage on the test host; real Wi-Fi hosts, like that user's Legion Go, are where it gets exercised next.
-
Smoother video with a bitrate cap or a wireless host link. When packets go out at close to the stream bitrate, as with
pacing_max_bitrate_kbpsor on a wireless host, sending one frame took long enough to hold up the next one. Video is now sent on its own thread in that case, and a keyframe request drops the frame still waiting to be sent instead of sending it first. With a 160 Mb/s cap at 120 fps on the host, picture age p95 fell from 19.4 to 16.0 ms and a requested keyframe arrived about 4 ms sooner (p95 61 instead of 65 ms). Wired hosts without a cap keep the previous path, which measured better there;video_send_thread = falseturns the new one off. -
Troubleshooting for the AMD video engine timeout. On the test RX 7900 XT with driver 32.0.31041.1004 the video encoder hung inside the driver four times in October testing: three times under heavy loss recovery at 80 to 160 Mb/s, on unchanged builds too, and once in a plain HEVC stream with an experimental encoder setting that is not in this release. Windows then resets the GPU (LiveKernelEvent 141), and the Radeon may stay in Code 31 until a reboot. Troubleshooting explains what to do and what to report. When a stream freezes, the log now names the stream thread and the step it stopped in, says whether the GPU still answered, and records encoder calls that take 50 ms or more.
-
Smaller fixes:
- Ten-bit SDR streams on AMD no longer ask for pre-analysis or the quality rate control modes, which need 8-bit input; they use peak VBR as HDR streams do. After the encoder refused a bitrate change, its peak and buffer limits now follow the bitrate it actually runs at.
- The host counts the error-correction reports Moonlight clients can send and shows them in the periodic stream timings line in the log, so a support log can show how often a client repaired or lost a frame. Nothing in the stream depends on them, and no real client has been seen sending one yet.
The ten-bit and bitrate fixes have unit tests and passed the HDR metadata test on the host GPU.
-
Under the hood: benchmark runs now reject captures that fell back to another capture method or size, and can trace every packet batch. The host can simulate a Wi-Fi outage on its own or the client's side for recovery tests, and the test client can save the received video for a header trace. Reconnect tests no longer fail now and then on a slow test-client retry.
Copying or building on this code? Please keep the copyright and license notices and credit RamazanKara as the original author. AI coding agents are asked to do the same; see AGENTS.md.
New in rc.30 #
- Butterpollo is now Rubylight. The name nods to Ruby, the red-haired mascot of ATI's Radeon cards. Updating changes only the names Windows shows: the console, tray, setup, the Start menu entry, the Apps entry, the service's display name and the firewall rule. Your settings, paired devices, apps, the
ApolloServiceservice, the install and profile folders and the file names (butterpollo.exe, Start Butterpollo.exe) stay as they are, so the update installs in place. New installs go toC:\Program Files\Rubylight. The installer is nowrubylight-setup-<version>.exe; releases keep abutterpollo-setupcopy so older hosts still find the update in-app, and the updater knows both repository names. Links to the old GitHub address will keep working when the repository is renamed. Checked on the RX 7900 XT host: an in-app style update kept all 9 paired clients, 4 apps and identical settings, and a 12 s HEVC stream delivered 706 of 706 frames. Known leftovers: a firewall rule named "Butterpollo Rust - local network streaming" from an early installer keeps its name, and rolling back to rc.29 keeps the Rubylight names in Windows Services and the Start menu. Both are harmless. - The audio loss warning reports real drops again. rc.29's warning missed them: Windows reports a dropped packet on the read after the late one, and it is now counted. On the host, 15 of 16 injected 40 ms stalls were reported, each with the 18-28 ms Windows dropped; normal streams stay silent. The device check rc.29 moved off the audio thread took 7.8 ms on average (max 11 ms) on the host.
New in rc.29 #
- A keyframe request no longer holds back a moving game. When a client lost a packet and asked for a keyframe, or for lost reference frames to be invalidated, rc.28 encoded the picture already sent again at once, and since rc.27 that counted toward the stream's frame rate. On a moving picture that sent the old picture twice and pushed the game's next frame back. With a game held at the stream's frame rate, for example by RTSS, frames then went out late and unevenly for about a second after each request. Now the request rides the game's next new frame; a still screen still gets its keyframe at once. Checked on the RX 7900 XT host at 120 fps HDR with 20 requests per 35 s run. Pictures sent twice went from 19 to 0 per run, worst picture age from 22-25 ms to 14-15 ms and p95 from 12-13 ms to 10-11 ms. With the picture changing at 240 Hz, pictures sent twice went from 8-10 to 0. A keyframe on a moving picture now arrives about 3 ms later (9.1 instead of 6.2 ms at 120 fps), because it waits for the new frame. On a still 1080p120 desktop it still arrives in 2.4 ms. Not yet tried over Wi-Fi with real packet loss.
- The physical monitor stays off when a fullscreen game loses focus. With the virtual display layout set to exclusive, pressing the Win key or Alt+Tab in an exclusive-fullscreen game made Windows switch the physical monitor back on, and it stayed on for the rest of the stream. While such a stream runs, Butterpollo now saves the stream's layout as the one Windows remembers for these displays, so Windows has nothing to switch back on. The saved layout is put back when the stream ends, or at the next stream if the host stopped without doing so. If Windows switches the monitor on anyway, for example at Ctrl+Alt+Del, the stream turns it off again once it has been on for half a second. Checked on the RX 7900 XT host with exclusive 1080p60 virtual display streams and 3 layout recalls each, the step Windows takes on Win or Alt+Tab: with rc.28 the physical monitor came back on 53 to 282 ms after each recall and stayed on, with rc.29 it stayed off in all 3. After each stream the physical monitor was primary again.
- Steadier audio, and no false "Audio lost on the host" warnings. A user's host log showed this warning every 5 seconds on an idle PC with any playback device, and they heard random crackling. About once a second, the thread that sends audio first asked Windows which playback device is the default, and only then read the captured audio; that most likely held the audio back by about 20 ms each time, which can crackle on the client. That check now runs on its own thread. The warning itself counted every read more than 22 ms after the previous one as lost audio, but Windows hands over audio in 10 ms packets, so a read 23 to 27 ms later still finds them all, and with nothing playing there is nothing to lose. It now counts only audio Windows reports it dropped, and names the cause: a late read means a busy CPU or a host problem, not video bitrate or GPU load; packets the network adapter refused mean lowering the video bitrate or checking the adapter. Checked with unit tests and a Windows build check; not yet measured on a host or with that user's PC.
- Under the hood: the release check now also streams HEVC with 16 keyframe requests while the picture moves at the stream's frame rate, and fails if the host encodes an unchanged picture again. rc.28 did so for 10 of the 16 requests.
New in rc.28 #
-
Crash reports work on processors with Intel AMX. On a PC whose processor has AMX, such as recent Xeons, a crash from a Windows exception left no crash dump, only an error file: Windows' dump writer failed with "Only part of a ReadProcessMemory or WriteProcessMemory request was completed" every time it read the crash details from the host. The crash helper now copies those details out of the host itself and gives the dump writer its own copy. Checked on GitHub's Windows runners: on one with a Xeon 6973P-C, the crash test failed 20 of 20 runs before and 0 of 20 now; on eight runners without AMX it never failed. The dump no longer carries the AVX register contents, which a stack trace does not need.
-
A quoted permanent virtual display count is applied.
dd_virtual_display_permanent_count, and its older namedd_vdd_static_monitor_count, was refused as invalid when written in quotes ("2") or as hex (0x2), so the driver kept its previous count. It is now read like every other number setting. Checked with unit tests. -
A new stream waits until the previous one has closed. A launch or resume that came while another stream was still closing could start before that stream had released its virtual display, audio and client commands, so the new stream could play while a display it shares was still being released. It now waits for every stream that is still closing, up to 5 seconds, and otherwise answers that another stream operation is still running, so the client can try again. Checked with reconnect tests that run without a GPU, and on the RX 7900 XT host: picture age, 120 fps and no dropped frames, the same as without the change.
-
Streams recover after a GPU reset. When the graphics driver reset during a stream, Butterpollo kept retrying with the lost GPU's capture device, and some waits on the dead driver had no time limit, so a stream, and even the console's server info, could hang. Now it notices the lost GPU, rebuilds capture and the encoder, sends the client a fresh keyframe and shows a warning on the stream card. If the GPU does not come back within 30 seconds, the stream ends with a message to check Device Manager and to restart Windows if the adapter shows Code 31; the console and server info stay reachable throughout. Checked with fault-injection tests and a host A/B run on the RX 7900 XT, where picture age was unchanged; a real driver reset was not triggered on purpose.
-
A keyframe after packet loss arrives sooner on a still screen. When a client lost a packet and asked for a keyframe while the picture was not changing, the request waited for the next frame period. It now wakes the stream at once: on a still 1080p120 desktop on the host, a requested keyframe arrived in 2.4 ms on average instead of 5.8 to 6.0 ms. Picture age with moving pictures was unchanged.
-
Smaller fixes:
- The software video conversion path no longer reads past the end of a captured picture, and refuses an empty one before converting it.
- An empty packet from the encoder is handled as empty instead of being read from a null pointer.
- The tray icon's menu no longer holds the tray's state while Windows runs the menu, which can call back into the tray.
- When the AMD encoder stalls, the log now records its queue and fence state. The encoder settings line in the log shows AV1's frame-skip setting; it asked the driver for a wrong property name before.
- HEVC streams on AMD no longer ask the encoder for H.264's B-frame setting, which HEVC does not have. Picture age on the host was unchanged.
No user reported a problem from the first three; they came from a code review and have unit tests.
-
Under the hood: the display and input code and the stream startup are split into smaller parts. Host A/B runs on the RX 7900 XT matched the previous build within noise. New tests cover reconnecting clients without a GPU and most of the feature list.
New in rc.27 #
-
Moonlight on LG TVs can connect. Moonlight TV for webOS and its fork Aurora found the host but said it seemed to be offline, and adding it by address failed with "cURL Error: Failure while receiving data from the peer". These apps make an older kind of client certificate (X.509 version 2) that Butterpollo's encrypted connection refused, so they never got as far as pairing; Sunshine and Vibepollo accept it. Butterpollo now accepts it too and still checks that the app holds the certificate's key. Checked with a certificate made by Moonlight TV's own code, over TLS 1.2 and 1.3, in a unit test and with curl.
-
PyroWave with VRR stays at the stream's frame rate. A user streaming PyroWave with VRR at 100 fps saw up to 136 fps go out. When no new frame has arrived for a while, Butterpollo sends the last picture again, and for PyroWave it did so after a single frame's time, 10 ms at 100 fps: the console shows 20 as the minimum frame rate, but until a value was saved, PyroWave used the full stream rate. Under VRR, a game frame that came a little late then went out right after such a repeat, and the repeat didn't count toward the cap. That game frame was also given only the 1 to 3 ms of data since the repeat, so it came out blurred. Now an unsaved minimum frame rate is 20 for PyroWave too, as the console shows and as in Vibepollo; every picture sent counts toward the stream's frame rate, repeats included; and a new PyroWave picture right after a repeat gets at least a whole frame's worth of data. Saving a minimum frame rate of 30, which fixed it for that user, is no longer needed. A saved 0 still makes PyroWave resend at the full stream rate; those resends now count too, so a late game frame waits for room instead. Checked with unit tests, a simulation of the frame pacing, and on the RX 7900 XT host over loopback: a 90 fps test game on a 100 fps PyroWave VRR stream went out at 144 to 151 fps before and at exactly 90 fps now, every frame new and at full size (median 494 KB instead of 130 to 217 KB), with picture age the same or up to 0.7 ms lower. A still PyroWave desktop now sends 20 repeats a second instead of 97. Not yet tried with a real game or over Wi-Fi.
-
RTSS caps the game at the stream's frame rate with RTSS 7.3.7. rc.25's fix was not enough: a user streaming at 100 fps still saw the game run at 115 to 145 fps. Butterpollo wrote the limit into RTSS's settings file and asked RTSS to reload it, but RTSS 7.3.7 keeps running the settings it already has open and ignores an edited file; Vibepollo ran into the same. With RTSS 7.3.7 or later, Butterpollo now hands the limit to RTSS through RTSS's own profile interface, as Vibepollo does, and gives your own limit back the same way when the stream ends, on disconnect and after a host crash. Older RTSS versions still get the file update, which they do reload. When Butterpollo has to start RTSS itself, it now waits until RTSS is ready before handing it the limit, since RTSS doesn't answer while it starts up, and when the stream ends it closes that RTSS again after putting your settings back in its file. If RTSS ever stops answering, the log now names the RTSS call it hung in. The log line "RTSS frame limit applied and verified" now names the RTSS version. On the RX 7900 XT host with RTSS 7.3.7 (RTSS.exe reports 7.3.5.28314), a renderer held at RTSS's own 120 fps limit dropped to 59–60 fps for the 20 s of a 59.94 fps stream limit and went back to 120 fps a second after the stream ended, with the same RTSS still running and no warnings. With RTSS closed beforehand, Butterpollo started it, the renderer went from 240 to 59–60 fps, and at the end RTSS was closed again, the renderer was back at 240 fps and RTSS's settings file held the original limit.
-
Remote Monitor streams follow the virtual display layout. With the layout set to exclusive, a Remote Monitor stream left your physical display on and primary next to the remote monitor, because only regular streams applied the layout. A Remote Monitor stream now applies it too, per device or host-wide: exclusive turns the other displays off and makes the remote monitor primary, the primary layouts make it primary, and extended or isolated leave it where the remote monitor layout placed it. Your displays come back when the stream ends, and a layout Windows refuses shows as a warning on the stream card instead of failing the launch. Checked with a unit test of the layout choice; not yet tried on a host.
-
Streams leave at exactly their frame rate. When the source runs faster than the stream, as the default virtual display at twice the stream rate or a 165 Hz game does, a 120 fps stream sent 121 frames a second and a 60 fps stream 60.3 to 60.6, which a client showing 120 Hz has to drop or queue. Pacing kept a 1% margin that only a source at exactly the stream rate needs; a faster source no longer gets it. On the RX 7900 XT host over loopback, 120 fps HDR and 1080p60 streams now leave at 120.0 and 60.0 fps, with picture age unchanged within run-to-run noise. Not yet checked on a real Moonlight client.
-
A frozen picture recovers on its own. The picture could freeze for good while the stream and its audio carried on: when the captured display left the desktop even briefly, for example during a layout change that switched it off and on, Windows stopped delivering frames without reporting an error, and the host kept sending the last picture. Butterpollo now notices within a second that the display is gone and reopens capture on the display that is there now, including a virtual display recreated in the meantime, without waiting for that display's HDR and mode to be restored first. Checked with a build and lint check only; not yet tried on a host.
-
The virtual display driver no longer goes missing. Artemis says the "SudoVDA" driver is not running or not installed whenever Butterpollo's virtual display driver is missing, and several paths left it missing. Updates from the console's update button never set the drivers up, only the installer did; they now install the display and gamepad drivers as the installer does. When the driver is still missing a minute after the Butterpollo service starts, for example because another program's uninstaller removed it or Windows needed a restart to finish installing it, the service now runs setup to install it again: at most once a day per version, never during a stream, and not if you installed with
--no-display-driver. That attempt is logged inlogs\driver-repair.login the settings folder. When a driver step fails, setup now gives the driver script's own reason instead of only pointing at the setup log, and the console's virtual display check says whether the driver is missing or the host runs outside the Butterpollo service. Checked with unit tests and a Windows build check; not yet tried on a host. -
A stream starts even when the virtual display driver is missing. When Artemis asked for a virtual display and the driver wasn't installed or wasn't working, Artemis warned that the driver was not running, and after you chose to proceed the stream still failed to start. It now streams your physical display instead, as Apollo and Vibepollo do, and the stream card says why. Run the installer again to set up the driver. Checked with a unit test of that choice; not yet tried with Artemis on a host.
-
Smaller fixes:
- When a frame is too large for the client's packet limit and has to be dropped, Moonlight now sees the gap and recovers as from any lost frame. Before, it decoded the next frames against the picture it never got, so the image was corrupted until the next keyframe.
- Error correction on very small frames no longer corrupts the packet header when a client asks for 3 or more recovery packets per frame.
- UPnP port mappings are now added on routers that refuse to list their existing mappings. Those routers got no mapping before, and the log said "could not be renewed" every minute.
- With
address_family = both, IPv4 clients were paced as if every packet carried a 20-byte larger IPv6 header. Pacing and PyroWave's error-correction budget now use the real packet size. - Setup now removes Sunshine, Apollo or another old host installed under a path with spaces, such as
C:\Program Files\Sunshine. Its uninstaller never started before, and setup only noted that the old host could not be removed. Setup also waits for that uninstaller to finish now: Apollo's returns at once and keeps working in the background, and on a test machine it deleted its service 2 seconds later, which is the service Butterpollo runs as. If an uninstaller just deleted that service, setup waits until Windows lets it create it again. Uninstalling Butterpollo also removesuninstall.exeand its folder again, which it used to leave behind. - Uninstalling one Butterpollo copy no longer removes the HDR Vulkan layer of another copy in a folder whose name starts the same, such as
ButterpolloandButterpollo2. An update now deletes files that were still in use at the next restart, as a full installation does, instead of leaving them. - Setup restricts access to its temporary folder before unpacking into it, because the unpacked host runs elevated from there while importing settings.
- The log line for the virtual display mode shows the refresh rate Windows actually applied, for example 119.88 Hz rather than 120.
The packet, UPnP and setup command-line fixes have unit tests; none of these was tried on a host yet.
New in rc.26 #
- The installer sets up the virtual display and gamepad drivers again. Since rc.22, setup runs the driver installers as SYSTEM through a one-time scheduled task and wrote that task's definition as UTF-8. Windows'
schtasks /XMLrefuses that file ("The task XML is malformed … unable to switch the encoding"), so the rc.22 to rc.25 installers never installed, updated or removed either driver; setup only noted it in its summary and carried on. The definition is now written as UTF-16, the way Task Scheduler exports tasks. Checked on a Windows Server 2022 runner: the rc.25 file is refused, the new one is accepted and runs as SYSTEM, also with the task's files on drive D: in a folder with spaces,&and non-ASCII letters, and a new installer test runs a SYSTEM task end to end in such a folder. If Artemis says the SudoVDA driver is not running or not installed, or the console says the virtual display is unavailable, run the rc.26 installer: it sets the drivers up. - Rumble reaches older Moonlight clients again. Clients that open a single network channel, such as one that reports itself as "Legacy Moonlight Client", never got rumble: the host sent controller feedback on a second channel those clients don't open, so it was dropped without a word. Rumble, trigger effects and other controller feedback now go on the main channel, as in Sunshine. Checked with unit tests of the packets and the one-channel delivery, and on the RX 7900 XT host with virtual Xbox, DualShock 4, DualSense and Switch Pro pads: rumble set on each pad came back through the host with both motor strengths intact. Not yet confirmed in a game on a real client.
- No more "controller feedback poll failed" warnings. The VHF driver answers "nothing pending" when there is no rumble to deliver; the host counted that as a failed poll and logged a warning every five seconds for the whole stream. It is now treated as an empty poll.
New in rc.25 #
- A Steam Deck can be a real Steam Deck on the host. With usbip-win2 installed and Steam running on the host, a Deck streaming with Moonlight now appears to Windows as the Deck's own USB controller, so Steam on the host recognises a Steam Deck and applies its Steam Input layout, with both trackpads, the gyro, the back grips, the Steam and "…" buttons and rumble. No new driver of ours is involved: Butterpollo serves the controller over USB/IP on
127.0.0.1and usbip-win2's signed driver plugs it in. Settings → Input → Steam Deck controller chooses Automatic, always a Steam Deck, or the virtual DualSense with mapped back grips as in rc.24; without usbip-win2 nothing changes. With Steam Input on for Moonlight on the Deck, the host still gets a Steam Deck but only its buttons, sticks and triggers, because Steam on the Deck keeps the rest from Moonlight. Stick touch and the left trackpad click can't be passed on: Moonlight doesn't carry them. Checked with unit tests of the report, the descriptors and the USB/IP exchange, and on the RX 7900 XT host with usbip-win2 0.9.8.1 and no client: the controller attached as 28de:1205 with its three HID interfaces, Steam named it "Steam Deck Controller" and loaded its Steam Deck support, and it detached cleanly. With Steam running, a second reader of the controller interface got 2,442 reports in 10 s (one every 4 ms) with the probe's button presses in them, and a rumble command sent to the controller reached Butterpollo with both motor speeds intact. Steam's own use of the input and a real Deck streaming have not been tried yet. Steam Deck has the setup. - ViGEmBus is no longer used. Every virtual controller now comes from the VHF gamepad driver the installer brings, which covers XInput games, rumble, motion, touchpads and the DualSense's adaptive triggers. The Xbox 360 and DualShock 4 (ViGEmBus) choices are gone: a saved
gamepad = x360becomes Xbox One andds4becomes DualShock 4, in the host settings, app and device overrides, and profiles imported from Sunshine, Apollo or Vibepollo. With ViGEmBus installed, Automatic used to give Xbox-type clients its Xbox 360 pad; they now get the VHF Xbox Series pad, which Steam's stable client can list twice until it ships the fix the Steam beta already has (troubleshooting). An installed ViGEmBus can be uninstalled. - RTSS takes the client's frame rate again, and gives yours back. With the frame limiter on, a stream from a 120 Hz client is meant to set RTSS's limit to 120 and return it to your own limit (for example 158 on a 165 Hz monitor) when the stream ends, as Vibepollo does. A user on rc.23 saw RTSS stay at 158 the whole time. RTSS runs as administrator, and Windows can drop an unelevated program's request to reload RTSS's settings: Butterpollo's RTSS helper ran as the signed-in user without elevation, so RTSS never picked up the new limit, even though its settings file had it. The service now runs that helper as SYSTEM in your session, which is how Vibepollo reaches RTSS. Restoring your limit uses the same helper, at stream end, on disconnect and after a host crash. Covered by a unit test at this release; rc.27 confirmed it on the test host with RTSS 7.3.7. A portable copy started without administrator rights still can't reach an elevated RTSS: run it as administrator or use the installed service.
- Lossless Scaling follows the game, as in Vibepollo. The game is picked the way Vibepollo picks it, by CPU use and peak memory over its first 10 seconds, taking its folder, launcher and executable into account, and the Lossless Scaling profile stays on that game for the whole session: when a launcher hands over to the game or the game restarts, the profile is rewritten and scaling restarts. Checked with tests that use stand-in processes; not yet tried with a real Lossless Scaling.
- An app that quits right after launch is reported. As in Vibepollo, the stream stays open when an app exits within five seconds of launch, as a launcher handing over does. When it exits with an error code, the tray shows "Launch Error" with the app and the code, and clicking it ends the app and the stream. Unit-tested; the notification has not been seen on a real desktop yet.
- Web console clean-up: leftover debug controls are gone, the pages share one look, layouts fit narrow windows and phones, and the fallback status messages are easier to read.
- Upgrades keep the whole profile, now tested end to end. New installer tests upgrade a full rc.23 profile (paired devices with their settings and credentials, app overrides and covers, API tokens, web sessions, logs) and compare every file afterwards, and again after a rollback at every step of replacing the package. Migration from Vibepollo, Apollo and Sunshine runs through the same tests. Nothing changes in how the installer behaves.
- Documentation checked against the code: the getting-started, troubleshooting, configuration, API, architecture, developer and release guides were corrected where they had fallen behind, and the service calls itself Butterpollo in its messages.
- Internal: a test that HDR metadata reaches the stream's keyframes, and test fixes so the motion probe stays on the display it was asked to draw on and the test receiver reads its frame markers on the decoder thread.
New in rc.24 #
- Controllers work with an Xbox 360 or DualShock 4 choice and no ViGEmBus. Choosing Xbox 360 (ViGEmBus) or DualShock 4 (ViGEmBus) as the controller type, or bringing
gamepad = x360ords4over from Sunshine, Apollo or Vibepollo, needs ViGEmBus, which the installer doesn't include. Without it, every controller did nothing, for example from an Android phone. These choices now use the bundled VHF Xbox One or DualShock 4 pad instead and the stream card says so; install ViGEmBus or choose Automatic to remove the warning. With ViGEmBus installed nothing changes. Checked with tests only, not yet with a real Android client. - Steam listing one controller twice: the Steam beta fixes it, as users report; troubleshooting has the details and the ViGEmBus workaround for Steam's stable client.
- Steam Deck: back grips, both trackpads and a hint when Steam Input hides them. With Steam Input off for Moonlight, a Deck gets a virtual DualSense with its gyro, and now:
- Back grips work. No virtual controller has back buttons, so L4, R4, L5 and R5 were dropped, as in Sunshine, Apollo and Vibepollo. Settings → Input → Back grips now sets what each one presses: a face button, bumper, fully pulled trigger, stick click, Back, Start, Guide, d-pad direction, touchpad click or Share. They default to nothing, and pressing an unmapped grip says where to set it. Xbox Elite paddles use the same settings.
- The right trackpad is no longer ignored. Both trackpads share the virtual pad's touch surface, left and right halves, when Moonlight sends them (builds with SDL 3).
- "No gyro" explained. With Steam Input on, Moonlight only sees a virtual Xbox controller. When a device named Steam Deck connects that way, the stream card says to disable Steam Input for Moonlight.
- Checked with unit tests only: no real Steam Deck has been tried. Steam Deck has the setup.
- Input a device isn't allowed to send is no longer dropped silently. Devices paired after the first start view-only, as in Apollo and Vibepollo, so their controller, touch, pen, mouse and keyboard input was ignored without a word. The stream card now names the permission to turn on under Devices.
- Launching again after a stream that is slow to close. When a stream had not finished releasing its encoder, capture and display within 5 s, for example because the graphics driver was slow to close the encoder, the old session stayed listed as running, and every later launch failed with "previous game session is still releasing" until the host restarted. The old session now leaves the list before those are released, and the log warns when a teardown outlasts the launch wait. A session whose stream fails to start is removed too, and restoring the frame limiter (RTSS) when a launch expires no longer holds up other clients' input. Checked with unit tests and the reconnect soak on the RX 7900 XT host: 66 of 67 runs passed, and the one failure is the HEVC 120 fps run under uncapped GPU load that fails on rc.23 as well. A real driver hang has not been reproduced.
- Smaller fixes from a code audit: a Vulkan layer check could write past a 4-byte buffer after a failed registry read, two Windows network helpers read lists through too narrow a pointer, and the AMF D3D12 context was released through a wrongly typed call. None is on the frame path. The old C++ host sources are gone from the repository.
New in rc.23 #
- Streams ride out a saturated GPU instead of ending. An RX 9070 XT streaming 4K60 HEVC beside a game froze and then lost the stream with "The encoder returned no frame for 100 ms": the host recreated a stalled encoder about ten times a second, and a fresh encoder's first 4K keyframe on a busy GPU never got time to arrive. A stall now waits 250 ms before the first recreation, then 500 ms, 1 s and 2 s, and the session gives up after 20 s instead of 10 s. Large frames keep the error correction that fits instead of none. Your encoder settings are never changed behind your back.
- PlayStation controllers can have adaptive triggers with ViGEmBus installed. Automatic now gives a PlayStation-type client the VHF DualSense instead of a ViGEmBus DualShock 4, because ViGEmBus cannot emulate a DualSense. Xbox-type clients, including a Steam Deck, stay on ViGEmBus, so Steam still lists one controller. Without the VHF driver, the DualShock 4 on ViGEmBus is used as before;
ds4andvhf_ds5still pick a driver explicitly. - Sturdier connections:
- A client resetting its connection while connecting no longer shuts the host down and ends the running game.
- A session whose control connection is lost now ends after the ping timeout instead of holding the encoder, capture and display until the host restarts.
- A Wi-Fi drop of 5-10 s no longer ends the stream: the network library's own 5 s timeout now follows the 10 s ping timeout.
- Streaming before sign-in and while Windows is locked (issue #6):
- A launch on the sign-in screen no longer fails with error 503 while looking for old Sunshine display files in a user folder that doesn't exist yet.
- A launch while Windows is locked no longer fails with error 503. Windows allows display changes then only from the lock screen's own desktop, and the host made them from the normal one, so every launch was refused whatever the capture method. Setting up, keeping and restoring the stream's display now happens on the lock screen's desktop, as capture and input already did. If the virtual display still can't be set up while locked, the stream shows the physical display and the stream card says so, as Vibepollo does. Tested by the owner, and confirmed by a reporter streaming from a locked VM with no monitor attached (#6).
- Better H.264 picture at low bitrates. AMF's adaptive quantization is now off by default for H.264: on two game clips at 1440p120 and 20 Mbps, turning it off raised VMAF by 1.0 and 9.4 points at the same encode time. HEVC and AV1 keep it on, where it made no clear difference. Set
amd_vbaqto turn it back on. The other AMF settings (quality presets, constant bitrate, low-latency usage, high-motion boost) were measured too and stay as they are: none improved the picture without slower encoding or larger frames. - AV1 at sizes AMD pads: at sizes such as 1968×2184, AMD encodes AV1 with extra rows and columns (1984×2186) that clients have to crop. The log now says so once per stream; HEVC avoids it.
- Playnite, as Vibepollo does it: games are brought to the front using the existing focus settings, Playnite fullscreen starts again after a game instead of ending the stream, a cover saved in the console is set in Playnite too, and the console can restart Playnite.
- Tray: notifications when an app starts, pauses, resumes and stops; a green or amber dot on the icon while an app streams or is paused; "Quit
"; and "Check for updates". - Console file picker: Browse buttons for an app's command and working folder and for the Lossless Scaling and RTSS paths.
- No latency change from rc.22. On the RX 7900 XT, rc.22 and this release alternated in one batch at 1968×2184 HDR 120 fps (HEVC and AV1), 1440p120 and 1080p60, idle and beside a game; picture age matched within the spread of the runs. Details in
rust/PERFORMANCE_WORK.md.
New in rc.22 #
- PyroWave holds 60 fps at high bitrates. The sender's pacer counted every send call and late wake-up on top of the paced wire time, and PyroWave's automatic rate left no room for headers and error correction. A frame that filled its budget took 20-22 ms to send, so 1080p60 at 400 Mbps, 1440p and 4K dropped to about 57 fps. Pacing now credits up to 0.5 ms of send time per batch, and PyroWave paces at 1.25× its demand when the link speed is unknown. The same frames now take 12.8-13.1 ms, about 78% of a 60 fps frame period, at 1080p, 1440p and 4K. The release check now also fails PyroWave streams that replace more than 1% of frames.
- No more silent "no codec" host after a GPU reset. If the host started before the graphics driver was ready, after a crash or a driver timeout, its encoder check gave up after six tries and every stream failed without saying why. It now keeps retrying until an encoder works, checks again when a client asks, and the console shows "No video encoder available … Retrying" in the meantime. Retries never run while a stream is starting.
- Stream display problems:
- VRR streams no longer settle at 60 Hz: the virtual display gets the stream's resolution and refresh after it is created and after every recovery. Before, Windows picked a saved mode, and for some 4K VRR streams that was 60 Hz, so a 116 fps game streamed at about 60 fps. The log now shows
virtual display mode applied requested=… actual=…, and the stream card warns if Windows kept a different mode. - The stream no longer silently shows your physical desktop: when Windows switched the virtual display off mid-stream (for example when a game reset display settings on exit), capture fell back to the main monitor, so the stream showed the host's desktop at its own resolution. Capture now waits for the stream's display to come back. A temporary "display busy" error no longer rebuilds the display, and a display Windows switched off is switched back on with the exclusive layout and HDR.
- Saved 60 Hz modes no longer cap VRR: a 60 Hz mode saved for a device also limited the game to 60 fps under VRR. The frame limiter now follows the stream rate, and a VRR stream never reuses a display prepared without VRR.
- VRR streams no longer settle at 60 Hz: the virtual display gets the stream's resolution and refresh after it is created and after every recovery. Before, Windows picked a saved mode, and for some 4K VRR streams that was 60 Hz, so a 116 fps game streamed at about 60 fps. The log now shows
- No silent downgrades:
- Automatic picks the encoder by GPU vendor (AMD → AMF, NVIDIA → NVENC). If it can't start, it tries other hardware encoders and says so on the stream card; it never falls back to software.
- The console's stream card shows the encoder in use, plus warnings for capture fallback, display modes or frame limits that weren't applied, audio route changes and loss, network limits, and missing input features.
- The console no longer shows NVENC settings on AMD hosts under Automatic.
- Install and update safety:
- A failed update keeps the previous installation working and keeps its backup for a retry.
- Manual reinstalls and upgrades use the same rollback as in-app updates.
- Incomplete packages are rejected before anything changes, and downgrades are refused.
- Migrating from Vibepollo, Apollo or Sunshine checks the old profile before replacing the old host.
- Driver setup works with non-ASCII user folders and treats "reboot needed" as success.
- A setup interrupted earlier can't run driver tasks later.
- Controllers:
- DualShock 4 rumble and lightbar now work on ViGEmBus.
- Repeated feedback failures show up in the log.
- Fewer ways for a stream to end:
- A PyroWave frame that can't be prepared drops that frame instead of ending the session.
- A handle leak in the capture threads is fixed.
- Wi-Fi pacing only where it helps: 2× pacing now applies only to confirmed wireless routes. Wired hosts behind a Hyper-V switch, VPN or Tailscale keep full-speed pacing again.
- Playnite plugin installed as you: the plugin is installed and removed as the signed-in user instead of SYSTEM, so a link in a user folder can't redirect a SYSTEM write or delete.
New in rc.21 #
- Security: the Playnite connection can no longer act as the host. When the host connected to Playnite's connector pipe, a program posing as Playnite could take on the host's identity, which is SYSTEM when it runs as a service. The host now allows the pipe server only to identify it, not to impersonate it. This issue predates rc.20.
- PyroWave bitrate guidance from measured picture quality. 6,048 decoded comparisons across desktop, game, dark and HDR scenes set two levels. At 60 fps the picture breaks down below about 139 Mbps (720p), 187 Mbps (1080p) and 747 Mbps (4K). It is clean from about 277, 399 and 1593 Mbps. The console's stream card shows red below the first level and amber below the second, the log warns, and the guides give the same numbers. At 4K, a clean PyroWave picture needs more than a gigabit link can carry; use HEVC or AV1 there.
- Crash reports can no longer hang the host. The host used to write its own crash dump, which Windows documents as able to deadlock; under load a test hung 1 run in 30. A separate reporter process now writes it. If that process can't start, the host still starts and logs a warning.
- Optional AMF limits for recovery frames. On Wi-Fi a lost frame triggers a full keyframe, several times a normal frame. A new opt-in frame-size cap cut the largest recovery frames at 4K60 and 40 Mbps from 266 to 90 KB (HEVC) and from 154 to 82 KB (AV1), at a small encode-time cost. The defaults are unchanged. AV1 bitrate changes during a stream now use the right AMF property.
- Fixes from a review of rc.20:
- A DualShock 4 battery level above 100% from a client no longer corrupts the controller report.
- After a quick change of the stream's display, absolute mouse input can no longer land on the previous display.
- One client disconnecting no longer releases another client's network acknowledgements before its input is applied.
- A ViGEm controller keeps its rumble if unplugging it fails.
- Release checks cover PyroWave. Every release now has to decode PyroWave end to end at 1080p60, SDR and HDR 4:4:4, with picture, motion, cadence and audio checks. The test tone no longer starves under CPU load, so audio checks measure the host rather than the test machine.
New in rc.20 #
- Controller input no longer waits behind a busy gamepad driver. Every controller update was a blocking call into the virtual gamepad driver, on the thread that also handles keyboard, mouse and touch. When a game loaded every CPU core, that call could stall for up to 287 ms and everything else waited behind it. Gamepads now run on their own thread, and a mouse move behind a stalled controller waits at most about 2 ms. The input thread also keeps its multimedia priority boost: behind time-critical load, input waited a median of 3.5 ms and now waits 17 µs.
- Snappier keyboard and mouse. Moonlight's acknowledgement goes out after its input is applied, saving about 25 µs per event. A burst of keyboard and mouse events reaches Windows in one call: 8 events take 38 µs instead of 180 µs. Setting up input when a session starts no longer blocks it for 3-4 ms.
- One controller in Steam again: ViGEmBus is back. Steam on the host often listed Butterpollo's virtual Xbox controller twice. Its bundled controller library races its XInput and GameInput backends, and when it happened, Start+Select could open Game Bar and the Steam keyboard. When ViGEmBus is installed, Automatic now uses it, as Vibepollo does: an Xbox 360 pad, or a DualShock 4 for PlayStation-type controllers. Steam lists it once. Without ViGEmBus, the virtual gamepad driver is used as before. The new
x360andds4choices pick ViGEm explicitly. - PyroWave sends sooner and is verified end to end. The first protected block of a frame is ready in 0.06 ms instead of 0.49 ms at 1080p120, and in 0.21 ms instead of 1.75 ms at 4K60, with identical bytes on the wire. 1,152 frames decoded with PyroWave's own decoder match between the compute and graphics conversion paths. Loss feedback from the client no longer forces extra frames.
- PyroWave warns when its bitrate is too low. At HEVC-like bitrates PyroWave shows only blurred grey blocks. Below one bit per pixel per frame (about 55 Mbps at 720p60, 125 Mbps at 1080p60, 500 Mbps at 4K60), the log and the console's stream card say so, and the setting's description gives the numbers.
- Steadier streams on Wi-Fi. Video on wireless and unknown network routes is now paced at twice the stream bitrate instead of bursting at up to 800 Mbps, so bursts don't overrun the access point; audio shares that path. Ethernet is unchanged, and
pacing_max_bitrate_kbpsstill overrides it. The log now also reports audio the host loses before sending it. - Playnite launches more reliably. Playnite is found and started in the right Windows session, a missing or partial plugin is repaired, failed library syncs retry promptly, and streams survive Steam and Epic launcher handoffs and Fullscreen mode. Failures now say exactly where the launch stopped. See "Playnite does not launch" in the troubleshooting guide.
- RX 9000 safety. SmartAccess Video combined with a forced low-latency mode, a combination that has caused GPU resets, is now prevented. A lost GPU is no longer treated as finished work, which could have reused frames unsafely. The troubleshooting guide has an RX 9000 driver section and a test checklist.
- AMF settings tell the truth. The script-free console saved the low-latency switch under a key nothing read; it now saves
amd_lowlatency_modeand offers the AV1 latency mode. With the default ultra-low latency usage the driver already applies both, so they stay on Driver default. A newamd_split_framesetting asks the driver to split frames across two encoders where it ships that off, as the original host did. On an RX 7900 XT the driver never splits, so nothing changes there. - Honest capture timing. The stream log now reports WGC's own delivery delay (about 1 ms on a virtual display), which the old numbers hid.
- Clearer limits. AMD encoders produce 4:2:0 only, RX 9000 included; 4:4:4 on AMD means PyroWave. The docs and the error message now say so.
- A new introduction film. It explains what Butterpollo is, follows one frame through the Radeon path, and shows the measured comparison with Vibepollo 2.0 beside a game.
New in rc.19 #
- Lower latency when the encoder cannot keep up. At 4K with a high refresh rate, or on an RX 9070 XT with its single HEVC encoder, one encode can take longer than a frame. The host kept claiming new pictures anyway, and up to eight queued in the encoder, each older when it came out. It now waits while two are in the encoder, enough to keep both encoder instances of a Radeon busy. At 5120x1440 HEVC 240 fps on an RX 7900 XT, whose encoder manages 220 fps there, the time from the game's frame to the packet fell from 42.7 to 11.1 ms, at the same 220 fps. Streams the encoder keeps up with are unchanged.
- PyroWave converts colour on the Radeon compute queue. Its conversion was the last one still running on the graphics queue, behind the game: beside a game using the whole GPU, a 1080p HDR 4:4:4 frame took 5.6-5.8 ms instead of 0.47 ms. It now runs on the same high-priority compute queue as the AMF path, at 0.54-0.57 ms beside that game, with identical output. At high bitrates PyroWave also starts sending up to 2 ms sooner, after a slow byte-by-byte check of every frame was made eight times faster.
- AMF no longer drops frames to stay on its bitrate. Frame skipping was left to the driver; a skipped frame shows on a VRR client as a held picture. It is now off for H.264 and HEVC, as in Vibepollo.
New in rc.18 #
- Two GPUs: encoding on the Radeon works. With a Radeon for games and encoding beside an NVIDIA card that drives the monitor, the encoder check at startup ignored the GPU chosen in the settings and tested the monitor's card instead. On automatic it found NVENC there; forced to AMF it offered Moonlight no codecs, and the RTSP handshake failed. The check and the encoder now use the selected GPU, and automatic tries that GPU's own encoder first: AMF on a Radeon. A Radeon PC with an Intel iGPU no longer risks picking Quick Sync either. Desktop Duplication, which can only capture on the GPU the monitor is connected to, now says so and points to WGC capture, which works across GPUs.
- A new launch film follows one frame through the Radeon path, shows the same-build compute result and what rc.17 brought players, with a new original score.
New in rc.17 #
- Steam Deck and other controllers with motion sensors get a virtual DualSense, as in Vibepollo. An Xbox-type controller with a gyro or touchpad, such as the Steam Deck, stayed an Xbox pad: it lost its gyro and touchpad, and Moonlight's quit combo opened Game Bar and Steam's keyboard on the host. With
motion_as_ds4andtouchpad_as_ds4both off it stays an Xbox pad. A controller that connected while the stream's display was still being set up also lost its motion sensors, because input was dropped until then; keyboard and controllers now work from the start. - The first launch no longer fails when an app's prep commands take a while. A launch expired 30 s after Moonlight asked for it, even while its prep commands were still running, so Moonlight then found no launch to connect to ("no authorized RTSP launch") although the game was running, and only a retry worked. The host also keeps answering while prep commands run, instead of showing as offline to every client.
- Lower latency:
- With the default install, the host learns of a captured frame the moment the capture helper has it instead of polling for it: about 0.7 ms less from the game's present to the packet.
- Captured audio is sent as soon as Windows delivers it (event mode, as in the C++ host), and the encoder's frame deadlines are met more precisely.
- Colour conversion on the compute queue runs in one pass instead of two.
- Taps and clicks with touch or a pen are released on time; they were 4 ms late on average, and key repeat had up to 8 ms of jitter.
- On a local network, video and audio are tagged for priority as Moonlight asks (DSCP 40 and 56), as Vibepollo does: Wi-Fi sends them ahead of other traffic. Moonlight does not ask for this over the internet.
- Smoother pacing:
- A keyframe the client asks for after packet loss, or a repeat of a still picture, no longer holds back or skips the game's next frame.
- With Desktop Duplication, mouse-look in a game (a hidden pointer that moves) no longer costs frames.
- VRR follows a game's uneven frame times more closely.
- The capture helper now gets its first frame on a still desktop, where it could fall back to Desktop Duplication for the whole session (seen on a Legion Go).
- The check that keeps a stream's virtual display alive costs 27 µs a second instead of up to 31 ms.
- One encoder hiccup no longer slows the rest of the session. Since rc.14 any encoder failure moved colour conversion to the graphics queue, which waits behind the game: 7.2 ms instead of 0.9 ms per frame beside a GPU-bound game. That now happens only after a second failure. An encoder that keeps failing no longer leaks memory, and a recovery that produces no frames ends after 5 s instead of continuing.
- Several clients on one PC:
- A second stream no longer changes the resolution, refresh rate or HDR of a display another stream is using.
- The saved display layout is restored when the last stream ends, not under a stream still running.
- The layout is also restored when a remote monitor joined during the stream; the physical monitors had stayed off.
- Clients sharing the virtual speakers with different speaker setups (a stereo phone and a 5.1 PC) no longer keep losing each other's audio.
- Quitting an app closes only the game the launching client's store client started, and never a program someone opened on the PC.
- Displays and HDR:
- An HDR client on a display without HDR streams in SDR instead of failing to launch.
- HDR is switched back on the display as it is connected at the stream's end, also after a driver reset or a TV that reconnected.
- A TV that was in standby when a stream ended gets its original HDR, mode and colour profile back even when the next stream uses it first.
- Your speakers come back after a stream. If they could not be set when a stream ended (a Bluetooth headset turned off, a TV's audio not back yet), every later stream restored Steam Streaming Speakers instead. The host now keeps the original devices and restores them once possible.
- Settings import from Sunshine, Apollo, Vibeshine and Vibepollo:
- It reads files those hosts write and load: sunshine.conf in UTF-16 or the ANSI code page, state files with a byte order mark or left empty, paired devices stored with
""lists, and device commands in Apollo's format. - A profile naming files that no longer exist, or holding large logs, links or oversized files, imports with warnings instead of failing.
- The imported profile is checked with the host's own loaders, so setup no longer reports success for a profile the host then refuses to start with.
- When an import fails, setup shows why and restarts the previous host's service, including Sunshine and Vibeshine.
- A previous host whose installer recorded no install folder is found anyway.
- One unusable device or app override is skipped with a warning instead of failing every stream of that device or app.
- It reads files those hosts write and load: sunshine.conf in UTF-16 or the ANSI code page, state files with a byte order mark or left empty, paired devices stored with
- Updates:
- A host set to a specific
bind_addressis no longer rolled back as "did not start". - If the service cannot be stopped, it is started again and the update is reported as failed.
- An update cut off by a power loss or crash is rolled back at the next start.
- Only the newest two folders of unfinished updates are kept.
- A host set to a specific
- Pairing: a second device asking to pair no longer takes over the PIN of the first, and nobody on the network can keep others from pairing.
- The web console's "Remember this device" keeps you signed in for 7 days. It signed you out after two hours. Releases now ship the console built from their own source; before, it was carried over from an earlier package.
- Games: a game that restarts itself through Steam (after an update or a settings change) no longer ends the stream, and a browser or store client that a game opens is no longer closed with it.
- Touch and pen: held touches and a resting pen are no longer cancelled while the other is in use, Shift held during key repeat is no longer released, pen pressure and buttons are sent as the C++ host sends them, and contacts are lifted when a stream ends.
- PyroWave follows
pacing_max_bitrate_kbpswhen it is set.
New in rc.16 #
- Audio without dropouts. Since rc.14 the host sent silence as soon as Windows had delivered no sound for 10 ms, but Windows delivers sound in chunks of about that size, so ordinary scheduling jitter put short gaps of silence into the stream. Silence now starts only after 50 ms without sound.
- The uninstaller removes only Butterpollo's files. Run outside an installation, it could take the folder it ran from (Downloads, for example) for the installation and delete all of it, and a custom install folder lost everything else in it. It now refuses when Butterpollo is not installed, deletes only the files the installation lists, and removes a folder only when nothing else is left in it.
- Touch, pen and absolute mouse land where you tap on a letterboxed stream. When the PC's display has another shape than the client (a 16:9 PC on a 4:3 iPad), taps were off by up to a few hundred pixels and the bottom of the screen, taskbar included, could not be reached. Input now skips the black bars as the original host did.
- Password guessing over the network is limited. Sign-ins sent with each request (Basic credentials) were not counted, so a device on the network could try passwords at full speed. They now share the login page's limit of 10 attempts a minute per address, this PC is never locked out, and a flood of addresses can no longer block sign-in.
- Two crashes that ended every stream are fixed: a controller whose profile changed after it connected, and one malformed cover-image request.
- Settings that stopped the host are refused or worked around. One bad value already in sunshine.conf no longer blocks every later save; a value the file would cut short (an unquoted
#) or that would swallow the settings after it (an unclosed[) is refused with the key named; and a network interface name in place of an address, or a folder as the log file, no longer keeps the host and its console down.
New in rc.15 #
- Fixes blurry VRR streams and fringed text. The host could encode faster than the stream's requested frame rate, spreading its bitrate across too many frames. VRR now caps encoding at the negotiated rate while taking new frames as they arrive.
- Restores duplicated displays more reliably when Windows needs to move a display to another desktop source.
- Uses the Butterpollo icon throughout the executables, Start menu and tray.
New in rc.14 #
- Streams survive encoder and GPU hiccups. An encoder error mid-stream ended the session, which Moonlight reports as error -7fffbffb; a game holding the GPU or a driver reset was enough. The host now recreates the encoder, keeps the hardware encoder the stream started with, converts on the graphics queue if the compute path failed, and asks for a keyframe. Only failures that last five seconds end the stream. A wait for GPU work that never finishes no longer freezes the picture for good; it gives up after two seconds and recovers the same way.
- Fewer frozen pictures on Radeon RX 9000 cards. The encoder input queue was forced to one frame for every client asking for VRR low latency, which exposes a known RDNA4 driver freeze where video stalls while audio plays. It is now set only on request, as in the original backend. A keyframe requested after a client resets its decoder now carries the AV1 sequence header, so the client can start again, and a frame the encoder loses is followed by a keyframe instead of damaged pictures.
- GPU safety. Captured textures could be released while the GPU was still copying or converting them, the kind of fault that hangs a driver; they are now kept until the GPU is done, and stale ones no longer pin gigabytes of video memory. The queues that copy and convert captured frames ran at global realtime priority, ahead of the desktop compositor every captured frame comes from; they now run at high priority, still ahead of a game's normal work (
compute_queue_realtime = truerestores it). An HDR stream on an RX 7900 XT had ended in an AMD driver timeout that took the GPU offline until a reboot; its cause is not proven, and these are the two likeliest contributors. - Quitting closes games that a store client started. The Xbox app, Epic, EA, Ubisoft Connect and Battle.net start games themselves, outside the processes an app launches, so quitting left them running. The host now notes the fullscreen game on the stream's display when it started after the launch, and quitting closes it like the app's own processes. Store clients themselves, the plain desktop and Steam or Playnite games are left alone.
- Radeon RX 6000 cards: colour settings a driver rejects no longer cost the encoder, the same kind of rejection as the HEVC fix in rc.12; a bitrate the driver refuses keeps the current one instead of failing every frame.
- Lower audio and input latency. Audio goes out as soon as Windows captures it instead of on a fixed 5 ms tick, which added 5 to 20 ms and could insert short silences. Input is handled the moment it arrives instead of after a 1 ms sleep, and a finished video frame is sent without waiting for the encoder's next one.
- The cursor shows on a PC without a mouse. Windows hides the pointer when no mouse is connected; while streaming, Mouse Keys are turned on so it appears, as in Sunshine. Reported with Moonlight on macOS.
- Hosts on Wi-Fi: connected Wi-Fi adapters run in media streaming mode while streaming, with fewer background scans and the stutter they cause. The desktop compositor is scheduled with multimedia priority.
- RTSS frame limiter applies again. Before changing a frame limit or a display, the host starts a helper that undoes it if the host dies. Where the host runs in a job that forbids that helper to leave it (a scheduled task, a launcher), starting it failed and the limiter gave up, so RTSS never got the stream's rate. The helper now starts inside the job in that case.
- The tray icon shows after a reboot. Windows keeps Explorer's "taskbar created" message from the elevated host, so an icon the taskbar refused at boot never appeared after sign-in. The host now accepts that message and retries until the icon is in.
- Firewall check: the installer allows the host on every network, but a block rule, which Windows leaves when its "allow access" prompt is dismissed, overrides it and makes manual port exceptions look necessary. The host now logs such rules by name at startup, and
--diagnosticslists them. - Input fixes:
- Absolute mouse, touch and pen follow the stream's display when it is created, recreated or changes resolution, instead of the display at the first input event.
- A key or button release that Windows refuses (a UAC prompt, the lock screen) no longer leaves the key repeating, or dead in later sessions.
- A long press with touch or a tablet right-clicks without left-clicking first, as in Sunshine.
- Print Screen works; a missing virtual gamepad driver no longer slows other input; unplugging a controller no longer leaves a phantom pad; each virtual pad logs the profile it got.
- Displays: when Windows switches a TV on together with the stream display, the host no longer hands Windows the TV's leftover modes while switching it off again, the likely reason Windows refused; if it still refuses, the stream starts with the TV on, as in rc.13. Restoring duplicated displays after a stream falls back to letting Windows choose a mode the group can show when it refuses the original one.
- Releases are built, checked, installed and published by one script, and each release tag gets its own CI run.
New in rc.13 #
- Fixes streams that would not start on rc.12 when Windows also switched on a TV or monitor that was off as the stream display arrived. Switching that display off again failed with "invalid parameter" (error 87), and the launch was cancelled. The host now lets Windows adjust the layout when it refuses the exact one, and if it still refuses, the stream starts with that display left on.
- Duplicated displays are put back after a stream even when Windows refuses the first display's exact mode for the group (error 31): Windows picks a mode the group can show, then every display gets its saved mode back. The display self-test found this with two duplicated stand-in TVs.
- Release tags get their own CI run, and releases are built, checked, installed and published by one script (
rust/release/release.ps1).
New in rc.12 #
- Fixes HEVC on Radeon RX 6000 cards (#5). The RX 6800 XT's HEVC encoder rejects the input colour-range property, which the host treated as fatal: AMF was dropped and the stream stayed black. The range properties are now optional; limited range is AMF's default, and a full-range stream logs a warning if the driver refuses them. This affected every release since rc.1.
- Fixes doubled and repeating input reported on rc.10:
key_repeat_delay = 0, which a Vibepollo profile uses to turn host key repeat off, made the host repeat a key immediately, so one tap typed two or three characters. 0 now turns repeat off and a negative value keeps the 500 ms default, as in Vibepollo.- A repeated press of a key or button that is already held is no longer sent to Windows again; with the host's own repeat it doubled the repeat rate, and a second button-down could become a double-click.
- A key released without the extended-key flag it was pressed with is now released, instead of staying down and repeating.
- An Xbox controller stays an Xbox pad even when it reports motion sensors or a touchpad, as in Vibepollo. A PlayStation pad is also claimed by Steam Input, so games could see the controller twice.
- rc.11 already stopped discarding delayed key releases as replays.
- Input that arrives before capture starts is no longer dropped when the stream's display is selected by device ID, as the console stores it.
- Other devices' requests no longer wait for a launch to finish preparing its display, audio and app.
- Adds a display self-test for support and development: run as SYSTEM,
butterpollo-service.exe --display-self-test REPORT.jsoncreates virtual displays, switches one off and back on, duplicates two of the driver's permanent displays as stand-in TVs, starts a stream display beside them, restores a cloned layout, and then puts the desktop back. - Measured, not changed: on an RFI-capable client under bursty loss, long-term reference recovery on AMD (
amd_ltr_frames) reduced the worst arrival spikes at heavy loss but produced more frame gaps than keyframe recovery, so it stays off by default. Wired 1080p60 at 80 Mbps showed no difference between the default packet pacing and a limit of twice the bitrate. For a client on Wi-Fi, settingpacing_max_bitrate_kbpsto about twice the stream bitrate smooths the bursts.
New in rc.11 #
- Fixes "virtual display did not become active before the deadline" (#4). Windows decides whether a newly connected display joins the desktop, and a layout it saved for the same displays (for example two TVs in duplicate mode) can leave the virtual display connected but switched off. The host waited for Windows and failed the launch after ten seconds; unplugging the other displays was the only workaround. When the virtual display stays off for a second after connecting, the host now switches it on itself beside the current displays, as Vibepollo's display helper does. The other displays keep their modes, positions and clone groups; only if the driver refuses that does Windows choose the modes, which the stream's layout restore undoes. A launch that still fails now says whether the display was connected but kept off.
- Codecs no longer stay unavailable after Windows starts. The host checks its encoders once at startup. When it started with Windows, the PyroWave check could run before anyone was signed in (
helper needs a signed-in user, oroptional codec probe exited before replying (0xc0000142)while the desktop was still being set up), and on this test PC the AMD encoder itself failed to open (AMF error 1), so the host offered no video codec at all until it restarted. If no standard codec is found, the host now probes again once a user is signed in, with growing pauses and at most six times; a PyroWave check that failed only for want of a session is repeated the same way, never during a stream. A probe that fails for any other reason, such as a crashing overlay, is not repeated.
Display layouts and recovery
- Restoring a saved layout with duplicated displays (two cloned TVs, for example) no longer fails every time. Clone groups are rebuilt before positions, and displays are found by device instead of by a Windows display name that changes when they rejoin their group.
- A setting that cannot be restored after an interrupted stream no longer stops the host from starting. Recovery attempts every step, logs what failed and continues; before, the same failure repeated on every start.
- A TV or monitor that is off or unplugged when a stream ends keeps its HDR, colour profile and mode entries until it is back, instead of keeping the stream's settings.
- The layout restored after a stream is taken before the virtual display is created, so what Windows changes when it arrives (a TV switched off, another primary display, a retimed display) is undone too.
- A display that disappears during a stream no longer keeps the rest of the layout from being restored.
- Displays that stay on keep their timing and position when the layout changes, such as a retained remote monitor during a restore.
- An expired startup guard on a virtual display no longer makes its recovery fail for the rest of the session.
- A TV that takes a resolution but refuses the refresh rate is put back after a failed launch.
- Disconnecting a remote monitor can no longer hang the host while its display is being recovered.
Streaming
- The control and input connection survives network errors such as a Wi-Fi roam, an ICMP reply or an oversized datagram. Before, its worker stopped and every later session had no input or control until the host restarted; it now also restarts itself.
- Controller and motion input get their own channels again (Moonlight asks for 48) instead of sharing one with keep-alive messages.
- Delayed keyframe requests and key releases are no longer discarded as replays behind many motion reports (a 4096-message window instead of 64).
- Moonlight's reference frame invalidation requests are read correctly instead of each becoming a full keyframe.
- A frame too large for Moonlight's packet format at very high bitrates is dropped and followed by a keyframe, instead of ending the session.
- Audio delay no longer grows after a scheduling hiccup: a backlog over 30 ms is trimmed back to two packets.
- PyroWave honours the configured pacing limit (
pacing_max_bitrate_kbps), which helps a client behind Wi-Fi, and no longer sends a catch-up burst after a send had to wait. Wired clients keep pacing at 95% of the host's Ethernet link. - The video clock wraps instead of freezing after 13 hours.
Moonlight compatibility
- Joining or resuming a running game from Moonlight for Android or iOS works:
/launchalways answers with a game session. - A wrong PIN no longer blocks pairing for every Moonlight device until the host restarts.
- A device that forgot this PC can pair again; it was refused as a duplicate until removed in the console. Vibepollo profiles that list a device twice now load.
- Quitting a game that is slow to exit no longer stalls other devices' requests.
- Odd client resolutions such as 2556x1179 are rounded down instead of refused.
- A device with the launch permission may resume its own game, as in Vibepollo.
- The app list reports HDR only when a codec has a 10-bit mode.
New in rc.10 #
- Correct HDR state on newer Windows versions. Wide-gamut SDR color management is no longer mistaken for HDR support or active HDR. The host uses Windows' dedicated HDR state and toggle APIs where available, retains the legacy path on older systems, waits for the requested state, and cancels its own pending request after a failed transition. The capture and pacing defaults from rc.9 remain unchanged.
- Restores previously inactive displays when Windows reactivates them during owned virtual-display startup and recovery. A brief guard removes only reactivated targets with the same identity, retains current modes and clone relationships, rejects an observed concurrent layout change, and expires after startup. It does not continuously enforce the desktop or establish that the reporter's phone issue is resolved.
- Verified native virtual-HDR pixels with HEVC and AV1 at 720p60 on the final RX 7900 XT host. Both captured FP16, decoded every frame and passed the unchanged color/freshness gates, with display restoration confirmed. HEVC had no long arrival gaps; AV1 had 31 intervals above 25 ms despite complete picture coverage. This validates the tested pixel path and reproduced display fix, not gap-free AV1 delivery, physical-panel calibration or client scanout.
- Adds a read-only environment collector under
toolsin the portable package. It records the exact host hash, GPU and driver versions, Windows build and network-adapter type without changing settings or uploading anything. This makes hardware-specific reports reproducible; it does not replace a test on the reporter's machine. - Validates DS4 and DualSense touch input through the installed signed driver: production packet decoding, two contacts, movement, release, cancellation, isolation of unsupported secondary pads and device cleanup. A true second touch surface still needs a new signed driver and device profile.
- Resolves the previous CLI test limitation. The unmodified Moonlight PC 6.2.0 client successfully lists apps and quits normally against rc.9 when its window and polling lifecycle are allowed to complete. Cold-cache CSV listing remains a separately reproduced client artwork-shutdown issue; plain listing or cached artwork is the workaround. An optional client source patch under
compatibility/moonlight-6.2.0passed eight focused Qt tests; it still requires a custom client build and Windows validation. - Keeps queue draining disabled after another saturation comparison: it reduced frame age but delivered fewer distinct pictures and more gaps. No additional performance setting was enabled on that evidence.
Current automated validation: 263 ordinary tests passed, with 27 environment-dependent tests excluded by default; formatting, Clippy with warnings denied and release builds passed. This includes eleven HDR-state tests and six virtual-display hotplug tests. Hardware test scope and package verification are recorded separately in PERFORMANCE.md and the release's VALIDATION.json.
New in rc.9 #
- Automatic capture now prefers Windows Graphics Capture. This applies to physical and virtual displays. Existing explicit capture choices are preserved, including imported
dxgiandwgccaliases. WGC uses the signed-in-user capture worker under the Windows service, with Desktop Duplication fallback when WGC cannot open and during lock/UAC desktop transitions. Compute copies remain enabled by default on supported AMD GPUs. - Smoother 60 FPS delivery with lower measured picture age. WGC-selected streams now align encoding to a stable source update when capture reports surplus updates. The guard falls back to ordinary pacing for irregular or slower sources and resets on recovery; explicit Desktop Duplication keeps its previous pacing default. In controlled local 720p60 AV1 runs, guarded pacing delivered 59.862–60.000 distinct FPS versus 57.650 and reduced estimated source-presentation-to-software-decode age by about 3.8 ms on average. This excludes remote network transit, client display scanout and input latency.
frame_pacing_source_phase=falserestores the previous WGC pacing for comparison. - Removes the automatic 1 ms WGC update limit above 60 FPS. Explicit zero restored approximately 121 FPS delivery in the earlier controlled 120 FPS comparison; an explicit override retains the previous interval for diagnosis. Loaded 120 FPS and slow-source checks passed with the phase guard inactive. An uncapped GPU stress test still fell to about 53–54 distinct FPS with either pacing setting, so this is not a cure for GPU saturation. See PERFORMANCE.md for the complete comparisons and failed cases.
- Avoids sending an unchanged frame just before a predicted fresh capture when a high minimum frame rate is configured. The wait has a fixed, short deadline; slower and static sources continue receiving repeats. In the local HEVC comparison with a 60 FPS minimum, fresh delivery improved from 57.700 to 59.793–59.860 FPS. The regular minimum-rate setting also passed at 60 and 120 FPS.
- Fixes H.264 decoding with Moonlight PC 6.2.0. Its Windows D3D11 decoder requests one reference frame. AMF previously enabled four long-term reference slots independently, causing repeated decoder failures and about 52.8 decoded FPS from a 60.3 FPS incoming stream. AMF now reserves long-term references only within the negotiated budget and uses IDR recovery when that budget is one. An incompatible H.264 intra-refresh request also falls back to IDR recovery instead of raising the client's limit. Clients with an unrestricted budget retain long-term-reference recovery. The FFmpeg NVENC fallback now sets its retained-picture buffer limit explicitly; NVIDIA execution still needs hardware validation.
- Tested the exact released Moonlight PC 6.2.0 application. Ten local 1280×720/60 sessions on the RX 7900 XT covered two connections each for H.264, HEVC, AV1, HEVC HDR and AV1 HDR. All selected the expected formats and D3D11 hardware rendering. Incoming and decoded rates matched, rendering remained about 60 FPS, and there were no decoder errors. Streaming windows closed and disconnected normally, and reconnecting worked. These are compatibility checks, separate from the distinct-picture and latency fixture.
- HDR requests and ten-bit codec decoding passed from an SDR desktop. This matrix does not validate native HDR source capture, displayed HDR brightness or colour accuracy. Stock Moonlight 6.2.0 supports the standard codecs; PyroWave and the host's 1000 Hz low-latency VRR mode still require Nonary's client. Moonlight's own client-side VRR rendering is a separate feature.
- Three additional AV1 checks verified Moonlight 6.2.0's existing AMD-padding compensation: 1080p SDR/HDR and 1968×2184 SDR. Client logs confirm the requested crop, hardware decoding and rendering without decoder errors. The underlying AMD bitstream still has padded dimensions; these checks verify the client's crop path, not pixel-edge or HDR display accuracy.
- Handles Moonlight PC 6.2.0's extended-key modifier, distinguishing keypad Enter from ordinary Enter through holds, repeats, release and disconnect cleanup. Existing key remaps and older clients retain their behavior. Controller touch parsing and batching now preserve the touchpad index. The bundled driver supports one touch surface with two contacts; secondary-pad events are safely ignored with a warning and cannot corrupt primary-pad state. Native second-touchpad support remains unavailable. Input was disabled during the streaming matrix, so live input on that application has not been newly validated.
- Follow-up checks on the unchanged rc.9 host verified natural exit of official Moonlight 6.2.0's plain
list, CSV listing with cached artwork, three successive app quits and an idle quit. Paired HTTPS/serverinfoand the web API agreed that each app had stopped. The earlier quit failure came from the harness hiding its window and allowing too little time for its next status poll. Cold-cachelist --csvstill has an upstream artwork-shutdown bug, reproduced without Butterpollo; use plainlistfor automation or CSV after artwork is cached. Streaming-window close and reconnect evidence remains separate. - Validation of the final runtime passed 246 ordinary tests, with 26 environment-dependent tests excluded by default, and Clippy with warnings denied. Four selected native checks separately passed: WGC reconnect/COM teardown, a 64-frame H.264 one-reference SPS/VUI and decode check, packet-loss reference recovery for the three AMD codecs, and exact GPU texture comparison. The ten exact-client connections and three crop checks above are additional checks. Final default-path motion results and capture-worker recovery are recorded separately in PERFORMANCE.md; package integrity and build provenance are recorded with the release assets.
New in rc.8 #
- Fixes "adding the firewall rule failed" when reinstalling after an automatic update. Internal extended-length paths no longer leak into Windows installation entries or firewall commands. Setup updates an existing rule in place and removes legacy allowances only after Butterpollo's rule succeeds, so a rejected replacement keeps the previous rules.
- Fixes RTSS automatic startup when Windows requires administrator privileges (error 740 /
0x800702E4). The installed service first tries a normal user launch, then retries that specific error with the signed-in user's elevated token. RTSS stays in the user's desktop session; the SDK helper remains unelevated. - The same startup path is used during limiter recovery, addressing the elevation failure that left restoration pending in the reporter's rc.6 log. Unrelated launch failures do not trigger an elevated retry. Portable hosts report how to start RTSS manually as administrator.
- Keeps a service-owned RTSS process alive when reconnecting after a failed restoration, and recognizes equivalent installation paths containing dot components or directory junctions.
- Reduces repeated CPU work in WGC's timing predictor by caching statistics when a new frame arrives. Pacing decisions remain unchanged; this is a small CPU optimization.
- Removes an unintended WGC capture throttle at 60 FPS and below by explicitly setting a zero minimum update interval. The untouched Windows default measured 16 ms and delivered only 55–57 capture updates/sec; explicit zero delivered about 217 in the same native background comparison. A brief AV1 background stream then passed the delivery-rate check at 60.585 FPS, versus 58.039 before, with zero decode errors. These checks do not establish distinct-picture delivery or reduced latency. Streams above 60 FPS retain the tested 1 ms request in rc.8, and WGC compute stays enabled by default. Later distinct-picture comparisons are recorded under rc.9 and in PERFORMANCE.md.
- Corrects a diagnostic timestamp that could describe an older frame after memory-address reuse. Adds optional presentation statistics to the motion probe and a polling comparison without frame notifications; these do not change normal streaming.
- Reproduced error 740 locally with RTSS 7.3.5, then verified an earlier rc.8 candidate's automatic startup through the installed Windows service, a measured 59.94 FPS cap, and restoration to 120 FPS on disconnect while Desktop stayed open. All 208 ordinary automated tests and the explicitly selected native WGC interval test passed. Isolated recovery checks cover user edits, absent settings, a pending journal after elevation denial, and reconnection after SDK failure. The final executable is now installed locally: its hash matches the tested candidate, setup completed successfully, the service reports rc.8, and the firewall rule targets the correct executable. The final build's complete limiter lifecycle and confirmation on the reporter's RX 9070 XT remain pending.
New in rc.7 #
- Updates notify first by default. Maintenance offers Install when idle, with automatic installation available as an opt-in under General settings. Release candidates follow the existing prerelease setting.
- The host downloads only the exact official Windows installer and verifies its size and GitHub SHA-256 digest. Downloads yield to new sessions; installation waits for one minute without streams, pending connections, remote monitors or host apps. New connections are held off during the installation handoff.
- The service updater stages and verifies the package, backs up replaced files, checks that the requested host version starts, and restores the previous package files after ordinary copying or startup failures. Settings, devices and drivers stay in place. Failed versions are not retried automatically.
- The update panel now displays release candidates correctly, along with download progress, cancellation and the last installation result. The basic HTML console also exposes update actions.
- Validation covers idle/session admission, cancellation, official HTTPS downloads, checksum failures, authentication and CSRF, isolated file recovery, and desktop/mobile UI states. The 199 ordinary automated tests and a separate official-download test passed. A full live service upgrade still needs validation; automatic installation remains opt-in.
New in rc.6 #
- A client's
virtualDisplay=0request now inherits the host's virtual-display setting, matching Vibepollo. Previously it disabled the configured virtual display, leaving the physical monitor active or failing capture when that monitor was off. A positive request also preserves the configured shared-display mode. Explicit app display choices still apply, and the device's "Always use a virtual display" option takes precedence. - Optional PyroWave capability checks now run in a separate user process. A crash or stall there leaves H.264, HEVC and AV1 available; the worker has a 15-second deadline and is cleaned up on failure. This contains the startup failure investigated in an rc.5 dump: RTSS called into the Vulkan loader during a Wallpaper Engine window callback after the probe's Vulkan modules had unloaded. It does not modify either overlay.
- The earlier AV1 fresh-frame shortfall occurred while Warhammer 3 occupied 92–99% of the GPU. With the game closed, the released rc.5 build delivered 59.84 fresh FPS at a 60 FPS target, decoding all 1,047 received frames without errors. This corrects the earlier performance finding; the RX 9070 XT tester's separate latency comparison remains unverified.
New in rc.5 #
- Fixes RTSS helper communication in service installations. The helper runs as the signed-in user and could not write its reply into the service's protected configuration folder. It now exchanges bounded messages over a private local pipe, with both process identities checked. The service retains responsibility for writing the RTSS profile; no folder permissions or helper privileges are changed.
- RTSS profile updates and restoration can proceed while the RTSS window holds the profile open without delete sharing. Unknown profile settings and keys that were originally absent are preserved. A failed write no longer leaves recovery pending when the profile never changed. Failures now log their actual cause and RTSS path, instead of only reporting that no limiter provider was available.
- Limiter ownership now follows pending and connected streams, independently of retained game displays. Disconnecting the final stream restores the original cap even when the desktop remains available to resume. Reconnecting reapplies the cap before streaming starts; an abandoned launch also releases its changes when it expires.
- Display logs now record the client's virtual-display request, selected display and layout. A capture startup timeout identifies the selected display and suggests a virtual display when streaming with the physical monitor off. The reported Artemide monitor wake-up issue remains under investigation; these diagnostics do not claim to fix it.
- Verified the installed SYSTEM service applying a 59.94 FPS RTSS cap on an RX 7900 XT. A separate SDK reader confirmed the fractional profile and enabled limiter, and a renderer requesting 240 FPS on a 120 Hz virtual display presented about 59.94 FPS. The service also captured a native HDR AV1 stream through the WGC user helper with compute enabled. The RTSS fixture additionally covers restoration, unrelated flags, helper crashes and a bounded timeout for a stalled SDK.
New in rc.4 #
- WGC selected by the installed service now runs capture in a hidden process belonging to the signed-in user. This addresses the
CreateForMonitorerror0x80070424observed when SYSTEM tried to open the per-user Windows capture broker. Three synchronized GPU textures transfer frames to the host without CPU readback; compute copies remain enabled on supported AMD GPUs. - The helper has bounded frame queues, peer identity checks and automatic cleanup. A helper exit reopens capture through the existing recovery path. Lock and UAC desktops select Desktop Duplication, with WGC retried when the normal desktop returns. Logs distinguish requested capture from the actual backend.
- Verified through the installed SYSTEM service on an RX 7900 XT: a signed-in-user helper captured a native HDR virtual display at 2184×1968 for an AV1 120 FPS session, with compute enabled at both ends. The client confirmed correct pictures and responsive input. This confirms the service fix; it is not a controlled 120 FPS performance measurement.
- Portable helper validation passed seven local 720p60 motion cases, including HEVC, AV1, HDR output from an SDR source, and compute disabled. Each sustained 60 fresh pictures per second with no decode errors. Forced helper termination recovered WGC in 357 ms and all 1,249 received pictures decoded successfully. The helper's performance under heavy GPU load remains to be measured.
New in rc.3 #
- A stream configured for WGC now falls back to Desktop Duplication if WGC cannot open at startup, matching capture recovery. Logs preserve the Windows error and identify the fallback. This keeps the stream available; it does not provide service-mode WGC or establish equivalent VRR/frame-generation capture.
- WGC compute copies are now on by default on supported AMD GPUs. On the RX 7900 XT fixture, mean decoded picture age fell from 14.8 to 11.5 ms at idle and from 52.9 to 34.6 ms under heavy GPU load. Loaded fresh-picture delivery was about 3% lower (51.4 to 50.0 FPS); neither path sustained 60 FPS under that stress. Unsupported sharing falls back to graphics copies. Set
wgc_compute_copy=falsein the configuration, restart the host and reconnect to compare;gpu_compute_conversion=falsedisables all compute copies and conversion. These measurements do not establish the RX 9070 XT result. - Capture recovery no longer adds a fixed 150 ms pause. Each active stream releases its old encoder, frames and filters before capture reopens. Failed reopen attempts retain a bounded retry delay.
- If compute cannot share a captured texture, fallback now copies that same frame. Previously a static desktop could stall while waiting for another update. Desktop Duplication also logs its actual format and dimensions on each open to help diagnose restarts.
- Active capture now keeps the display awake, matching Vibepollo. The request ends with the capture worker and restores the thread's previous power requirements. This addresses capture being starved when Windows turns off an idle display.
- Video packet pacing no longer produces catch-up bursts after a late send. It accounts for wire overhead and caps known local Ethernet routes at 80% of link speed. A wired host still cannot infer a Wi-Fi client's capacity; the reported RX 9070 XT latency difference remains under investigation.
New in rc.2 #
- The mouse pointer no longer disappears. Desktop Duplication reports the pointer's shape only when it changes, so every capture restart (a mode change, the secure desktop, another client's display arriving) lost the pointer until it changed shape. It is now carried into the new capture.
- HDR10 metadata in AMF streams. HEVC and AV1 streams from AMD now carry the mastering display and content light level metadata and signal their colour range, as FFmpeg-based hosts do. Verified in the bitstream for HEVC and AV1, limited and full range.
- Host errors are no longer silent. A stream that ends on a host error used to return Moonlight quietly to the app list, as if the stream had been closed on purpose. Moonlight now shows an error with a code, and the host log says why (
session failed). - A packet Windows can't send right away no longer ends the stream. Video and audio packets are dropped instead, as Vibepollo does, and the video socket gets Vibepollo's 1 MB send buffer.
- Measured against Vibepollo 2.0. At 1080p60 HEVC HDR on the same PC with the same encoder settings, the picture reached the client in 13.8 ms against 16.0 ms idle, and in 42.4 ms against 96.4 ms beside a game-like GPU load, with 51 rather than 24 new pictures a second. Decoded HDR colours match the expected values on both hosts.
Install #
rubylight-setup-2.1.0.exeinstalls or upgrades the host, theApolloServiceservice, the virtual display and gamepad drivers, firewall rules and shortcuts, and can uninstall them. Settings, paired devices, the library and covers are kept.- For a portable copy, extract
rubylight-2.1.0-windows-x64.zipand open Start Rubylight.exe. The first launch offers to import a Vibepollo or Apollo profile and leaves the original untouched. Install the drivers separately in that case. - These are unsigned test builds. Keep a copy of your configuration and the previous installer for rollback.
Lower latency #
- On supported AMD GPUs, captured frames are copied and converted on D3D12 compute queues, and AMF encodes from D3D12. This reduces waiting on the game's graphics queue. In the historical full AV1 HDR 1968×2184 120 fps comparison beside a heavy GPU load, the picture arrived 7-9 ms sooner, and 50 rather than 45 new pictures reached the client each second. Idle, it arrived about 0.6 ms sooner; encoding was already at the hardware limit (2.8-3.3 ms).
gpu_compute_conversion(Settings › Capture) turns this off. - Current defaults use WGC capture with guarded source-phase pacing, AMF
speedquality, and a virtual display at twice the stream rate. The earlier Desktop Duplication comparisons remain in PERFORMANCE.md as historical measurements; explicit Desktop Duplication remains available. - Video FEC uses 21-29% less CPU time than the C++ host, with identical output; encrypted packets avoid extra copies.
Vibepollo 2.0 features #
- Moonlight pairing (PIN and one-time PIN), per-device permissions, display modes and overrides,
/bitrate,/unpair, and the virtual display, permission and frame limiter replies of Vibepollo. - H.264, HEVC, AV1, HDR, 10-bit SDR, 4:4:4 where supported, PyroWave and VRR with Nonary's Moonlight client, and reference frame invalidation.
- Per-device virtual displays with exclusive, extended, primary and isolated layouts, golden layout restore and the restore hotkey.
- Steam library sync with covers, and streams that end when the Steam game exits. Playnite through Vibepollo's plugin (shipped in the package), including the fullscreen app. Lossless Scaling profiles and frame generation per app.
- Frame limiting through RTSS and NVIDIA profiles, NVIDIA Smooth Motion and RTX HDR.
- A new web console: overview with performance history, library with Steam and Playnite, devices, settings, logs, maintenance and API tokens.
- Tray notifications for pairing requests, new versions and an app starting, pausing, resuming and stopping, and "Check for updates" in the tray menu; release checks that skip streams.
Left out on purpose: WebRTC streaming, session history and host statistics pages, the SudoVDA fallback, and Linux and macOS hosting.
Details: Features for everything the host does, PERFORMANCE.md for measurements and how to reproduce them.