use super::adapters::{self, AdlAdapter};
use super::ffi::{
ADL_OK, AdapterInfo, AdapterInfoArray, Adl2AdapterAdapterInfoGet,
Adl2AdapterNumberOfAdaptersGet, Adl2MainControlCreate, Adl2NewQueryPmLogDataGet,
Adl2OverdriveCaps, AdlContextHandle, AdlPmLogDataBuffer, AdlPmLogDataOutput,
};
use libloading::os::windows::LOAD_LIBRARY_SEARCH_SYSTEM32;
use once_cell::sync::OnceCell;
use std::ffi::{c_int, c_void};
use std::sync::Mutex;
use std::time::{Duration, Instant};
use windows::Win32::System::Memory::{GetProcessHeap, HEAP_FLAGS, HeapAlloc};
const ADL_DLL_PATH: &str = r"C:\Windows\System32\atiadlxx.dll";
const MIN_OVERDRIVE_VERSION: c_int = 7;
const RESCAN_INTERVAL: Duration = Duration::from_secs(60);
unsafe extern "system" fn adl_malloc(size: c_int) -> *mut c_void {
if size <= 0 {
return std::ptr::null_mut();
}
unsafe {
let Ok(heap) = GetProcessHeap() else {
return std::ptr::null_mut();
};
HeapAlloc(heap, HEAP_FLAGS(0), size as usize)
}
}
struct AdlRuntime {
_library: libloading::Library,
context: AdlContextHandle,
number_of_adapters: Adl2AdapterNumberOfAdaptersGet,
overdrive_caps: Adl2OverdriveCaps,
query_pmlog: Adl2NewQueryPmLogDataGet,
adapter_info_get: Option<Adl2AdapterAdapterInfoGet>,
chosen_index: Option<c_int>,
last_scan: Option<Instant>,
adapter_inventory: Option<Vec<AdlAdapter>>,
inventory_scanned_at: Option<Instant>,
}
unsafe impl Send for AdlRuntime {}
impl AdlRuntime {
fn open() -> Option<Self> {
let library = unsafe {
libloading::os::windows::Library::load_with_flags(
ADL_DLL_PATH,
LOAD_LIBRARY_SEARCH_SYSTEM32,
)
}
.ok()?;
let library: libloading::Library = library.into();
let (create, number_of_adapters, overdrive_caps, query_pmlog, adapter_info_get) = unsafe {
let create = *library
.get::<Adl2MainControlCreate>(b"ADL2_Main_Control_Create\0")
.ok()?;
let number_of_adapters = *library
.get::<Adl2AdapterNumberOfAdaptersGet>(b"ADL2_Adapter_NumberOfAdapters_Get\0")
.ok()?;
let overdrive_caps = *library
.get::<Adl2OverdriveCaps>(b"ADL2_Overdrive_Caps\0")
.ok()?;
let query_pmlog = *library
.get::<Adl2NewQueryPmLogDataGet>(b"ADL2_New_QueryPMLogData_Get\0")
.ok()?;
let adapter_info_get = library
.get::<Adl2AdapterAdapterInfoGet>(b"ADL2_Adapter_AdapterInfo_Get\0")
.ok()
.map(|symbol| *symbol);
(
create,
number_of_adapters,
overdrive_caps,
query_pmlog,
adapter_info_get,
)
};
let mut context: AdlContextHandle = std::ptr::null_mut();
let status = unsafe { create(adl_malloc, 1, &mut context) };
if status != ADL_OK || context.is_null() {
return None;
}
Some(Self {
_library: library,
context,
number_of_adapters,
overdrive_caps,
query_pmlog,
adapter_info_get,
chosen_index: None,
last_scan: None,
adapter_inventory: None,
inventory_scanned_at: None,
})
}
fn scan_for_capable_adapter(&mut self) {
self.last_scan = Some(Instant::now());
self.chosen_index = None;
let mut count: c_int = 0;
if unsafe { (self.number_of_adapters)(self.context, &mut count) } != ADL_OK || count <= 0 {
return;
}
let count = adapters::clamp_scan_count(count);
let mut preferred = Vec::new();
let mut fallback = Vec::new();
for index in 0..count {
let (mut supported, mut enabled, mut version) = (0, 0, 0);
let status = unsafe {
(self.overdrive_caps)(
self.context,
index,
&mut supported,
&mut enabled,
&mut version,
)
};
let _ = enabled;
if status == ADL_OK && supported != 0 && version >= MIN_OVERDRIVE_VERSION {
preferred.push(index);
} else {
fallback.push(index);
}
}
for index in preferred.into_iter().chain(fallback) {
if self.read_pmlog(index).is_some() {
self.chosen_index = Some(index);
return;
}
}
}
fn read_pmlog(&self, index: c_int) -> Option<AdlPmLogDataOutput> {
let mut buffer = Box::<AdlPmLogDataBuffer>::default();
let output = (&raw mut *buffer).cast::<AdlPmLogDataOutput>();
let status = unsafe { (self.query_pmlog)(self.context, index, output) };
if status != ADL_OK {
return None;
}
buffer.validated().copied()
}
fn sample(&mut self) -> Option<AdlPmLogDataOutput> {
let due_for_scan = match (self.chosen_index, self.last_scan) {
(_, None) => true,
(None, Some(at)) => at.elapsed() >= RESCAN_INTERVAL,
(Some(_), _) => false,
};
if due_for_scan {
self.scan_for_capable_adapter();
}
let index = self.chosen_index?;
match self.read_pmlog(index) {
Some(output) => Some(output),
None => {
self.chosen_index = None;
self.last_scan = None;
None
}
}
}
fn probe_adapter_info(&self) -> AdapterProbe {
let Some(adapter_info_get) = self.adapter_info_get else {
return AdapterProbe::NoEntryPoint;
};
let mut count: c_int = 0;
if unsafe { (self.number_of_adapters)(self.context, &mut count) } != ADL_OK
|| !adapters::plausible_adapter_count(count)
{
return AdapterProbe::CallFailed;
}
let mut buffer = AdapterInfoArray::for_count(count as usize);
let input_size = buffer.input_size();
let status = unsafe { adapter_info_get(self.context, buffer.as_mut_ptr(), input_size) };
if status != ADL_OK {
return AdapterProbe::CallFailed;
}
let accepted = buffer.validated();
AdapterProbe::Rows {
rows: buffer.requested_entries().to_vec(),
accepted,
}
}
fn adapter_inventory(&mut self) -> Option<&[AdlAdapter]> {
let due = match self.inventory_scanned_at {
None => true,
Some(at) => at.elapsed() >= RESCAN_INTERVAL,
};
if due {
self.inventory_scanned_at = Some(Instant::now());
self.adapter_inventory = match self.probe_adapter_info() {
AdapterProbe::Rows {
accepted: Some(populated),
..
} => Some(adapters::parse_adapters(&populated)),
_ => None,
};
}
self.adapter_inventory.as_deref()
}
}
pub enum AdapterProbe {
NoEntryPoint,
CallFailed,
Rows {
rows: Vec<AdapterInfo>,
accepted: Option<Vec<AdapterInfo>>,
},
}
static RUNTIME: OnceCell<Mutex<Option<AdlRuntime>>> = OnceCell::new();
fn runtime() -> &'static Mutex<Option<AdlRuntime>> {
RUNTIME.get_or_init(|| Mutex::new(AdlRuntime::open()))
}
pub fn sample() -> Option<AdlPmLogDataOutput> {
let mut guard = match runtime().lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
guard.as_mut()?.sample()
}
pub fn sample_adapter(index: i32) -> Option<AdlPmLogDataOutput> {
let guard = match runtime().lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
guard.as_ref()?.read_pmlog(index)
}
pub fn adapter_inventory() -> Option<Vec<AdlAdapter>> {
let mut guard = match runtime().lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
guard
.as_mut()?
.adapter_inventory()
.map(<[AdlAdapter]>::to_vec)
}
pub fn adapter_info_probe() -> Option<AdapterProbe> {
let guard = match runtime().lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
Some(guard.as_ref()?.probe_adapter_info())
}
pub fn library_available() -> bool {
match runtime().lock() {
Ok(guard) => guard.is_some(),
Err(poisoned) => poisoned.into_inner().is_some(),
}
}
pub fn selected_adapter_index() -> Option<i32> {
let mut guard = match runtime().lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
let runtime = guard.as_mut()?;
if runtime.last_scan.is_none() {
runtime.scan_for_capable_adapter();
}
runtime.chosen_index
}
pub fn dll_path() -> &'static str {
ADL_DLL_PATH
}