player/
lib.rs

1//! This is a player library for WebGPU traces.
2
3#![cfg(not(target_arch = "wasm32"))]
4#![warn(clippy::allow_attributes, unsafe_op_in_unsafe_fn)]
5
6extern crate wgpu_core as wgc;
7extern crate wgpu_types as wgt;
8
9use wgc::{command::Command, device::trace, identity::IdentityManager};
10
11use std::{borrow::Cow, fs, path::Path};
12
13pub trait GlobalPlay {
14    fn encode_commands(
15        &self,
16        encoder: wgc::id::CommandEncoderId,
17        commands: Vec<Command>,
18        command_buffer_id_manager: &mut IdentityManager<wgc::id::markers::CommandBuffer>,
19    ) -> wgc::id::CommandBufferId;
20    fn process(
21        &self,
22        device: wgc::id::DeviceId,
23        queue: wgc::id::QueueId,
24        action: trace::Action,
25        dir: &Path,
26        command_encoder_id_manager: &mut IdentityManager<wgc::id::markers::CommandEncoder>,
27        command_buffer_id_manager: &mut IdentityManager<wgc::id::markers::CommandBuffer>,
28    );
29}
30
31impl GlobalPlay for wgc::global::Global {
32    fn encode_commands(
33        &self,
34        encoder: wgc::id::CommandEncoderId,
35        commands: Vec<Command>,
36        command_buffer_id_manager: &mut IdentityManager<wgc::id::markers::CommandBuffer>,
37    ) -> wgc::id::CommandBufferId {
38        for command in commands {
39            match command {
40                Command::CopyBufferToBuffer {
41                    src,
42                    src_offset,
43                    dst,
44                    dst_offset,
45                    size,
46                } => self
47                    .command_encoder_copy_buffer_to_buffer(
48                        encoder, src, src_offset, dst, dst_offset, size,
49                    )
50                    .unwrap(),
51                Command::CopyBufferToTexture { src, dst, size } => self
52                    .command_encoder_copy_buffer_to_texture(encoder, &src, &dst, &size)
53                    .unwrap(),
54                Command::CopyTextureToBuffer { src, dst, size } => self
55                    .command_encoder_copy_texture_to_buffer(encoder, &src, &dst, &size)
56                    .unwrap(),
57                Command::CopyTextureToTexture { src, dst, size } => self
58                    .command_encoder_copy_texture_to_texture(encoder, &src, &dst, &size)
59                    .unwrap(),
60                Command::ClearBuffer { dst, offset, size } => self
61                    .command_encoder_clear_buffer(encoder, dst, offset, size)
62                    .unwrap(),
63                Command::ClearTexture {
64                    dst,
65                    subresource_range,
66                } => self
67                    .command_encoder_clear_texture(encoder, dst, &subresource_range)
68                    .unwrap(),
69                Command::WriteTimestamp {
70                    query_set_id,
71                    query_index,
72                } => self
73                    .command_encoder_write_timestamp(encoder, query_set_id, query_index)
74                    .unwrap(),
75                Command::ResolveQuerySet {
76                    query_set_id,
77                    start_query,
78                    query_count,
79                    destination,
80                    destination_offset,
81                } => self
82                    .command_encoder_resolve_query_set(
83                        encoder,
84                        query_set_id,
85                        start_query,
86                        query_count,
87                        destination,
88                        destination_offset,
89                    )
90                    .unwrap(),
91                Command::PushDebugGroup(marker) => self
92                    .command_encoder_push_debug_group(encoder, &marker)
93                    .unwrap(),
94                Command::PopDebugGroup => self.command_encoder_pop_debug_group(encoder).unwrap(),
95                Command::InsertDebugMarker(marker) => self
96                    .command_encoder_insert_debug_marker(encoder, &marker)
97                    .unwrap(),
98                Command::RunComputePass {
99                    base,
100                    timestamp_writes,
101                } => {
102                    self.compute_pass_end_with_unresolved_commands(
103                        encoder,
104                        base,
105                        timestamp_writes.as_ref(),
106                    );
107                }
108                Command::RunRenderPass {
109                    base,
110                    target_colors,
111                    target_depth_stencil,
112                    timestamp_writes,
113                    occlusion_query_set_id,
114                } => {
115                    self.render_pass_end_with_unresolved_commands(
116                        encoder,
117                        base,
118                        &target_colors,
119                        target_depth_stencil.as_ref(),
120                        timestamp_writes.as_ref(),
121                        occlusion_query_set_id,
122                    );
123                }
124                Command::BuildAccelerationStructures { blas, tlas } => {
125                    let blas_iter = blas.iter().map(|x| {
126                        let geometries = match &x.geometries {
127                            wgc::ray_tracing::TraceBlasGeometries::TriangleGeometries(
128                                triangle_geometries,
129                            ) => {
130                                let iter = triangle_geometries.iter().map(|tg| {
131                                    wgc::ray_tracing::BlasTriangleGeometry {
132                                        size: &tg.size,
133                                        vertex_buffer: tg.vertex_buffer,
134                                        index_buffer: tg.index_buffer,
135                                        transform_buffer: tg.transform_buffer,
136                                        first_vertex: tg.first_vertex,
137                                        vertex_stride: tg.vertex_stride,
138                                        first_index: tg.first_index,
139                                        transform_buffer_offset: tg.transform_buffer_offset,
140                                    }
141                                });
142                                wgc::ray_tracing::BlasGeometries::TriangleGeometries(Box::new(iter))
143                            }
144                        };
145                        wgc::ray_tracing::BlasBuildEntry {
146                            blas_id: x.blas_id,
147                            geometries,
148                        }
149                    });
150
151                    let tlas_iter = tlas.iter().map(|x| {
152                        let instances = x.instances.iter().map(|instance| {
153                            instance
154                                .as_ref()
155                                .map(|instance| wgc::ray_tracing::TlasInstance {
156                                    blas_id: instance.blas_id,
157                                    transform: &instance.transform,
158                                    custom_data: instance.custom_data,
159                                    mask: instance.mask,
160                                })
161                        });
162                        wgc::ray_tracing::TlasPackage {
163                            tlas_id: x.tlas_id,
164                            instances: Box::new(instances),
165                            lowest_unmodified: x.lowest_unmodified,
166                        }
167                    });
168
169                    self.command_encoder_build_acceleration_structures(
170                        encoder, blas_iter, tlas_iter,
171                    )
172                    .unwrap();
173                }
174            }
175        }
176        let (cmd_buf, error) = self.command_encoder_finish(
177            encoder,
178            &wgt::CommandBufferDescriptor { label: None },
179            Some(command_buffer_id_manager.process()),
180        );
181        if let Some(e) = error {
182            panic!("{e}");
183        }
184        cmd_buf
185    }
186
187    fn process(
188        &self,
189        device: wgc::id::DeviceId,
190        queue: wgc::id::QueueId,
191        action: trace::Action,
192        dir: &Path,
193        command_encoder_id_manager: &mut IdentityManager<wgc::id::markers::CommandEncoder>,
194        command_buffer_id_manager: &mut IdentityManager<wgc::id::markers::CommandBuffer>,
195    ) {
196        use wgc::device::trace::Action;
197        log::debug!("action {action:?}");
198        //TODO: find a way to force ID perishing without excessive `maintain()` calls.
199        match action {
200            Action::Init { .. } => {
201                panic!("Unexpected Action::Init: has to be the first action only")
202            }
203            Action::ConfigureSurface { .. }
204            | Action::Present(_)
205            | Action::DiscardSurfaceTexture(_) => {
206                panic!("Unexpected Surface action: winit feature is not enabled")
207            }
208            Action::CreateBuffer(id, desc) => {
209                let (_, error) = self.device_create_buffer(device, &desc, Some(id));
210                if let Some(e) = error {
211                    panic!("{e}");
212                }
213            }
214            Action::FreeBuffer(id) => {
215                self.buffer_destroy(id);
216            }
217            Action::DestroyBuffer(id) => {
218                self.buffer_drop(id);
219            }
220            Action::CreateTexture(id, desc) => {
221                let (_, error) = self.device_create_texture(device, &desc, Some(id));
222                if let Some(e) = error {
223                    panic!("{e}");
224                }
225            }
226            Action::FreeTexture(id) => {
227                self.texture_destroy(id);
228            }
229            Action::DestroyTexture(id) => {
230                self.texture_drop(id);
231            }
232            Action::CreateTextureView {
233                id,
234                parent_id,
235                desc,
236            } => {
237                let (_, error) = self.texture_create_view(parent_id, &desc, Some(id));
238                if let Some(e) = error {
239                    panic!("{e}");
240                }
241            }
242            Action::DestroyTextureView(id) => {
243                self.texture_view_drop(id).unwrap();
244            }
245            Action::CreateExternalTexture { id, desc, planes } => {
246                let (_, error) =
247                    self.device_create_external_texture(device, &desc, &planes, Some(id));
248                if let Some(e) = error {
249                    panic!("{e}");
250                }
251            }
252            Action::FreeExternalTexture(id) => {
253                self.external_texture_destroy(id);
254            }
255            Action::DestroyExternalTexture(id) => {
256                self.external_texture_drop(id);
257            }
258            Action::CreateSampler(id, desc) => {
259                let (_, error) = self.device_create_sampler(device, &desc, Some(id));
260                if let Some(e) = error {
261                    panic!("{e}");
262                }
263            }
264            Action::DestroySampler(id) => {
265                self.sampler_drop(id);
266            }
267            Action::GetSurfaceTexture { id, parent_id } => {
268                self.surface_get_current_texture(parent_id, Some(id))
269                    .unwrap()
270                    .texture
271                    .unwrap();
272            }
273            Action::CreateBindGroupLayout(id, desc) => {
274                let (_, error) = self.device_create_bind_group_layout(device, &desc, Some(id));
275                if let Some(e) = error {
276                    panic!("{e}");
277                }
278            }
279            Action::DestroyBindGroupLayout(id) => {
280                self.bind_group_layout_drop(id);
281            }
282            Action::CreatePipelineLayout(id, desc) => {
283                let (_, error) = self.device_create_pipeline_layout(device, &desc, Some(id));
284                if let Some(e) = error {
285                    panic!("{e}");
286                }
287            }
288            Action::DestroyPipelineLayout(id) => {
289                self.pipeline_layout_drop(id);
290            }
291            Action::CreateBindGroup(id, desc) => {
292                let (_, error) = self.device_create_bind_group(device, &desc, Some(id));
293                if let Some(e) = error {
294                    panic!("{e}");
295                }
296            }
297            Action::DestroyBindGroup(id) => {
298                self.bind_group_drop(id);
299            }
300            Action::CreateShaderModule { id, desc, data } => {
301                log::debug!("Creating shader from {data}");
302                let code = fs::read_to_string(dir.join(&data)).unwrap();
303                let source = if data.ends_with(".wgsl") {
304                    wgc::pipeline::ShaderModuleSource::Wgsl(Cow::Owned(code.clone()))
305                } else if data.ends_with(".ron") {
306                    let module = ron::de::from_str(&code).unwrap();
307                    wgc::pipeline::ShaderModuleSource::Naga(module)
308                } else {
309                    panic!("Unknown shader {data}");
310                };
311                let (_, error) = self.device_create_shader_module(device, &desc, source, Some(id));
312                if let Some(e) = error {
313                    println!("shader compilation error:\n---{code}\n---\n{e}");
314                }
315            }
316            Action::CreateShaderModulePassthrough {
317                id,
318                data,
319                entry_point,
320                label,
321                num_workgroups,
322                runtime_checks,
323            } => {
324                let spirv = data.iter().find_map(|a| {
325                    if a.ends_with(".spv") {
326                        let data = fs::read(dir.join(a)).unwrap();
327                        assert!(data.len() % 4 == 0);
328
329                        Some(Cow::Owned(bytemuck::pod_collect_to_vec(&data)))
330                    } else {
331                        None
332                    }
333                });
334                let dxil = data.iter().find_map(|a| {
335                    if a.ends_with(".dxil") {
336                        let vec = std::fs::read(dir.join(a)).unwrap();
337                        Some(Cow::Owned(vec))
338                    } else {
339                        None
340                    }
341                });
342                let hlsl = data.iter().find_map(|a| {
343                    if a.ends_with(".hlsl") {
344                        let code = fs::read_to_string(dir.join(a)).unwrap();
345                        Some(Cow::Owned(code))
346                    } else {
347                        None
348                    }
349                });
350                let msl = data.iter().find_map(|a| {
351                    if a.ends_with(".msl") {
352                        let code = fs::read_to_string(dir.join(a)).unwrap();
353                        Some(Cow::Owned(code))
354                    } else {
355                        None
356                    }
357                });
358                let glsl = data.iter().find_map(|a| {
359                    if a.ends_with(".glsl") {
360                        let code = fs::read_to_string(dir.join(a)).unwrap();
361                        Some(Cow::Owned(code))
362                    } else {
363                        None
364                    }
365                });
366                let wgsl = data.iter().find_map(|a| {
367                    if a.ends_with(".wgsl") {
368                        let code = fs::read_to_string(dir.join(a)).unwrap();
369                        Some(Cow::Owned(code))
370                    } else {
371                        None
372                    }
373                });
374                let desc = wgt::CreateShaderModuleDescriptorPassthrough {
375                    entry_point,
376                    label,
377                    num_workgroups,
378                    runtime_checks,
379
380                    spirv,
381                    dxil,
382                    hlsl,
383                    msl,
384                    glsl,
385                    wgsl,
386                };
387                let (_, error) = unsafe {
388                    self.device_create_shader_module_passthrough(device, &desc, Some(id))
389                };
390                if let Some(e) = error {
391                    println!("shader compilation error: {e}");
392                }
393            }
394            Action::DestroyShaderModule(id) => {
395                self.shader_module_drop(id);
396            }
397            Action::CreateComputePipeline { id, desc } => {
398                let (_, error) = self.device_create_compute_pipeline(device, &desc, Some(id));
399                if let Some(e) = error {
400                    panic!("{e}");
401                }
402            }
403            Action::DestroyComputePipeline(id) => {
404                self.compute_pipeline_drop(id);
405            }
406            Action::CreateRenderPipeline { id, desc } => {
407                let (_, error) = self.device_create_render_pipeline(device, &desc, Some(id));
408                if let Some(e) = error {
409                    panic!("{e}");
410                }
411            }
412            Action::CreateMeshPipeline { id, desc } => {
413                let (_, error) = self.device_create_mesh_pipeline(device, &desc, Some(id));
414                if let Some(e) = error {
415                    panic!("{e}");
416                }
417            }
418            Action::DestroyRenderPipeline(id) => {
419                self.render_pipeline_drop(id);
420            }
421            Action::CreatePipelineCache { id, desc } => {
422                let _ = unsafe { self.device_create_pipeline_cache(device, &desc, Some(id)) };
423            }
424            Action::DestroyPipelineCache(id) => {
425                self.pipeline_cache_drop(id);
426            }
427            Action::CreateRenderBundle { id, desc, base } => {
428                let bundle =
429                    wgc::command::RenderBundleEncoder::new(&desc, device, Some(base)).unwrap();
430                let (_, error) = self.render_bundle_encoder_finish(
431                    bundle,
432                    &wgt::RenderBundleDescriptor { label: desc.label },
433                    Some(id),
434                );
435                if let Some(e) = error {
436                    panic!("{e}");
437                }
438            }
439            Action::DestroyRenderBundle(id) => {
440                self.render_bundle_drop(id);
441            }
442            Action::CreateQuerySet { id, desc } => {
443                let (_, error) = self.device_create_query_set(device, &desc, Some(id));
444                if let Some(e) = error {
445                    panic!("{e}");
446                }
447            }
448            Action::DestroyQuerySet(id) => {
449                self.query_set_drop(id);
450            }
451            Action::WriteBuffer {
452                id,
453                data,
454                range,
455                queued,
456            } => {
457                let bin = std::fs::read(dir.join(data)).unwrap();
458                let size = (range.end - range.start) as usize;
459                if queued {
460                    self.queue_write_buffer(queue, id, range.start, &bin)
461                        .unwrap();
462                } else {
463                    self.device_set_buffer_data(id, range.start, &bin[..size])
464                        .unwrap();
465                }
466            }
467            Action::WriteTexture {
468                to,
469                data,
470                layout,
471                size,
472            } => {
473                let bin = std::fs::read(dir.join(data)).unwrap();
474                self.queue_write_texture(queue, &to, &bin, &layout, &size)
475                    .unwrap();
476            }
477            Action::Submit(_index, ref commands) if commands.is_empty() => {
478                self.queue_submit(queue, &[]).unwrap();
479            }
480            Action::Submit(_index, commands) => {
481                let (encoder, error) = self.device_create_command_encoder(
482                    device,
483                    &wgt::CommandEncoderDescriptor { label: None },
484                    Some(command_encoder_id_manager.process()),
485                );
486                if let Some(e) = error {
487                    panic!("{e}");
488                }
489                let cmdbuf = self.encode_commands(encoder, commands, command_buffer_id_manager);
490                self.queue_submit(queue, &[cmdbuf]).unwrap();
491            }
492            Action::CreateBlas { id, desc, sizes } => {
493                self.device_create_blas(device, &desc, sizes, Some(id));
494            }
495            Action::DestroyBlas(id) => {
496                self.blas_drop(id);
497            }
498            Action::CreateTlas { id, desc } => {
499                self.device_create_tlas(device, &desc, Some(id));
500            }
501            Action::DestroyTlas(id) => {
502                self.tlas_drop(id);
503            }
504        }
505    }
506}