diff options
Diffstat (limited to '')
-rw-r--r-- | gfx/wgpu/player/src/bin/play.rs | 179 | ||||
-rw-r--r-- | gfx/wgpu/player/src/lib.rs | 313 |
2 files changed, 492 insertions, 0 deletions
diff --git a/gfx/wgpu/player/src/bin/play.rs b/gfx/wgpu/player/src/bin/play.rs new file mode 100644 index 0000000000..522d7e64b7 --- /dev/null +++ b/gfx/wgpu/player/src/bin/play.rs @@ -0,0 +1,179 @@ +/* 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::{GlobalPlay as _, IdentityPassThroughFactory}; +use wgc::{device::trace, gfx_select}; + +use std::{ + fs, + path::{Path, PathBuf}, +}; + +fn main() { + #[cfg(feature = "winit")] + use winit::{event_loop::EventLoop, window::WindowBuilder}; + + wgpu_subscriber::initialize_default_subscriber( + std::env::var("WGPU_CHROME_TRACE") + .as_ref() + .map(Path::new) + .ok(), + ); + + #[cfg(feature = "renderdoc")] + #[cfg_attr(feature = "winit", allow(unused))] + let mut rd = renderdoc::RenderDoc::<renderdoc::V110>::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<trace::Action> = 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::PRIMARY, + ); + 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 + .request_adapter( + &wgc::instance::RequestAdapterOptions { + power_preference: wgt::PowerPreference::LowPower, + #[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)).unwrap(); + log::info!("Picked '{}'", info.name); + let id = wgc::id::TypedId::zip(1, 0, backend); + let (_, error) = gfx_select!(adapter => global.adapter_request_device( + adapter, + &desc, + None, + id + )); + if let Some(e) = error { + panic!("{:?}", e); + } + id + } + _ => 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)).unwrap(); + } + #[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/gfx/wgpu/player/src/lib.rs b/gfx/wgpu/player/src/lib.rs new file mode 100644 index 0000000000..140ff70503 --- /dev/null +++ b/gfx/wgpu/player/src/lib.rs @@ -0,0 +1,313 @@ +/* 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 library for WebGPU traces. + * + * # Notes + * - we call device_maintain_ids() before creating any refcounted resource, + * which is basically everything except for BGL and shader modules, + * so that we don't accidentally try to use the same ID. +!*/ + +use wgc::device::trace; + +use std::{borrow::Cow, fmt::Debug, fs, marker::PhantomData, path::Path}; + +#[derive(Debug)] +pub struct IdentityPassThrough<I>(PhantomData<I>); + +impl<I: Clone + Debug + wgc::id::TypedId> wgc::hub::IdentityHandler<I> for IdentityPassThrough<I> { + type Input = I; + fn process(&self, id: I, backend: wgt::Backend) -> I { + let (index, epoch, _backend) = id.unzip(); + I::zip(index, epoch, backend) + } + fn free(&self, _id: I) {} +} + +pub struct IdentityPassThroughFactory; + +impl<I: Clone + Debug + wgc::id::TypedId> wgc::hub::IdentityHandlerFactory<I> + for IdentityPassThroughFactory +{ + type Filter = IdentityPassThrough<I>; + fn spawn(&self, _min_index: u32) -> Self::Filter { + IdentityPassThrough(PhantomData) + } +} +impl wgc::hub::GlobalIdentityHandlerFactory for IdentityPassThroughFactory {} + +pub trait GlobalPlay { + fn encode_commands<B: wgc::hub::GfxBackend>( + &self, + encoder: wgc::id::CommandEncoderId, + commands: Vec<trace::Command>, + ) -> wgc::id::CommandBufferId; + fn process<B: wgc::hub::GfxBackend>( + &self, + device: wgc::id::DeviceId, + action: trace::Action, + dir: &Path, + comb_manager: &mut wgc::hub::IdentityManager, + ); +} + +impl GlobalPlay for wgc::hub::Global<IdentityPassThroughFactory> { + fn encode_commands<B: wgc::hub::GfxBackend>( + &self, + encoder: wgc::id::CommandEncoderId, + commands: Vec<trace::Command>, + ) -> wgc::id::CommandBufferId { + for command in commands { + match command { + trace::Command::CopyBufferToBuffer { + src, + src_offset, + dst, + dst_offset, + size, + } => self + .command_encoder_copy_buffer_to_buffer::<B>( + encoder, src, src_offset, dst, dst_offset, size, + ) + .unwrap(), + trace::Command::CopyBufferToTexture { src, dst, size } => self + .command_encoder_copy_buffer_to_texture::<B>(encoder, &src, &dst, &size) + .unwrap(), + trace::Command::CopyTextureToBuffer { src, dst, size } => self + .command_encoder_copy_texture_to_buffer::<B>(encoder, &src, &dst, &size) + .unwrap(), + trace::Command::CopyTextureToTexture { src, dst, size } => self + .command_encoder_copy_texture_to_texture::<B>(encoder, &src, &dst, &size) + .unwrap(), + trace::Command::RunComputePass { base } => { + self.command_encoder_run_compute_pass_impl::<B>(encoder, base.as_ref()) + .unwrap(); + } + trace::Command::RunRenderPass { + base, + target_colors, + target_depth_stencil, + } => { + self.command_encoder_run_render_pass_impl::<B>( + encoder, + base.as_ref(), + &target_colors, + target_depth_stencil.as_ref(), + ) + .unwrap(); + } + } + } + let (cmd_buf, error) = self + .command_encoder_finish::<B>(encoder, &wgt::CommandBufferDescriptor { label: None }); + if let Some(e) = error { + panic!("{:?}", e); + } + cmd_buf + } + + fn process<B: wgc::hub::GfxBackend>( + &self, + device: wgc::id::DeviceId, + action: trace::Action, + dir: &Path, + comb_manager: &mut wgc::hub::IdentityManager, + ) { + use wgc::device::trace::Action as A; + log::info!("action {:?}", action); + match action { + A::Init { .. } => panic!("Unexpected Action::Init: has to be the first action only"), + A::CreateSwapChain { .. } | A::PresentSwapChain(_) => { + panic!("Unexpected SwapChain action: winit feature is not enabled") + } + A::CreateBuffer(id, desc) => { + self.device_maintain_ids::<B>(device).unwrap(); + let (_, error) = self.device_create_buffer::<B>(device, &desc, id); + if let Some(e) = error { + panic!("{:?}", e); + } + } + A::FreeBuffer(id) => { + self.buffer_destroy::<B>(id).unwrap(); + } + A::DestroyBuffer(id) => { + self.buffer_drop::<B>(id, true); + } + A::CreateTexture(id, desc) => { + self.device_maintain_ids::<B>(device).unwrap(); + let (_, error) = self.device_create_texture::<B>(device, &desc, id); + if let Some(e) = error { + panic!("{:?}", e); + } + } + A::FreeTexture(id) => { + self.texture_destroy::<B>(id).unwrap(); + } + A::DestroyTexture(id) => { + self.texture_drop::<B>(id, true); + } + A::CreateTextureView { + id, + parent_id, + desc, + } => { + self.device_maintain_ids::<B>(device).unwrap(); + let (_, error) = self.texture_create_view::<B>(parent_id, &desc, id); + if let Some(e) = error { + panic!("{:?}", e); + } + } + A::DestroyTextureView(id) => { + self.texture_view_drop::<B>(id).unwrap(); + } + A::CreateSampler(id, desc) => { + self.device_maintain_ids::<B>(device).unwrap(); + let (_, error) = self.device_create_sampler::<B>(device, &desc, id); + if let Some(e) = error { + panic!("{:?}", e); + } + } + A::DestroySampler(id) => { + self.sampler_drop::<B>(id); + } + A::GetSwapChainTexture { id, parent_id } => { + if let Some(id) = id { + self.swap_chain_get_current_texture_view::<B>(parent_id, id) + .unwrap() + .view_id + .unwrap(); + } + } + A::CreateBindGroupLayout(id, desc) => { + let (_, error) = self.device_create_bind_group_layout::<B>(device, &desc, id); + if let Some(e) = error { + panic!("{:?}", e); + } + } + A::DestroyBindGroupLayout(id) => { + self.bind_group_layout_drop::<B>(id); + } + A::CreatePipelineLayout(id, desc) => { + self.device_maintain_ids::<B>(device).unwrap(); + let (_, error) = self.device_create_pipeline_layout::<B>(device, &desc, id); + if let Some(e) = error { + panic!("{:?}", e); + } + } + A::DestroyPipelineLayout(id) => { + self.pipeline_layout_drop::<B>(id); + } + A::CreateBindGroup(id, desc) => { + self.device_maintain_ids::<B>(device).unwrap(); + let (_, error) = self.device_create_bind_group::<B>(device, &desc, id); + if let Some(e) = error { + panic!("{:?}", e); + } + } + A::DestroyBindGroup(id) => { + self.bind_group_drop::<B>(id); + } + A::CreateShaderModule { id, data, label } => { + let desc = wgc::pipeline::ShaderModuleDescriptor { + source: if data.ends_with(".wgsl") { + let code = fs::read_to_string(dir.join(data)).unwrap(); + wgc::pipeline::ShaderModuleSource::Wgsl(Cow::Owned(code)) + } else { + let byte_vec = fs::read(dir.join(data)).unwrap(); + let spv = byte_vec + .chunks(4) + .map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]])) + .collect::<Vec<_>>(); + wgc::pipeline::ShaderModuleSource::SpirV(Cow::Owned(spv)) + }, + label, + }; + let (_, error) = self.device_create_shader_module::<B>(device, &desc, id); + if let Some(e) = error { + panic!("{:?}", e); + } + } + A::DestroyShaderModule(id) => { + self.shader_module_drop::<B>(id); + } + A::CreateComputePipeline(id, desc) => { + self.device_maintain_ids::<B>(device).unwrap(); + let (_, _, error) = + self.device_create_compute_pipeline::<B>(device, &desc, id, None); + if let Some(e) = error { + panic!("{:?}", e); + } + } + A::DestroyComputePipeline(id) => { + self.compute_pipeline_drop::<B>(id); + } + A::CreateRenderPipeline(id, desc) => { + self.device_maintain_ids::<B>(device).unwrap(); + let (_, _, error) = + self.device_create_render_pipeline::<B>(device, &desc, id, None); + if let Some(e) = error { + panic!("{:?}", e); + } + } + A::DestroyRenderPipeline(id) => { + self.render_pipeline_drop::<B>(id); + } + A::CreateRenderBundle { id, desc, base } => { + let bundle = + wgc::command::RenderBundleEncoder::new(&desc, device, Some(base)).unwrap(); + let (_, error) = self.render_bundle_encoder_finish::<B>( + bundle, + &wgt::RenderBundleDescriptor { label: desc.label }, + id, + ); + if let Some(e) = error { + panic!("{:?}", e); + } + } + A::DestroyRenderBundle(id) => { + self.render_bundle_drop::<B>(id); + } + A::WriteBuffer { + id, + data, + range, + queued, + } => { + let bin = std::fs::read(dir.join(data)).unwrap(); + let size = (range.end - range.start) as usize; + if queued { + self.queue_write_buffer::<B>(device, id, range.start, &bin) + .unwrap(); + } else { + self.device_wait_for_buffer::<B>(device, id).unwrap(); + self.device_set_buffer_sub_data::<B>(device, id, range.start, &bin[..size]) + .unwrap(); + } + } + A::WriteTexture { + to, + data, + layout, + size, + } => { + let bin = std::fs::read(dir.join(data)).unwrap(); + self.queue_write_texture::<B>(device, &to, &bin, &layout, &size) + .unwrap(); + } + A::Submit(_index, commands) => { + let (encoder, error) = self.device_create_command_encoder::<B>( + device, + &wgt::CommandEncoderDescriptor { label: None }, + comb_manager.alloc(device.backend()), + ); + if let Some(e) = error { + panic!("{:?}", e); + } + let cmdbuf = self.encode_commands::<B>(encoder, commands); + self.queue_submit::<B>(device, &[cmdbuf]).unwrap(); + } + } + } +} |