diff --git a/Cargo.toml b/Cargo.toml index 7958bd98a..b9d820c1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "player", "wgpu-core", "wgpu-types", ] diff --git a/player/Cargo.toml b/player/Cargo.toml new file mode 100644 index 000000000..aa51b5652 --- /dev/null +++ b/player/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "player" +version = "0.1.0" +authors = [ + "Dzmitry Malyshau ", +] +edition = "2018" +description = "WebGPU trace player" +homepage = "https://github.com/gfx-rs/wgpu" +repository = "https://github.com/gfx-rs/wgpu" +keywords = ["graphics"] +license = "MPL-2.0" +publish = false + +[features] + +[dependencies] +env_logger = "0.7" +ron = { version = "0.5" } +winit = { version = "0.22" } + +[dependencies.wgt] +path = "../wgpu-types" +package = "wgpu-types" +version = "0.5" +features = ["replay"] + +[dependencies.wgc] +path = "../wgpu-core" +package = "wgpu-core" +version = "0.5" +features = ["replay", "raw-window-handle"] diff --git a/player/src/main.rs b/player/src/main.rs new file mode 100644 index 000000000..65329259d --- /dev/null +++ b/player/src/main.rs @@ -0,0 +1,121 @@ +/* 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/. */ + +use wgc::device::trace; + +use std::{ + fmt::Debug, + fs::File, + marker::PhantomData, + path::{Path, PathBuf}, +}; +use winit::{event_loop::EventLoop, window::WindowBuilder}; + +macro_rules! gfx_select { + ($id:expr => $global:ident.$method:ident( $($param:expr),+ )) => { + match $id.backend() { + #[cfg(not(any(target_os = "ios", target_os = "macos")))] + wgt::Backend::Vulkan => $global.$method::( $($param),+ ), + #[cfg(any(target_os = "ios", target_os = "macos"))] + wgt::Backend::Metal => $global.$method::( $($param),+ ), + #[cfg(windows)] + wgt::Backend::Dx12 => $global.$method::( $($param),+ ), + #[cfg(windows)] + wgt::Backend::Dx11 => $global.$method::( $($param),+ ), + _ => unreachable!() + } + }; +} + +#[derive(Debug)] +struct IdentityPassThrough(PhantomData); + +impl wgc::hub::IdentityHandler for IdentityPassThrough { + 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) {} +} + +struct IdentityPassThroughFactory; + +impl wgc::hub::IdentityHandlerFactory + for IdentityPassThroughFactory +{ + type Filter = IdentityPassThrough; + fn spawn(&self, _min_index: u32) -> Self::Filter { + IdentityPassThrough(PhantomData) + } +} +impl wgc::hub::GlobalIdentityHandlerFactory for IdentityPassThroughFactory {} + +fn main() { + env_logger::init(); + + let folder = match std::env::args().nth(1) { + Some(arg) if Path::new(&arg).is_dir() => PathBuf::from(arg), + _ => panic!("Provide the folder path as the parameter"), + }; + + let file = File::open(folder.join(trace::FILE_NAME)).unwrap(); + let actions: Vec = ron::de::from_reader(file).unwrap(); + + let event_loop = EventLoop::new(); + let mut builder = WindowBuilder::new(); + builder = builder.with_title("wgpu player"); + let window = builder.build(&event_loop).unwrap(); + + let global = wgc::hub::Global::new("player", IdentityPassThroughFactory); + let mut surface_id_manager = wgc::hub::IdentityManager::from_index(1); + let mut adapter_id_manager = wgc::hub::IdentityManager::default(); + let mut device_id_manager = wgc::hub::IdentityManager::default(); + + let (_size, surface) = { + let size = window.inner_size(); + let id = surface_id_manager.alloc(wgt::Backend::Empty); + let surface = global.instance_create_surface(&window, id); + (size, surface) + }; + + let adapter = global + .pick_adapter( + &wgc::instance::RequestAdapterOptions { + power_preference: wgt::PowerPreference::Default, + compatible_surface: Some(surface), + }, + wgc::instance::AdapterInputs::IdSet( + &vec![ + adapter_id_manager.alloc(wgt::Backend::Vulkan), + adapter_id_manager.alloc(wgt::Backend::Dx12), + adapter_id_manager.alloc(wgt::Backend::Metal), + ], + |id| wgc::id::TypedId::unzip(*id).2, + ), + ) + .unwrap(); + + let limits = match actions.first() { + Some(&trace::Action::Init { ref limits }) => limits.clone(), + other => panic!("Unsupported first action: {:?}", other), + }; + + let device = gfx_select!(adapter => global.adapter_request_device( + adapter, + &wgt::DeviceDescriptor { + extensions: wgt::Extensions { + anisotropic_filtering: false, + }, + limits, + }, + device_id_manager.alloc(wgt::Backend::Vulkan) + )); + + for action in actions.into_iter().skip(1) { + match action { + _ => {} + } + } +} diff --git a/wgpu-core/Cargo.toml b/wgpu-core/Cargo.toml index c6ad0c832..49e0497ec 100644 --- a/wgpu-core/Cargo.toml +++ b/wgpu-core/Cargo.toml @@ -34,6 +34,7 @@ gfx-descriptor = "0.1" gfx-memory = "0.1" parking_lot = "0.10" peek-poke = "0.2" +raw-window-handle = { version = "0.3", optional = true } ron = { version = "0.5", optional = true } serde = { version = "1.0", features = ["serde_derive"], optional = true } smallvec = "1" diff --git a/wgpu-core/src/command/compute.rs b/wgpu-core/src/command/compute.rs index ea6e87d78..8d35b9493 100644 --- a/wgpu-core/src/command/compute.rs +++ b/wgpu-core/src/command/compute.rs @@ -28,7 +28,7 @@ enum PipelineState { #[derive(Clone, Copy, Debug, PeekPoke)] #[cfg_attr(feature = "trace", derive(serde::Serialize))] #[cfg_attr(feature = "replay", derive(serde::Deserialize))] -pub(crate) enum ComputeCommand { +pub enum ComputeCommand { SetBindGroup { index: u8, num_dynamic_offsets: u8, diff --git a/wgpu-core/src/command/mod.rs b/wgpu-core/src/command/mod.rs index cf82f972a..1716bf69b 100644 --- a/wgpu-core/src/command/mod.rs +++ b/wgpu-core/src/command/mod.rs @@ -27,7 +27,7 @@ use peek_poke::PeekPoke; use std::{marker::PhantomData, mem, ptr, slice, thread::ThreadId}; #[derive(Clone, Copy, Debug, PeekPoke)] -pub(crate) struct PhantomSlice(PhantomData); +pub struct PhantomSlice(PhantomData); impl Default for PhantomSlice { fn default() -> Self { diff --git a/wgpu-core/src/command/render.rs b/wgpu-core/src/command/render.rs index e74138f53..6c8b0222d 100644 --- a/wgpu-core/src/command/render.rs +++ b/wgpu-core/src/command/render.rs @@ -58,7 +58,7 @@ pub struct Rect { #[derive(Clone, Copy, Debug, PeekPoke)] #[cfg_attr(feature = "trace", derive(serde::Serialize))] #[cfg_attr(feature = "replay", derive(serde::Deserialize))] -pub(crate) enum RenderCommand { +pub enum RenderCommand { SetBindGroup { index: u8, num_dynamic_offsets: u8, diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index 981988c60..5bd78afbe 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -31,8 +31,8 @@ use std::{ }; mod life; -#[cfg(feature = "trace")] -pub(crate) mod trace; +#[cfg(any(feature = "trace", feature = "replay"))] +pub mod trace; #[cfg(feature = "trace")] use trace::{Action, Trace}; diff --git a/wgpu-core/src/device/trace.rs b/wgpu-core/src/device/trace.rs index d0a594b17..c8a82f956 100644 --- a/wgpu-core/src/device/trace.rs +++ b/wgpu-core/src/device/trace.rs @@ -6,12 +6,19 @@ use crate::{ command::{BufferCopyView, TextureCopyView}, id, }; -use std::{io::Write as _, ops::Range}; +use std::ops::Range; +#[cfg(feature = "trace")] +use std::io::Write as _; //TODO: consider a readable Id that doesn't include the backend + type FileName = String; -#[derive(serde::Serialize)] +pub const FILE_NAME: &str = "trace.ron"; + +#[derive(Debug)] +#[cfg_attr(feature = "trace", derive(serde::Serialize))] +#[cfg_attr(feature = "replay", derive(serde::Deserialize))] pub enum BindingResource { Buffer { id: id::BufferId, @@ -22,8 +29,10 @@ pub enum BindingResource { TextureView(id::TextureViewId), } -#[derive(serde::Serialize)] -pub(crate) enum Action { +#[derive(Debug)] +#[cfg_attr(feature = "trace", derive(serde::Serialize))] +#[cfg_attr(feature = "replay", derive(serde::Deserialize))] +pub enum Action { Init { limits: wgt::Limits, }, @@ -88,8 +97,10 @@ pub(crate) enum Action { Submit(Vec), } -#[derive(Debug, serde::Serialize)] -pub(crate) enum Command { +#[derive(Debug)] +#[cfg_attr(feature = "trace", derive(serde::Serialize))] +#[cfg_attr(feature = "replay", derive(serde::Deserialize))] +pub enum Command { CopyBufferToBuffer { src: id::BufferId, src_offset: wgt::BufferAddress, @@ -124,6 +135,7 @@ pub(crate) enum Command { }, } +#[cfg(feature = "trace")] #[derive(Debug)] pub struct Trace { path: std::path::PathBuf, @@ -132,10 +144,11 @@ pub struct Trace { binary_id: usize, } +#[cfg(feature = "trace")] impl Trace { pub fn new(path: &std::path::Path) -> Result { log::info!("Tracing into '{:?}'", path); - let mut file = std::fs::File::create(path.join("trace.ron"))?; + let mut file = std::fs::File::create(path.join(FILE_NAME))?; file.write(b"[\n")?; Ok(Trace { path: path.to_path_buf(), @@ -164,6 +177,7 @@ impl Trace { } } +#[cfg(feature = "trace")] impl Drop for Trace { fn drop(&mut self) { let _ = self.file.write(b"]"); diff --git a/wgpu-core/src/hub.rs b/wgpu-core/src/hub.rs index 2d1ef24a5..5b806558f 100644 --- a/wgpu-core/src/hub.rs +++ b/wgpu-core/src/hub.rs @@ -25,7 +25,7 @@ use wgt::Backend; #[cfg(debug_assertions)] use std::cell::Cell; -use std::{fmt::Debug, iter, marker::PhantomData, ops}; +use std::{fmt::Debug, marker::PhantomData, ops}; /// A simple structure to manage identities of objects. #[derive(Debug)] @@ -44,6 +44,13 @@ impl Default for IdentityManager { } impl IdentityManager { + pub fn from_index(min_index: u32) -> Self { + IdentityManager { + free: (0 .. min_index).collect(), + epochs: vec![1; min_index as usize], + } + } + pub fn alloc(&mut self, backend: Backend) -> I { match self.free.pop() { Some(index) => I::zip(index, self.epochs[index as usize], backend), @@ -262,10 +269,7 @@ pub struct IdentityManagerFactory; impl IdentityHandlerFactory for IdentityManagerFactory { type Filter = Mutex; fn spawn(&self, min_index: Index) -> Self::Filter { - let mut man = IdentityManager::default(); - man.free.extend(0..min_index); - man.epochs.extend(iter::repeat(1).take(min_index as usize)); - Mutex::new(man) + Mutex::new(IdentityManager::from_index(min_index)) } } @@ -549,6 +553,7 @@ impl Global { impl Drop for Global { fn drop(&mut self) { + log::info!("Dropping Global"); // destroy surfaces for (_, (surface, _)) in self.surfaces.data.write().map.drain() { self.instance.destroy_surface(surface); diff --git a/wgpu-core/src/instance.rs b/wgpu-core/src/instance.rs index 8346472ce..0995ef65e 100644 --- a/wgpu-core/src/instance.rs +++ b/wgpu-core/src/instance.rs @@ -188,7 +188,7 @@ impl From for DeviceType { pub enum AdapterInputs<'a, I> { IdSet(&'a [I], fn(&I) -> Backend), - Mask(BackendBit, fn() -> I), + Mask(BackendBit, fn(Backend) -> I), } impl AdapterInputs<'_, I> { @@ -197,7 +197,7 @@ impl AdapterInputs<'_, I> { AdapterInputs::IdSet(ids, ref fun) => ids.iter().find(|id| fun(id) == b).cloned(), AdapterInputs::Mask(bits, ref fun) => { if bits.contains(b.into()) { - Some(fun()) + Some(fun(b)) } else { None } @@ -207,6 +207,85 @@ impl AdapterInputs<'_, I> { } impl Global { + #[cfg(feature = "raw-window-handle")] + pub fn instance_create_surface( + &self, + window: &W, + id_in: Input, + ) -> SurfaceId { + use raw_window_handle::RawWindowHandle as Rwh; + + let surface = match window.raw_window_handle() { + #[cfg(target_os = "ios")] + Rwh::IOS(h) => Surface { + #[cfg(feature = "gfx-backend-vulkan")] + vulkan: None, + metal: self + .instance + .metal + .create_surface_from_uiview(h.ui_view, cfg!(debug_assertions)), + }, + #[cfg(target_os = "macos")] + Rwh::MacOS(h) => { + //TODO: figure out when this is needed, and how to get that without `objc` + //use objc::{msg_send, runtime::Object, sel, sel_impl}; + //let ns_view = if h.ns_view.is_null() { + // let ns_window = h.ns_window as *mut Object; + // unsafe { msg_send![ns_window, contentView] } + //} else { + // h.ns_view + //}; + Surface { + #[cfg(feature = "gfx-backend-vulkan")] + vulkan: self + .instance + .vulkan + .as_ref() + .map(|inst| inst.create_surface_from_ns_view(h.ns_view)), + metal: self + .instance + .metal + .create_surface_from_nsview(h.ns_view, cfg!(debug_assertions)), + } + } + #[cfg(all(unix, not(target_os = "ios"), not(target_os = "macos")))] + Rwh::Xlib(h) => Surface { + vulkan: self + .instance + .vulkan + .as_ref() + .map(|inst| inst.create_surface_from_xlib(h.display as _, h.window as _)), + }, + #[cfg(all(unix, not(target_os = "ios"), not(target_os = "macos")))] + Rwh::Wayland(h) => Surface { + vulkan: self + .instance + .vulkan + .as_ref() + .map(|inst| inst.create_surface_from_wayland(h.display, h.surface)), + }, + #[cfg(windows)] + Rwh::Windows(h) => Surface { + vulkan: self + .instance + .vulkan + .as_ref() + .map(|inst| inst.create_surface_from_hwnd(std::ptr::null_mut(), h.hwnd)), + dx12: self + .instance + .dx12 + .as_ref() + .map(|inst| inst.create_surface_from_hwnd(h.hwnd)), + dx11: self.instance.dx11.create_surface_from_hwnd(h.hwnd), + }, + _ => panic!("Unsupported window handle"), + }; + + let mut token = Token::root(); + self.surfaces + .register_identity(id_in, surface, &mut token) + } + pub fn enumerate_adapters(&self, inputs: AdapterInputs>) -> Vec { let instance = &self.instance; let mut token = Token::root();