7649ccb66b
apple / swift (push) Successful in 1m26s
ci / rust (push) Failing after 38s
ci / web (push) Successful in 52s
windows-host / package (push) Successful in 7m26s
apple / screenshots (push) Successful in 6m8s
decky / build-publish (push) Successful in 13s
android / android (push) Successful in 4m58s
arch / build-publish (push) Successful in 5m31s
ci / docs-site (push) Successful in 1m1s
deb / build-publish (push) Successful in 3m20s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 6s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
ci / bench (push) Successful in 4m52s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 10m11s
docker / deploy-docs (push) Successful in 18s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 10m11s
Direct-SDK AMF encoder (encode/windows/amf.rs), the AMD analogue of the direct-NVENC path, replacing the libavcodec *_amf dispatch. C-vtable FFI pinned to AMF headers v1.4.36, runtime-loaded from the driver's amfrt64.dll (no build feature, no new dependency) exactly as NVENC loads its DLL. - AVC/HEVC (SDR NV12 + 10-bit HDR P010) and AV1 (RDNA3+, probed); a bounded poll retires the libavcodec ~2-frame output hold; native in-place reset(). - Intra-refresh wave (PUNKTFUNK_INTRA_REFRESH), in-band HDR mastering/CLL metadata (*InHDRMetadata -> HEVC SEI / AV1 OBU), and a native codec probe feeding the GameStream advertisement (windows_backend_is_ffmpeg -> windows_backend_is_probed). - AMD dispatch / advertisement / 4:4:4 are native-only; the libavcodec AMF fallback and the PUNKTFUNK_AMF_FFMPEG hatch are removed. FFmpeg serves QSV only (its AMF path retained solely as the latency A/B comparator). - Overload back-pressure: submit bounds in-flight surfaces below the input ring, draining finished AUs (buffered for poll, FIFO-preserved) to free a slot and retry on AMF_INPUT_FULL instead of tearing the encoder down and forcing an IDR; this also closes a latent ring-overwrite corruption seen under load on-glass. Validated on the lab Ryzen iGPU (AMF runtime 1.4.37): HEVC/AVC across a native reset, HEVC Main10 mastering+CLL SEIs byte-verified, intra-refresh accepted, a backpressure burst FIFO-clean, and end-to-end via the macOS client. Measured §5.2 latency A/B: native encode_us p50 ~5 ms (0.31 frame periods) vs libavcodec ~17 ms (1.01). 4:4:4 stays unsupported (VCN hardware limit). Live-gated tests skip cleanly on non-AMD boxes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
170 lines
6.8 KiB
Rust
170 lines
6.8 KiB
Rust
//! The `/serverinfo` capability/status XML Moonlight GETs before pairing and each launch.
|
|
|
|
use super::{Host, APP_VERSION, GFE_VERSION, SERVER_CODEC_MODE_SUPPORT};
|
|
|
|
/// Build the `<root status_code="200">…</root>` serverinfo document. `https` selects the
|
|
/// paired-HTTPS variant (real MAC); `paired` is whether the HTTPS peer presented a client cert
|
|
/// that is in the paired allow-list (drives `PairStatus`). Element names are case-sensitive and
|
|
/// match what moonlight-common-c parses.
|
|
pub fn serverinfo_xml(host: &Host, https: bool, paired: bool) -> String {
|
|
// MAC is hidden over plain HTTP (no per-client identity there).
|
|
let mac = if https {
|
|
"01:02:03:04:05:06"
|
|
} else {
|
|
"00:00:00:00:00:00"
|
|
};
|
|
// PairStatus reflects the real allow-list: 1 only when the HTTPS peer's client-cert
|
|
// fingerprint is pinned (the nvhttp handler computes `paired`); 0 otherwise (incl. plain HTTP).
|
|
let pair_status = u8::from(paired);
|
|
let codec_mode_support = codec_mode_support();
|
|
format!(
|
|
r#"<?xml version="1.0" encoding="utf-8"?>
|
|
<root status_code="200">
|
|
<hostname>{hostname}</hostname>
|
|
<appversion>{APP_VERSION}</appversion>
|
|
<GfeVersion>{GFE_VERSION}</GfeVersion>
|
|
<uniqueid>{uniqueid}</uniqueid>
|
|
<HttpsPort>{https_port}</HttpsPort>
|
|
<ExternalPort>{http_port}</ExternalPort>
|
|
<MaxLumaPixelsHEVC>1869449984</MaxLumaPixelsHEVC>
|
|
<mac>{mac}</mac>
|
|
<LocalIP>{local_ip}</LocalIP>
|
|
<ServerCodecModeSupport>{codec_mode_support}</ServerCodecModeSupport>
|
|
<PairStatus>{pair_status}</PairStatus>
|
|
<currentgame>0</currentgame>
|
|
<state>SUNSHINE_SERVER_FREE</state>
|
|
</root>
|
|
"#,
|
|
hostname = host.hostname,
|
|
uniqueid = host.uniqueid,
|
|
https_port = host.https_port,
|
|
http_port = host.http_port,
|
|
local_ip = host.local_ip,
|
|
)
|
|
}
|
|
|
|
/// The `<ServerCodecModeSupport>` mask to advertise: the SDR baseline ([`base_codec_mode_support`]) plus
|
|
/// the HEVC Main10 (HDR) bit when the host can actually deliver HDR ([`apply_hdr`] /
|
|
/// [`crate::gamestream::host_hdr_capable`]). Without the Main10 bit Moonlight never offers its HDR
|
|
/// toggle; with it, enabling HDR client-side negotiates Main10 and the IDD-push path streams BT.2020 PQ.
|
|
fn codec_mode_support() -> u32 {
|
|
apply_hdr(
|
|
base_codec_mode_support(),
|
|
crate::gamestream::host_hdr_capable(),
|
|
)
|
|
}
|
|
|
|
/// Add the HEVC Main10 (HDR) bit to `base` when `hdr` and HEVC is advertised — pure so the
|
|
/// HDR-layering is unit-testable without a GPU. (HDR streaming uses HEVC Main10; AV1 Main10 is left
|
|
/// off until the GameStream AV1 path is live-confirmed.)
|
|
fn apply_hdr(base: u32, hdr: bool) -> u32 {
|
|
if hdr && base & super::SCM_HEVC != 0 {
|
|
base | super::SCM_HEVC_MAIN10
|
|
} else {
|
|
base
|
|
}
|
|
}
|
|
|
|
/// The **SDR baseline** mask. On the VAAPI (AMD/Intel) backend it reflects what the GPU can ACTUALLY
|
|
/// encode (probed — AV1 is narrow, and an old iGPU might lack HEVC), so a Moonlight client never
|
|
/// negotiates a codec the encoder can't open. NVENC and the GPU-less software path keep the
|
|
/// Moonlight-validated static superset. HDR (Main10) is layered on by [`codec_mode_support`].
|
|
fn base_codec_mode_support() -> u32 {
|
|
#[cfg(target_os = "linux")]
|
|
if crate::encode::linux_zero_copy_is_vaapi() {
|
|
if let Some(m) = probed_mask(crate::encode::vaapi_codec_support()) {
|
|
return m;
|
|
}
|
|
}
|
|
// Windows AMD/Intel (AMF/QSV): advertise only what the GPU actually encodes (AV1 is narrow, an
|
|
// old iGPU might lack HEVC). AMF probes natively (no build feature needed); QSV needs the
|
|
// libavcodec build. NVENC and the GPU-less software path keep the static superset.
|
|
#[cfg(target_os = "windows")]
|
|
if crate::encode::windows_backend_is_probed() {
|
|
if let Some(m) = probed_mask(crate::encode::windows_codec_support()) {
|
|
return m;
|
|
}
|
|
}
|
|
SERVER_CODEC_MODE_SUPPORT
|
|
}
|
|
|
|
/// Turn a probed [`CodecSupport`](crate::encode::CodecSupport) into a `ServerCodecModeSupport` mask,
|
|
/// or `None` if the probe found nothing — meaning the GPU wasn't usable at probe time (GPU-less CI,
|
|
/// a misconfigured/wrong-vendor host), NOT that it encodes zero codecs; the caller then advertises
|
|
/// the static superset (pre-probe behaviour) rather than claiming nothing.
|
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
fn probed_mask(caps: crate::encode::CodecSupport) -> Option<u32> {
|
|
use super::{SCM_AV1_MAIN8, SCM_H264, SCM_HEVC};
|
|
let mut m = 0;
|
|
if caps.h264 {
|
|
m |= SCM_H264;
|
|
}
|
|
if caps.h265 {
|
|
m |= SCM_HEVC;
|
|
}
|
|
if caps.av1 {
|
|
m |= SCM_AV1_MAIN8;
|
|
}
|
|
(m != 0).then_some(m)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::gamestream::{SCM_AV1_MAIN8, SCM_H264, SCM_HEVC, SCM_HEVC_MAIN10};
|
|
|
|
/// The advertised codec mask: H.264 + HEVC + AV1 Main8 (= 65793), and explicitly *no*
|
|
/// 10-bit bits — Moonlight gates its HDR mode on those, which we can't deliver (8-bit
|
|
/// SDR capture). Flag values are moonlight-common-c `Limelight.h`.
|
|
#[test]
|
|
fn codec_mode_support_mask() {
|
|
assert_eq!(SERVER_CODEC_MODE_SUPPORT, 0x1 | 0x100 | 0x10000);
|
|
assert_eq!(SERVER_CODEC_MODE_SUPPORT, 65793);
|
|
assert_eq!(
|
|
SERVER_CODEC_MODE_SUPPORT & SCM_HEVC_MAIN10,
|
|
0,
|
|
"no 10-bit/HDR claim"
|
|
);
|
|
assert_eq!(
|
|
SERVER_CODEC_MODE_SUPPORT,
|
|
SCM_H264 | SCM_HEVC | SCM_AV1_MAIN8
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn apply_hdr_adds_main10_only_when_capable_and_hevc() {
|
|
// HDR-capable + HEVC advertised → Main10 added.
|
|
assert_eq!(
|
|
apply_hdr(SCM_H264 | SCM_HEVC | SCM_AV1_MAIN8, true),
|
|
SCM_H264 | SCM_HEVC | SCM_AV1_MAIN8 | SCM_HEVC_MAIN10
|
|
);
|
|
// Not HDR-capable → baseline unchanged (no HDR claim).
|
|
assert_eq!(
|
|
apply_hdr(SCM_H264 | SCM_HEVC | SCM_AV1_MAIN8, false),
|
|
SCM_H264 | SCM_HEVC | SCM_AV1_MAIN8
|
|
);
|
|
// HDR-capable but a GPU with no HEVC at all → no Main10 (you can't do Main10 without HEVC).
|
|
assert_eq!(apply_hdr(SCM_H264, true), SCM_H264);
|
|
}
|
|
|
|
#[test]
|
|
fn serverinfo_xml_carries_codec_mask() {
|
|
let host = Host {
|
|
hostname: "test".into(),
|
|
uniqueid: "uid".into(),
|
|
local_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST),
|
|
http_port: 47989,
|
|
https_port: 47984,
|
|
};
|
|
let xml = serverinfo_xml(&host, false, false);
|
|
// The mask is the GPU-aware value (NVENC/no-GPU → the static 65793; a VAAPI host →
|
|
// whatever it probes). Assert the XML embeds exactly what `codec_mode_support()` returns,
|
|
// so the test is deterministic regardless of the build host's GPU.
|
|
let mask = codec_mode_support();
|
|
assert!(mask != 0, "must advertise at least one codec");
|
|
assert!(xml.contains(&format!(
|
|
"<ServerCodecModeSupport>{mask}</ServerCodecModeSupport>"
|
|
)));
|
|
}
|
|
}
|