use super::*;
use std::os::windows::io::{AsRawHandle, BorrowedHandle};
use windows::Win32::{
Foundation::{HANDLE, WAIT_ABANDONED, WAIT_TIMEOUT},
Graphics::Dxgi::IDXGIKeyedMutex,
};
#[derive(Debug)]
pub(super) struct KeyedLease {
mutex: IDXGIKeyedMutex,
release_key: u64,
}
impl Drop for KeyedLease {
fn drop(&mut self) {
if let Err(error) = unsafe { self.mutex.ReleaseSync(self.release_key) } {
log::error!("DXGI snapshot ownership release failed: {error}");
}
}
}
#[derive(Clone, Debug)]
pub struct DxgiFrame {
texture: ID3D11Texture2D,
extent: Size<DevicePixels>,
lease: Option<Arc<KeyedLease>>,
}
impl DxgiFrame {
pub unsafe fn from_bgra(texture: ID3D11Texture2D) -> Result<Self> {
Self::validate(texture, false)
}
fn validate(texture: ID3D11Texture2D, keyed: bool) -> Result<Self> {
let mut desc = D3D11_TEXTURE2D_DESC::default();
unsafe { texture.GetDesc(&mut desc) };
ensure!(
desc.Format == DXGI_FORMAT_B8G8R8A8_UNORM,
"DXGI frame requires BGRA8 UNORM"
);
ensure!(
desc.Width > 0 && desc.Height > 0 && desc.Width <= 16384 && desc.Height <= 16384,
"video dimensions must be 1..16384"
);
ensure!(
desc.ArraySize == 1 && desc.MipLevels == 1 && desc.SampleDesc.Count == 1,
"DXGI frame requires one non-multisampled mip and array slice"
);
let required = (D3D11_BIND_SHADER_RESOURCE.0 | D3D11_BIND_RENDER_TARGET.0) as u32;
ensure!(
desc.BindFlags & required == required && desc.Usage == D3D11_USAGE_DEFAULT,
"DXGI frame requires a default-usage shader-readable render target"
);
ensure!(
keyed || desc.MiscFlags & (D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX.0 as u32) == 0,
"keyed-mutex surfaces require an explicit synchronization adapter"
);
Ok(Self {
texture,
lease: None,
extent: size(
DevicePixels(desc.Width as i32),
DevicePixels(desc.Height as i32),
),
})
}
pub unsafe fn try_from_shared_bgra(
device: &ID3D11Device,
handle: BorrowedHandle<'_>,
ready_key: u64,
release_key: u64,
) -> Result<Option<Self>> {
ensure!(
ready_key != release_key,
"DXGI producer and consumer keys must differ"
);
let device: ID3D11Device1 = device.cast()?;
let texture: ID3D11Texture2D =
unsafe { device.OpenSharedResource1(HANDLE(handle.as_raw_handle()))? };
let mut frame = Self::validate(texture, true)?;
let mutex: IDXGIKeyedMutex = frame
.texture
.cast()
.context("shared BGRA surface must provide a keyed mutex")?;
let status =
unsafe { (Interface::vtable(&mutex).AcquireSync)(mutex.as_raw(), ready_key, 0) };
match status.0 {
0 => {}
code if code == WAIT_TIMEOUT.0 as i32 => return Ok(None),
code if code == WAIT_ABANDONED.0 as i32 => {
anyhow::bail!("DXGI shared surface abandoned; recreate the surface")
}
_ => anyhow::bail!("DXGI keyed acquisition failed: {status:?}"),
}
frame.lease = Some(Arc::new(KeyedLease { mutex, release_key }));
Ok(Some(frame))
}
pub fn size(&self) -> Size<DevicePixels> {
self.extent
}
}
impl gpui::CustomContent for DxgiFrame {
fn prepare(
&self,
backend: &dyn std::any::Any,
_: Size<DevicePixels>,
) -> Result<Box<dyn std::any::Any>> {
let context = backend
.downcast_ref::<Dx11CustomContext>()
.context("DXGI frames require native Direct3D11")?;
ensure!(
unsafe { self.texture.GetDevice()? }.as_raw() == context.device.as_raw(),
"DXGI frame belongs to another device; use try_from_shared_bgra on the host device"
);
let (mut srv, mut rtv) = (None, None);
unsafe {
context
.device
.CreateShaderResourceView(&self.texture, None, Some(&mut srv))?;
context
.device
.CreateRenderTargetView(&self.texture, None, Some(&mut rtv))?;
}
Ok(Box::new(Dx11CustomOutput {
bundle: TextureBundle {
texture: self.texture.clone(),
srv: srv.context("missing DXGI shader view")?,
rtv: rtv.context("missing DXGI target view")?,
extent: self.extent,
_dxgi_lease: self.lease.clone(),
},
source_sized: true,
owner: context.owner.clone(),
recording: context.recording.clone(),
}))
}
}