Skip to content

Commit 0d1fd6b

Browse files
committed
Bug fix 🐛
1 parent 5d70b2b commit 0d1fd6b

8 files changed

Lines changed: 102 additions & 31 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "windows-capture"
3-
version = "2.0.0-alpha.2"
3+
version = "2.0.0-alpha.3"
44
authors = ["NiiightmareXD"]
55
edition = "2024"
66
description = "Fastest Windows Screen Capture Library For Rust 🔥"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Add this dependency to your `Cargo.toml`:
3939

4040
```toml
4141
[dependencies]
42-
windows-capture = "2.0.0-alpha.2"
42+
windows-capture = "2.0.0-alpha.3"
4343
```
4444

4545
Or run this command:

src/d3d11.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ pub fn create_direct3d_device(d3d_device: &ID3D11Device) -> Result<IDirect3DDevi
108108
pub struct StagingTexture {
109109
inner: ID3D11Texture2D,
110110
desc: D3D11_TEXTURE2D_DESC,
111+
is_mapped: bool,
111112
}
112113

113114
impl StagingTexture {
@@ -130,7 +131,7 @@ impl StagingTexture {
130131
unsafe {
131132
device.CreateTexture2D(&desc, None, Some(&mut tex))?;
132133
}
133-
Ok(Self { inner: tex.unwrap(), desc })
134+
Ok(Self { inner: tex.unwrap(), desc, is_mapped: false })
134135
}
135136

136137
/// Gets the underlying [`windows::Win32::Graphics::Direct3D11::ID3D11Texture2D`].
@@ -147,7 +148,22 @@ impl StagingTexture {
147148
self.desc
148149
}
149150

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+
150164
/// 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.
151167
pub fn from_raw_checked(tex: ID3D11Texture2D) -> Option<Self> {
152168
let mut desc = D3D11_TEXTURE2D_DESC::default();
153169
unsafe { tex.GetDesc(&mut desc) };
@@ -158,6 +174,6 @@ impl StagingTexture {
158174
return None;
159175
}
160176

161-
Some(Self { inner: tex, desc })
177+
Some(Self { inner: tex, desc, is_mapped: false })
162178
}
163179
}

src/dxgi_duplication_api.rs

Lines changed: 77 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use std::path::Path;
3030
use std::{fs, io, slice};
3131

