Skip to main content

windows_capture/
d3d11.rs

1use windows::Graphics::DirectX::Direct3D11::IDirect3DDevice;
2use windows::Win32::Foundation::HMODULE;
3use windows::Win32::Graphics::Direct3D::{
4    D3D_DRIVER_TYPE_HARDWARE, D3D_FEATURE_LEVEL, D3D_FEATURE_LEVEL_9_1, D3D_FEATURE_LEVEL_9_2, D3D_FEATURE_LEVEL_9_3,
5    D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_1,
6};
7use windows::Win32::Graphics::Direct3D11::{
8    D3D11_CPU_ACCESS_READ, D3D11_CPU_ACCESS_WRITE, D3D11_CREATE_DEVICE_BGRA_SUPPORT, D3D11_SDK_VERSION,
9    D3D11_TEXTURE2D_DESC, D3D11_USAGE_STAGING, D3D11CreateDevice, ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D,
10};
11use windows::Win32::Graphics::Dxgi::Common::{DXGI_FORMAT, DXGI_SAMPLE_DESC};
12use windows::Win32::Graphics::Dxgi::IDXGIDevice;
13use windows::Win32::System::WinRT::Direct3D11::CreateDirect3D11DeviceFromDXGIDevice;
14use windows::core::Interface;
15
16#[derive(thiserror::Error, Eq, PartialEq, Clone, Debug)]
17/// Errors that can occur when creating or working with Direct3D devices and textures.
18pub enum Error {
19    /// The created device does not support at least feature level 11.0.
20    #[error("Failed to create DirectX device with the recommended feature levels")]
21    FeatureLevelNotSatisfied,
22    /// A Windows Runtime/Win32 API call failed.
23    ///
24    /// Wraps [`windows::core::Error`].
25    #[error("Windows API Error: {0}")]
26    WindowsError(#[from] windows::core::Error),
27}
28
29/// A wrapper to send a DirectX device across threads.
30pub struct SendDirectX<T>(pub T);
31
32impl<T> SendDirectX<T> {
33    /// Constructs a new `SendDirectX` instance.
34    #[inline]
35    #[must_use]
36    pub const fn new(device: T) -> Self {
37        Self(device)
38    }
39}
40
41#[allow(clippy::non_send_fields_in_send_ty)]
42unsafe impl<T> Send for SendDirectX<T> {}
43
44/// Creates an [`windows::Win32::Graphics::Direct3D11::ID3D11Device`] and an
45/// [`windows::Win32::Graphics::Direct3D11::ID3D11DeviceContext`].
46///
47/// # Errors
48///
49/// - [`Error::WindowsError`] when the underlying `D3D11CreateDevice` call fails
50/// - [`Error::FeatureLevelNotSatisfied`] when the created device does not support at least feature
51///   level 11.0
52#[inline]
53pub fn create_d3d_device() -> Result<(ID3D11Device, ID3D11DeviceContext), Error> {
54    // Array of Direct3D feature levels.
55    // The feature levels are listed in descending order of capability.
56    // The highest feature level supported by the system is at index 0.
57    // The lowest feature level supported by the system is at the last index.
58    let feature_flags = [
59        D3D_FEATURE_LEVEL_11_1,
60        D3D_FEATURE_LEVEL_11_0,
61        D3D_FEATURE_LEVEL_10_1,
62        D3D_FEATURE_LEVEL_10_0,
63        D3D_FEATURE_LEVEL_9_3,
64        D3D_FEATURE_LEVEL_9_2,
65        D3D_FEATURE_LEVEL_9_1,
66    ];
67
68    let mut d3d_device = None;
69    let mut feature_level = D3D_FEATURE_LEVEL::default();
70    let mut d3d_device_context = None;
71    unsafe {
72        D3D11CreateDevice(
73            None,
74            D3D_DRIVER_TYPE_HARDWARE,
75            HMODULE::default(),
76            D3D11_CREATE_DEVICE_BGRA_SUPPORT,
77            Some(&feature_flags),
78            D3D11_SDK_VERSION,
79            Some(&mut d3d_device),
80            Some(&mut feature_level),
81            Some(&mut d3d_device_context),
82        )?;
83    };
84
85    if feature_level.0 < D3D_FEATURE_LEVEL_11_0.0 {
86        return Err(Error::FeatureLevelNotSatisfied);
87    }
88
89    Ok((d3d_device.unwrap(), d3d_device_context.unwrap()))
90}
91
92/// Creates an [`windows::Graphics::DirectX::Direct3D11::IDirect3DDevice`] from an
93/// [`windows::Win32::Graphics::Direct3D11::ID3D11Device`].
94///
95/// # Errors
96///
97/// - [`Error::WindowsError`] when creating the Direct3D11 device wrapper fails
98#[inline]
99pub fn create_direct3d_device(d3d_device: &ID3D11Device) -> Result<IDirect3DDevice, Error> {
100    let dxgi_device: IDXGIDevice = d3d_device.cast()?;
101    let inspectable = unsafe { CreateDirect3D11DeviceFromDXGIDevice(&dxgi_device)? };
102    let device: IDirect3DDevice = inspectable.cast()?;
103
104    Ok(device)
105}
106
107/// Reusable CPU-read/write staging texture wrapper.
108pub struct StagingTexture {
109    inner: ID3D11Texture2D,
110    desc: D3D11_TEXTURE2D_DESC,
111    is_mapped: bool,
112}
113
114impl StagingTexture {
115    /// Create a staging texture suitable for CPU read/write with the given geometry/format.
116    pub fn new(device: &ID3D11Device, width: u32, height: u32, format: DXGI_FORMAT) -> Result<Self, Error> {
117        let desc = D3D11_TEXTURE2D_DESC {
118            Width: width,
119            Height: height,
120            MipLevels: 1,
121            ArraySize: 1,
122            Format: format,
123            SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
124            Usage: D3D11_USAGE_STAGING,
125            BindFlags: 0,
126            CPUAccessFlags: (D3D11_CPU_ACCESS_READ.0 | D3D11_CPU_ACCESS_WRITE.0) as u32,
127            MiscFlags: 0,
128        };
129
130        let mut tex = None;
131        unsafe {
132            device.CreateTexture2D(&desc, None, Some(&mut tex))?;
133        }
134        Ok(Self { inner: tex.unwrap(), desc, is_mapped: false })
135    }
136
137    /// Gets the underlying [`windows::Win32::Graphics::Direct3D11::ID3D11Texture2D`].
138    #[inline]
139    #[must_use]
140    pub const fn texture(&self) -> &ID3D11Texture2D {
141        &self.inner
142    }
143
144    /// Gets the description of the texture.
145    #[inline]
146    #[must_use]
147    pub const fn desc(&self) -> D3D11_TEXTURE2D_DESC {
148        self.desc
149    }
150
151    /// Checks if the texture is currently mapped.
152    #[inline]
153    #[must_use]
154    pub const fn is_mapped(&self) -> bool {
155        self.is_mapped
156    }
157
158    /// Marks the texture as mapped or unmapped.
159    #[inline]
160    pub const fn set_mapped(&mut self, mapped: bool) {
161        self.is_mapped = mapped;
162    }
163
164    /// Validate an externally constructed texture as a CPU staging texture.
165    /// The texture must have been created with `D3D11_USAGE_STAGING` usage and
166    /// `D3D11_CPU_ACCESS_READ` and `D3D11_CPU_ACCESS_WRITE` CPU access flags.
167    pub fn from_raw_checked(tex: ID3D11Texture2D) -> Option<Self> {
168        let mut desc = D3D11_TEXTURE2D_DESC::default();
169        unsafe { tex.GetDesc(&mut desc) };
170        let is_staging = desc.Usage == D3D11_USAGE_STAGING;
171        let has_cpu_rw = (desc.CPUAccessFlags & (D3D11_CPU_ACCESS_READ.0 | D3D11_CPU_ACCESS_WRITE.0) as u32) != 0;
172
173        if !is_staging || !has_cpu_rw {
174            return None;
175        }
176
177        Some(Self { inner: tex, desc, is_mapped: false })
178    }
179}