Add native self-hosted instance connection to fluxer_desktop
Trimmed monorepo checkout (fluxer_desktop + packages/voice_engine_v2 + tools/ci) with a "Connect to a Different Server" menu item and popout that lets the desktop app switch to any self-hosted Fluxer instance, plus fixes for well-known discovery on single-domain self-hosted deployments and a false-positive ERR_ABORTED on same-origin client redirects during the switch. Defaults to chat.fluxr.chat and uses an isolated userData directory from the official build.
This commit is contained in:
@@ -0,0 +1,551 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
#![cfg(target_os = "windows")]
|
||||
use fluxer_encoder_ring::NVENC_COMPLETION_RING_CAPACITY;
|
||||
use fluxer_encoder_ring::encoder_handoff::EncoderCompletionCallback;
|
||||
use fluxer_encoder_ring::{
|
||||
AmfD3D11Handoff, AmfHandoff, EncodedBitstream, EncoderDims, EncoderError, EncoderSubmission,
|
||||
NvencD3D11Handoff, NvencHandoff, PicParams, QsvD3D11Handoff, QsvHandoff, RingError,
|
||||
};
|
||||
use windows::Win32::Graphics::Direct3D::{D3D_DRIVER_TYPE_UNKNOWN, D3D_FEATURE_LEVEL_11_0};
|
||||
use windows::Win32::Graphics::Direct3D11::{
|
||||
D3D11_BIND_RENDER_TARGET, D3D11_BIND_SHADER_RESOURCE, D3D11_CREATE_DEVICE_BGRA_SUPPORT,
|
||||
D3D11_CREATE_DEVICE_VIDEO_SUPPORT, D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX,
|
||||
D3D11_RESOURCE_MISC_SHARED_NTHANDLE, D3D11_SDK_VERSION, D3D11_TEXTURE2D_DESC,
|
||||
D3D11_USAGE_DEFAULT, D3D11CreateDevice, ID3D11Device, ID3D11Multithread,
|
||||
};
|
||||
use windows::Win32::Graphics::Dxgi::Common::{DXGI_FORMAT_NV12, DXGI_SAMPLE_DESC};
|
||||
use windows::Win32::Graphics::Dxgi::{
|
||||
CreateDXGIFactory1, IDXGIAdapter1, IDXGIFactory1, IDXGIResource1,
|
||||
};
|
||||
use windows::core::Interface;
|
||||
|
||||
const VENDOR_NVIDIA: u32 = 0x10DE;
|
||||
const VENDOR_AMD: u32 = 0x1002;
|
||||
const VENDOR_INTEL: u32 = 0x8086;
|
||||
|
||||
fn enum_adapters() -> Vec<IDXGIAdapter1> {
|
||||
let factory_result: windows::core::Result<IDXGIFactory1> = unsafe { CreateDXGIFactory1() };
|
||||
let factory = match factory_result {
|
||||
Ok(f) => f,
|
||||
Err(_) => return Vec::new(),
|
||||
};
|
||||
let mut out = Vec::new();
|
||||
let mut index: u32 = 0;
|
||||
loop {
|
||||
let result: windows::core::Result<IDXGIAdapter1> = unsafe { factory.EnumAdapters1(index) };
|
||||
match result {
|
||||
Ok(adapter) => {
|
||||
out.push(adapter);
|
||||
index = index.saturating_add(1);
|
||||
}
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn try_create_device_for_vendor(vendor_id: u32) -> Option<ID3D11Device> {
|
||||
let adapters = enum_adapters();
|
||||
for adapter in adapters {
|
||||
let desc = match unsafe { adapter.GetDesc1() } {
|
||||
Ok(d) => d,
|
||||
Err(_) => continue,
|
||||
};
|
||||
if desc.VendorId != vendor_id {
|
||||
continue;
|
||||
}
|
||||
let mut device: Option<ID3D11Device> = None;
|
||||
let feature_levels = [D3D_FEATURE_LEVEL_11_0];
|
||||
let flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_VIDEO_SUPPORT;
|
||||
let result = unsafe {
|
||||
D3D11CreateDevice(
|
||||
Some(&adapter.cast().ok()?),
|
||||
D3D_DRIVER_TYPE_UNKNOWN,
|
||||
Default::default(),
|
||||
flags,
|
||||
Some(&feature_levels),
|
||||
D3D11_SDK_VERSION,
|
||||
Some(&mut device),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
};
|
||||
if let (Ok(()), Some(d)) = (result, device) {
|
||||
if let Ok(mt) = d.cast::<ID3D11Multithread>() {
|
||||
let _ = unsafe { mt.SetMultithreadProtected(true) };
|
||||
}
|
||||
return Some(d);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn try_create_device() -> Option<ID3D11Device> {
|
||||
try_create_device_for_vendor(VENDOR_NVIDIA)
|
||||
.or_else(|| try_create_device_for_vendor(VENDOR_INTEL))
|
||||
.or_else(|| try_create_device_for_vendor(VENDOR_AMD))
|
||||
}
|
||||
|
||||
fn try_create_shared_nv12(device: &ID3D11Device, width: u32, height: u32) -> Option<u64> {
|
||||
let desc = D3D11_TEXTURE2D_DESC {
|
||||
Width: width,
|
||||
Height: height,
|
||||
MipLevels: 1,
|
||||
ArraySize: 1,
|
||||
Format: DXGI_FORMAT_NV12,
|
||||
SampleDesc: DXGI_SAMPLE_DESC {
|
||||
Count: 1,
|
||||
Quality: 0,
|
||||
},
|
||||
Usage: D3D11_USAGE_DEFAULT,
|
||||
BindFlags: (D3D11_BIND_RENDER_TARGET.0 | D3D11_BIND_SHADER_RESOURCE.0) as u32,
|
||||
CPUAccessFlags: 0,
|
||||
MiscFlags: (D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX.0 | D3D11_RESOURCE_MISC_SHARED_NTHANDLE.0)
|
||||
as u32,
|
||||
};
|
||||
let mut texture = None;
|
||||
unsafe {
|
||||
device
|
||||
.CreateTexture2D(&desc, None, Some(&mut texture))
|
||||
.ok()?;
|
||||
}
|
||||
let texture = texture?;
|
||||
let resource: IDXGIResource1 = texture.cast().ok()?;
|
||||
let shared = unsafe {
|
||||
resource
|
||||
.CreateSharedHandle(None, 0x3, windows::core::PCWSTR::null())
|
||||
.ok()?
|
||||
};
|
||||
Some(shared.0 as u64)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nvenc_real_encode_one_frame_via_dummy_shared_texture() {
|
||||
let device = match try_create_device_for_vendor(VENDOR_NVIDIA) {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("skip: no NVIDIA D3D11 device available");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let dims = EncoderDims::new(1920, 1080);
|
||||
let handoff_result = NvencD3D11Handoff::new(device.clone(), dims, 5_000_000);
|
||||
let mut handoff = match handoff_result {
|
||||
Ok(h) => h,
|
||||
Err(EncoderError::SdkNotFound { .. }) => {
|
||||
eprintln!("skip: NVENC runtime DLL not available");
|
||||
return;
|
||||
}
|
||||
Err(EncoderError::SessionInitFailed { vendor, status }) => {
|
||||
eprintln!("skip: NVENC session init failed: {vendor} status={status}");
|
||||
return;
|
||||
}
|
||||
Err(other) => {
|
||||
eprintln!("skip: NVENC init unexpected error: {other:?}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let shared = match try_create_shared_nv12(&device, 1920, 1080) {
|
||||
Some(h) => h,
|
||||
None => {
|
||||
eprintln!("skip: shared NV12 texture creation failed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let slot = match NvencHandoff::register_slot(&mut handoff, shared, 0, dims) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("skip: register_slot failed: {e:?}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let pic = PicParams::new(0, true);
|
||||
let submit_result = NvencHandoff::encode_shared_async(&mut handoff, slot, 0, dims, pic);
|
||||
if submit_result.is_err() {
|
||||
eprintln!("skip: encode_shared_async failed: {submit_result:?}");
|
||||
NvencHandoff::unregister_slot(&mut handoff, slot);
|
||||
return;
|
||||
}
|
||||
let mut bitstream = None;
|
||||
for _ in 0..200 {
|
||||
bitstream = NvencHandoff::poll_completed(&mut handoff, slot);
|
||||
if bitstream.is_some() {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
NvencHandoff::unregister_slot(&mut handoff, slot);
|
||||
let bs = match bitstream {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
eprintln!("nvenc: no bitstream produced within timeout; backend init succeeded");
|
||||
return;
|
||||
}
|
||||
};
|
||||
assert!(!bs.data.is_empty(), "non-empty bitstream");
|
||||
assert!(bs.is_keyframe, "first frame must be keyframe");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn amf_real_encode_one_frame_via_dummy_shared_texture() {
|
||||
let device = match try_create_device_for_vendor(VENDOR_AMD) {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("skip: no AMD D3D11 device available (expected on Intel/NVIDIA-only boxes)");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let dims = EncoderDims::new(1920, 1080);
|
||||
let handoff_result = AmfD3D11Handoff::new(device.clone(), dims, 5_000_000);
|
||||
let mut handoff = match handoff_result {
|
||||
Ok(h) => h,
|
||||
Err(EncoderError::SdkNotFound { .. }) => {
|
||||
eprintln!("skip: AMF runtime DLL not available (expected on non-AMD hardware)");
|
||||
return;
|
||||
}
|
||||
Err(other) => {
|
||||
eprintln!("skip: AMF init unexpected: {other:?}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let shared = match try_create_shared_nv12(&device, 1920, 1080) {
|
||||
Some(h) => h,
|
||||
None => {
|
||||
eprintln!("skip: shared NV12 texture creation failed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let slot = match AmfHandoff::register_slot(&mut handoff, shared, 0, dims) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("skip: AMF register_slot failed: {e:?}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let pic = PicParams::new(0, true);
|
||||
let _ = AmfHandoff::encode_shared_async(&mut handoff, slot, 0, dims, pic);
|
||||
let mut bitstream = None;
|
||||
for _ in 0..200 {
|
||||
bitstream = AmfHandoff::poll_completed(&mut handoff, slot);
|
||||
if bitstream.is_some() {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
AmfHandoff::unregister_slot(&mut handoff, slot);
|
||||
if let Some(bs) = bitstream {
|
||||
assert!(!bs.data.is_empty(), "non-empty AMF bitstream");
|
||||
} else {
|
||||
eprintln!("amf: no bitstream produced within timeout; backend init succeeded");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qsv_modern_dispatcher_session_init_succeeds_on_real_iris_xe() {
|
||||
let device = match try_create_device_for_vendor(VENDOR_INTEL) {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("skip: no Intel D3D11 device available");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let dims = EncoderDims::new(1280, 720);
|
||||
let handoff_result = QsvD3D11Handoff::new(device, dims, 5_000_000);
|
||||
match handoff_result {
|
||||
Ok(_) => {
|
||||
eprintln!("qsv: modern dispatcher session + SetHandle + encode_init succeeded");
|
||||
}
|
||||
Err(EncoderError::SdkNotFound { .. }) => {
|
||||
eprintln!("skip: QSV runtime DLL not available");
|
||||
}
|
||||
Err(e) => {
|
||||
panic!("qsv modern dispatcher init MUST succeed on Tiger Lake Iris Xe: {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qsv_real_encode_one_frame_via_dummy_shared_texture() {
|
||||
let device = match try_create_device_for_vendor(VENDOR_INTEL) {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("skip: no Intel D3D11 device available");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let dims = EncoderDims::new(1920, 1080);
|
||||
let handoff_result = QsvD3D11Handoff::new(device.clone(), dims, 5_000_000);
|
||||
let mut handoff = match handoff_result {
|
||||
Ok(h) => h,
|
||||
Err(EncoderError::SdkNotFound { .. }) => {
|
||||
eprintln!("skip: QSV runtime DLL not available");
|
||||
return;
|
||||
}
|
||||
Err(EncoderError::SessionInitFailed { vendor, status }) => {
|
||||
eprintln!("skip: QSV session init failed: {vendor} status={status}");
|
||||
return;
|
||||
}
|
||||
Err(other) => {
|
||||
eprintln!("skip: QSV init unexpected: {other:?}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let shared = match try_create_shared_nv12(&device, 1920, 1080) {
|
||||
Some(h) => h,
|
||||
None => {
|
||||
eprintln!("skip: shared NV12 texture creation failed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let slot = match QsvHandoff::register_slot(&mut handoff, shared, 0, dims) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("skip: QSV register_slot failed: {e:?}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let pic = PicParams::new(0, true);
|
||||
let _ = QsvHandoff::encode_shared_async(&mut handoff, slot, 0, dims, pic);
|
||||
let mut bitstream = None;
|
||||
for _ in 0..200 {
|
||||
bitstream = QsvHandoff::poll_completed(&mut handoff, slot);
|
||||
if bitstream.is_some() {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
QsvHandoff::unregister_slot(&mut handoff, slot);
|
||||
if let Some(bs) = bitstream {
|
||||
assert!(!bs.data.is_empty(), "non-empty QSV bitstream");
|
||||
} else {
|
||||
eprintln!("qsv: no bitstream produced within timeout; backend init succeeded");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_dont_block_rapid_submit_then_poll_never_hangs() {
|
||||
let device = match try_create_device() {
|
||||
Some(d) => d,
|
||||
None => return,
|
||||
};
|
||||
let dims = EncoderDims::new(640, 360);
|
||||
let mut handoff = match NvencD3D11Handoff::new(device.clone(), dims, 1_000_000) {
|
||||
Ok(h) => h,
|
||||
Err(_) => return,
|
||||
};
|
||||
let shared = match try_create_shared_nv12(&device, 640, 360) {
|
||||
Some(h) => h,
|
||||
None => return,
|
||||
};
|
||||
let slot = match NvencHandoff::register_slot(&mut handoff, shared, 0, dims) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return,
|
||||
};
|
||||
let start = std::time::Instant::now();
|
||||
for i in 0..10 {
|
||||
let pic = PicParams::new((i as u64) * 16_666, false);
|
||||
let _ = NvencHandoff::encode_shared_async(&mut handoff, slot, 0, dims, pic);
|
||||
let _ = NvencHandoff::poll_completed(&mut handoff, slot);
|
||||
}
|
||||
let elapsed = start.elapsed();
|
||||
assert!(
|
||||
elapsed.as_secs() < 5,
|
||||
"10 submit+poll cycles must complete in < 5s, was {elapsed:?}"
|
||||
);
|
||||
NvencHandoff::unregister_slot(&mut handoff, slot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cleanup_on_drop_releases_resources() {
|
||||
let device = match try_create_device() {
|
||||
Some(d) => d,
|
||||
None => return,
|
||||
};
|
||||
let dims = EncoderDims::new(640, 360);
|
||||
{
|
||||
let _handoff = match NvencD3D11Handoff::new(device.clone(), dims, 1_000_000) {
|
||||
Ok(h) => h,
|
||||
Err(_) => return,
|
||||
};
|
||||
}
|
||||
let _handoff2 = NvencD3D11Handoff::new(device.clone(), dims, 1_000_000);
|
||||
}
|
||||
|
||||
struct CountingCallback {
|
||||
seen: Vec<(u64, u32)>,
|
||||
}
|
||||
|
||||
impl EncoderCompletionCallback for CountingCallback {
|
||||
fn on_complete(&mut self, sequence: u64, encoded_bytes: u32) {
|
||||
self.seen.push((sequence, encoded_bytes));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nvenc_compression_handoff_encodes_one_frame_via_keyed_mutex() {
|
||||
let device = match try_create_device_for_vendor(VENDOR_NVIDIA) {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("skip: no NVIDIA D3D11 device available");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let dims = EncoderDims::new(1280, 720);
|
||||
let mut handoff = match NvencD3D11Handoff::new(device.clone(), dims, 2_000_000) {
|
||||
Ok(h) => h,
|
||||
Err(EncoderError::SdkNotFound { .. }) => {
|
||||
eprintln!("skip: NVENC runtime DLL not available");
|
||||
return;
|
||||
}
|
||||
Err(other) => {
|
||||
eprintln!("skip: NVENC init unexpected: {other:?}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let shared = match try_create_shared_nv12(&device, 1280, 720) {
|
||||
Some(h) => h,
|
||||
None => {
|
||||
eprintln!("skip: shared NV12 texture creation failed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let submission = EncoderSubmission::new(shared, 0, dims, 1);
|
||||
let mut callback = CountingCallback { seen: Vec::new() };
|
||||
NvencHandoff::encode_shared(&mut handoff, submission, &mut callback)
|
||||
.expect("encode_shared first frame ok");
|
||||
assert_eq!(callback.seen.len(), 1, "callback fired once");
|
||||
assert_eq!(callback.seen[0].0, 1, "callback sequence matches");
|
||||
let mut payload: Option<EncodedBitstream> = None;
|
||||
for _ in 0..200 {
|
||||
let slot = fluxer_encoder_ring::HandoffSlot::new(0, shared);
|
||||
payload = NvencHandoff::poll_completed(&mut handoff, slot);
|
||||
if payload.is_some() {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
let bs = match payload {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
eprintln!("nvenc keyed-mutex: no bitstream within timeout; init succeeded");
|
||||
return;
|
||||
}
|
||||
};
|
||||
assert!(!bs.data.is_empty(), "keyed-mutex encoded payload non-empty");
|
||||
assert!(bs.is_keyframe, "first frame must be IDR keyframe");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nvenc_compression_handoff_handles_back_pressure() {
|
||||
let device = match try_create_device_for_vendor(VENDOR_NVIDIA) {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("skip: no NVIDIA D3D11 device available");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let dims = EncoderDims::new(640, 360);
|
||||
let mut handoff = match NvencD3D11Handoff::new(device.clone(), dims, 1_000_000) {
|
||||
Ok(h) => h,
|
||||
Err(EncoderError::SdkNotFound { .. }) => {
|
||||
eprintln!("skip: NVENC runtime DLL not available");
|
||||
return;
|
||||
}
|
||||
Err(other) => {
|
||||
eprintln!("skip: NVENC init unexpected: {other:?}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let shared = match try_create_shared_nv12(&device, 640, 360) {
|
||||
Some(h) => h,
|
||||
None => {
|
||||
eprintln!("skip: shared NV12 texture creation failed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let total: u64 = (NVENC_COMPLETION_RING_CAPACITY as u64) + 8;
|
||||
let mut callback = CountingCallback { seen: Vec::new() };
|
||||
let mut accepted = 0usize;
|
||||
let mut full_drops = 0usize;
|
||||
for seq in 1..=total {
|
||||
let submission = EncoderSubmission::new(shared, 0, dims, seq);
|
||||
match NvencHandoff::encode_shared(&mut handoff, submission, &mut callback) {
|
||||
Ok(()) => {
|
||||
accepted += 1;
|
||||
}
|
||||
Err(RingError::FullDropped { .. }) => {
|
||||
full_drops += 1;
|
||||
assert_eq!(
|
||||
handoff.pending_completion(),
|
||||
NVENC_COMPLETION_RING_CAPACITY,
|
||||
"pre-encode drop only when completion ring is full"
|
||||
);
|
||||
}
|
||||
Err(other) => panic!("unexpected encode_shared error: {other:?}"),
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(2));
|
||||
}
|
||||
assert_eq!(
|
||||
accepted + full_drops,
|
||||
total as usize,
|
||||
"every submission accounted"
|
||||
);
|
||||
let pending = handoff.pending_completion();
|
||||
assert!(
|
||||
pending <= NVENC_COMPLETION_RING_CAPACITY,
|
||||
"completion ring stays bounded"
|
||||
);
|
||||
let mut drained = 0usize;
|
||||
for _ in 0..400 {
|
||||
let slot = fluxer_encoder_ring::HandoffSlot::new(0, shared);
|
||||
let bs = NvencHandoff::poll_completed(&mut handoff, slot);
|
||||
match bs {
|
||||
Some(_) => {
|
||||
drained += 1;
|
||||
}
|
||||
None => {
|
||||
if drained >= accepted {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
drained, accepted,
|
||||
"no accepted frame is silently discarded post-encode"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nvenc_attach_then_detach_smoke() {
|
||||
let device = match try_create_device_for_vendor(VENDOR_NVIDIA) {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
eprintln!("skip: no NVIDIA D3D11 device available");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let dims = EncoderDims::new(640, 360);
|
||||
let handoff_result = NvencD3D11Handoff::new(device.clone(), dims, 1_000_000);
|
||||
let handoff = match handoff_result {
|
||||
Ok(h) => h,
|
||||
Err(EncoderError::SdkNotFound { .. }) => {
|
||||
eprintln!("skip: NVENC runtime DLL not available");
|
||||
return;
|
||||
}
|
||||
Err(other) => {
|
||||
eprintln!("skip: NVENC init unexpected: {other:?}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
assert_eq!(
|
||||
handoff.pending_completion(),
|
||||
0,
|
||||
"fresh handoff has empty ring"
|
||||
);
|
||||
assert_eq!(handoff.completed_count(), 0, "fresh handoff completed=0");
|
||||
drop(handoff);
|
||||
let _again = NvencD3D11Handoff::new(device, dims, 1_000_000);
|
||||
}
|
||||
Reference in New Issue
Block a user