use alpha2_gpu::GpuContext;
use alpha2_native_gpu::SharedD3D12Context;
use crate::adapter_choice::{AdapterPolicy, AdapterRecord, choose_adapter, decode_refusals};
pub struct Dx12Choice {
pub gpu: GpuContext,
pub shared: Option<Result<SharedD3D12Context, String>>,
pub decode_refused: Option<String>,
pub records: Vec<AdapterRecord>,
pub chosen: Option<usize>,
}
impl Dx12Choice {
pub const fn without_scan(gpu: GpuContext) -> Self {
Self {
gpu,
shared: None,
decode_refused: None,
records: Vec::new(),
chosen: None,
}
}
}
pub enum ScanError {
Unavailable(alpha2_gpu::GpuError),
Pin(String),
}
impl std::fmt::Debug for ScanError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unavailable(error) => write!(formatter, "{error:?}"),
Self::Pin(reason) => formatter.write_str(reason),
}
}
}
struct Opened {
gpu: GpuContext,
shared: Option<Result<SharedD3D12Context, String>>,
}
pub async fn scan(
policy: &AdapterPolicy,
decode: bool,
encode: bool,
) -> Result<Dx12Choice, ScanError> {
let exhaustive = encode || *policy != AdapterPolicy::Default;
let mut records = Vec::new();
let mut opened: Vec<Option<Opened>> = Vec::new();
for (index, adapter) in GpuContext::adapters_by_preference_on(wgpu::Backends::DX12).await {
let info = adapter.get_info();
let software = alpha2_native_gpu::d3d12_adapter_identity(&adapter)
.map_or(info.device_type == wgpu::DeviceType::Cpu, |identity| {
identity.software
});
let mut record = AdapterRecord {
index,
name: info.name.clone(),
software,
renders: Err("not opened".to_owned()),
decodes: Err(if decode {
"not opened".to_owned()
} else {
"decode is in software".to_owned()
}),
encodes: Vec::new(),
};
let pinned = policy.names(&record);
let hardware_renders = records
.iter()
.any(|other: &AdapterRecord| !other.software && other.renders.is_ok());
if software && hardware_renders && !pinned {
record.renders =
Err("a software adapter, not opened while a hardware one renders".to_owned());
records.push(record);
opened.push(None);
continue;
}
let device = match GpuContext::open_adapter(adapter).await {
Ok(gpu) => {
record.renders = Ok(());
let shared = (decode || encode).then(|| {
SharedD3D12Context::from_gpu(gpu.clone()).map_err(|error| error.to_string())
});
ask(&mut record, shared.as_ref(), decode, encode);
Some(Opened { gpu, shared })
}
Err(error) => {
record.renders = Err(error.to_string());
None
}
};
let taken = !exhaustive
&& !record.software
&& record.renders.is_ok()
&& (!decode || record.decodes.is_ok());
records.push(record);
opened.push(device);
if taken {
break;
}
}
let chosen = match choose_adapter(
&records,
policy,
decode,
crate::adapter_choice::DEFAULT_ENCODE_CODEC,
) {
Ok(chosen) => chosen,
Err(reason) if policy.is_pin() => return Err(ScanError::Pin(reason)),
Err(_) => {
return GpuContext::request_high_performance_on(wgpu::Backends::DX12)
.await
.map(Dx12Choice::without_scan)
.map_err(ScanError::Unavailable);
}
};
let Some(Opened { gpu, shared }) = opened.get_mut(chosen).and_then(Option::take) else {
return Err(ScanError::Pin(format!(
"{} was chosen without a device",
records[chosen].name
)));
};
let decode_refused = (decode && records[chosen].decodes.is_err())
.then(|| decode_refusals(&records, chosen, policy.is_pin()));
drop(opened);
Ok(Dx12Choice {
gpu,
shared,
decode_refused,
records,
chosen: Some(chosen),
})
}
#[cfg_attr(not(feature = "hwencode"), allow(unused_variables))]
fn ask(
record: &mut AdapterRecord,
shared: Option<&Result<SharedD3D12Context, String>>,
decode: bool,
encode: bool,
) {
let Some(shared) = shared else {
return;
};
if decode {
record.decodes = match shared {
Ok(context) if context.decode_planes() => Ok(()),
Ok(_) => Err("D3D12 adapter does not expose NV12 plane views".to_owned()),
Err(reason) => Err(reason.clone()),
};
}
#[cfg(feature = "hwencode")]
if encode {
record.encodes = crate::adapter_choice::ENCODE_CODECS
.iter()
.map(|&codec| {
let answer = match shared {
Ok(context) => crate::d3d12va_probe::device_encodes(context, codec),
Err(reason) => Err(format!("{}: {reason}", record.name)),
};
(codec, answer)
})
.collect();
}
}