96 lines
3.9 KiB
Rust
96 lines
3.9 KiB
Rust
//! End-to-end zero-copy proof: import a VAAPI NV12 surface as wgpu textures, render
|
|
//! known values into them via Vulkan, read the surface back, and verify the bytes —
|
|
//! proving the GPU wrote straight into the encoder's surface with no CPU upload.
|
|
|
|
#![cfg(target_os = "linux")]
|
|
|
|
use gpu_video_encoder::{dmabuf, vaapi, vk_device};
|
|
|
|
#[test]
|
|
fn zerocopy_render_into_vaapi_surface() {
|
|
let drm = match vk_device::create() {
|
|
Ok(d) => d,
|
|
Err(e) => {
|
|
eprintln!("[zerocopy] no Vulkan device, skipping: {e}");
|
|
return;
|
|
}
|
|
};
|
|
let surf = match vaapi::MappedSurface::alloc(640, 480) {
|
|
Ok(s) => s,
|
|
Err(e) => {
|
|
eprintln!("[zerocopy] no VAAPI surface, skipping: {e}");
|
|
return;
|
|
}
|
|
};
|
|
eprintln!(
|
|
"[zerocopy] surface: modifier=0x{:016x} y(off={},pitch={}) uv(off={},pitch={}) size={}",
|
|
surf.modifier, surf.y_offset, surf.y_pitch, surf.uv_offset, surf.uv_pitch, surf.size
|
|
);
|
|
|
|
let imported = match dmabuf::import(&drm, &surf) {
|
|
Ok(i) => i,
|
|
Err(e) => panic!("dma-buf import failed: {e}"),
|
|
};
|
|
eprintln!("[zerocopy] imported surface as wgpu Y(R8) + UV(RG8) textures");
|
|
|
|
// Render known constants via clear: Y=0.5(->128), U=0.25(->64), V=0.75(->191).
|
|
let y_view = imported.y.create_view(&Default::default());
|
|
let uv_view = imported.uv.create_view(&Default::default());
|
|
let mut enc = drm.device.create_command_encoder(&Default::default());
|
|
{
|
|
enc.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
label: Some("clear-y"),
|
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
view: &y_view,
|
|
resolve_target: None,
|
|
depth_slice: None,
|
|
ops: wgpu::Operations {
|
|
load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.5, g: 0.0, b: 0.0, a: 0.0 }),
|
|
store: wgpu::StoreOp::Store,
|
|
},
|
|
})],
|
|
depth_stencil_attachment: None,
|
|
timestamp_writes: None,
|
|
occlusion_query_set: None,
|
|
});
|
|
enc.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
label: Some("clear-uv"),
|
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
view: &uv_view,
|
|
resolve_target: None,
|
|
depth_slice: None,
|
|
ops: wgpu::Operations {
|
|
load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.25, g: 0.75, b: 0.0, a: 0.0 }),
|
|
store: wgpu::StoreOp::Store,
|
|
},
|
|
})],
|
|
depth_stencil_attachment: None,
|
|
timestamp_writes: None,
|
|
occlusion_query_set: None,
|
|
});
|
|
}
|
|
drm.queue.submit(Some(enc.finish()));
|
|
let _ = drm.device.poll(wgpu::PollType::wait_indefinitely());
|
|
|
|
// Read the VAAPI surface back and check what the GPU wrote.
|
|
let nv12 = surf.readback_nv12().expect("readback");
|
|
let (w, h) = (640usize, 480usize);
|
|
let y_plane = &nv12[..w * h];
|
|
let uv_plane = &nv12[w * h..];
|
|
|
|
let near = |v: u8, t: i32| (v as i32 - t).abs() <= 3;
|
|
let y_ok = y_plane.iter().filter(|&&v| near(v, 128)).count();
|
|
let u_ok = uv_plane.iter().step_by(2).filter(|&&v| near(v, 64)).count();
|
|
let v_ok = uv_plane.iter().skip(1).step_by(2).filter(|&&v| near(v, 191)).count();
|
|
eprintln!(
|
|
"[zerocopy] Y~128: {}/{}, U~64: {}/{}, V~191: {}/{}",
|
|
y_ok, w * h, u_ok, uv_plane.len() / 2, v_ok, uv_plane.len() / 2
|
|
);
|
|
|
|
let frac = |ok: usize, n: usize| ok as f64 / n as f64;
|
|
assert!(frac(y_ok, w * h) > 0.98, "Y plane not the rendered value (sample {:?})", &y_plane[..8]);
|
|
assert!(frac(u_ok, uv_plane.len() / 2) > 0.98, "U not rendered value");
|
|
assert!(frac(v_ok, uv_plane.len() / 2) > 0.98, "V not rendered value");
|
|
eprintln!("[zerocopy] ✅ GPU rendered straight into the VAAPI surface (verified via readback)");
|
|
}
|