use std::sync::{Arc, Mutex};
use windows::{
core::Interface,
Win32::{
Foundation::HMODULE,
Graphics::{
Direct3D::D3D_DRIVER_TYPE_HARDWARE,
Direct3D10::ID3D10Multithread,
Direct3D11::{
D3D11CreateDevice, ID3D11Device, ID3D11DeviceContext, ID3D11Resource,
ID3D11Texture2D, ID3D11VideoDevice, D3D11_CPU_ACCESS_READ,
D3D11_CREATE_DEVICE_BGRA_SUPPORT, D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
D3D11_DECODER_PROFILE_H264_VLD_FGT, D3D11_DECODER_PROFILE_H264_VLD_NOFGT,
D3D11_DECODER_PROFILE_HEVC_VLD_MAIN, D3D11_MAPPED_SUBRESOURCE, D3D11_MAP_READ,
D3D11_SDK_VERSION, D3D11_TEXTURE2D_DESC, D3D11_USAGE_STAGING,
},
Dxgi::Common::{DXGI_FORMAT_NV12, DXGI_SAMPLE_DESC},
},
Media::MediaFoundation::{IMFDXGIDeviceManager, MFCreateDXGIDeviceManager},
},
};
use crate::{VideoCodec, VideoError};
use super::runtime::platform_error;
#[derive(Clone)]
pub(crate) struct D3dDevice {
pub(crate) device: ID3D11Device,
pub(crate) manager: IMFDXGIDeviceManager,
context: ID3D11DeviceContext,
staging: Arc<Mutex<Option<StagingTexture>>>,
}
struct StagingTexture {
resource: ID3D11Resource,
width: u32,
height: u32,
}
impl D3dDevice {
pub(crate) fn new() -> Result<Self, VideoError> {
let mut device = None;
let mut context = None;
unsafe {
D3D11CreateDevice(
None,
D3D_DRIVER_TYPE_HARDWARE,
HMODULE::default(),
D3D11_CREATE_DEVICE_VIDEO_SUPPORT | D3D11_CREATE_DEVICE_BGRA_SUPPORT,
None,
D3D11_SDK_VERSION,
Some(&mut device),
None,
Some(&mut context),
)
}
.map_err(|error| platform_error("D3D11CreateDevice", error))?;
let device = device.ok_or(VideoError::Backend {
backend: "media-foundation",
operation: "create D3D11 device",
message: "D3D11 returned no device".to_owned(),
})?;
let context = context.ok_or(VideoError::Backend {
backend: "media-foundation",
operation: "create D3D11 device",
message: "D3D11 returned no immediate context".to_owned(),
})?;
let multithread: ID3D10Multithread = device
.cast()
.map_err(|error| platform_error("ID3D10Multithread::QueryInterface", error))?;
let _ = unsafe { multithread.SetMultithreadProtected(true) };
let mut reset_token = 0;
let mut manager = None;
unsafe { MFCreateDXGIDeviceManager(&mut reset_token, &mut manager) }
.map_err(|error| platform_error("MFCreateDXGIDeviceManager", error))?;
let manager = manager.ok_or(VideoError::Backend {
backend: "media-foundation",
operation: "create DXGI device manager",
message: "Media Foundation returned no device manager".to_owned(),
})?;
unsafe { manager.ResetDevice(&device, reset_token) }
.map_err(|error| platform_error("IMFDXGIDeviceManager::ResetDevice", error))?;
Ok(Self {
device,
manager,
context,
staging: Arc::new(Mutex::new(None)),
})
}
pub(crate) fn supports_hardware_decode(&self, codec: VideoCodec) -> bool {
let Ok(video_device) = self.device.cast::<ID3D11VideoDevice>() else {
return false;
};
let desired_profiles = match codec {
VideoCodec::H264 => &[
D3D11_DECODER_PROFILE_H264_VLD_NOFGT,
D3D11_DECODER_PROFILE_H264_VLD_FGT,
][..],
VideoCodec::H265 => &[D3D11_DECODER_PROFILE_HEVC_VLD_MAIN][..],
};
let profile_count = unsafe { video_device.GetVideoDecoderProfileCount() };
(0..profile_count).any(|index| {
let Ok(profile) = (unsafe { video_device.GetVideoDecoderProfile(index) }) else {
return false;
};
desired_profiles.contains(&profile)
&& unsafe {
video_device
.CheckVideoDecoderFormat(&profile, DXGI_FORMAT_NV12)
.is_ok_and(|supported| supported.as_bool())
}
})
}
pub(crate) fn copy_nv12(
&self,
source: &ID3D11Texture2D,
source_subresource: u32,
dimensions: crate::FrameDimensions,
) -> Result<super::WindowsNv12Frame, VideoError> {
let mut source_desc = D3D11_TEXTURE2D_DESC::default();
unsafe { source.GetDesc(&mut source_desc) };
if source_desc.Format != DXGI_FORMAT_NV12 {
return Err(VideoError::Backend {
backend: "media-foundation",
operation: "read D3D11 frame",
message: format!("expected NV12 texture, got {:?}", source_desc.Format),
});
}
let source_resource: ID3D11Resource = source
.cast()
.map_err(|error| platform_error("ID3D11Texture2D::QueryInterface", error))?;
let mut staging = self.staging.lock().map_err(|_| VideoError::Backend {
backend: "media-foundation",
operation: "lock D3D11 staging texture",
message: "staging texture lock was poisoned".to_owned(),
})?;
if staging.as_ref().is_none_or(|texture| {
texture.width != source_desc.Width || texture.height != source_desc.Height
}) {
*staging = Some(self.create_staging_texture(&source_desc)?);
}
let staging = staging
.as_ref()
.expect("staging texture was initialized above");
unsafe {
self.context.CopySubresourceRegion(
&staging.resource,
0,
0,
0,
0,
&source_resource,
source_subresource,
None,
)
};
let mut mapped = D3D11_MAPPED_SUBRESOURCE::default();
unsafe {
self.context
.Map(&staging.resource, 0, D3D11_MAP_READ, 0, Some(&mut mapped))
}
.map_err(|error| platform_error("ID3D11DeviceContext::Map", error))?;
let _unmap = UnmapGuard {
context: &self.context,
resource: &staging.resource,
};
if mapped.pData.is_null() {
return Err(VideoError::Backend {
backend: "media-foundation",
operation: "map D3D11 staging texture",
message: "D3D11 returned a null mapped pointer".to_owned(),
});
}
let row_pitch = mapped.RowPitch as usize;
let coded_height = source_desc.Height as usize;
let total_rows = coded_height.saturating_add(coded_height.div_ceil(2));
let bytes = unsafe {
std::slice::from_raw_parts(
mapped.pData.cast::<u8>(),
row_pitch.saturating_mul(total_rows),
)
};
let width = dimensions.width.min(source_desc.Width) as usize;
let height = dimensions.height.min(source_desc.Height) as usize;
let mut y = vec![0; width.saturating_mul(height)];
let chroma_rows = height.div_ceil(2);
let mut uv = vec![0; width.saturating_mul(chroma_rows)];
for row in 0..height {
let source_offset = row * row_pitch;
y[row * width..(row + 1) * width]
.copy_from_slice(&bytes[source_offset..source_offset + width]);
}
let chroma_offset = coded_height * row_pitch;
for row in 0..chroma_rows {
let source_offset = chroma_offset + row * row_pitch;
uv[row * width..(row + 1) * width]
.copy_from_slice(&bytes[source_offset..source_offset + width]);
}
Ok(super::WindowsNv12Frame {
dimensions: crate::FrameDimensions {
width: width as u32,
height: height as u32,
},
y,
uv,
stride: width,
})
}
fn create_staging_texture(
&self,
source: &D3D11_TEXTURE2D_DESC,
) -> Result<StagingTexture, VideoError> {
let description = D3D11_TEXTURE2D_DESC {
Width: source.Width,
Height: source.Height,
MipLevels: 1,
ArraySize: 1,
Format: source.Format,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Usage: D3D11_USAGE_STAGING,
BindFlags: 0,
CPUAccessFlags: D3D11_CPU_ACCESS_READ.0 as u32,
MiscFlags: 0,
};
let mut texture = None;
unsafe {
self.device
.CreateTexture2D(&description, None, Some(&mut texture))
}
.map_err(|error| platform_error("ID3D11Device::CreateTexture2D", error))?;
let texture = texture.ok_or_else(|| VideoError::Backend {
backend: "media-foundation",
operation: "create D3D11 staging texture",
message: "D3D11 returned no staging texture".to_owned(),
})?;
let resource = texture
.cast()
.map_err(|error| platform_error("ID3D11Texture2D::QueryInterface", error))?;
Ok(StagingTexture {
resource,
width: source.Width,
height: source.Height,
})
}
}
struct UnmapGuard<'a> {
context: &'a ID3D11DeviceContext,
resource: &'a ID3D11Resource,
}
impl Drop for UnmapGuard<'_> {
fn drop(&mut self) {
unsafe { self.context.Unmap(self.resource, 0) };
}
}