1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
#![cfg(test)]
#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::print_stderr,
reason = "unit tests"
)]
use super::resized_geometry;
use mediaway_common::VideoGeometry;
#[cfg(windows)]
mod hardware {
#![allow(
unsafe_code,
reason = "real win32 window creation for a real-hardware WGC capture smoke test"
)]
use crate::desktop::{CaptureOutputPreference, DesktopVideoCapture, DesktopVideoCaptureConfig};
use crate::windows::{GpuDevice, GpuDeviceOptions};
use crate::windows_desktop::WindowsWindowCapture;
use mediaway_common::{GpuBufferHandle, NativeHandle, Rational, VideoFrameStorage};
use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM};
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
use windows::Win32::UI::WindowsAndMessaging::{
CW_USEDEFAULT, CreateWindowExW, DefWindowProcW, DestroyWindow, RegisterClassW,
SW_SHOWNORMAL, ShowWindow, UnregisterClassW, WINDOW_EX_STYLE, WNDCLASSW,
WS_OVERLAPPEDWINDOW,
};
use windows::core::PCWSTR;
/// Minimal `WNDPROC` — this test never needs custom message handling.
unsafe extern "system" fn wndproc(
hwnd: HWND,
msg: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
// SAFETY: forwards to the default window procedure, exactly as any minimal win32
// window does when it has no custom message handling of its own.
unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
}
/// Real, visible top-level window this test creates/destroys — a real capture target
/// for WGC, which cannot capture a nonexistent/minimized/off-screen window.
struct TestWindow {
hwnd: HWND,
class_name: Vec<u16>,
}
impl TestWindow {
fn create() -> Option<Self> {
let class_name: Vec<u16> = "MediawayWgcSmokeTestWindow\0".encode_utf16().collect();
let instance = unsafe { GetModuleHandleW(None) }.ok()?;
let class = WNDCLASSW {
lpfnWndProc: Some(wndproc),
hInstance: instance.into(),
lpszClassName: PCWSTR(class_name.as_ptr()),
..Default::default()
};
// SAFETY: `class_name`/`class` are both live for the duration of this call.
if unsafe { RegisterClassW(&raw const class) } == 0 {
return None;
}
let title: Vec<u16> = "mediaway wgc smoke test\0".encode_utf16().collect();
// SAFETY: standard CreateWindowExW call with a registered class name and no
// parent/menu; every out-param is `None`/default, matching a plain top-level window.
let hwnd = unsafe {
CreateWindowExW(
WINDOW_EX_STYLE::default(),
PCWSTR(class_name.as_ptr()),
PCWSTR(title.as_ptr()),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
320,
240,
None,
None,
Some(instance.into()),
None,
)
}
.ok()?;
if hwnd.is_invalid() {
return None;
}
// SAFETY: hwnd is a live window just created above.
let _ = unsafe { ShowWindow(hwnd, SW_SHOWNORMAL) };
Some(Self { hwnd, class_name })
}
}
impl Drop for TestWindow {
fn drop(&mut self) {
// SAFETY: hwnd/class_name were created by this same struct's `create`.
unsafe {
let _ = DestroyWindow(self.hwnd);
let _ = UnregisterClassW(PCWSTR(self.class_name.as_ptr()), None);
}
}
}
/// Real hardware smoke test: a real win32 window, a real D3D11 device, a real WGC
/// session — proves `WindowsWindowCapture`'s already-Zero-Copy code path (no
/// `CopyResource`/`memcpy` anywhere in `poll_frame`, see `wgc.rs`) actually delivers a
/// real `GpuBufferHandle::DirectX11` frame end to end. Per `adr/windows/0004`'s own
/// acceptance criterion ("README Window cell can move toward 🆗/⚡ once CI machines
/// prove capture") — this is that proof.
///
/// Skips gracefully (never fails the default suite) at any missing capability: no WGC
/// support, window creation failure, or no frame delivered within the bounded poll
/// window (WGC delivery is async and this test cannot control compositor timing).
#[test]
fn wgc_window_capture_delivers_zero_copy_frame_or_skip() {
let _guard = crate::windows_desktop::HARDWARE_TEST_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let Some(window) = TestWindow::create() else {
eprintln!("skip: could not create a real test window");
return;
};
let device = match GpuDevice::create(GpuDeviceOptions::default()) {
Ok(d) => d,
Err(e) => {
eprintln!("skip: GpuDevice::create failed ({e:?})");
return;
}
};
let Some(window_handle) = NativeHandle::new(window.hwnd.0 as usize) else {
eprintln!("skip: null test window handle");
return;
};
let mut cfg = DesktopVideoCaptureConfig::window(window_handle, Rational::new(1, 30));
cfg.output = CaptureOutputPreference::ZeroCopyGpu;
cfg.gpu_device = Some(device.handle());
let mut capture = match WindowsWindowCapture::open(&cfg) {
Ok(c) => c,
Err(e) => {
eprintln!("skip: WindowsWindowCapture::open failed ({e:?})");
return;
}
};
// WGC frame delivery is async — poll with a bounded retry loop rather than a single
// attempt (same shape this crate's own poll-based backends already use elsewhere).
let mut delivered = None;
for _ in 0..50 {
match capture.poll_frame() {
Ok(Some(frame)) => {
delivered = Some(frame);
break;
}
Ok(None) => std::thread::sleep(std::time::Duration::from_millis(20)),
Err(e) => {
eprintln!("skip: poll_frame failed ({e:?})");
return;
}
}
}
let Some(frame) = delivered else {
eprintln!("skip: no frame delivered within the bounded poll window");
return;
};
assert!(
frame.width > 0 && frame.height > 0,
"expected a real frame size"
);
assert!(
matches!(
frame.storage,
VideoFrameStorage::Gpu(GpuBufferHandle::DirectX11 { .. })
),
"expected a Zero-Copy DirectX11 handle, got {:?}",
frame.storage
);
let _ = capture.release_frame();
eprintln!(
"wgc window capture: real Zero-Copy frame delivered ({}x{})",
frame.width, frame.height
);
}
}
/// Full hardware-driven resize (actually resizing a captured window/monitor mid-session
/// and observing `Direct3D11CaptureFramePool::Recreate` take effect) is not practically
/// automatable in this suite — it needs a real WGC session plus a window an external
/// actor resizes on a timeline this test can't control. Instead, this exercises the pure
/// decision logic `poll_frame` uses to detect a size change, extracted so it is testable
/// without `WinRT` calls.
#[test]
fn resized_geometry_none_when_size_unchanged() {
let current = VideoGeometry {
width: 1920,
height: 1080,
};
assert_eq!(resized_geometry(current, 1920, 1080), None);
}
#[test]
fn resized_geometry_some_when_width_changes() {
let current = VideoGeometry {
width: 1920,
height: 1080,
};
assert_eq!(
resized_geometry(current, 1280, 1080),
Some(VideoGeometry {
width: 1280,
height: 1080,
})
);
}
#[test]
fn resized_geometry_some_when_height_changes() {
let current = VideoGeometry {
width: 1920,
height: 1080,
};
assert_eq!(
resized_geometry(current, 1920, 720),
Some(VideoGeometry {
width: 1920,
height: 720,
})
);
}
#[test]
fn resized_geometry_some_on_first_frame_from_zero_geometry() {
// `stream_info.geometry()` starts non-zero at `open()` in practice, but the closed/
// uninitialized path uses a `0x0` placeholder — confirm that is always treated as a
// mismatch (never accidentally suppresses a legitimate first Recreate).
let current = VideoGeometry {
width: 0,
height: 0,
};
assert_eq!(
resized_geometry(current, 800, 600),
Some(VideoGeometry {
width: 800,
height: 600,
})
);
}