32+
use rayon::iter::{IntoParallelIterator, ParallelIterator};
3233
use windows::Win32::Graphics::Direct3D11::{
3334
D3D11_BOX, D3D11_CPU_ACCESS_READ, D3D11_CPU_ACCESS_WRITE, D3D11_MAP_READ_WRITE, D3D11_MAPPED_SUBRESOURCE,
3435
D3D11_TEXTURE2D_DESC, D3D11_USAGE_STAGING, ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D,
@@ -100,6 +101,8 @@ pub struct DxgiDuplicationApi {
100101
duplication: IDXGIOutputDuplication,
101102
/// Description of the duplication, including format and dimensions.
102103
duplication_desc: DXGI_OUTDUPL_DESC,
104+
/// Whether the internal staging texture is currently holding a frame.
105+
is_holding_frame: bool,
103106
}
104107

105108
impl DxgiDuplicationApi {
@@ -141,7 +144,7 @@ impl DxgiDuplicationApi {
141144
// Get the duplication description to determine the format for our internal texture.
142145
let duplication_desc = unsafe { duplication.GetDesc() };
143146

144-
Ok(Self { d3d_device, d3d_device_context, duplication, duplication_desc })
147+
Ok(Self { d3d_device, d3d_device_context, duplication, duplication_desc, is_holding_frame: false })
145148
}
146149

147150
/// Gets the underlying [`windows::Win32::Graphics::Direct3D11::ID3D11Device`] associated with
@@ -199,21 +202,26 @@ impl DxgiDuplicationApi {
199202
let mut frame_info = DXGI_OUTDUPL_FRAME_INFO::default();
200203
let mut resource = None;
201204

205+
// Release the previous frame if we were holding one
206+
if self.is_holding_frame {
207+
unsafe { self.duplication.ReleaseFrame() }?;
208+
}
209+
202210
// Acquire frame
203-
unsafe {
204-
match self.duplication.AcquireNextFrame(timeout_ms, &mut frame_info, &mut resource) {
205-
Ok(()) => (),
206-
Err(e) => {
207-
if e.code() == DXGI_ERROR_WAIT_TIMEOUT {
208-
return Err(Error::Timeout);
209-
} else if e.code() == DXGI_ERROR_ACCESS_LOST {
210-
return Err(Error::AccessLost);
211-
} else {
212-
return Err(Error::WindowsError(e));
213-
}
211+
match unsafe { self.duplication.AcquireNextFrame(timeout_ms, &mut frame_info, &mut resource) } {
212+
Ok(()) => (),
213+
Err(e) => {
214+
if e.code() == DXGI_ERROR_WAIT_TIMEOUT {
215+
return Err(Error::Timeout);
216+
} else if e.code() == DXGI_ERROR_ACCESS_LOST {
217+
return Err(Error::AccessLost);
218+
} else {
219+
return Err(Error::WindowsError(e));
214220
}
215221
}
216222
}
223+
self.is_holding_frame = true;
224+
217225
let resource = resource.unwrap();
218226

219227
// Convert the resource to an ID3D11Texture2D.
@@ -458,6 +466,12 @@ impl<'a> DxgiDuplicationFrame<'a> {
458466
return Err(Error::InvalidStagingTexture("format must match the frame"));
459467
}
460468

469+
// Unmap if was previously mapped
470+
if staging.is_mapped() {
471+
unsafe { self.d3d_device_context.Unmap(staging.texture(), 0) };
472+
staging.set_mapped(false);
473+
}
474+
461475
// Copy the acquired duplication texture into the provided staging texture
462476
unsafe {
463477
self.d3d_device_context.CopyResource(staging.texture(), &self.texture);
@@ -468,6 +482,7 @@ impl<'a> DxgiDuplicationFrame<'a> {
468482
unsafe {
469483
self.d3d_device_context.Map(staging.texture(), 0, D3D11_MAP_READ_WRITE, 0, Some(&mut mapped))?;
470484
}
485+
staging.set_mapped(true);
471486

472487
// SAFETY: staging lives for 's and remains alive while the FrameBuffer is borrowed.
473488
let mapped_frame_data = unsafe {
@@ -521,6 +536,12 @@ impl<'a> DxgiDuplicationFrame<'a> {
521536
return Err(Error::InvalidStagingTexture("staging texture too small for crop region"));
522537
}
523538

539+
// Unmap if was previously mapped
540+
if staging.is_mapped() {
541+
unsafe { self.d3d_device_context.Unmap(staging.texture(), 0) };
542+
staging.set_mapped(false);
543+
}
544+
524545
// Define the source region to copy
525546
let src_box = D3D11_BOX { left: start_x, top: start_y, front: 0, right: end_x, bottom: end_y, back: 1 };
526547

@@ -543,6 +564,7 @@ impl<'a> DxgiDuplicationFrame<'a> {
543564
unsafe {
544565
self.d3d_device_context.Map(staging.texture(), 0, D3D11_MAP_READ_WRITE, 0, Some(&mut mapped))?;
545566
}
567+
staging.set_mapped(true);
546568

547569
// SAFETY: staging lives for 's and remains alive while the FrameBuffer is borrowed.
548570
let mapped_frame_data =
@@ -585,15 +607,6 @@ impl<'a> DxgiDuplicationFrame<'a> {
585607
}
586608
}
587609

588-
impl Drop for DxgiDuplicationFrame<'_> {
589-
fn drop(&mut self) {
590-
// Release the frame back to the duplication interface.
591-
unsafe {
592-
let _ = self.duplication.ReleaseFrame();
593-
}
594-
}
595-
}
596-
597610
/// Represents a frame buffer containing pixel data.
598611
///
599612
/// # Example
@@ -668,6 +681,43 @@ impl<'a> DxgiDuplicationFrameBuffer<'a> {
668681
self.width * 4 != self.row_pitch
669682
}
670683

684+
/// Gets the pixel data without padding.
685+
#[inline]
686+
#[must_use]
687+
pub fn as_nopadding_buffer<'b>(&'b self, buffer: &'b mut Vec<u8>) -> &'b [u8] {
688+
if !self.has_padding() {
689+
return self.raw_buffer;
690+
}
691+
692+
let multiplier = match self.color_format {
693+
ColorFormat::Rgba16F => 8,
694+
ColorFormat::Rgba8 => 4,
695+
ColorFormat::Bgra8 => 4,
696+
};
697+
698+
let frame_size = (self.width * self.height * multiplier) as usize;
699+
if buffer.capacity() < frame_size {
700+
buffer.resize(frame_size, 0);
701+
}
702+
703+
let width_size = (self.width * multiplier) as usize;
704+
let buffer_address = buffer.as_mut_ptr() as isize;
705+
(0..self.height).into_par_iter().for_each(|y| {
706+
let index = (y * self.row_pitch) as usize;
707+
let ptr = buffer_address as *mut u8;
708+
709+
unsafe {
710+
std::ptr::copy_nonoverlapping(
711+
self.raw_buffer.as_ptr().add(index),
712+
ptr.add(y as usize * width_size),
713+
width_size,
714+
);
715+
}
716+
});
717+
718+
&buffer[0..frame_size]
719+
}
720+
671721
/// Gets the raw pixel data, which may include padding.
672722
#[inline]
673723
#[must_use]
@@ -681,7 +731,12 @@ impl<'a> DxgiDuplicationFrameBuffer<'a> {
681731
let width = self.width;
682732
let height = self.height;
683733

684-
let bytes = ImageEncoder::new(format, self.color_format)?.encode(self.as_raw_buffer(), width, height)?;
734+
let mut buffer = Vec::new();
735+
let bytes = ImageEncoder::new(format, self.color_format)?.encode(
736+
self.as_nopadding_buffer(&mut buffer),
737+
width,
738+
height,
739+
)?;
685740

686741
fs::write(path, bytes)?;
687742

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
//!
2525
//! ```toml
2626
//! [dependencies]
27-
//! windows-capture = "2.0.0-alpha.2"
27+
//! windows-capture = "2.0.0-alpha.3"
2828
//! ```
2929
//! Or run this command:
3030
//!

windows-capture-python/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "windows-capture-python"
3-
version = "2.0.0-alpha.2"
3+
version = "2.0.0-alpha.3"
44
authors = ["NiiightmareXD"]
55
edition = "2021"
66
description = "Fastest Windows Screen Capture Library For Python 🔥"

windows-capture-python/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "maturin"
44

55
[project]
66
name = "windows-capture"
7-
version = "2.0.0-alpha.2"
7+
version = "2.0.0-alpha.3"
88
description = "Fastest Windows Screen Capture Library For Python 🔥"
99
readme = "README-Python.md"
1010
requires-python = ">=3.9"

0 commit comments

Comments
 (0)