use core::ffi::c_void;
use core::mem::ManuallyDrop;
use core::sync::atomic::{AtomicU32, Ordering};
use std::collections::HashMap;
use std::sync::Mutex;
use windows::Win32::Foundation::{E_NOINTERFACE, E_POINTER, S_OK};
use windows::Win32::Graphics::Direct3D11::{ID3D11Device, ID3D11DeviceChild};
use windows::Win32::Graphics::Direct3D12::ID3D12Object;
use windows::Win32::Graphics::Dxgi::IDXGIObject;
use windows::core::{GUID, HRESULT, IUnknown, IUnknown_Vtbl, Interface};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Kind {
Device,
Factory,
Adapter,
SwapChain,
}
impl core::fmt::Display for Kind {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.write_str(match self {
Kind::Device => "device",
Kind::Factory => "factory",
Kind::Adapter => "adapter",
Kind::SwapChain => "swap chain",
})
}
}
#[derive(Default)]
struct Faces {
by_pointer: HashMap<usize, (Kind, usize)>,
pointers: HashMap<usize, Vec<usize>>,
}
static FACES: Mutex<Option<Faces>> = Mutex::new(None);
static ADOPTING: Mutex<()> = Mutex::new(());
fn faces<R>(f: impl FnOnce(&mut Faces) -> R) -> R {
f(FACES
.lock()
.unwrap_or_else(|e| e.into_inner())
.get_or_insert_with(Default::default))
}
pub unsafe fn add_ref(object: *mut c_void) -> u32 {
unsafe { ((**(object as *const *const IUnknown_Vtbl)).AddRef)(object) }
}
pub unsafe fn release(object: *mut c_void) -> u32 {
unsafe { ((**(object as *const *const IUnknown_Vtbl)).Release)(object) }
}
pub fn unowned<T: Interface>(owned: T) -> ManuallyDrop<T> {
let kept = ManuallyDrop::new(owned);
unsafe { release(kept.as_raw()) };
kept
}
pub unsafe fn identity(object: *mut c_void) -> Option<usize> {
let mut unknown = core::ptr::null_mut();
let hr = unsafe { crate::com::query(object, &IUnknown::IID, &mut unknown) };
if hr.is_err() || unknown.is_null() {
return None;
}
unsafe { release(unknown) };
Some(unknown as usize)
}
pub unsafe fn find(real: *mut c_void) -> Option<(Kind, usize)> {
if real.is_null() {
return None;
}
if let Some(found) = faces(|f| f.by_pointer.get(&(real as usize)).copied()) {
return Some(found);
}
let id = unsafe { identity(real) }?;
faces(|f| f.by_pointer.get(&id).copied())
}
pub unsafe fn register(kind: Kind, wrapper: usize, pointers: &[*mut c_void]) {
let mut keys: Vec<usize> = pointers.iter().map(|&p| p as usize).collect();
if let Some(&first) = pointers.first()
&& let Some(id) = unsafe { identity(first) }
{
keys.push(id);
}
keys.sort_unstable();
keys.dedup();
faces(|f| {
for &k in &keys {
f.by_pointer.insert(k, (kind, wrapper));
}
f.pointers.insert(wrapper, keys);
});
}
pub fn forget(wrapper: usize) {
faces(|f| {
for k in f.pointers.remove(&wrapper).unwrap_or_default() {
if f.by_pointer.get(&k).is_some_and(|&(_, w)| w == wrapper) {
f.by_pointer.remove(&k);
}
}
});
}
pub unsafe fn adopt(real: *mut c_void, make: impl FnOnce() -> Option<usize>) -> Option<usize> {
bury();
let _adopting = ADOPTING.lock().unwrap_or_else(|e| e.into_inner());
if let Some((_, wrapper)) = unsafe { find(real) } {
return Some(wrapper);
}
make()
}
struct Grave(#[allow(dead_code)] Box<dyn core::any::Any>);
unsafe impl Send for Grave {}
static GRAVES: Mutex<Vec<Grave>> = Mutex::new(Vec::new());
pub unsafe fn later<T: 'static>(thing: T) {
GRAVES
.lock()
.unwrap_or_else(|e| e.into_inner())
.push(Grave(Box::new(thing)));
}
pub fn defer(f: impl FnOnce() + Send + 'static) {
struct Deferred(Option<Box<dyn FnOnce() + Send>>);
impl Drop for Deferred {
fn drop(&mut self) {
if let Some(f) = self.0.take() {
f();
}
}
}
unsafe { later(Deferred(Some(Box::new(f)))) };
}
pub fn bury() {
if crate::d3d12::releasing() {
return;
}
loop {
let graves = core::mem::take(&mut *GRAVES.lock().unwrap_or_else(|e| e.into_inner()));
if graves.is_empty() {
break;
}
drop(graves);
}
}
const SENTINEL_KEY: GUID = GUID::from_u128(0x6c6f_6375_6d00_4a5e_9d1b_7a2f_8c3e_0003);
#[repr(C)]
struct Sentinel {
vtbl: *const IUnknown_Vtbl,
refs: AtomicU32,
gone: Mutex<Option<Box<dyn FnOnce() + Send>>>,
}
static SENTINEL_VTBL: IUnknown_Vtbl = IUnknown_Vtbl {
QueryInterface: sentinel_query,
AddRef: sentinel_add_ref,
Release: sentinel_release,
};
unsafe extern "system" fn sentinel_query(
this: *mut c_void,
iid: *const GUID,
out: *mut *mut c_void,
) -> HRESULT {
if iid.is_null() || out.is_null() {
return E_POINTER;
}
if unsafe { *iid } == IUnknown::IID {
unsafe {
sentinel_add_ref(this);
*out = this;
}
return S_OK;
}
unsafe { *out = core::ptr::null_mut() };
E_NOINTERFACE
}
unsafe extern "system" fn sentinel_add_ref(this: *mut c_void) -> u32 {
unsafe { (*(this as *const Sentinel)).refs.fetch_add(1, Ordering::Relaxed) + 1 }
}
unsafe extern "system" fn sentinel_release(this: *mut c_void) -> u32 {
let left = unsafe { (*(this as *const Sentinel)).refs.fetch_sub(1, Ordering::AcqRel) - 1 };
if left == 0 {
let sentinel = unsafe { Box::from_raw(this as *mut Sentinel) };
let gone = sentinel.gone.lock().unwrap_or_else(|e| e.into_inner()).take();
drop(sentinel);
if let Some(gone) = gone {
gone();
}
}
left
}
pub unsafe fn watch(real: *mut c_void, gone: impl FnOnce() + Send + 'static) -> bool {
let sentinel = Box::into_raw(Box::new(Sentinel {
vtbl: &SENTINEL_VTBL,
refs: AtomicU32::new(1),
gone: Mutex::new(Some(Box::new(gone))),
}));
let unknown = unsafe { IUnknown::from_raw(sentinel.cast()) };
let object = unsafe { IUnknown::from_raw_borrowed(&real) }.expect("a live object");
let set = if let Ok(o) = object.cast::<ID3D11Device>() {
unsafe { o.SetPrivateDataInterface(&SENTINEL_KEY, &unknown) }
} else if let Ok(o) = object.cast::<ID3D11DeviceChild>() {
unsafe { o.SetPrivateDataInterface(&SENTINEL_KEY, &unknown) }
} else if let Ok(o) = object.cast::<IDXGIObject>() {
unsafe { o.SetPrivateDataInterface(&SENTINEL_KEY, &unknown) }
} else if let Ok(o) = object.cast::<ID3D12Object>() {
unsafe { o.SetPrivateDataInterface(&SENTINEL_KEY, &unknown) }
} else {
Err(E_NOINTERFACE.into())
};
if let Err(e) = &set {
crate::log::log!(
"object {real:p} takes no private-data interface ({e}): locum cannot see it destroyed"
);
unsafe { (*sentinel).gone.lock().unwrap_or_else(|e| e.into_inner()).take() };
}
drop(unknown);
set.is_ok()
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use windows::Win32::Foundation::HMODULE;
use windows::Win32::Graphics::Direct3D::{D3D_DRIVER_TYPE, D3D_DRIVER_TYPE_WARP, D3D_FEATURE_LEVEL};
use windows::Win32::Graphics::Direct3D11::*;
fn warp() -> ID3D11Device {
type Create = unsafe extern "system" fn(
*mut c_void,
D3D_DRIVER_TYPE,
HMODULE,
D3D11_CREATE_DEVICE_FLAG,
*const D3D_FEATURE_LEVEL,
u32,
u32,
*mut *mut c_void,
*mut D3D_FEATURE_LEVEL,
*mut *mut c_void,
) -> HRESULT;
let create =
unsafe { crate::system::export::<Create>(crate::system::system().d3d11, c"D3D11CreateDevice") }
.unwrap();
let mut device = core::ptr::null_mut();
unsafe {
create(
core::ptr::null_mut(),
D3D_DRIVER_TYPE_WARP,
HMODULE::default(),
D3D11_CREATE_DEVICE_FLAG(0),
core::ptr::null(),
0,
D3D11_SDK_VERSION,
&mut device,
core::ptr::null_mut(),
core::ptr::null_mut(),
)
}
.ok()
.expect("a WARP device");
unsafe { ID3D11Device::from_raw(device) }
}
#[test]
fn a_watch_ends_with_its_object() {
let device = warp();
let gone = Arc::new(AtomicBool::new(false));
let seen = gone.clone();
let before = unsafe { add_ref(device.as_raw()) };
unsafe { release(device.as_raw()) };
assert!(unsafe { watch(device.as_raw(), move || seen.store(true, Ordering::SeqCst)) });
let after = unsafe { add_ref(device.as_raw()) };
unsafe { release(device.as_raw()) };
assert_eq!(before, after, "the watch takes no reference to the object");
let child = unsafe {
let mut b = None;
device
.CreateBuffer(
&D3D11_BUFFER_DESC {
ByteWidth: 16,
BindFlags: D3D11_BIND_CONSTANT_BUFFER.0 as u32,
..Default::default()
},
None,
Some(&mut b),
)
.unwrap();
b.unwrap()
};
drop(device);
assert!(!gone.load(Ordering::SeqCst), "a child keeps its device");
drop(child);
assert!(
gone.load(Ordering::SeqCst),
"the device's destruction runs the watch"
);
}
#[test]
fn faces_are_found_by_any_pointer() {
let device = warp();
let dxgi = device
.cast::<windows::Win32::Graphics::Dxgi::IDXGIDevice>()
.unwrap();
unsafe { register(Kind::Device, 0x1000, &[device.as_raw()]) };
assert_eq!(unsafe { find(dxgi.as_raw()) }, Some((Kind::Device, 0x1000)));
forget(0x1000);
assert_eq!(unsafe { find(device.as_raw()) }, None);
}
}