test(host): Windows on-hardware NVENC reconfigure smoke — 20→60→10 Mbps in place, zero IDRs
ci / web (push) Successful in 1m16s
ci / docs-site (push) Successful in 1m37s
arch / build-publish (push) Successful in 10m16s
decky / build-publish (push) Successful in 36s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
ci / bench (push) Successful in 5m11s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 10s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m2s
android / android (push) Successful in 15m11s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 6m51s
docker / deploy-docs (push) Successful in 16s
deb / build-publish (push) Successful in 12m48s
ci / rust (push) Successful in 16m56s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 12m32s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 12m47s
apple / swift (push) Successful in 4m30s
windows-host / package (push) Successful in 14m43s
apple / screenshots (push) Successful in 21m55s

The Windows twin of nvenc_cuda_reconfigure_no_idr, green on the .173 RTX
box (release profile — the dev-profile test binary trips a pre-existing
LNK2019 on the sdk crate's unused safe EncodeAPI statics, which release
LTO strips).

Chasing this also uncovered why the live A/B kept rebuilding: the
PunktfunkHost service runs C:\Users\Public\punktfunk-native's exe, not
the Developer clone deploy-host.ps1 had been rebuilding.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 20:20:29 +02:00
parent 9bf72cdfb5
commit a87b279c2b
@@ -1966,6 +1966,126 @@ mod tests {
} }
} }
/// ON-HARDWARE (RTX box `.173`): the Phase 3.2 in-place rate retarget — encode a few frames,
/// `reconfigure_bitrate` mid-stream (up AND down), keep encoding, and assert every
/// post-reconfigure AU is a P-frame (`nvEncReconfigureEncoder` with `resetEncoder=0` /
/// `forceIDR=0` must NOT restart the stream). The Windows twin of the Linux backend's
/// `nvenc_cuda_reconfigure_no_idr`. Run:
/// cargo test -p punktfunk-host --features nvenc -- --ignored nvenc_reconfigure --nocapture
#[test]
#[ignore = "requires an NVIDIA GPU + driver — run manually on the RTX box (.173)"]
fn nvenc_reconfigure_no_idr() {
let _ = tracing_subscriber::fmt().with_test_writer().try_init();
const W: u32 = 1280;
const H: u32 = 720;
// SAFETY: (test-only) same straight-line D3D11/DXGI setup as `encode_pattern`.
unsafe {
let factory: IDXGIFactory1 = CreateDXGIFactory1().expect("DXGI factory");
let mut adapter = None;
for i in 0.. {
let Ok(a) = factory.EnumAdapters1(i) else {
break;
};
let desc = a.GetDesc1().expect("adapter desc");
if desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE.0 as u32 == 0 {
adapter = Some(a);
break;
}
}
let adapter = adapter.expect("no hardware DXGI adapter");
let (device, _ctx) = crate::capture::dxgi::make_device(&adapter).expect("make_device");
let bytes = probe_pattern(W as usize, H as usize);
let init = D3D11_SUBRESOURCE_DATA {
pSysMem: bytes.as_ptr() as *const _,
SysMemPitch: W * 4,
SysMemSlicePitch: 0,
};
let desc = D3D11_TEXTURE2D_DESC {
Width: W,
Height: H,
MipLevels: 1,
ArraySize: 1,
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Usage: D3D11_USAGE_DEFAULT,
BindFlags: D3D11_BIND_RENDER_TARGET.0 as u32,
CPUAccessFlags: 0,
MiscFlags: 0,
};
let mut tex = None;
device
.CreateTexture2D(&desc, Some(&init), Some(&mut tex))
.expect("pattern texture");
let tex = tex.expect("null pattern texture");
let mut enc = NvencD3d11Encoder::open(
Codec::H265,
PixelFormat::Bgra,
W,
H,
60,
20_000_000,
8,
ChromaFormat::Yuv420,
)
.expect("NVENC open");
let submit_and_poll = |enc: &mut NvencD3d11Encoder, range: std::ops::Range<u64>| {
let mut keyframes = 0usize;
let mut aus = 0usize;
for i in range {
let frame = CapturedFrame {
width: W,
height: H,
pts_ns: i * 16_666_667,
format: PixelFormat::Bgra,
payload: FramePayload::D3d11(D3d11Frame {
texture: tex.clone(),
device: device.clone(),
}),
};
enc.submit_indexed(&frame, i as u32).expect("submit");
while let Some(au) = enc.poll().expect("poll") {
aus += 1;
keyframes += au.keyframe as usize;
}
}
enc.flush().ok();
while let Ok(Some(au)) = enc.poll() {
aus += 1;
keyframes += au.keyframe as usize;
}
(aus, keyframes)
};
let (aus, kfs) = submit_and_poll(&mut enc, 0..4);
assert!(aus > 0, "no AUs before the reconfigure");
assert_eq!(kfs, 1, "exactly the opening IDR before the reconfigure");
assert!(
enc.reconfigure_bitrate(60_000_000),
"in-place reconfigure to 60 Mbps must succeed on RTX NVENC"
);
let (aus, kfs) = submit_and_poll(&mut enc, 4..8);
assert!(aus > 0, "no AUs after the up-reconfigure");
assert_eq!(kfs, 0, "an in-place rate retarget must not emit an IDR");
assert!(
enc.reconfigure_bitrate(10_000_000),
"in-place reconfigure down to 10 Mbps must succeed"
);
let (aus, kfs) = submit_and_poll(&mut enc, 8..12);
assert!(aus > 0, "no AUs after the down-reconfigure");
assert_eq!(kfs, 0, "an in-place rate retarget must not emit an IDR");
println!("nvenc (Windows) reconfigure smoke: 20→60→10 Mbps in place, zero IDRs");
}
}
/// ON-GLASS (RTX box): the measurement gating the AYUV 4:4:4 work — encodes the probe /// ON-GLASS (RTX box): the measurement gating the AYUV 4:4:4 work — encodes the probe
/// pattern through the REAL ARGB-input NVENC session once with `chromaFormatIDC=3`/FREXT /// pattern through the REAL ARGB-input NVENC session once with `chromaFormatIDC=3`/FREXT
/// and once as plain 4:2:0, so offline analysis of the two bitstreams answers (1) whether /// and once as plain 4:2:0, so offline analysis of the two bitstreams answers (1) whether