1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 /*! Tester for WebGPU
6  *  It enumerates the available backends on the system,
7  *  and run the tests through them.
8  *
9  *  Test requirements:
10  *    - all IDs have the backend `Empty`
11  *    - all expected buffers have `MAP_READ` usage
12  *    - last action is `Submit`
13  *    - no swapchain use
14 !*/
15 
16 use player::{GlobalPlay, IdentityPassThroughFactory};
17 use std::{
18     fs::{read_to_string, File},
19     io::{Read, Seek, SeekFrom},
20     path::{Path, PathBuf},
21     ptr, slice,
22 };
23 
24 #[derive(serde::Deserialize)]
25 struct RawId {
26     index: u32,
27     epoch: u32,
28 }
29 
30 #[derive(serde::Deserialize)]
31 enum ExpectedData {
32     Raw(Vec<u8>),
33     File(String, usize),
34 }
35 
36 impl ExpectedData {
len(&self) -> usize37     fn len(&self) -> usize {
38         match self {
39             ExpectedData::Raw(vec) => vec.len(),
40             ExpectedData::File(_, size) => *size,
41         }
42     }
43 }
44 
45 #[derive(serde::Deserialize)]
46 struct Expectation {
47     name: String,
48     buffer: RawId,
49     offset: wgt::BufferAddress,
50     data: ExpectedData,
51 }
52 
53 #[derive(serde::Deserialize)]
54 struct Test<'a> {
55     features: wgt::Features,
56     expectations: Vec<Expectation>,
57     actions: Vec<wgc::device::trace::Action<'a>>,
58 }
59 
map_callback(status: wgc::resource::BufferMapAsyncStatus, _user_data: *mut u8)60 extern "C" fn map_callback(status: wgc::resource::BufferMapAsyncStatus, _user_data: *mut u8) {
61     match status {
62         wgc::resource::BufferMapAsyncStatus::Success => (),
63         _ => panic!("Unable to map"),
64     }
65 }
66 
67 impl Test<'_> {
load(path: PathBuf, backend: wgt::Backend) -> Self68     fn load(path: PathBuf, backend: wgt::Backend) -> Self {
69         let backend_name = match backend {
70             wgt::Backend::Vulkan => "Vulkan",
71             wgt::Backend::Metal => "Metal",
72             wgt::Backend::Dx12 => "Dx12",
73             wgt::Backend::Dx11 => "Dx11",
74             wgt::Backend::Gl => "Gl",
75             _ => unreachable!(),
76         };
77         let string = read_to_string(path).unwrap().replace("Empty", backend_name);
78         ron::de::from_str(&string).unwrap()
79     }
80 
run( self, dir: &Path, global: &wgc::hub::Global<IdentityPassThroughFactory>, adapter: wgc::id::AdapterId, test_num: u32, )81     fn run(
82         self,
83         dir: &Path,
84         global: &wgc::hub::Global<IdentityPassThroughFactory>,
85         adapter: wgc::id::AdapterId,
86         test_num: u32,
87     ) {
88         let backend = adapter.backend();
89         let device = wgc::id::TypedId::zip(test_num, 0, backend);
90         let (_, error) = wgc::gfx_select!(adapter => global.adapter_request_device(
91             adapter,
92             &wgt::DeviceDescriptor {
93                 label: None,
94                 features: self.features | wgt::Features::MAPPABLE_PRIMARY_BUFFERS,
95                 limits: wgt::Limits::default(),
96             },
97             None,
98             device
99         ));
100         if let Some(e) = error {
101             panic!("{:?}", e);
102         }
103 
104         let mut command_buffer_id_manager = wgc::hub::IdentityManager::default();
105         println!("\t\t\tRunning...");
106         for action in self.actions {
107             wgc::gfx_select!(device => global.process(device, action, dir, &mut command_buffer_id_manager));
108         }
109         println!("\t\t\tMapping...");
110         for expect in &self.expectations {
111             let buffer = wgc::id::TypedId::zip(expect.buffer.index, expect.buffer.epoch, backend);
112             wgc::gfx_select!(device => global.buffer_map_async(
113                 buffer,
114                 expect.offset .. expect.offset+expect.data.len() as wgt::BufferAddress,
115                 wgc::resource::BufferMapOperation {
116                     host: wgc::device::HostMap::Read,
117                     callback: map_callback,
118                     user_data: ptr::null_mut(),
119                 }
120             ))
121             .unwrap();
122         }
123 
124         println!("\t\t\tWaiting...");
125         wgc::gfx_select!(device => global.device_poll(device, true)).unwrap();
126 
127         for expect in self.expectations {
128             println!("\t\t\tChecking {}", expect.name);
129             let buffer = wgc::id::TypedId::zip(expect.buffer.index, expect.buffer.epoch, backend);
130             let (ptr, size) =
131                 wgc::gfx_select!(device => global.buffer_get_mapped_range(buffer, expect.offset, Some(expect.data.len() as wgt::BufferAddress)))
132                     .unwrap();
133             let contents = unsafe { slice::from_raw_parts(ptr, size as usize) };
134             let expected_data = match expect.data {
135                 ExpectedData::Raw(vec) => vec,
136                 ExpectedData::File(name, size) => {
137                     let mut bin = vec![0; size];
138                     let mut file = File::open(dir.join(name)).unwrap();
139                     file.seek(SeekFrom::Start(expect.offset)).unwrap();
140                     file.read_exact(&mut bin[..]).unwrap();
141 
142                     bin
143                 }
144             };
145 
146             if &expected_data[..] != contents {
147                 panic!(
148                     "Test expectation is not met!\nBuffer content was:\n{:?}\nbut expected:\n{:?}",
149                     contents, expected_data
150                 );
151             }
152         }
153 
154         wgc::gfx_select!(device => global.clear_backend(()));
155     }
156 }
157 
158 #[derive(serde::Deserialize)]
159 struct Corpus {
160     backends: wgt::BackendBit,
161     tests: Vec<String>,
162 }
163 
164 const BACKENDS: &[wgt::Backend] = &[
165     wgt::Backend::Vulkan,
166     wgt::Backend::Metal,
167     wgt::Backend::Dx12,
168     wgt::Backend::Dx11,
169     wgt::Backend::Gl,
170 ];
171 
172 impl Corpus {
run_from(path: PathBuf)173     fn run_from(path: PathBuf) {
174         println!("Corpus {:?}", path);
175         let dir = path.parent().unwrap();
176         let corpus: Corpus = ron::de::from_reader(File::open(&path).unwrap()).unwrap();
177 
178         let global = wgc::hub::Global::new("test", IdentityPassThroughFactory, corpus.backends);
179         for &backend in BACKENDS {
180             if !corpus.backends.contains(backend.into()) {
181                 continue;
182             }
183             let adapter = match global.request_adapter(
184                 &wgc::instance::RequestAdapterOptions {
185                     power_preference: wgt::PowerPreference::LowPower,
186                     compatible_surface: None,
187                 },
188                 wgc::instance::AdapterInputs::IdSet(
189                     &[wgc::id::TypedId::zip(0, 0, backend)],
190                     |id| id.backend(),
191                 ),
192             ) {
193                 Ok(adapter) => adapter,
194                 Err(_) => continue,
195             };
196 
197             println!("\tBackend {:?}", backend);
198             let supported_features =
199                 wgc::gfx_select!(adapter => global.adapter_features(adapter)).unwrap();
200             let mut test_num = 0;
201             for test_path in &corpus.tests {
202                 println!("\t\tTest '{:?}'", test_path);
203                 let test = Test::load(dir.join(test_path), adapter.backend());
204                 if !supported_features.contains(test.features) {
205                     println!(
206                         "\t\tSkipped due to missing features {:?}",
207                         test.features - supported_features
208                     );
209                     continue;
210                 }
211                 test.run(dir, &global, adapter, test_num);
212                 test_num += 1;
213             }
214         }
215     }
216 }
217 
218 #[test]
test_api()219 fn test_api() {
220     env_logger::init();
221 
222     Corpus::run_from(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/data/all.ron"))
223 }
224