From acef9206a6157289f3fb13c360951c6646783562 Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Thu, 16 Jul 2020 23:56:47 -0400 Subject: [PATCH 1/2] Player-based GPU test framework --- Cargo.lock | 1 + player/Cargo.toml | 8 ++ player/README.md | 5 +- player/src/bin/play.rs | 166 ++++++++++++++++++++++++++++ player/src/{main.rs => lib.rs} | 176 ++---------------------------- player/tests/data/all.ron | 17 +++ player/tests/data/buffer-copy.ron | 23 ++++ player/tests/data/data1.bin | Bin 0 -> 16 bytes player/tests/test.rs | 169 ++++++++++++++++++++++++++++ 9 files changed, 396 insertions(+), 169 deletions(-) create mode 100644 player/src/bin/play.rs rename player/src/{main.rs => lib.rs} (71%) create mode 100644 player/tests/data/all.ron create mode 100644 player/tests/data/buffer-copy.ron create mode 100644 player/tests/data/data1.bin create mode 100644 player/tests/test.rs diff --git a/Cargo.lock b/Cargo.lock index 8e0c22335b..64bf8bdff4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -978,6 +978,7 @@ dependencies = [ "raw-window-handle", "renderdoc", "ron", + "serde", "wgpu-core", "wgpu-types", "winit", diff --git a/player/Cargo.toml b/player/Cargo.toml index f744e5cbd6..7e4befa6ee 100644 --- a/player/Cargo.toml +++ b/player/Cargo.toml @@ -12,6 +12,11 @@ keywords = ["graphics"] license = "MPL-2.0" publish = false +[lib] + +[[bin]] +name = "play" + [features] [dependencies] @@ -36,3 +41,6 @@ features = ["replay", "raw-window-handle"] [target.'cfg(all(unix, not(target_os = "ios"), not(target_os = "macos")))'.dependencies] gfx-backend-vulkan = { version = "0.5", features = ["x11"] } + +[dev-dependencies] +serde = "1" diff --git a/player/README.md b/player/README.md index 707c6df413..36a9780ee3 100644 --- a/player/README.md +++ b/player/README.md @@ -1,10 +1,11 @@ # wgpu player -This is application that allows replaying the `wgpu` workloads recorded elsewhere. +This is application that allows replaying the `wgpu` workloads recorded elsewhere. You must use the player built from +the same revision as an application was linking to, or otherwise the data may fail to load. Launch as: ```rust -player +play ``` When built with "winit" feature, it's able to replay the workloads that operate on a swapchain. It renders each frame sequentially, then waits for the user to close the window. When built without "winit", it launches in console mode and can replay any trace that doesn't use swapchains. diff --git a/player/src/bin/play.rs b/player/src/bin/play.rs new file mode 100644 index 0000000000..03cf63d2d1 --- /dev/null +++ b/player/src/bin/play.rs @@ -0,0 +1,166 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/*! This is a player for WebGPU traces. +!*/ + +use player::{gfx_select, GlobalPlay as _, IdentityPassThroughFactory}; +use wgc::device::trace; + +use std::{ + fs, + path::{Path, PathBuf}, +}; + +fn main() { + #[cfg(feature = "winit")] + use winit::{event_loop::EventLoop, window::WindowBuilder}; + + env_logger::init(); + + #[cfg(feature = "renderdoc")] + #[cfg_attr(feature = "winit", allow(unused))] + let mut rd = renderdoc::RenderDoc::::new() + .expect("Failed to connect to RenderDoc: are you running without it?"); + + //TODO: setting for the backend bits + //TODO: setting for the target frame, or controls + + let dir = match std::env::args().nth(1) { + Some(arg) if Path::new(&arg).is_dir() => PathBuf::from(arg), + _ => panic!("Provide the dir path as the parameter"), + }; + + log::info!("Loading trace '{:?}'", dir); + let file = fs::File::open(dir.join(trace::FILE_NAME)).unwrap(); + let mut actions: Vec = ron::de::from_reader(file).unwrap(); + actions.reverse(); // allows us to pop from the top + log::info!("Found {} actions", actions.len()); + + #[cfg(feature = "winit")] + let event_loop = { + log::info!("Creating a window"); + EventLoop::new() + }; + #[cfg(feature = "winit")] + let window = WindowBuilder::new() + .with_title("wgpu player") + .with_resizable(false) + .build(&event_loop) + .unwrap(); + + let global = + wgc::hub::Global::new("player", IdentityPassThroughFactory, wgt::BackendBit::all()); + let mut command_buffer_id_manager = wgc::hub::IdentityManager::default(); + + #[cfg(feature = "winit")] + let surface = + global.instance_create_surface(&window, wgc::id::TypedId::zip(0, 1, wgt::Backend::Empty)); + + let device = match actions.pop() { + Some(trace::Action::Init { desc, backend }) => { + log::info!("Initializing the device for backend: {:?}", backend); + let adapter = global + .pick_adapter( + &wgc::instance::RequestAdapterOptions { + power_preference: wgt::PowerPreference::Default, + #[cfg(feature = "winit")] + compatible_surface: Some(surface), + #[cfg(not(feature = "winit"))] + compatible_surface: None, + }, + wgc::instance::AdapterInputs::IdSet( + &[wgc::id::TypedId::zip(0, 0, backend)], + |id| id.backend(), + ), + ) + .expect("Unable to find an adapter for selected backend"); + + let info = gfx_select!(adapter => global.adapter_get_info(adapter)); + log::info!("Picked '{}'", info.name); + gfx_select!(adapter => global.adapter_request_device( + adapter, + &desc, + None, + wgc::id::TypedId::zip(1, 0, wgt::Backend::Empty) + )) + .expect("Failed to request device") + } + _ => panic!("Expected Action::Init"), + }; + log::info!("Executing actions"); + #[cfg(not(feature = "winit"))] + { + #[cfg(feature = "renderdoc")] + rd.start_frame_capture(std::ptr::null(), std::ptr::null()); + + while let Some(action) = actions.pop() { + gfx_select!(device => global.process(device, action, &dir, &mut command_buffer_id_manager)); + } + + #[cfg(feature = "renderdoc")] + rd.end_frame_capture(std::ptr::null(), std::ptr::null()); + gfx_select!(device => global.device_poll(device, true)); + } + #[cfg(feature = "winit")] + { + use winit::{ + event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event_loop::ControlFlow, + }; + + let mut frame_count = 0; + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Poll; + match event { + Event::MainEventsCleared => { + window.request_redraw(); + } + Event::RedrawRequested(_) => loop { + match actions.pop() { + Some(trace::Action::CreateSwapChain { id, desc }) => { + log::info!("Initializing the swapchain"); + assert_eq!(id.to_surface_id(), surface); + window.set_inner_size(winit::dpi::PhysicalSize::new( + desc.width, + desc.height, + )); + gfx_select!(device => global.device_create_swap_chain(device, surface, &desc)); + } + Some(trace::Action::PresentSwapChain(id)) => { + frame_count += 1; + log::debug!("Presenting frame {}", frame_count); + gfx_select!(device => global.swap_chain_present(id)); + break; + } + Some(action) => { + gfx_select!(device => global.process(device, action, &dir, &mut command_buffer_id_manager)); + } + None => break, + } + }, + Event::WindowEvent { event, .. } => match event { + WindowEvent::KeyboardInput { + input: + KeyboardInput { + virtual_keycode: Some(VirtualKeyCode::Escape), + state: ElementState::Pressed, + .. + }, + .. + } + | WindowEvent::CloseRequested => { + *control_flow = ControlFlow::Exit; + } + _ => {} + }, + Event::LoopDestroyed => { + log::info!("Closing"); + gfx_select!(device => global.device_poll(device, true)); + } + _ => {} + } + }); + } +} diff --git a/player/src/main.rs b/player/src/lib.rs similarity index 71% rename from player/src/main.rs rename to player/src/lib.rs index 1a1823a7ad..5c33c79979 100644 --- a/player/src/main.rs +++ b/player/src/lib.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -/*! This is a player for WebGPU traces. +/*! This is a player library for WebGPU traces. * * # Notes * - we call device_maintain_ids() before creating any refcounted resource, @@ -12,15 +12,9 @@ use wgc::device::trace; -use std::{ - ffi::CString, - fmt::Debug, - fs, - marker::PhantomData, - path::{Path, PathBuf}, - ptr, -}; +use std::{ffi::CString, fmt::Debug, fs, marker::PhantomData, path::Path, ptr}; +#[macro_export] macro_rules! gfx_select { ($id:expr => $global:ident.$method:ident( $($param:expr),+ )) => { match $id.backend() { @@ -56,7 +50,7 @@ impl Label { } #[derive(Debug)] -struct IdentityPassThrough(PhantomData); +pub struct IdentityPassThrough(PhantomData); impl wgc::hub::IdentityHandler for IdentityPassThrough { type Input = I; @@ -67,7 +61,7 @@ impl wgc::hub::IdentityHandler for Ident fn free(&self, _id: I) {} } -struct IdentityPassThroughFactory; +pub struct IdentityPassThroughFactory; impl wgc::hub::IdentityHandlerFactory for IdentityPassThroughFactory @@ -79,7 +73,7 @@ impl wgc::hub::IdentityHandlerFactory } impl wgc::hub::GlobalIdentityHandlerFactory for IdentityPassThroughFactory {} -trait GlobalExt { +pub trait GlobalPlay { fn encode_commands( &self, encoder: wgc::id::CommandEncoderId, @@ -89,12 +83,12 @@ trait GlobalExt { &self, device: wgc::id::DeviceId, action: trace::Action, - dir: &PathBuf, + dir: &Path, comb_manager: &mut wgc::hub::IdentityManager, ); } -impl GlobalExt for wgc::hub::Global { +impl GlobalPlay for wgc::hub::Global { fn encode_commands( &self, encoder: wgc::id::CommandEncoderId, @@ -148,7 +142,7 @@ impl GlobalExt for wgc::hub::Global { &self, device: wgc::id::DeviceId, action: trace::Action, - dir: &PathBuf, + dir: &Path, comb_manager: &mut wgc::hub::IdentityManager, ) { use wgc::device::trace::Action as A; @@ -416,155 +410,3 @@ impl GlobalExt for wgc::hub::Global { } } } - -fn main() { - #[cfg(feature = "winit")] - use winit::{event_loop::EventLoop, window::WindowBuilder}; - - env_logger::init(); - - #[cfg(feature = "renderdoc")] - #[cfg_attr(feature = "winit", allow(unused))] - let mut rd = renderdoc::RenderDoc::::new() - .expect("Failed to connect to RenderDoc: are you running without it?"); - - //TODO: setting for the backend bits - //TODO: setting for the target frame, or controls - - let dir = match std::env::args().nth(1) { - Some(arg) if Path::new(&arg).is_dir() => PathBuf::from(arg), - _ => panic!("Provide the dir path as the parameter"), - }; - - log::info!("Loading trace '{:?}'", dir); - let file = fs::File::open(dir.join(trace::FILE_NAME)).unwrap(); - let mut actions: Vec = ron::de::from_reader(file).unwrap(); - actions.reverse(); // allows us to pop from the top - log::info!("Found {} actions", actions.len()); - - #[cfg(feature = "winit")] - let event_loop = { - log::info!("Creating a window"); - EventLoop::new() - }; - #[cfg(feature = "winit")] - let window = WindowBuilder::new() - .with_title("wgpu player") - .with_resizable(false) - .build(&event_loop) - .unwrap(); - - let global = - wgc::hub::Global::new("player", IdentityPassThroughFactory, wgt::BackendBit::all()); - let mut command_buffer_id_manager = wgc::hub::IdentityManager::default(); - - #[cfg(feature = "winit")] - let surface = - global.instance_create_surface(&window, wgc::id::TypedId::zip(0, 1, wgt::Backend::Empty)); - - let device = match actions.pop() { - Some(trace::Action::Init { desc, backend }) => { - log::info!("Initializing the device for backend: {:?}", backend); - let adapter = global - .pick_adapter( - &wgc::instance::RequestAdapterOptions { - power_preference: wgt::PowerPreference::Default, - #[cfg(feature = "winit")] - compatible_surface: Some(surface), - #[cfg(not(feature = "winit"))] - compatible_surface: None, - }, - wgc::instance::AdapterInputs::IdSet( - &[wgc::id::TypedId::zip(0, 0, backend)], - |id| id.backend(), - ), - ) - .expect("Unable to find an adapter for selected backend"); - - let info = gfx_select!(adapter => global.adapter_get_info(adapter)); - log::info!("Picked '{}'", info.name); - gfx_select!(adapter => global.adapter_request_device( - adapter, - &desc, - None, - wgc::id::TypedId::zip(1, 0, wgt::Backend::Empty) - )) - .expect("Failed to request device") - } - _ => panic!("Expected Action::Init"), - }; - log::info!("Executing actions"); - #[cfg(not(feature = "winit"))] - { - #[cfg(feature = "renderdoc")] - rd.start_frame_capture(ptr::null(), ptr::null()); - - while let Some(action) = actions.pop() { - gfx_select!(device => global.process(device, action, &dir, &mut command_buffer_id_manager)); - } - - #[cfg(feature = "renderdoc")] - rd.end_frame_capture(ptr::null(), ptr::null()); - gfx_select!(device => global.device_poll(device, true)); - } - #[cfg(feature = "winit")] - { - use winit::{ - event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, - event_loop::ControlFlow, - }; - - let mut frame_count = 0; - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Poll; - match event { - Event::MainEventsCleared => { - window.request_redraw(); - } - Event::RedrawRequested(_) => loop { - match actions.pop() { - Some(trace::Action::CreateSwapChain { id, desc }) => { - log::info!("Initializing the swapchain"); - assert_eq!(id.to_surface_id(), surface); - window.set_inner_size(winit::dpi::PhysicalSize::new( - desc.width, - desc.height, - )); - gfx_select!(device => global.device_create_swap_chain(device, surface, &desc)); - } - Some(trace::Action::PresentSwapChain(id)) => { - frame_count += 1; - log::debug!("Presenting frame {}", frame_count); - gfx_select!(device => global.swap_chain_present(id)); - break; - } - Some(action) => { - gfx_select!(device => global.process(device, action, &dir, &mut command_buffer_id_manager)); - } - None => break, - } - }, - Event::WindowEvent { event, .. } => match event { - WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(VirtualKeyCode::Escape), - state: ElementState::Pressed, - .. - }, - .. - } - | WindowEvent::CloseRequested => { - *control_flow = ControlFlow::Exit; - } - _ => {} - }, - Event::LoopDestroyed => { - log::info!("Closing"); - gfx_select!(device => global.device_poll(device, true)); - } - _ => {} - } - }); - } -} diff --git a/player/tests/data/all.ron b/player/tests/data/all.ron new file mode 100644 index 0000000000..7f8733aa6f --- /dev/null +++ b/player/tests/data/all.ron @@ -0,0 +1,17 @@ +( + backends: (bits: 0x7), + tests: [ + ( + path: "buffer-copy.ron", + features: (bits: 0x0), + expectations: [ + ( + name: "basic", + buffer: (index: 0, epoch: 1), + offset: 0, + data: [0x00, 0x00, 0x80, 0xBF], + ) + ], + ), + ], +) \ No newline at end of file diff --git a/player/tests/data/buffer-copy.ron b/player/tests/data/buffer-copy.ron new file mode 100644 index 0000000000..f24f2f52c2 --- /dev/null +++ b/player/tests/data/buffer-copy.ron @@ -0,0 +1,23 @@ +[ + CreateBuffer( + id: Id(0, 1, Empty), + desc: ( + label: "", + size: 16, + usage: ( + bits: 41, + ), + mapped_at_creation: false, + ), + ), + WriteBuffer( + id: Id(0, 1, Empty), + data: "data1.bin", + range: ( + start: 0, + end: 16, + ), + queued: true, + ), + Submit(1, []), +] \ No newline at end of file diff --git a/player/tests/data/data1.bin b/player/tests/data/data1.bin new file mode 100644 index 0000000000000000000000000000000000000000..1e54fc7a5ed2a83c59556f0eb408703eae457212 GIT binary patch literal 16 RcmZQzXxPsH!S+Df9snWq1N{I1 literal 0 HcmV?d00001 diff --git a/player/tests/test.rs b/player/tests/test.rs new file mode 100644 index 0000000000..5c5690d910 --- /dev/null +++ b/player/tests/test.rs @@ -0,0 +1,169 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/*! Tester for WebGPU + * It enumerates the available backends on the system, + * and run the tests through them. + * + * Test requirements: + * - all IDs have the backend `Empty` + * - all expected buffers have `MAP_READ` usage + * - last action is `Submit` +!*/ + +use player::{gfx_select, GlobalPlay, IdentityPassThroughFactory}; +use std::{ + fs::{read_to_string, File}, + path::{Path, PathBuf}, + ptr, slice, +}; + +#[derive(serde::Deserialize)] +struct RawId { + index: u32, + epoch: u32, +} + +#[derive(serde::Deserialize)] +struct Expectation { + name: String, + buffer: RawId, + offset: wgt::BufferAddress, + data: Vec, +} + +#[derive(serde::Deserialize)] +struct Test { + path: String, + features: wgt::Features, + expectations: Vec, +} + +#[derive(serde::Deserialize)] +struct Corpus { + backends: wgt::BackendBit, + tests: Vec, +} + +const BACKENDS: &[wgt::Backend] = &[ + wgt::Backend::Vulkan, + wgt::Backend::Metal, + wgt::Backend::Dx12, + wgt::Backend::Dx11, + wgt::Backend::Gl, +]; + +extern "C" fn map_callback(status: wgc::resource::BufferMapAsyncStatus, _user_data: *mut u8) { + match status { + wgc::resource::BufferMapAsyncStatus::Success => (), + _ => panic!("Unable to map"), + } +} + +fn run_test( + test: Test, + dir: &Path, + global: &wgc::hub::Global, + adapter: wgc::id::AdapterId, +) { + println!("\t\tTest '{:?}'", test.path); + let device = gfx_select!(adapter => global.adapter_request_device( + adapter, + &wgt::DeviceDescriptor { + features: test.features | wgt::Features::MAPPABLE_PRIMARY_BUFFERS, + limits: wgt::Limits::default(), + shader_validation: true, + }, + None, + wgc::id::TypedId::zip(1, 0, adapter.backend()) + )) + .unwrap(); + + let backend = device.backend(); + let backend_name = match backend { + wgt::Backend::Vulkan => "Vulkan", + wgt::Backend::Metal => "Metal", + wgt::Backend::Dx12 => "Dx12", + wgt::Backend::Dx11 => "Dx11", + wgt::Backend::Gl => "Gl", + _ => unreachable!(), + }; + let string = read_to_string(dir.join(test.path)) + .unwrap() + .replace("Empty", backend_name); + let actions: Vec = ron::de::from_str(&string).unwrap(); + + let mut command_buffer_id_manager = wgc::hub::IdentityManager::default(); + println!("\t\t\tRunning..."); + for action in actions { + gfx_select!(device => global.process(device, action, dir, &mut command_buffer_id_manager)); + } + println!("\t\t\tMapping..."); + for expect in &test.expectations { + let buffer = wgc::id::TypedId::zip(expect.buffer.index, expect.buffer.epoch, backend); + gfx_select!(device => global.buffer_map_async( + buffer, + expect.offset .. expect.offset+expect.data.len() as wgt::BufferAddress, + wgc::resource::BufferMapOperation { + host: wgc::device::HostMap::Read, + callback: map_callback, + user_data: ptr::null_mut(), + } + )); + } + + println!("\t\t\tWaiting..."); + gfx_select!(device => global.device_poll(device, true)); + + for expect in test.expectations { + println!("\t\t\tChecking {}", expect.name); + let buffer = wgc::id::TypedId::zip(expect.buffer.index, expect.buffer.epoch, backend); + let ptr = + gfx_select!(device => global.buffer_get_mapped_range(buffer, expect.offset, None)); + let contents = unsafe { slice::from_raw_parts(ptr, expect.data.len()) }; + assert_eq!(&expect.data[..], contents); + } +} + +fn run_corpus(path: PathBuf) { + println!("Corpus {:?}", path); + let mut corpus: Corpus = ron::de::from_reader(File::open(&path).unwrap()).unwrap(); + + let global = wgc::hub::Global::new("test", IdentityPassThroughFactory, corpus.backends); + for &backend in BACKENDS { + if !corpus.backends.contains(backend.into()) { + continue; + } + let adapter = match global.pick_adapter( + &wgc::instance::RequestAdapterOptions { + power_preference: wgt::PowerPreference::Default, + compatible_surface: None, + }, + wgc::instance::AdapterInputs::IdSet(&[wgc::id::TypedId::zip(0, 0, backend)], |id| { + id.backend() + }), + ) { + Some(adapter) => adapter, + None => continue, + }; + + println!("\tBackend {:?}", backend); + let supported_features = gfx_select!(adapter => global.adapter_features(adapter)); + for test in corpus.tests.drain(..) { + if !supported_features.contains(test.features) { + println!( + "\t\tSkipped due to missing features {:?}", + test.features - supported_features + ); + continue; + } + run_test(test, path.parent().unwrap(), &global, adapter); + } + } +} + +#[test] +fn test_api() { + run_corpus(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/data/all.ron")) +} From eb7bdcd012d4528a8dce8da9a777be3dc59c9846 Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Fri, 17 Jul 2020 10:59:02 -0400 Subject: [PATCH 2/2] playtest: move the actions together with expectations --- player/Cargo.toml | 5 - player/tests/data/all.ron | 13 +- player/tests/data/buffer-copy.ron | 51 ++++--- player/tests/test.rs | 216 ++++++++++++++++-------------- 4 files changed, 144 insertions(+), 141 deletions(-) diff --git a/player/Cargo.toml b/player/Cargo.toml index 7e4befa6ee..6e625a7c9b 100644 --- a/player/Cargo.toml +++ b/player/Cargo.toml @@ -12,11 +12,6 @@ keywords = ["graphics"] license = "MPL-2.0" publish = false -[lib] - -[[bin]] -name = "play" - [features] [dependencies] diff --git a/player/tests/data/all.ron b/player/tests/data/all.ron index 7f8733aa6f..19d7d805b7 100644 --- a/player/tests/data/all.ron +++ b/player/tests/data/all.ron @@ -1,17 +1,6 @@ ( backends: (bits: 0x7), tests: [ - ( - path: "buffer-copy.ron", - features: (bits: 0x0), - expectations: [ - ( - name: "basic", - buffer: (index: 0, epoch: 1), - offset: 0, - data: [0x00, 0x00, 0x80, 0xBF], - ) - ], - ), + "buffer-copy.ron", ], ) \ No newline at end of file diff --git a/player/tests/data/buffer-copy.ron b/player/tests/data/buffer-copy.ron index f24f2f52c2..cd3105e6e4 100644 --- a/player/tests/data/buffer-copy.ron +++ b/player/tests/data/buffer-copy.ron @@ -1,23 +1,34 @@ -[ - CreateBuffer( - id: Id(0, 1, Empty), - desc: ( - label: "", - size: 16, - usage: ( - bits: 41, +( + features: (bits: 0x0), + expectations: [ + ( + name: "basic", + buffer: (index: 0, epoch: 1), + offset: 0, + data: [0x00, 0x00, 0x80, 0xBF], + ) + ], + actions: [ + CreateBuffer( + id: Id(0, 1, Empty), + desc: ( + label: "", + size: 16, + usage: ( + bits: 41, + ), + mapped_at_creation: false, ), - mapped_at_creation: false, ), - ), - WriteBuffer( - id: Id(0, 1, Empty), - data: "data1.bin", - range: ( - start: 0, - end: 16, + WriteBuffer( + id: Id(0, 1, Empty), + data: "data1.bin", + range: ( + start: 0, + end: 16, + ), + queued: true, ), - queued: true, - ), - Submit(1, []), -] \ No newline at end of file + Submit(1, []), + ], +) \ No newline at end of file diff --git a/player/tests/test.rs b/player/tests/test.rs index 5c5690d910..6c3f2bab17 100644 --- a/player/tests/test.rs +++ b/player/tests/test.rs @@ -10,6 +10,7 @@ * - all IDs have the backend `Empty` * - all expected buffers have `MAP_READ` usage * - last action is `Submit` + * - no swapchain use !*/ use player::{gfx_select, GlobalPlay, IdentityPassThroughFactory}; @@ -35,25 +36,11 @@ struct Expectation { #[derive(serde::Deserialize)] struct Test { - path: String, features: wgt::Features, expectations: Vec, + actions: Vec, } -#[derive(serde::Deserialize)] -struct Corpus { - backends: wgt::BackendBit, - tests: Vec, -} - -const BACKENDS: &[wgt::Backend] = &[ - wgt::Backend::Vulkan, - wgt::Backend::Metal, - wgt::Backend::Dx12, - wgt::Backend::Dx11, - wgt::Backend::Gl, -]; - extern "C" fn map_callback(status: wgc::resource::BufferMapAsyncStatus, _user_data: *mut u8) { match status { wgc::resource::BufferMapAsyncStatus::Success => (), @@ -61,109 +48,130 @@ extern "C" fn map_callback(status: wgc::resource::BufferMapAsyncStatus, _user_da } } -fn run_test( - test: Test, - dir: &Path, - global: &wgc::hub::Global, - adapter: wgc::id::AdapterId, -) { - println!("\t\tTest '{:?}'", test.path); - let device = gfx_select!(adapter => global.adapter_request_device( - adapter, - &wgt::DeviceDescriptor { - features: test.features | wgt::Features::MAPPABLE_PRIMARY_BUFFERS, - limits: wgt::Limits::default(), - shader_validation: true, - }, - None, - wgc::id::TypedId::zip(1, 0, adapter.backend()) - )) - .unwrap(); - - let backend = device.backend(); - let backend_name = match backend { - wgt::Backend::Vulkan => "Vulkan", - wgt::Backend::Metal => "Metal", - wgt::Backend::Dx12 => "Dx12", - wgt::Backend::Dx11 => "Dx11", - wgt::Backend::Gl => "Gl", - _ => unreachable!(), - }; - let string = read_to_string(dir.join(test.path)) - .unwrap() - .replace("Empty", backend_name); - let actions: Vec = ron::de::from_str(&string).unwrap(); - - let mut command_buffer_id_manager = wgc::hub::IdentityManager::default(); - println!("\t\t\tRunning..."); - for action in actions { - gfx_select!(device => global.process(device, action, dir, &mut command_buffer_id_manager)); - } - println!("\t\t\tMapping..."); - for expect in &test.expectations { - let buffer = wgc::id::TypedId::zip(expect.buffer.index, expect.buffer.epoch, backend); - gfx_select!(device => global.buffer_map_async( - buffer, - expect.offset .. expect.offset+expect.data.len() as wgt::BufferAddress, - wgc::resource::BufferMapOperation { - host: wgc::device::HostMap::Read, - callback: map_callback, - user_data: ptr::null_mut(), - } - )); +impl Test { + fn load(path: PathBuf, backend: wgt::Backend) -> Self { + let backend_name = match backend { + wgt::Backend::Vulkan => "Vulkan", + wgt::Backend::Metal => "Metal", + wgt::Backend::Dx12 => "Dx12", + wgt::Backend::Dx11 => "Dx11", + wgt::Backend::Gl => "Gl", + _ => unreachable!(), + }; + let string = read_to_string(path).unwrap().replace("Empty", backend_name); + ron::de::from_str(&string).unwrap() } - println!("\t\t\tWaiting..."); - gfx_select!(device => global.device_poll(device, true)); + fn run( + self, + dir: &Path, + global: &wgc::hub::Global, + adapter: wgc::id::AdapterId, + ) { + let backend = adapter.backend(); + let device = gfx_select!(adapter => global.adapter_request_device( + adapter, + &wgt::DeviceDescriptor { + features: self.features | wgt::Features::MAPPABLE_PRIMARY_BUFFERS, + limits: wgt::Limits::default(), + shader_validation: true, + }, + None, + wgc::id::TypedId::zip(1, 0, backend) + )) + .unwrap(); + + let mut command_buffer_id_manager = wgc::hub::IdentityManager::default(); + println!("\t\t\tRunning..."); + for action in self.actions { + gfx_select!(device => global.process(device, action, dir, &mut command_buffer_id_manager)); + } + println!("\t\t\tMapping..."); + for expect in &self.expectations { + let buffer = wgc::id::TypedId::zip(expect.buffer.index, expect.buffer.epoch, backend); + gfx_select!(device => global.buffer_map_async( + buffer, + expect.offset .. expect.offset+expect.data.len() as wgt::BufferAddress, + wgc::resource::BufferMapOperation { + host: wgc::device::HostMap::Read, + callback: map_callback, + user_data: ptr::null_mut(), + } + )); + } - for expect in test.expectations { - println!("\t\t\tChecking {}", expect.name); - let buffer = wgc::id::TypedId::zip(expect.buffer.index, expect.buffer.epoch, backend); - let ptr = - gfx_select!(device => global.buffer_get_mapped_range(buffer, expect.offset, None)); - let contents = unsafe { slice::from_raw_parts(ptr, expect.data.len()) }; - assert_eq!(&expect.data[..], contents); + println!("\t\t\tWaiting..."); + gfx_select!(device => global.device_poll(device, true)); + + for expect in self.expectations { + println!("\t\t\tChecking {}", expect.name); + let buffer = wgc::id::TypedId::zip(expect.buffer.index, expect.buffer.epoch, backend); + let ptr = + gfx_select!(device => global.buffer_get_mapped_range(buffer, expect.offset, None)); + let contents = unsafe { slice::from_raw_parts(ptr, expect.data.len()) }; + assert_eq!(&expect.data[..], contents); + } } } -fn run_corpus(path: PathBuf) { - println!("Corpus {:?}", path); - let mut corpus: Corpus = ron::de::from_reader(File::open(&path).unwrap()).unwrap(); +#[derive(serde::Deserialize)] +struct Corpus { + backends: wgt::BackendBit, + tests: Vec, +} - let global = wgc::hub::Global::new("test", IdentityPassThroughFactory, corpus.backends); - for &backend in BACKENDS { - if !corpus.backends.contains(backend.into()) { - continue; - } - let adapter = match global.pick_adapter( - &wgc::instance::RequestAdapterOptions { - power_preference: wgt::PowerPreference::Default, - compatible_surface: None, - }, - wgc::instance::AdapterInputs::IdSet(&[wgc::id::TypedId::zip(0, 0, backend)], |id| { - id.backend() - }), - ) { - Some(adapter) => adapter, - None => continue, - }; +const BACKENDS: &[wgt::Backend] = &[ + wgt::Backend::Vulkan, + wgt::Backend::Metal, + wgt::Backend::Dx12, + wgt::Backend::Dx11, + wgt::Backend::Gl, +]; - println!("\tBackend {:?}", backend); - let supported_features = gfx_select!(adapter => global.adapter_features(adapter)); - for test in corpus.tests.drain(..) { - if !supported_features.contains(test.features) { - println!( - "\t\tSkipped due to missing features {:?}", - test.features - supported_features - ); +impl Corpus { + fn run_from(path: PathBuf) { + println!("Corpus {:?}", path); + let dir = path.parent().unwrap(); + let corpus: Corpus = ron::de::from_reader(File::open(&path).unwrap()).unwrap(); + + let global = wgc::hub::Global::new("test", IdentityPassThroughFactory, corpus.backends); + for &backend in BACKENDS { + if !corpus.backends.contains(backend.into()) { continue; } - run_test(test, path.parent().unwrap(), &global, adapter); + let adapter = match global.pick_adapter( + &wgc::instance::RequestAdapterOptions { + power_preference: wgt::PowerPreference::Default, + compatible_surface: None, + }, + wgc::instance::AdapterInputs::IdSet( + &[wgc::id::TypedId::zip(0, 0, backend)], + |id| id.backend(), + ), + ) { + Some(adapter) => adapter, + None => continue, + }; + + println!("\tBackend {:?}", backend); + let supported_features = gfx_select!(adapter => global.adapter_features(adapter)); + for test_path in &corpus.tests { + println!("\t\tTest '{:?}'", test_path); + let test = Test::load(dir.join(test_path), adapter.backend()); + if !supported_features.contains(test.features) { + println!( + "\t\tSkipped due to missing features {:?}", + test.features - supported_features + ); + continue; + } + test.run(dir, &global, adapter); + } } } } #[test] fn test_api() { - run_corpus(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/data/all.ron")) + Corpus::run_from(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/data/all.ron")) }