Skip to main content

windows_capture/
d3d11.rs

1use std::slice;
2
3use windows::Graphics::DirectX::Direct3D11::IDirect3DDevice;
4use windows::Win32::Foundation::HMODULE;
5use windows::Win32::Graphics::Direct3D::{
6    D3D_DRIVER_TYPE_HARDWARE, D3D_FEATURE_LEVEL, D3D_FEATURE_LEVEL_9_1, D3D_FEATURE_LEVEL_9_2, D3D_FEATURE_LEVEL_9_3,
7    D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_1,
8};
9use windows::Win32::Graphics::Direct3D11::{
10    D3D11_CPU_ACCESS_READ, D3D11_CPU_ACCESS_WRITE, D3D11_CREATE_DEVICE_BGRA_SUPPORT, D3D11_MAP_READ_WRITE,
11    D3D11_MAPPED_SUBRESOURCE, D3D11_SDK_VERSION, D3D11_TEXTURE2D_DESC, D3D11_USAGE_STAGING, D3D11CreateDevice,
12    ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D,
13};
14use windows::Win32::Graphics::Dxgi::Common::{DXGI_FORMAT, DXGI_SAMPLE_DESC};
15use windows::Win32::Graphics::Dxgi::IDXGIDevice;
16use windows::Win32::System::WinRT::Direct3D11::CreateDirect3D11DeviceFromDXGIDevice;
17use windows::core::Interface;
18
19#[derive(thiserror::Error, Eq, PartialEq, Clone, Debug)]
20/// Errors that can occur when creating or working with Direct3D devices and textures.
21pub enum Error {
22    /// The created device does not support at least feature level 11.0.
23    #[error("Failed to create DirectX device with the recommended feature levels")]
24    FeatureLevelNotSatisfied,
25    /// A Win32 API reported success but did not populate the requested output value.
26    #[error("Windows API succeeded but did not return {0}")]
27    UnexpectedNullResult(&'static str),
28    /// A Windows Runtime/Win32 API call failed.
29    ///
30    /// Wraps [`windows::core::Error`].
31    #[error("Windows API Error: {0}")]
32    WindowsError(#[from] windows::core::Error),
33}
34
35/// A wrapper to send a DirectX device across threads.
36pub struct SendDirectX<T>(pub T);
37
38impl<T> SendDirectX<T> {
39    /// Constructs a new `SendDirectX` instance.
40    #[inline]
41    #[must_use]
42    pub const fn new(device: T) -> Self {
43        Self(device)
44    }
45}
46
47#[allow(clippy::non_send_fields_in_send_ty)]
48unsafe impl<T> Send for SendDirectX<T> {}
49
50enum StagingTextureHandle<'a> {
51    Owned(StagingTexture),
52    Borrowed(&'a mut StagingTexture),
53}
54
55impl StagingTextureHandle<'_> {
56    const fn texture(&self) -> &ID3D11Texture2D {
57        match self {
58            Self::Owned(texture) => texture.texture(),
59            Self::Borrowed(texture) => texture.texture(),
60        }
61    }
62
63    const fn set_mapped(&mut self, mapped: bool) {
64        match self {
65            Self::Owned(texture) => texture.set_mapped(mapped),
66            Self::Borrowed(texture) => texture.set_mapped(mapped),
67        }
68    }
69}
70
71/// A mapped staging texture that automatically unmaps itself when dropped.
72pub(crate) struct MappedStagingTexture<'a> {
73    context: &'a ID3D11DeviceContext,
74    texture: StagingTextureHandle<'a>,
75    mapped: D3D11_MAPPED_SUBRESOURCE,
76}
77
78impl<'a> MappedStagingTexture<'a> {
79    /// Maps an owned staging texture.
80    pub fn map_owned(context: &'a ID3D11DeviceContext, texture: StagingTexture) -> Result<Self, windows::core::Error> {
81        Self::map(context, StagingTextureHandle::Owned(texture))
82    }
83
84    /// Maps a caller-provided staging texture after making sure it is currently unmapped.
85    pub fn map_borrowed(
86        context: &'a ID3D11DeviceContext,
87        texture: &'a mut StagingTexture,
88    ) -> Result<Self, windows::core::Error> {
89        unmap_staging_texture(context, texture);
90        Self::map(context, StagingTextureHandle::Borrowed(texture))
91    }
92
93    fn map(
94        context: &'a ID3D11DeviceContext,
95        mut texture: StagingTextureHandle<'a>,
96    ) -> Result<Self, windows::core::Error> {
97        let mut mapped = D3D11_MAPPED_SUBRESOURCE::default();
98        unsafe {
99            context.Map(texture.texture(), 0, D3D11_MAP_READ_WRITE, 0, Some(&mut mapped))?;
100        }
101        texture.set_mapped(true);
102
103        Ok(Self { context, texture, mapped })
104    }
105
106    /// Returns the mapped bytes as an immutable slice for the requested number of rows.
107    #[must_use]
108    pub const fn as_slice(&self, rows: u32) -> &[u8] {
109        let len = rows as usize * self.mapped.RowPitch as usize;
110        unsafe { slice::from_raw_parts(self.mapped.pData.cast(), len) }
111    }
112
113    /// Returns the mapped bytes as a mutable slice for the requested number of rows.
114    #[must_use]
115    pub const fn as_mut_slice(&mut self, rows: u32) -> &mut [u8] {
116        let len = rows as usize * self.mapped.RowPitch as usize;
117        unsafe { slice::from_raw_parts_mut(self.mapped.pData.cast(), len) }
118    }
119
120    /// Returns the row pitch reported by D3D11 for the mapped texture.
121    #[must_use]
122    pub const fn row_pitch(&self) -> u32 {
123        self.mapped.RowPitch
124    }
125
126    /// Returns the depth pitch reported by D3D11 for the mapped texture.
127    #[must_use]
128    pub const fn depth_pitch(&self) -> u32 {
129        self.mapped.DepthPitch
130    }
131}
132
133impl Drop for MappedStagingTexture<'_> {
134    fn drop(&mut self) {
135        unsafe {
136            self.context.Unmap(self.texture.texture(), 0);
137        }
138        self.texture.set_mapped(false);
139    }
140}
141
142/// Unmaps a staging texture if it is currently mapped.
143pub(crate) fn unmap_staging_texture(context: &ID3D11DeviceContext, texture: &mut StagingTexture) {
144    if texture.is_mapped() {
145        unsafe {
146            context.Unmap(texture.texture(), 0);
147        }
148        texture.set_mapped(false);
149    }
150}
151
152/// Creates an [`windows::Win32::Graphics::Direct3D11::ID3D11Device`] and an
153/// [`windows::Win32::Graphics::Direct3D11::ID3D11DeviceContext`].
154///
155/// # Errors
156///
157/// - [`Error::WindowsError`] when the underlying `D3D11CreateDevice` call fails
158/// - [`Error::FeatureLevelNotSatisfied`] when the created device does not support at least feature
159///   level 11.0
160#[inline]
161pub fn create_d3d_device() -> Result<(ID3D11Device, ID3D11DeviceContext), Error> {
162    // Array of Direct3D feature levels.
163    // The feature levels are listed in descending order of capability.
164    // The highest feature level supported by the system is at index 0.
165    // The lowest feature level supported by the system is at the last index.
166    let feature_flags = [
167        D3D_FEATURE_LEVEL_11_1,
168        D3D_FEATURE_LEVEL_11_0,
169        D3D_FEATURE_LEVEL_10_1,
170        D3D_FEATURE_LEVEL_10_0,
171        D3D_FEATURE_LEVEL_9_3,
172        D3D_FEATURE_LEVEL_9_2,
173        D3D_FEATURE_LEVEL_9_1,
174    ];
175
176    let mut d3d_device = None;
177    let mut feature_level = D3D_FEATURE_LEVEL::default();
178    let mut d3d_device_context = None;
179    unsafe {
180        D3D11CreateDevice(
181            None,
182            D3D_DRIVER_TYPE_HARDWARE,
183            HMODULE::default(),
184            D3D11_CREATE_DEVICE_BGRA_SUPPORT,
185            Some(&feature_flags),
186            D3D11_SDK_VERSION,
187            Some(&mut d3d_device),
188            Some(&mut feature_level),
189            Some(&mut d3d_device_context),
190        )?;
191    };
192
193    if feature_level.0 < D3D_FEATURE_LEVEL_11_0.0 {
194        return Err(Error::FeatureLevelNotSatisfied);
195    }
196
197    let d3d_device = d3d_device.ok_or(Error::UnexpectedNullResult("an `ID3D11Device`"))?;
198    let d3d_device_context = d3d_device_context.ok_or(Error::UnexpectedNullResult("an `ID3D11DeviceContext`"))?;
199
200    Ok((d3d_device, d3d_device_context))
201}
202
203/// Creates an [`windows::Graphics::DirectX::Direct3D11::IDirect3DDevice`] from an
204/// [`windows::Win32::Graphics::Direct3D11::ID3D11Device`].
205///
206/// # Errors
207///
208/// - [`Error::WindowsError`] when creating the Direct3D11 device wrapper fails
209#[inline]
210pub fn create_direct3d_device(d3d_device: &ID3D11Device) -> Result<IDirect3DDevice, Error> {
211    let dxgi_device: IDXGIDevice = d3d_device.cast()?;
212    let inspectable = unsafe { CreateDirect3D11DeviceFromDXGIDevice(&dxgi_device)? };
213    let device: IDirect3DDevice = inspectable.cast()?;
214
215    Ok(device)
216}
217
218/// Reusable CPU-read/write staging texture wrapper.
219pub struct StagingTexture {
220    inner: ID3D11Texture2D,
221    desc: D3D11_TEXTURE2D_DESC,
222    is_mapped: bool,
223}
224
225impl StagingTexture {
226    /// Create a staging texture suitable for CPU read/write with the given geometry/format.
227    pub fn new(device: &ID3D11Device, width: u32, height: u32, format: DXGI_FORMAT) -> Result<Self, Error> {
228        let desc = D3D11_TEXTURE2D_DESC {
229            Width: width,
230            Height: height,
231            MipLevels: 1,
232            ArraySize: 1,
233            Format: format,
234            SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
235            Usage: D3D11_USAGE_STAGING,
236            BindFlags: 0,
237            CPUAccessFlags: (D3D11_CPU_ACCESS_READ.0 | D3D11_CPU_ACCESS_WRITE.0) as u32,
238            MiscFlags: 0,
239        };
240
241        let mut tex = None;
242        unsafe {
243            device.CreateTexture2D(&desc, None, Some(&mut tex))?;
244        }
245        let inner = tex.ok_or(Error::UnexpectedNullResult("an `ID3D11Texture2D`"))?;
246
247        Ok(Self { inner, desc, is_mapped: false })
248    }
249
250    /// Gets the underlying [`windows::Win32::Graphics::Direct3D11::ID3D11Texture2D`].
251    #[inline]
252    #[must_use]
253    pub const fn texture(&self) -> &ID3D11Texture2D {
254        &self.inner
255    }
256
257    /// Gets the description of the texture.
258    #[inline]
259    #[must_use]
260    pub const fn desc(&self) -> D3D11_TEXTURE2D_DESC {
261        self.desc
262    }
263
264    /// Checks if the texture is currently mapped.
265    #[inline]
266    #[must_use]
267    pub const fn is_mapped(&self) -> bool {
268        self.is_mapped
269    }
270
271    /// Marks the texture as mapped or unmapped.
272    #[inline]
273    pub const fn set_mapped(&mut self, mapped: bool) {
274        self.is_mapped = mapped;
275    }
276
277    /// Validate an externally constructed texture as a CPU staging texture.
278    /// The texture must have been created with `D3D11_USAGE_STAGING` usage and
279    /// `D3D11_CPU_ACCESS_READ` and `D3D11_CPU_ACCESS_WRITE` CPU access flags.
280    pub fn from_raw_checked(tex: ID3D11Texture2D) -> Option<Self> {
281        let mut desc = D3D11_TEXTURE2D_DESC::default();
282        unsafe { tex.GetDesc(&mut desc) };
283        let is_staging = desc.Usage == D3D11_USAGE_STAGING;
284        let cpu_rw_mask = (D3D11_CPU_ACCESS_READ.0 | D3D11_CPU_ACCESS_WRITE.0) as u32;
285        let has_cpu_rw = (desc.CPUAccessFlags & cpu_rw_mask) == cpu_rw_mask;
286
287        if !is_staging || !has_cpu_rw {
288            return None;
289        }
290
291        Some(Self { inner: tex, desc, is_mapped: false })
292    }
293}