use super::adapter::{select_adapter, AdapterSelection};
use super::com;
use std::ffi::c_void;
use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle, RawHandle};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Mutex, OnceLock};
use windows::core::{IUnknown, Interface, PCWSTR};
use windows::Win32::Foundation::{
CloseHandle, DuplicateHandle, DUPLICATE_SAME_ACCESS, GENERIC_ALL, HANDLE, HMODULE,
INVALID_HANDLE_VALUE, WAIT_OBJECT_0,
};
use windows::Win32::Graphics::Direct3D::{
D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_UNKNOWN, D3D_DRIVER_TYPE_WARP, D3D_FEATURE_LEVEL,
D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_12_0, D3D_FEATURE_LEVEL_12_1,
};
use windows::Win32::Graphics::Direct3D11::{
D3D11CreateDevice, ID3D11Device, ID3D11Device1, ID3D11Device5, ID3D11DeviceContext,
ID3D11DeviceContext4, ID3D11Fence, ID3D11Multithread, D3D11_CREATE_DEVICE_BGRA_SUPPORT,
D3D11_CREATE_DEVICE_DEBUG, D3D11_CREATE_DEVICE_FLAG, D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
D3D11_FENCE_FLAG_SHARED, D3D11_SDK_VERSION,
};
use windows::Win32::Graphics::Dxgi::{IDXGIAdapter, IDXGIDevice};
use windows::Win32::System::Memory::{
CreateFileMappingW, MapViewOfFile, OpenFileMappingW, UnmapViewOfFile, FILE_MAP_READ,
FILE_MAP_WRITE, MEMORY_MAPPED_VIEW_ADDRESS, PAGE_READWRITE,
};
use windows::Win32::System::Threading::{
CreateEventW, GetCurrentProcess, GetCurrentProcessId, WaitForSingleObject,
};
const RENDEZVOUS_VERSION: u64 = 2;
const RENDEZVOUS_CLAIMING: u64 = u64::MAX;
const RENDEZVOUS_CLAIM_SPINS: u32 = 1024;
const RENDEZVOUS_CLAIM_YIELDS: u32 = 64;
const RENDEZVOUS_CLAIM_SLEEP_US: u64 = 50;
const RENDEZVOUS_BYTES: usize = 32;
const COUNTER_OFFSET: usize = 24;
const WARP_DESCRIPTION: &str = "Microsoft Basic Render Driver";
const ADOPTED_LABEL: &str = "adopted from another copy in this process";
pub struct GpuCompletion {
pub fence: OwnedHandle,
pub value: u64,
}
enum Counter {
Shared(&'static AtomicU64),
Local(AtomicU64),
}
impl Counter {
fn word(&self) -> &AtomicU64 {
match self {
Counter::Shared(word) => word,
Counter::Local(word) => word,
}
}
}
#[cfg_attr(not(feature = "static"), allow(dead_code))]
pub struct D3d11Device {
dev: ID3D11Device,
dev1: Option<ID3D11Device1>,
dev5: Option<ID3D11Device5>,
ctx: ID3D11DeviceContext,
ctx4: Option<ID3D11DeviceContext4>,
mt: Option<ID3D11Multithread>,
fence: Option<ID3D11Fence>,
fence_handle: Option<OwnedHandle>,
adapter: Option<IDXGIAdapter>,
counter: Counter,
signal_lock: Mutex<()>,
adapter_label: String,
luid: (i32, u32),
is_warp: bool,
creation_flags: u32,
}
static DEVICE: OnceLock<std::result::Result<D3d11Device, String>> = OnceLock::new();
static EXTERNAL: Mutex<Option<usize>> = Mutex::new(None);
pub fn device() -> crate::Result<&'static D3d11Device> {
DEVICE
.get_or_init(|| create().map_err(|e| e.to_string()))
.as_ref()
.map_err(|s| crate::Error::IoError(std::io::Error::other(s.clone())))
}
pub unsafe fn use_external_device(ptr: *mut c_void) -> crate::Result<()> {
if ptr.is_null() {
return Err(crate::Error::InvalidArgument(
"use_external_device: null device".into(),
));
}
if let Some(cached) = DEVICE.get() {
return Err(crate::Error::InvalidOperation(match cached {
Err(e) => format!(
"use_external_device: D3D11 device creation already failed in this process \
and the failure is cached: {e}"
),
Ok(_) => "use_external_device: the D3D11 device is already initialized".into(),
}));
}
if read_rendezvous().is_some() {
return Err(crate::Error::InvalidOperation(
"use_external_device: the D3D11 device is already initialized by another \
copy of this library in this process"
.into(),
));
}
if !device_pointer_is_live(ptr as usize) {
return Err(crate::Error::InvalidArgument(
"use_external_device: pointer is not a live ID3D11Device".into(),
));
}
*EXTERNAL.lock().unwrap_or_else(|e| e.into_inner()) = Some(ptr as usize);
Ok(())
}
fn adopt(ptr: usize) -> crate::Result<ID3D11Device> {
unsafe { ID3D11Device::from_raw_borrowed(&(ptr as *mut c_void)) }
.cloned()
.ok_or_else(|| crate::Error::InvalidArgument("D3D11 device pointer is null".into()))
}
fn create() -> crate::Result<D3d11Device> {
if let Some(view) = open_rendezvous() {
return adopt_published(view);
}
let external = *EXTERNAL.lock().unwrap_or_else(|e| e.into_inner());
if let Some(ptr) = external {
return finish(adopt(ptr)?, "host-provided device".into(), None);
}
let selected = select_adapter();
let warp = selected.selection == AdapterSelection::Warp;
let debug = std::env::var("EDGEFIRST_D3D11_DEBUG")
.map(|v| v == "1")
.unwrap_or(false);
let base = if debug {
D3D11_CREATE_DEVICE_DEBUG
} else {
D3D11_CREATE_DEVICE_FLAG(0)
};
let attempts = [
base | D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
base | D3D11_CREATE_DEVICE_BGRA_SUPPORT,
base,
];
let levels = [
D3D_FEATURE_LEVEL_12_1,
D3D_FEATURE_LEVEL_12_0,
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
];
let mut last = None;
for flags in attempts {
let mut dev: Option<ID3D11Device> = None;
let mut fl = D3D_FEATURE_LEVEL::default();
let adapter = selected
.adapter
.as_ref()
.map(|a| &*a.adapter)
.filter(|_| !warp);
let driver = if warp {
D3D_DRIVER_TYPE_WARP
} else if adapter.is_some() {
D3D_DRIVER_TYPE_UNKNOWN
} else {
D3D_DRIVER_TYPE_HARDWARE
};
let result = unsafe {
D3D11CreateDevice(
adapter,
driver,
HMODULE::default(),
flags,
Some(&levels),
D3D11_SDK_VERSION,
Some(&mut dev),
Some(&mut fl),
None,
)
};
match result {
Ok(()) => {
let dev = dev.ok_or_else(|| {
crate::Error::IoError(std::io::Error::other(
"D3D11CreateDevice returned no device",
))
})?;
let d = finish(dev, selected.label.clone(), warp.then_some(true))?;
log::info!(
"D3D11 device up on {} (flags {:#x}, feature level {:#x})",
d.adapter_label,
flags.0,
fl.0
);
return Ok(d);
}
Err(e) => {
log::debug!(
"D3D11CreateDevice(flags {:#x}) failed: {:#010x}; retrying with fewer flags",
flags.0,
e.code().0 as u32
);
last = Some(e);
}
}
}
com::hr(
"D3D11CreateDevice",
Err(last.expect("the attempt list is not empty")),
)
}
fn finish(dev: ID3D11Device, label: String, is_warp: Option<bool>) -> crate::Result<D3d11Device> {
let mut d = wrap(dev, label, is_warp, None)?;
match publish_rendezvous(d.raw() as usize, d.fence_handle_value()) {
Published::Ours(counter) => {
d.counter = Counter::Shared(counter);
Ok(d)
}
Published::Other(view) => {
log::warn!(
"the rendezvous for this process already names D3D11 device {:#x}; dropping the wrapper \
just built for {:#x} and adopting the published device, fence and counter",
view.device_ptr,
d.raw() as usize
);
drop(d);
adopt_published(view)
}
Published::Unavailable => Ok(d),
}
}
fn adopt_published(view: RendezvousView) -> crate::Result<D3d11Device> {
let dev = adopt(view.device_ptr)?;
wrap(dev, ADOPTED_LABEL.into(), None, Some(&view))
}
fn wrap(
dev: ID3D11Device,
adapter_label: String,
is_warp: Option<bool>,
published: Option<&RendezvousView>,
) -> crate::Result<D3d11Device> {
let dev1 = dev.cast::<ID3D11Device1>().ok();
let dev5 = dev.cast::<ID3D11Device5>().ok();
let ctx = com::hr("ID3D11Device::GetImmediateContext", unsafe {
dev.GetImmediateContext()
})?;
let ctx4 = ctx.cast::<ID3D11DeviceContext4>().ok();
let mt = dev.cast::<ID3D11Multithread>().ok();
if let Some(mt) = &mt {
let _ = unsafe { mt.SetMultithreadProtected(true) };
}
let (fence, fence_handle, counter) = fence_and_counter(dev5.as_ref(), published)?;
let (luid, is_warp_detected, adapter) = adapter_identity(&dev);
let creation_flags = unsafe { dev.GetCreationFlags() };
Ok(D3d11Device {
dev,
dev1,
dev5,
ctx,
ctx4,
mt,
fence,
fence_handle,
adapter,
counter,
signal_lock: Mutex::new(()),
adapter_label,
luid,
is_warp: is_warp.unwrap_or(is_warp_detected),
creation_flags,
})
}
fn fence_and_counter(
dev5: Option<&ID3D11Device5>,
published: Option<&RendezvousView>,
) -> crate::Result<(Option<ID3D11Fence>, Option<OwnedHandle>, Counter)> {
let Some(d5) = dev5 else {
if published.is_some() {
log::warn!("no ID3D11Device5 on this device: the published fence cannot be opened and this copy has no fence at all");
}
return Ok((None, None, Counter::Local(AtomicU64::new(0))));
};
if let Some(view) = published {
match open_published_fence(d5, view) {
Ok((fence, handle)) => return Ok((Some(fence), Some(handle), Counter::Shared(view.counter))),
Err(e) => log::warn!(
"cannot open the fence published for this process ({e}); this copy signals a fence of its own \
and its completion values are not comparable with the other copies'"
),
}
}
let mut f: Option<ID3D11Fence> = None;
com::hr("ID3D11Device5::CreateFence", unsafe {
d5.CreateFence(0, D3D11_FENCE_FLAG_SHARED, &mut f)
})?;
let fence = f.ok_or_else(|| {
crate::Error::IoError(std::io::Error::other("CreateFence returned no fence"))
})?;
let h = com::hr("ID3D11Fence::CreateSharedHandle", unsafe {
fence.CreateSharedHandle(None, GENERIC_ALL.0, PCWSTR::null())
})?;
Ok((
Some(fence),
Some(unsafe { OwnedHandle::from_raw_handle(h.0) }),
Counter::Local(AtomicU64::new(0)),
))
}
fn open_published_fence(
d5: &ID3D11Device5,
view: &RendezvousView,
) -> crate::Result<(ID3D11Fence, OwnedHandle)> {
if view.fence_handle == 0 {
return Err(crate::Error::InvalidOperation(
"the rendezvous names no fence".into(),
));
}
let mut f: Option<ID3D11Fence> = None;
com::hr("ID3D11Device5::OpenSharedFence", unsafe {
d5.OpenSharedFence(HANDLE(view.fence_handle as *mut c_void), &mut f)
})?;
let fence = f.ok_or_else(|| {
crate::Error::IoError(std::io::Error::other("OpenSharedFence returned no fence"))
})?;
Ok((fence, duplicate_raw_handle(view.fence_handle as RawHandle)?))
}
fn adapter_identity(dev: &ID3D11Device) -> ((i32, u32), bool, Option<IDXGIAdapter>) {
let Ok(dxgi) = dev.cast::<IDXGIDevice>() else {
return ((0, 0), false, None);
};
let adapter = match unsafe { dxgi.GetAdapter() } {
Ok(a) => a,
Err(e) => {
log::debug!("IDXGIDevice::GetAdapter failed: {e}");
return ((0, 0), false, None);
}
};
let desc = match unsafe { adapter.GetDesc() } {
Ok(d) => d,
Err(e) => {
log::debug!("IDXGIAdapter::GetDesc failed: {e}");
return ((0, 0), false, Some(adapter));
}
};
let len = desc
.Description
.iter()
.position(|&c| c == 0)
.unwrap_or(desc.Description.len());
let description = String::from_utf16_lossy(&desc.Description[..len]);
let luid = (desc.AdapterLuid.HighPart, desc.AdapterLuid.LowPart);
(luid, description.contains(WARP_DESCRIPTION), Some(adapter))
}
fn rendezvous_name() -> Vec<u16> {
let pid = unsafe { GetCurrentProcessId() };
com::wide(&format!("Local\\edgefirst-d3d11-device-{pid}"))
}
struct RendezvousView {
device_ptr: usize,
fence_handle: usize,
counter: &'static AtomicU64,
}
enum Published {
Ours(&'static AtomicU64),
Other(RendezvousView),
Unavailable,
}
pub(crate) fn read_rendezvous() -> Option<usize> {
let name = rendezvous_name();
let map = unsafe { OpenFileMappingW(FILE_MAP_READ.0, false, PCWSTR(name.as_ptr())) }.ok()?;
let view = unsafe { MapViewOfFile(map, FILE_MAP_READ, 0, 0, RENDEZVOUS_BYTES) };
let result = if view.Value.is_null() {
None
} else {
let version = unsafe { settled_version(&*(view.Value as *const AtomicU64)) };
let ptr = if version == RENDEZVOUS_VERSION {
unsafe { std::ptr::read_unaligned(view.Value.byte_add(8) as *const u64) as usize }
} else {
0
};
let _ = unsafe { UnmapViewOfFile(view) };
(ptr != 0 && device_pointer_is_live(ptr)).then_some(ptr)
};
let _ = unsafe { CloseHandle(map) };
result
}
fn settled_version(version: &AtomicU64) -> u64 {
for turn in 0..RENDEZVOUS_CLAIM_SPINS {
let seen = version.load(Ordering::Acquire);
if seen != 0 && seen != RENDEZVOUS_CLAIMING {
return seen;
}
claim_backoff(turn);
}
version.load(Ordering::Acquire)
}
fn claim_backoff(turn: u32) {
if turn < RENDEZVOUS_CLAIM_YIELDS {
std::thread::yield_now();
} else {
std::thread::sleep(std::time::Duration::from_micros(RENDEZVOUS_CLAIM_SLEEP_US));
}
}
fn open_rendezvous() -> Option<RendezvousView> {
let name = rendezvous_name();
let map = unsafe { OpenFileMappingW(FILE_MAP_WRITE.0, false, PCWSTR(name.as_ptr())) }.ok()?;
let view = unsafe { MapViewOfFile(map, FILE_MAP_WRITE, 0, 0, RENDEZVOUS_BYTES) };
if view.Value.is_null() {
let _ = unsafe { CloseHandle(map) };
return None;
}
match unsafe { published_record(view) } {
Some(record) => Some(record),
None => {
unsafe {
let _ = UnmapViewOfFile(view);
let _ = CloseHandle(map);
}
None
}
}
}
unsafe fn published_record(view: MEMORY_MAPPED_VIEW_ADDRESS) -> Option<RendezvousView> {
let version = unsafe { settled_version(&*(view.Value as *const AtomicU64)) };
if version != RENDEZVOUS_VERSION {
return None;
}
let (device_ptr, fence_handle) = unsafe {
(
std::ptr::read_unaligned(view.Value.byte_add(8) as *const u64) as usize,
std::ptr::read_unaligned(view.Value.byte_add(16) as *const u64) as usize,
)
};
if device_ptr == 0 || !device_pointer_is_live(device_ptr) {
return None;
}
let counter = unsafe { &*(view.Value.byte_add(COUNTER_OFFSET) as *const AtomicU64) };
Some(RendezvousView {
device_ptr,
fence_handle,
counter,
})
}
fn publish_rendezvous(device_ptr: usize, fence_handle: usize) -> Published {
let name = rendezvous_name();
let map = unsafe {
CreateFileMappingW(
INVALID_HANDLE_VALUE,
None,
PAGE_READWRITE,
0,
RENDEZVOUS_BYTES as u32,
PCWSTR(name.as_ptr()),
)
};
let map = match map {
Ok(map) => map,
Err(_) => {
log::warn!(
"cannot create the D3D11 rendezvous mapping ({}); other copies of this crate in this process will create their own device",
std::io::Error::last_os_error()
);
return Published::Unavailable;
}
};
let view = unsafe { MapViewOfFile(map, FILE_MAP_WRITE, 0, 0, RENDEZVOUS_BYTES) };
if view.Value.is_null() {
log::warn!(
"cannot map the D3D11 rendezvous ({}); other copies of this crate in this process will create their own device",
std::io::Error::last_os_error()
);
let _ = unsafe { CloseHandle(map) };
return Published::Unavailable;
}
let version = unsafe { &*(view.Value as *const AtomicU64) };
for turn in 0..RENDEZVOUS_CLAIM_SPINS {
if version
.compare_exchange(0, RENDEZVOUS_CLAIMING, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
return unsafe { write_claimed_record(view, device_ptr, fence_handle) };
}
match version.load(Ordering::Acquire) {
0 | RENDEZVOUS_CLAIMING => claim_backoff(turn),
seen if seen == RENDEZVOUS_VERSION => {
if let Some(record) = unsafe { published_record(view) } {
return Published::Other(record);
}
if version
.compare_exchange(
seen,
RENDEZVOUS_CLAIMING,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
{
return unsafe { write_claimed_record(view, device_ptr, fence_handle) };
}
}
other => {
log::warn!(
"the D3D11 rendezvous for this process holds version {other:#x}, not \
{RENDEZVOUS_VERSION}; this copy keeps its own device and other copies \
of this crate will create theirs"
);
return unmapped(view, map);
}
}
}
log::warn!(
"the D3D11 rendezvous claim for this process never settled; this copy keeps its own device"
);
unmapped(view, map)
}
fn unmapped(view: MEMORY_MAPPED_VIEW_ADDRESS, map: HANDLE) -> Published {
unsafe {
let _ = UnmapViewOfFile(view);
let _ = CloseHandle(map);
}
Published::Unavailable
}
unsafe fn write_claimed_record(
view: MEMORY_MAPPED_VIEW_ADDRESS,
device_ptr: usize,
fence_handle: usize,
) -> Published {
unsafe {
std::ptr::write_unaligned(view.Value.byte_add(8) as *mut u64, device_ptr as u64);
std::ptr::write_unaligned(view.Value.byte_add(16) as *mut u64, fence_handle as u64);
}
let (version, counter) = unsafe {
(
&*(view.Value as *const AtomicU64),
&*(view.Value.byte_add(COUNTER_OFFSET) as *const AtomicU64),
)
};
version.store(RENDEZVOUS_VERSION, Ordering::Release);
Published::Ours(counter)
}
fn device_pointer_is_live(ptr: usize) -> bool {
unsafe { IUnknown::from_raw_borrowed(&(ptr as *mut c_void)) }
.is_some_and(|u| u.cast::<ID3D11Device>().is_ok())
}
#[cfg_attr(not(feature = "static"), allow(dead_code))] impl D3d11Device {
pub fn raw(&self) -> *mut c_void {
self.dev.as_raw()
}
pub(crate) fn dev(&self) -> &ID3D11Device {
&self.dev
}
pub(crate) fn dev1(&self) -> Option<&ID3D11Device1> {
self.dev1.as_ref()
}
pub(crate) fn dev5(&self) -> Option<&ID3D11Device5> {
self.dev5.as_ref()
}
pub(crate) fn ctx(&self) -> &ID3D11DeviceContext {
&self.ctx
}
pub(crate) fn ctx4(&self) -> Option<&ID3D11DeviceContext4> {
self.ctx4.as_ref()
}
pub(crate) fn fence(&self) -> Option<&ID3D11Fence> {
self.fence.as_ref()
}
pub fn is_warp(&self) -> bool {
self.is_warp
}
pub fn adapter_label(&self) -> &str {
&self.adapter_label
}
pub fn luid(&self) -> (i32, u32) {
self.luid
}
pub fn creation_flags(&self) -> u32 {
self.creation_flags
}
pub fn signal_supported(&self) -> bool {
self.fence.is_some() && self.ctx4.is_some()
}
pub fn signal(&self) -> Option<u64> {
self.signal_with(true)
}
pub fn signal_deferred(&self) -> Option<u64> {
self.signal_with(false)
}
pub fn flush(&self) {
unsafe { self.ctx.Flush() };
}
fn signal_with(&self, submit: bool) -> Option<u64> {
let (fence, ctx4) = (self.fence.as_ref()?, self.ctx4.as_ref()?);
let _local = self
.mt
.is_none()
.then(|| self.signal_lock.lock().unwrap_or_else(|e| e.into_inner()));
if let Some(mt) = &self.mt {
unsafe { mt.Enter() };
}
let value = self.counter.word().fetch_add(1, Ordering::AcqRel) + 1;
let signalled = unsafe { ctx4.Signal(fence, value) };
if submit && signalled.is_ok() {
unsafe { self.ctx().Flush() };
}
if let Some(mt) = &self.mt {
unsafe { mt.Leave() };
}
if let Err(e) = signalled {
log::warn!("ID3D11DeviceContext4::Signal({value}) failed: {e}");
return None;
}
Some(value)
}
pub fn last_signalled(&self) -> u64 {
self.counter.word().load(Ordering::Acquire)
}
pub fn completed_value(&self) -> u64 {
self.fence
.as_ref()
.map_or(0, |f| unsafe { f.GetCompletedValue() })
}
pub fn event_for(&self, value: u64) -> std::io::Result<OwnedHandle> {
let fence = self
.fence
.as_ref()
.ok_or_else(|| std::io::Error::other("no fence"))?;
event_on(fence, value)
}
pub fn fence_shared_handle(&self) -> std::io::Result<OwnedHandle> {
let src = self
.fence_handle
.as_ref()
.ok_or_else(|| std::io::Error::other("no fence"))?;
duplicate_handle(src)
}
pub(crate) fn fence_handle_value(&self) -> usize {
self.fence_handle
.as_ref()
.map_or(0, |h| h.as_raw_handle() as usize)
}
pub(crate) fn fence_is_shared(&self) -> bool {
matches!(self.counter, Counter::Shared(_))
}
pub(crate) fn exported_fence_handle_value(&self) -> usize {
if self.fence_is_shared() {
self.fence_handle_value()
} else {
0
}
}
#[allow(dead_code)]
pub(crate) fn adapter_ptr(&self) -> *mut c_void {
self.adapter
.as_ref()
.map_or(std::ptr::null_mut(), |a| a.as_raw())
}
}
fn event_on(fence: &ID3D11Fence, value: u64) -> std::io::Result<OwnedHandle> {
let ev = unsafe { CreateEventW(None, true, false, PCWSTR::null()) }
.map_err(|_| std::io::Error::last_os_error())?;
if let Err(e) = unsafe { fence.SetEventOnCompletion(value, ev) } {
let _ = unsafe { CloseHandle(ev) };
return Err(std::io::Error::other(format!(
"SetEventOnCompletion: {:#010x}",
e.code().0 as u32
)));
}
Ok(unsafe { OwnedHandle::from_raw_handle(ev.0) })
}
#[cfg_attr(not(feature = "static"), allow(dead_code))] pub(crate) fn wait_cpu_for(
fence: &ID3D11Fence,
value: u64,
timeout_ms: u32,
) -> std::io::Result<()> {
let ev = event_on(fence, value)?;
match unsafe { WaitForSingleObject(HANDLE(ev.as_raw_handle()), timeout_ms) } {
WAIT_OBJECT_0 => Ok(()),
other => Err(std::io::Error::other(format!(
"WaitForSingleObject on fence value {value}: {:#010x}",
other.0
))),
}
}
fn duplicate(src: HANDLE) -> std::io::Result<OwnedHandle> {
let mut out = HANDLE(std::ptr::null_mut());
unsafe {
DuplicateHandle(
GetCurrentProcess(),
src,
GetCurrentProcess(),
&mut out,
0,
false,
DUPLICATE_SAME_ACCESS,
)
}
.map_err(|_| std::io::Error::last_os_error())?;
Ok(unsafe { OwnedHandle::from_raw_handle(out.0) })
}
pub(crate) fn duplicate_handle(src: &OwnedHandle) -> std::io::Result<OwnedHandle> {
duplicate(HANDLE(src.as_raw_handle()))
}
pub(crate) fn duplicate_raw_handle(raw: RawHandle) -> std::io::Result<OwnedHandle> {
duplicate(HANDLE(raw))
}
#[cfg(test)]
mod tests {
use super::*;
use windows::Win32::Foundation::WAIT_OBJECT_0;
use windows::Win32::System::Threading::WaitForSingleObject;
#[test]
fn device_is_a_process_singleton_with_protection_and_a_fence() {
let a = device().expect("D3D11 device");
let b = device().unwrap();
assert!(std::ptr::eq(a, b));
assert_ne!(a.raw(), std::ptr::null_mut());
assert!(
a.signal_supported(),
"ID3D11Device5 is Windows 10 1703+; this box has it"
);
assert!(
a.creation_flags() & D3D11_CREATE_DEVICE_BGRA_SUPPORT.0 != 0,
"BGRA_SUPPORT requested"
);
}
#[test]
fn signal_then_event_is_set_and_completed_value_catches_up() {
let d = device().unwrap();
let v = d.signal().expect("fence");
let ev = d.event_for(v).unwrap();
let w = unsafe { WaitForSingleObject(HANDLE(ev.as_raw_handle()), 5000) };
assert_eq!(w, WAIT_OBJECT_0);
assert!(d.completed_value() >= v);
assert!(d.last_signalled() >= v);
}
#[test]
fn use_external_device_after_first_use_is_refused() {
let d = device().unwrap();
let err = unsafe { use_external_device(d.raw()) }.unwrap_err();
assert!(matches!(err, crate::Error::InvalidOperation(_)));
}
#[test]
fn use_external_device_sees_the_device_a_sibling_copy_published() {
let d = device().unwrap();
assert_eq!(
read_rendezvous(),
Some(d.raw() as usize),
"the device is published, so a sibling copy's rendezvous check finds it"
);
let err = unsafe { use_external_device(d.raw()) }.unwrap_err();
assert!(matches!(err, crate::Error::InvalidOperation(_)));
}
#[test]
fn rendezvous_mapping_names_this_process_and_carries_the_device() {
let d = device().unwrap();
let published = read_rendezvous().expect("mapping exists after device()");
assert_eq!(published, d.raw() as usize);
}
#[test]
fn adopted_device_shares_the_fence_and_counter() {
let first = device().unwrap();
let before = first.last_signalled();
let view = open_rendezvous().expect("the process published a rendezvous");
let second = adopt_published(view).expect("adopt the published device");
assert_eq!(second.raw(), first.raw());
assert_ne!(
second.fence_handle_value(),
0,
"the adopted copy duplicated the published handle"
);
let value = second.signal().expect("fence");
assert!(
value > before,
"the second copy allocated {value} from the shared counter, not past {before}"
);
assert_eq!(
first.last_signalled(),
second.last_signalled(),
"both copies read one counter"
);
let ev = first.event_for(value).unwrap();
let w = unsafe { WaitForSingleObject(HANDLE(ev.as_raw_handle()), 5000) };
assert_eq!(
w, WAIT_OBJECT_0,
"the first copy's fence is the one the second signalled"
);
assert!(first.completed_value() >= value);
}
#[test]
fn a_reader_waits_out_a_claim_instead_of_calling_the_record_stale() {
let word = std::sync::Arc::new(AtomicU64::new(RENDEZVOUS_CLAIMING));
let claimer = std::sync::Arc::clone(&word);
let publisher = std::thread::spawn(move || {
std::thread::yield_now();
claimer.store(RENDEZVOUS_VERSION, Ordering::Release);
});
assert_eq!(settled_version(&word), RENDEZVOUS_VERSION);
publisher.join().unwrap();
assert_eq!(settled_version(&AtomicU64::new(0)), 0);
}
#[test]
fn two_threads_racing_to_publish_end_on_one_device() {
let d = device().unwrap();
let (ptr, fence_handle) = (d.raw() as usize, d.fence_handle_value());
let seen = std::thread::scope(|s| {
let threads: Vec<_> = (0..2)
.map(|_| {
s.spawn(move || match publish_rendezvous(ptr, fence_handle) {
Published::Other(view) => Some(view.device_ptr),
Published::Ours(_) => Some(ptr),
Published::Unavailable => None,
})
})
.collect();
threads
.into_iter()
.map(|t| t.join().unwrap())
.collect::<Vec<_>>()
});
assert_eq!(seen, vec![Some(ptr), Some(ptr)]);
}
#[test]
fn a_copy_with_a_private_fence_exports_no_fence_handle() {
let d = device().unwrap();
assert!(d.fence_is_shared());
assert_eq!(d.exported_fence_handle_value(), d.fence_handle_value());
assert_ne!(d.exported_fence_handle_value(), 0);
let private = wrap(d.dev().clone(), "private fence".into(), None, None).unwrap();
assert!(!private.fence_is_shared());
assert_ne!(
private.fence_handle_value(),
0,
"it has a fence, just not the one the process agreed on"
);
assert_eq!(
private.exported_fence_handle_value(),
0,
"a descriptor or a blob from this copy carries no completion at all"
);
assert!(private.signal().is_some_and(|v| v > 0));
}
#[test]
fn duplicate_handle_round_trips_the_fence_handle() {
let d = device().unwrap();
let (a, b) = (
d.fence_shared_handle().unwrap(),
d.fence_shared_handle().unwrap(),
);
assert_ne!(a.as_raw_handle(), b.as_raw_handle());
assert!(!a.as_raw_handle().is_null() && !b.as_raw_handle().is_null());
drop(a);
let mut fence: Option<ID3D11Fence> = None;
unsafe {
d.dev5()
.unwrap()
.OpenSharedFence(HANDLE(b.as_raw_handle()), &mut fence)
}
.expect("the surviving duplicate still names the fence");
assert!(fence.is_some());
}
}