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::{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<trace::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<trace::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                trace::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                trace::Command::CopyBufferToTexture { src, dst, size } => self
52                    .command_encoder_copy_buffer_to_texture(encoder, &src, &dst, &size)
53                    .unwrap(),
54                trace::Command::CopyTextureToBuffer { src, dst, size } => self
55                    .command_encoder_copy_texture_to_buffer(encoder, &src, &dst, &size)
56                    .unwrap(),
57                trace::Command::CopyTextureToTexture { src, dst, size } => self
58                    .command_encoder_copy_texture_to_texture(encoder, &src, &dst, &size)
59                    .unwrap(),
60                trace::Command::ClearBuffer { dst, offset, size } => self
61                    .command_encoder_clear_buffer(encoder, dst, offset, size)
62                    .unwrap(),
63                trace::Command::ClearTexture {
64                    dst,
65                    subresource_range,
66                } => self
67                    .command_encoder_clear_texture(encoder, dst, &subresource_range)
68                    .unwrap(),
69                trace::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                trace::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                trace::Command::PushDebugGroup(marker) => self
92                    .command_encoder_push_debug_group(encoder, &marker)
93                    .unwrap(),
94                trace::Command::PopDebugGroup => {
95                    self.command_encoder_pop_debug_group(encoder).unwrap()
96                }
97                trace::Command::InsertDebugMarker(marker) => self
98                    .command_encoder_insert_debug_marker(encoder, &marker)
99                    .unwrap(),
100                trace::Command::RunComputePass {
101                    base,
102                    timestamp_writes,
103                } => {
104                    self.compute_pass_end_with_unresolved_commands(
105                        encoder,
106                        base,
107                        timestamp_writes.as_ref(),
108                    );
109                }
110                trace::Command::RunRenderPass {
111                    base,
112                    target_colors,
113                    target_depth_stencil,
114                    timestamp_writes,
115                    occlusion_query_set_id,
116                } => {
117                    self.render_pass_end_with_unresolved_commands(
118                        encoder,
119                        base,
120                        &target_colors,
121                        target_depth_stencil.as_ref(),
122                        timestamp_writes.as_ref(),
123                        occlusion_query_set_id,
124                    );
125                }
126                trace::Command::BuildAccelerationStructures { blas, tlas } => {
127                    let blas_iter = blas.iter().map(|x| {
128                        let geometries = match &x.geometries {
129                            wgc::ray_tracing::TraceBlasGeometries::TriangleGeometries(
130                                triangle_geometries,
131                            ) => {
132                                let iter = triangle_geometries.iter().map(|tg| {
133                                    wgc::ray_tracing::BlasTriangleGeometry {
134                                        size: &tg.size,
135                                        vertex_buffer: tg.vertex_buffer,
136                                        index_buffer: tg.index_buffer,
137                                        transform_buffer: tg.transform_buffer,
138                                        first_vertex: tg.first_vertex,
139                                        vertex_stride: tg.vertex_stride,
140                                        first_index: tg.first_index,
141                                        transform_buffer_offset: tg.transform_buffer_offset,
142                                    }
143                                });
144                                wgc::ray_tracing::BlasGeometries::TriangleGeometries(Box::new(iter))
145                            }
146                        };
147                        wgc::ray_tracing::BlasBuildEntry {
148                            blas_id: x.blas_id,
149                            geometries,
150                        }
151                    });
152
153                    let tlas_iter = tlas.iter().map(|x| {
154                        let instances = x.instances.iter().map(|instance| {
155                            instance
156                                .as_ref()
157                                .map(|instance| wgc::ray_tracing::TlasInstance {
158                                    blas_id: instance.blas_id,
159                                    transform: &instance.transform,
160                                    custom_data: instance.custom_data,
161                                    mask: instance.mask,
162                                })
163                        });
164                        wgc::ray_tracing::TlasPackage {
165                            tlas_id: x.tlas_id,
166                            instances: Box::new(instances),
167                            lowest_unmodified: x.lowest_unmodified,
168                        }
169                    });
170
171                    self.command_encoder_build_acceleration_structures(
172                        encoder, blas_iter, tlas_iter,
173                    )
174                    .unwrap();
175                }
176            }
177        }
178        let (cmd_buf, error) = self.command_encoder_finish(
179            encoder,
180            &wgt::CommandBufferDescriptor { label: None },
181            Some(command_buffer_id_manager.process()),
182        );
183        if let Some(e) = error {
184            panic!("{e}");
185        }
186        cmd_buf
187    }
188
189    fn process(
190        &self,
191        device: wgc::id::DeviceId,
192        queue: wgc::id::QueueId,
193        action: trace::Action,
194        dir: &Path,
195        command_encoder_id_manager: &mut IdentityManager<wgc::id::markers::CommandEncoder>,
196        command_buffer_id_manager: &mut IdentityManager<wgc::id::markers::CommandBuffer>,
197    ) {
198        use wgc::device::trace::Action;
199        log::debug!("action {action:?}");
200        //TODO: find a way to force ID perishing without excessive `maintain()` calls.
201        match action {
202            Action::Init { .. } => {
203                panic!("Unexpected Action::Init: has to be the first action only")
204            }
205            Action::ConfigureSurface { .. }
206            | Action::Present(_)
207            | Action::DiscardSurfaceTexture(_) => {
208                panic!("Unexpected Surface action: winit feature is not enabled")
209            }
210            Action::CreateBuffer(id, desc) => {
211                let (_, error) = self.device_create_buffer(device, &desc, Some(id));
212                if let Some(e) = error {
213                    panic!("{e}");
214                }
215            }
216            Action::FreeBuffer(id) => {
217                self.buffer_destroy(id);
218            }
219            Action::DestroyBuffer(id) => {
220                self.buffer_drop(id);
221            }
222            Action::CreateTexture(id, desc) => {
223                let (_, error) = self.device_create_texture(device, &desc, Some(id));
224                if let Some(e) = error {
225                    panic!("{e}");
226                }
227            }
228            Action::FreeTexture(id) => {
229                self.texture_destroy(id);
230            }
231            Action::DestroyTexture(id) => {
232                self.texture_drop(id);
233            }
234            Action::CreateTextureView {
235                id,
236                parent_id,
237                desc,
238            } => {
239                let (_, error) = self.texture_create_view(parent_id, &desc, Some(id));
240                if let Some(e) = error {
241                    panic!("{e}");
242                }
243            }
244            Action::DestroyTextureView(id) => {
245                self.texture_view_drop(id).unwrap();
246            }
247            Action::CreateExternalTexture { id, desc, planes } => {
248                let (_, error) =
249                    self.device_create_external_texture(device, &desc, &planes, Some(id));
250                if let Some(e) = error {
251                    panic!("{e}");
252                }
253            }
254            Action::FreeExternalTexture(id) => {
255                self.external_texture_destroy(id);
256            }
257            Action::DestroyExternalTexture(id) => {
258                self.external_texture_drop(id);
259            }
260            Action::CreateSampler(id, desc) => {
261                let (_, error) = self.device_create_sampler(device, &desc, Some(id));
262                if let Some(e) = error {
263                    panic!("{e}");
264                }
265            }
266            Action::DestroySampler(id) => {
267                self.sampler_drop(id);
268            }
269            Action::GetSurfaceTexture { id, parent_id } => {
270                self.surface_get_current_texture(parent_id, Some(id))
271                    .unwrap()
272                    .texture
273                    .unwrap();
274            }
275            Action::CreateBindGroupLayout(id, desc) => {
276                let (_, error) = self.device_create_bind_group_layout(device, &desc, Some(id));
277                if let Some(e) = error {
278                    panic!("{e}");
279                }
280            }
281            Action::DestroyBindGroupLayout(id) => {
282                self.bind_group_layout_drop(id);
283            }
284            Action::CreatePipelineLayout(id, desc) => {
285                let (_, error) = self.device_create_pipeline_layout(device, &desc, Some(id));
286                if let Some(e) = error {
287                    panic!("{e}");
288                }
289            }
290            Action::DestroyPipelineLayout(id) => {
291                self.pipeline_layout_drop(id);
292            }
293            Action::CreateBindGroup(id, desc) => {
294                let (_, error) = self.device_create_bind_group(device, &desc, Some(id));
295                if let Some(e) = error {
296                    panic!("{e}");
297                }
298            }
299            Action::DestroyBindGroup(id) => {
300                self.bind_group_drop(id);
301            }
302            Action::CreateShaderModule { id, desc, data } => {
303                log::debug!("Creating shader from {data}");
304                let code = fs::read_to_string(dir.join(&data)).unwrap();
305                let source = if data.ends_with(".wgsl") {
306                    wgc::pipeline::ShaderModuleSource::Wgsl(Cow::Owned(code.clone()))
307                } else if data.ends_with(".ron") {
308                    let module = ron::de::from_str(&code).unwrap();
309                    wgc::pipeline::ShaderModuleSource::Naga(module)
310                } else {
311                    panic!("Unknown shader {data}");
312                };
313                let (_, error) = self.device_create_shader_module(device, &desc, source, Some(id));
314                if let Some(e) = error {
315                    println!("shader compilation error:\n---{code}\n---\n{e}");
316                }
317            }
318            Action::DestroyShaderModule(id) => {
319                self.shader_module_drop(id);
320            }
321            Action::CreateComputePipeline { id, desc } => {
322                let (_, error) = self.device_create_compute_pipeline(device, &desc, Some(id));
323                if let Some(e) = error {
324                    panic!("{e}");
325                }
326            }
327            Action::DestroyComputePipeline(id) => {
328                self.compute_pipeline_drop(id);
329            }
330            Action::CreateRenderPipeline { id, desc } => {
331                let (_, error) = self.device_create_render_pipeline(device, &desc, Some(id));
332                if let Some(e) = error {
333                    panic!("{e}");
334                }
335            }
336            Action::DestroyRenderPipeline(id) => {
337                self.render_pipeline_drop(id);
338            }
339            Action::CreatePipelineCache { id, desc } => {
340                let _ = unsafe { self.device_create_pipeline_cache(device, &desc, Some(id)) };
341            }
342            Action::DestroyPipelineCache(id) => {
343                self.pipeline_cache_drop(id);
344            }
345            Action::CreateRenderBundle { id, desc, base } => {
346                let bundle =
347                    wgc::command::RenderBundleEncoder::new(&desc, device, Some(base)).unwrap();
348                let (_, error) = self.render_bundle_encoder_finish(
349                    bundle,
350                    &wgt::RenderBundleDescriptor { label: desc.label },
351                    Some(id),
352                );
353                if let Some(e) = error {
354                    panic!("{e}");
355                }
356            }
357            Action::DestroyRenderBundle(id) => {
358                self.render_bundle_drop(id);
359            }
360            Action::CreateQuerySet { id, desc } => {
361                let (_, error) = self.device_create_query_set(device, &desc, Some(id));
362                if let Some(e) = error {
363                    panic!("{e}");
364                }
365            }
366            Action::DestroyQuerySet(id) => {
367                self.query_set_drop(id);
368            }
369            Action::WriteBuffer {
370                id,
371                data,
372                range,
373                queued,
374            } => {
375                let bin = std::fs::read(dir.join(data)).unwrap();
376                let size = (range.end - range.start) as usize;
377                if queued {
378                    self.queue_write_buffer(queue, id, range.start, &bin)
379                        .unwrap();
380                } else {
381                    self.device_set_buffer_data(id, range.start, &bin[..size])
382                        .unwrap();
383                }
384            }
385            Action::WriteTexture {
386                to,
387                data,
388                layout,
389                size,
390            } => {
391                let bin = std::fs::read(dir.join(data)).unwrap();
392                self.queue_write_texture(queue, &to, &bin, &layout, &size)
393                    .unwrap();
394            }
395            Action::Submit(_index, ref commands) if commands.is_empty() => {
396                self.queue_submit(queue, &[]).unwrap();
397            }
398            Action::Submit(_index, commands) => {
399                let (encoder, error) = self.device_create_command_encoder(
400                    device,
401                    &wgt::CommandEncoderDescriptor { label: None },
402                    Some(command_encoder_id_manager.process()),
403                );
404                if let Some(e) = error {
405                    panic!("{e}");
406                }
407                let cmdbuf = self.encode_commands(encoder, commands, command_buffer_id_manager);
408                self.queue_submit(queue, &[cmdbuf]).unwrap();
409            }
410            Action::CreateBlas { id, desc, sizes } => {
411                self.device_create_blas(device, &desc, sizes, Some(id));
412            }
413            Action::DestroyBlas(id) => {
414                self.blas_drop(id);
415            }
416            Action::CreateTlas { id, desc } => {
417                self.device_create_tlas(device, &desc, Some(id));
418            }
419            Action::DestroyTlas(id) => {
420                self.tlas_drop(id);
421            }
422        }
423    }
424}