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