From 9eaeb095d9cf1a9881e1e6f2c3d90f710d80102e Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Mon, 31 Jan 2022 22:21:44 +0100 Subject: [PATCH 01/36] [render] add transform, assets, input, window and render --- .cargo/config.toml | 8 + Cargo.toml | 18 + crates/assets-loader/CHANGELOG.md | 6 + crates/assets-loader/Cargo.toml | 25 + crates/assets-loader/README.md | 34 + crates/assets-loader/src/lib.rs | 145 ++++ crates/assets-loader/src/path.rs | 676 ++++++++++++++++++ crates/assets-loader/src/platform/android.rs | 57 ++ crates/assets-loader/src/platform/fs.rs | 102 +++ crates/assets-loader/src/platform/mod.rs | 31 + crates/assets-loader/src/platform/wasm.rs | 60 ++ crates/assets/CHANGELOG.md | 6 + crates/assets/Cargo.toml | 13 + crates/assets/README.md | 34 + crates/assets/src/lib.rs | 248 +++++++ crates/input/CHANGELOG.md | 6 + crates/input/Cargo.toml | 11 + crates/input/README.md | 34 + crates/input/src/lib.rs | 35 + crates/render-wgpu/CHANGELOG.md | 6 + crates/render-wgpu/Cargo.toml | 36 + crates/render-wgpu/README.md | 34 + .../render-wgpu/examples/render-wgpu-demo.rs | 71 ++ crates/render-wgpu/src/backend.rs | 32 + crates/render-wgpu/src/convert.rs | 314 ++++++++ crates/render-wgpu/src/lib.rs | 293 ++++++++ crates/render-wgpu/src/surface.rs | 98 +++ crates/render/CHANGELOG.md | 6 + crates/render/Cargo.toml | 27 + crates/render/README.md | 34 + crates/render/macros/Cargo.toml | 25 + crates/render/macros/src/binding_layout.rs | 247 +++++++ crates/render/macros/src/compile_shader.rs | 24 + crates/render/macros/src/lib.rs | 35 + crates/render/macros/src/utils.rs | 24 + crates/render/src/backend.rs | 28 + crates/render/src/buffer.rs | 40 ++ crates/render/src/camera.rs | 314 ++++++++ crates/render/src/lib.rs | 77 ++ crates/render/src/mesh/mod.rs | 137 ++++ crates/render/src/pipeline.rs | 78 ++ crates/render/src/shader/mod.rs | 102 +++ crates/render/src/shader/preprocessor.rs | 279 ++++++++ crates/render/src/texture/descriptor.rs | 296 ++++++++ crates/render/src/texture/image.rs | 154 ++++ crates/render/src/texture/mod.rs | 6 + crates/render/src/view.rs | 76 ++ crates/transform/CHANGELOG.md | 6 + crates/transform/Cargo.toml | 14 + crates/transform/README.md | 34 + crates/transform/src/components/mod.rs | 5 + crates/transform/src/components/parent.rs | 18 + crates/transform/src/components/transform.rs | 176 +++++ crates/transform/src/lib.rs | 37 + crates/window-winit/CHANGELOG.md | 6 + crates/window-winit/Cargo.toml | 32 + crates/window-winit/README.md | 34 + .../examples/window-winit-demo.rs | 57 ++ crates/window-winit/src/lib.rs | 448 ++++++++++++ crates/window/CHANGELOG.md | 6 + crates/window/Cargo.toml | 15 + crates/window/README.md | 34 + crates/window/src/event.rs | 21 + crates/window/src/lib.rs | 38 + crates/window/src/window.rs | 159 ++++ run-wasm/Cargo.toml | 11 + run-wasm/src/main.rs | 3 + 67 files changed, 5596 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 crates/assets-loader/CHANGELOG.md create mode 100644 crates/assets-loader/Cargo.toml create mode 100644 crates/assets-loader/README.md create mode 100644 crates/assets-loader/src/lib.rs create mode 100644 crates/assets-loader/src/path.rs create mode 100644 crates/assets-loader/src/platform/android.rs create mode 100644 crates/assets-loader/src/platform/fs.rs create mode 100644 crates/assets-loader/src/platform/mod.rs create mode 100644 crates/assets-loader/src/platform/wasm.rs create mode 100644 crates/assets/CHANGELOG.md create mode 100644 crates/assets/Cargo.toml create mode 100644 crates/assets/README.md create mode 100644 crates/assets/src/lib.rs create mode 100644 crates/input/CHANGELOG.md create mode 100644 crates/input/Cargo.toml create mode 100644 crates/input/README.md create mode 100644 crates/input/src/lib.rs create mode 100644 crates/render-wgpu/CHANGELOG.md create mode 100644 crates/render-wgpu/Cargo.toml create mode 100644 crates/render-wgpu/README.md create mode 100644 crates/render-wgpu/examples/render-wgpu-demo.rs create mode 100644 crates/render-wgpu/src/backend.rs create mode 100644 crates/render-wgpu/src/convert.rs create mode 100644 crates/render-wgpu/src/lib.rs create mode 100644 crates/render-wgpu/src/surface.rs create mode 100644 crates/render/CHANGELOG.md create mode 100644 crates/render/Cargo.toml create mode 100644 crates/render/README.md create mode 100644 crates/render/macros/Cargo.toml create mode 100644 crates/render/macros/src/binding_layout.rs create mode 100644 crates/render/macros/src/compile_shader.rs create mode 100644 crates/render/macros/src/lib.rs create mode 100644 crates/render/macros/src/utils.rs create mode 100644 crates/render/src/backend.rs create mode 100644 crates/render/src/buffer.rs create mode 100644 crates/render/src/camera.rs create mode 100644 crates/render/src/lib.rs create mode 100644 crates/render/src/mesh/mod.rs create mode 100644 crates/render/src/pipeline.rs create mode 100644 crates/render/src/shader/mod.rs create mode 100644 crates/render/src/shader/preprocessor.rs create mode 100644 crates/render/src/texture/descriptor.rs create mode 100644 crates/render/src/texture/image.rs create mode 100644 crates/render/src/texture/mod.rs create mode 100644 crates/render/src/view.rs create mode 100644 crates/transform/CHANGELOG.md create mode 100644 crates/transform/Cargo.toml create mode 100644 crates/transform/README.md create mode 100644 crates/transform/src/components/mod.rs create mode 100644 crates/transform/src/components/parent.rs create mode 100644 crates/transform/src/components/transform.rs create mode 100644 crates/transform/src/lib.rs create mode 100644 crates/window-winit/CHANGELOG.md create mode 100644 crates/window-winit/Cargo.toml create mode 100644 crates/window-winit/README.md create mode 100644 crates/window-winit/examples/window-winit-demo.rs create mode 100644 crates/window-winit/src/lib.rs create mode 100644 crates/window/CHANGELOG.md create mode 100644 crates/window/Cargo.toml create mode 100644 crates/window/README.md create mode 100644 crates/window/src/event.rs create mode 100644 crates/window/src/lib.rs create mode 100644 crates/window/src/window.rs create mode 100644 run-wasm/Cargo.toml create mode 100644 run-wasm/src/main.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..586c1ed --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,8 @@ + +[target.wasm32-unknown-unknown] +rustflags = [ + "--cfg=web_sys_unstable_apis" +] + +[alias] +run-wasm = "run --release --package run-wasm --" diff --git a/Cargo.toml b/Cargo.toml index aae69eb..c171b6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "crates/*", "crates/*/macros", + "run-wasm", "benches" ] default-members = [ @@ -18,11 +19,28 @@ edition = "2024" rust-version = "1.86" [workspace.dependencies] +glam = "0.22" +serde = "1.0" +raw-window-handle = "0.5" slotmap = "1.0" +typemap = "0.3" +anyhow = "1.0" +thiserror = "1.0" +tracing = "0.1" +bitflags = "1.3" fnv = "1.0" +radsort = "0.1" +bytemuck = "1.12" +downcast-rs = "1.2" +ambassador = "0.3" +blocking = "1.3" threadpool = "1.8" backtrace = "0.3" atomic_refcell = "0.1" +palette = { version = "0.6", default-features = false } +image = { version = "0.24", default-features = false } +encase = { version = "0.4", features = ["glam"], default-features = false } +encase_derive_impl = { version = "0.4" } crossbeam-utils = "0.8" darling = "0.20" proc-macro2 = "1.0" diff --git a/crates/assets-loader/CHANGELOG.md b/crates/assets-loader/CHANGELOG.md new file mode 100644 index 0000000..fa542aa --- /dev/null +++ b/crates/assets-loader/CHANGELOG.md @@ -0,0 +1,6 @@ +# `pulz-assets-loader` Changelog +All notable changes to this crate will be documented in this file. + +## Unreleased (DATE) + + * Initial version diff --git a/crates/assets-loader/Cargo.toml b/crates/assets-loader/Cargo.toml new file mode 100644 index 0000000..e43bf53 --- /dev/null +++ b/crates/assets-loader/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "pulz-assets-loader" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true +readme = "README.md" + +[dependencies] +pulz-ecs = { path = "../ecs" } +pulz-assets = { path = "../assets" } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +blocking = { workspace = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = { workspace = true } +wasm-bindgen-futures = { workspace = true } +js-sys = { workspace = true } +web-sys = { workspace = true, features = [ 'Request', 'Response', "Window", ]} + +[target.'cfg(target_os = "android")'.dependencies] +ndk = { workspace = true } +ndk-glue = { workspace = true } diff --git a/crates/assets-loader/README.md b/crates/assets-loader/README.md new file mode 100644 index 0000000..ad064fe --- /dev/null +++ b/crates/assets-loader/README.md @@ -0,0 +1,34 @@ +# `pulz-assets-loader` + + + +[![Crates.io](https://img.shields.io/crates/v/pulz-assets-loader.svg?label=pulz-assets-loader)](https://crates.io/crates/pulz-assets-loader) +[![docs.rs](https://docs.rs/pulz-assets-loader/badge.svg)](https://docs.rs/pulz-assets-loader/) +[![license: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](#license) +[![Rust CI](https://github.com/HellButcher/pulz/actions/workflows/rust.yml/badge.svg)](https://github.com/HellButcher/pulz/actions/workflows/rust.yml) + + +**TODO** + +## Example + + +**TODO** + +## License + +[license]: #license + +This project is licensed under either of + +* MIT license ([LICENSE-MIT] or ) +* Apache License, Version 2.0, ([LICENSE-APACHE] or ) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[LICENSE-MIT]: ../../LICENSE-MIT +[LICENSE-APACHE]: ../../LICENSE-APACHE diff --git a/crates/assets-loader/src/lib.rs b/crates/assets-loader/src/lib.rs new file mode 100644 index 0000000..cb5c585 --- /dev/null +++ b/crates/assets-loader/src/lib.rs @@ -0,0 +1,145 @@ +#![warn( + // missing_docs, + // rustdoc::missing_doc_code_examples, + future_incompatible, + rust_2018_idioms, + unused, + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_qualifications, + unused_crate_dependencies, + clippy::cargo, + clippy::multiple_crate_versions, + clippy::empty_line_after_outer_attr, + clippy::fallible_impl_from, + clippy::redundant_pub_crate, + clippy::use_self, + clippy::suspicious_operation_groupings, + clippy::useless_let_if_seq, + // clippy::missing_errors_doc, + // clippy::missing_panics_doc, + clippy::wildcard_imports +)] +#![doc(html_logo_url = "https://raw.githubusercontent.com/HellButcher/pulz/master/docs/logo.png")] +#![doc(html_no_source)] +#![doc = include_str!("../README.md")] + +use std::{future::Future, io::Cursor}; + +use path::{AssetPath, AssetPathBuf}; +use platform::AssetOpen; + +pub mod path; +pub mod platform; + +pub trait LoadAsset { + type Future: Future>; + type Error: From; + fn load(&self, load: Load<'_>) -> Self::Future; +} + +impl LoadAsset for T +where + T: Fn(Load<'_>) -> F, + F: Future>, + E: From, +{ + type Future = F; + type Error = E; + fn load(&self, load: Load<'_>) -> Self::Future { + self(load) + } +} + +pub struct Load<'a> { + buffer: Cursor>, + path: AssetPathBuf, + server: &'a AssetServer, +} + +impl Load<'_> { + #[inline] + pub fn path(&self) -> &AssetPath { + &self.path + } + + #[inline] + pub fn into_vec(self) -> Vec { + self.buffer.into_inner() + } + + #[inline] + pub fn cursor_mut(&mut self) -> &'_ mut Cursor> { + &mut self.buffer + } + + #[inline] + pub fn as_slice(&self) -> &'_ [u8] { + self.buffer.get_ref() + } + + #[inline] + pub async fn load(&self, path: impl AsRef) -> std::io::Result> { + self._load(path.as_ref()).await + } + async fn _load(&self, path: &AssetPath) -> std::io::Result> { + let full_path = if self.path.is_directory() { + self.path.join(path) + } else if let Some(parent) = self.path.parent() { + parent.join(path) + } else { + let mut p = AssetPathBuf::from("/"); + p.push(path); + p + }; + self.server._load(full_path).await + } +} + +impl AsRef<[u8]> for Load<'_> { + #[inline] + fn as_ref(&self) -> &[u8] { + self.as_slice() + } +} + +pub struct AssetServer { + io: Box, +} + +impl AssetServer { + pub fn new() -> Self { + Self::with(platform::default_platform_io()) + } + #[inline] + pub fn with(io: impl AssetOpen + 'static) -> Self { + Self { io: Box::new(io) } + } +} + +impl AssetServer { + pub async fn load_with>( + &self, + path: impl AsRef, + loader: L, + ) -> Result { + let load = self.load(path).await?; + loader.load(load).await + } + + pub async fn load(&self, path: impl AsRef) -> std::io::Result> { + let mut abs_base = AssetPathBuf::from("/"); + abs_base.push(path); + self._load(abs_base).await + } + + async fn _load(&self, path: AssetPathBuf) -> std::io::Result> { + let buffer = self.io.load(&path).await?; + Ok(Load { + buffer: Cursor::new(buffer), + path, + server: self, + }) + } +} diff --git a/crates/assets-loader/src/path.rs b/crates/assets-loader/src/path.rs new file mode 100644 index 0000000..340096d --- /dev/null +++ b/crates/assets-loader/src/path.rs @@ -0,0 +1,676 @@ +pub use std::path::Path; +use std::{ + borrow::{Borrow, Cow}, + iter::FusedIterator, + ops::Deref, + str::FromStr, +}; + +#[repr(transparent)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct AssetPathBuf(String); + +#[repr(transparent)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct AssetPath(str); + +impl AssetPathBuf { + #[inline] + pub const fn new() -> Self { + Self(String::new()) + } + + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + Self(String::with_capacity(capacity)) + } + + pub fn as_path(&self) -> &AssetPath { + self + } + + pub fn push>(&mut self, path: P) { + self._push(path.as_ref()) + } + + fn _push(&mut self, path: &AssetPath) { + if path.0.is_empty() { + return; + } + // absolute `path` replaces `self` + if path.is_absolute() { + self.0.truncate(0); + } else { + // trim fragment + if let Some(pos) = self.0.find('#') { + self.0.truncate(pos); + } + + // push missing seperator + if !self.0.is_empty() && !self.0.ends_with(AssetPath::is_seperator) { + self.0.push('/'); + } + } + self.0.reserve(path.0.len()); + for comp in path.components() { + match comp { + Component::RootDir => { + self.0.truncate(0); + self.0.push('/'); + } + Component::ParentDir => { + if !self.pop() && self.0.is_empty() { + self.0.push_str("../"); + } + } + Component::File(f) => self.0.push_str(f), + Component::Directory(f) => { + self.0.push_str(f); + self.0.push('/'); + } + Component::Fragment(f) => { + self.0.push('#'); + self.0.push_str(f); + } + } + } + } + + pub fn pop(&mut self) -> bool { + let Some(parent) = self.parent() else { + return false; + }; + let len = parent.0.trim_end_matches(AssetPath::is_seperator).len(); + self.0.truncate(len + 1); + true + } + + pub fn set_file_name>(&mut self, file_name: S) { + self._set_file_name(file_name.as_ref()) + } + + fn _set_file_name(&mut self, file_name: &str) { + if self.file_name().is_some() { + let popped = self.pop(); + debug_assert!(popped); + } + self.push(file_name); + } + + pub fn set_fragment>(&mut self, fragment: S) { + self._set_fragment(fragment.as_ref()) + } + + fn _set_fragment(&mut self, fragment: &str) { + if let Some(pos) = self.0.find('#') { + self.0.truncate(pos + 1); + } else { + self.0.push('#'); + } + self.0.push_str(fragment); + } + + #[inline] + pub fn into_string(self) -> String { + self.0 + } + + #[inline] + pub fn capacity(&self) -> usize { + self.0.capacity() + } + + #[inline] + pub fn clear(&mut self) { + self.0.clear() + } + + #[inline] + pub fn reserve(&mut self, additional: usize) { + self.0.reserve(additional) + } + + #[inline] + pub fn reserve_exact(&mut self, additional: usize) { + self.0.reserve_exact(additional) + } + + #[inline] + pub fn shrink_to_fit(&mut self) { + self.0.shrink_to_fit() + } +} + +impl Deref for AssetPathBuf { + type Target = AssetPath; + #[inline] + fn deref(&self) -> &Self::Target { + AssetPath::new(&self.0) + } +} + +impl> From<&T> for AssetPathBuf { + #[inline] + fn from(s: &T) -> Self { + Self::from(s.as_ref().to_string()) + } +} + +impl From for AssetPathBuf { + #[inline] + fn from(s: String) -> Self { + Self(s) + } +} +impl From for String { + #[inline] + fn from(path_buf: AssetPathBuf) -> Self { + path_buf.0 + } +} +impl FromStr for AssetPathBuf { + type Err = core::convert::Infallible; + + #[inline] + fn from_str(s: &str) -> Result { + Ok(Self::from(s)) + } +} + +impl Borrow for AssetPathBuf { + #[inline] + fn borrow(&self) -> &AssetPath { + self.deref() + } +} + +impl Default for AssetPathBuf { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl AssetPath { + fn is_seperator(c: char) -> bool { + c == '/' || c == '\\' + } + + pub fn new + ?Sized>(s: &S) -> &Self { + let s: *const str = s.as_ref(); + unsafe { &*(s as *const Self) } + } + + #[inline] + pub fn as_str(&self) -> &str { + &self.0 + } + + pub fn to_path_buf(&self) -> AssetPathBuf { + AssetPathBuf::from(self.0.to_string()) + } + + #[inline] + pub fn is_absolute(&self) -> bool { + self.0.starts_with(Self::is_seperator) + } + + #[inline] + pub fn is_relative(&self) -> bool { + !self.is_absolute() + } + + #[inline] + pub fn is_directory(&self) -> bool { + let path = if let Some(pos) = self.0.find('#') { + &self.0[..pos] + } else { + &self.0 + }; + path.ends_with(Self::is_seperator) + } + + #[inline] + fn split_fragment(&self) -> (&str, Option<&str>) { + if let Some(pos) = self.0.find('#') { + (&self.0[..pos], Some(&self.0[pos + 1..])) + } else { + (&self.0, None) + } + } + + #[inline] + fn split_parent_fragment(&self) -> (Option<&str>, &str, Option<&str>) { + let (path, frag) = self.split_fragment(); + if let Some(pos) = path + .trim_end_matches(Self::is_seperator) + .rfind(Self::is_seperator) + { + let parent = &path[..pos + 1]; + (Some(parent), &path[parent.len()..], frag) + } else { + (None, path, frag) + } + } + + pub fn parent(&self) -> Option<&Self> { + let (parent, _file, _frag) = self.split_parent_fragment(); + parent.map(Self::new) + } + + pub fn file_name(&self) -> Option<&str> { + let (_parent, file, _frag) = self.split_parent_fragment(); + let file = file.trim_end_matches(Self::is_seperator); + if file.is_empty() { + None + } else { + Some(file) + } + } + + #[inline] + fn split_parent_file_extension_fragment( + &self, + ) -> (Option<&str>, Option<&str>, Option<&str>, Option<&str>) { + let (parent, file, frag) = self.split_parent_fragment(); + if file.ends_with(Self::is_seperator) { + let filename = file.trim_end_matches(Self::is_seperator); + if filename.is_empty() { + (parent, None, None, frag) + } else { + (parent, Some(filename), None, frag) + } + } else if file.starts_with('.') { + (parent, Some(file), None, frag) + } else if let Some(pos) = file.find('.') { + (parent, Some(&file[..pos]), Some(&file[pos..]), frag) + } else { + (parent, Some(file), None, frag) + } + } + + pub fn file_stem(&self) -> Option<&str> { + let (_, stem, _, _) = self.split_parent_file_extension_fragment(); + stem + } + + pub fn extension(&self) -> Option<&str> { + let (_, _, ext, _) = self.split_parent_file_extension_fragment(); + ext + } + + pub fn fragment(&self) -> Option<&str> { + let (_path, frag) = self.split_fragment(); + frag + } + + pub fn components(&self) -> Components<'_> { + Components { + path: &self.0, + state: State::Start, + } + } + + pub fn normalize(&self) -> AssetPathBuf { + let mut res = AssetPathBuf::new(); + res.push(self); + res + } + + pub fn iter(&self) -> Iter<'_> { + Iter(self.components()) + } + + pub fn join>(&self, path: P) -> AssetPathBuf { + self._join(path.as_ref()) + } + + fn _join(&self, path: &Self) -> AssetPathBuf { + let mut buf = if path.is_absolute() { + AssetPathBuf::new() + } else { + self.to_path_buf() + }; + buf.push(path); + buf + } + + pub fn with_file_name>(&self, file_name: S) -> AssetPathBuf { + self._with_file_name(file_name.as_ref()) + } + + fn _with_file_name(&self, file_name: &str) -> AssetPathBuf { + let mut buf = self.to_path_buf(); + buf.set_file_name(file_name); + buf + } + + pub fn with_fragment>(&self, fragment: S) -> AssetPathBuf { + self._with_fragment(fragment.as_ref()) + } + + fn _with_fragment(&self, fragment: &str) -> AssetPathBuf { + let mut buf = self.to_path_buf(); + buf.set_fragment(fragment); + buf + } +} + +impl std::fmt::Debug for AssetPath { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(&self.0, formatter) + } +} + +impl std::fmt::Display for AssetPath { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, formatter) + } +} + +impl ToOwned for AssetPath { + type Owned = AssetPathBuf; + #[inline] + fn to_owned(&self) -> Self::Owned { + self.to_path_buf() + } +} + +impl AsRef for AssetPath { + #[inline] + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl AsRef for AssetPath { + #[inline] + fn as_ref(&self) -> &Path { + self.0.as_ref() + } +} + +impl AsRef for AssetPath { + #[inline] + fn as_ref(&self) -> &Self { + self + } +} + +impl AsRef for AssetPathBuf { + #[inline] + fn as_ref(&self) -> &AssetPath { + self + } +} + +impl AsRef for AssetPathBuf { + #[inline] + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl AsRef for AssetPathBuf { + #[inline] + fn as_ref(&self) -> &Path { + self.0.as_ref() + } +} + +impl AsRef for str { + #[inline] + fn as_ref(&self) -> &AssetPath { + AssetPath::new(self) + } +} + +impl AsRef for Cow<'_, str> { + #[inline] + fn as_ref(&self) -> &AssetPath { + AssetPath::new(self) + } +} +impl AsRef for String { + #[inline] + fn as_ref(&self) -> &AssetPath { + AssetPath::new(self) + } +} + +impl<'a> IntoIterator for &'a AssetPathBuf { + type Item = &'a str; + type IntoIter = Iter<'a>; + #[inline] + fn into_iter(self) -> Iter<'a> { + self.iter() + } +} + +impl<'a> IntoIterator for &'a AssetPath { + type Item = &'a str; + type IntoIter = Iter<'a>; + #[inline] + fn into_iter(self) -> Iter<'a> { + self.iter() + } +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum Component<'a> { + RootDir, + ParentDir, + Directory(&'a str), + File(&'a str), + Fragment(&'a str), +} + +impl<'a> Component<'a> { + pub fn as_str(self) -> &'a str { + match self { + Component::RootDir => "/", + Component::ParentDir => "..", + Component::Directory(s) | Component::File(s) | Component::Fragment(s) => s, + } + } + + fn from_str(comp: &'a str, is_dir: bool) -> Option { + if comp.starts_with('#') { + Some(Component::Fragment(&comp[1..])) + } else if comp == ".." { + Some(Component::ParentDir) + } else if !comp.is_empty() && comp != "." { + if is_dir { + Some(Component::Directory(comp)) + } else { + Some(Component::File(comp)) + } + } else { + None + } + } +} + +impl AsRef for Component<'_> { + #[inline] + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl AsRef for Component<'_> { + #[inline] + fn as_ref(&self) -> &AssetPath { + self.as_str().as_ref() + } +} + +#[derive(Clone)] +pub struct Components<'a> { + // The path left to parse components from + path: &'a str, + + state: State, +} + +#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] +enum State { + Start = 1, // / or . or nothing + Body = 2, // foo/bar/baz + Done = 4, +} + +impl<'a> Components<'a> { + pub fn as_path(&self) -> &'a AssetPath { + AssetPath::new(self.path) + } +} + +impl AsRef for Components<'_> { + #[inline] + fn as_ref(&self) -> &AssetPath { + self.as_path() + } +} + +impl AsRef for Components<'_> { + #[inline] + fn as_ref(&self) -> &str { + self.as_path().as_str() + } +} + +impl<'a> Iterator for Components<'a> { + type Item = Component<'a>; + + fn next(&mut self) -> Option> { + loop { + match self.state { + State::Done => return None, + State::Start => { + self.state = State::Body; + if self.path.starts_with(AssetPath::is_seperator) { + self.path = self.path.trim_start_matches(AssetPath::is_seperator); + return Some(Component::RootDir); + } + } + State::Body => { + if self.path.is_empty() { + self.state = State::Done; + return None; + } + if self.path.starts_with('#') { + self.state = State::Done; + return Some(Component::Fragment(&self.path[1..])); + } + let mut is_dir = false; + let comp; + if let Some(pos) = self.path.find(|c| AssetPath::is_seperator(c) || c == '#') { + comp = &self.path[..pos]; + self.path = &self.path[pos..]; + if self.path.starts_with(AssetPath::is_seperator) { + is_dir = true; + self.path = self.path.trim_start_matches(AssetPath::is_seperator); + } + } else { + // last component + self.state = State::Done; + comp = self.path; + } + if let Some(comp) = Component::from_str(comp, is_dir) { + return Some(comp); + } + } + } + } + } +} + +impl<'a> DoubleEndedIterator for Components<'a> { + fn next_back(&mut self) -> Option> { + loop { + match self.state { + State::Done => return None, + State::Start => { + self.state = State::Body; + if let Some(pos) = self.path.find('#') { + let frag = &self.path[pos + 1..]; + self.path = &self.path[..pos]; + return Some(Component::Fragment(frag)); + } + } + State::Body => { + let mut is_dir = false; + if self.path.ends_with(AssetPath::is_seperator) { + self.path = self.path.trim_end_matches(AssetPath::is_seperator); + is_dir = true; + } + if self.path.is_empty() { + self.state = State::Done; + if is_dir { + return Some(Component::RootDir); + } else { + return None; + } + } + + let comp; + if let Some(pos) = self.path.rfind(AssetPath::is_seperator) { + comp = &self.path[pos + 1..]; + self.path = &self.path[..pos + 1]; + } else { + // last component + comp = self.path; + self.state = State::Done; + } + if let Some(comp) = Component::from_str(comp, is_dir) { + return Some(comp); + } + } + } + } + } +} + +impl FusedIterator for Components<'_> {} + +#[derive(Clone)] +pub struct Iter<'a>(Components<'a>); + +impl<'a> Iter<'a> { + #[inline] + pub fn as_path(&self) -> &'a AssetPath { + self.0.as_path() + } +} + +impl AsRef for Iter<'_> { + #[inline] + fn as_ref(&self) -> &AssetPath { + self.as_path() + } +} + +impl AsRef for Iter<'_> { + #[inline] + fn as_ref(&self) -> &str { + self.as_path().as_str() + } +} + +impl<'a> Iterator for Iter<'a> { + type Item = &'a str; + #[inline] + fn next(&mut self) -> Option { + self.0.next().map(Component::as_str) + } +} + +impl<'a> DoubleEndedIterator for Iter<'a> { + #[inline] + fn next_back(&mut self) -> Option { + self.0.next_back().map(Component::as_str) + } +} + +impl FusedIterator for Iter<'_> {} diff --git a/crates/assets-loader/src/platform/android.rs b/crates/assets-loader/src/platform/android.rs new file mode 100644 index 0000000..df8aefa --- /dev/null +++ b/crates/assets-loader/src/platform/android.rs @@ -0,0 +1,57 @@ +use ndk::asset::Asset as AndroidAsset; + +pub struct AndroidAssetLoaderIo { + base: Cow<'static, str>, +} + +impl AndroidAssetLoaderIo { + pub const fn new() -> Self { + Self { + base: Cow::Borrowed(""), + } + } + + #[inline] + pub fn with_base(mut self, base: impl Into>) -> Self { + self.set_base(base); + self + } + + #[inline] + pub fn set_base(&mut self, base: impl Into>) -> &mut Self { + self.base = base.into(); + self + } + + fn resolve_path(&self, asset_path: &AssetPath) -> String { + // make absolute & normalize + let norm_asset_path = AssetPathBuf::with_capacity(asset_path.as_str().len() + 1); + norm_asset_path.push("/"); + norm_asset_path.push(asset_path); + + let base = self.base.trim_end_matches('/'); + let result = String::with_capacity(base.len() + asset_path.len()); + result.push_str(base); + result.push_str(&norm_asset_path); + result + } + + fn open(&self, resolved_path: String) -> std::io::Result> { + let resolved_path = CString::new(resolved_path.into_bytes()).ok()?; + + let asset = ndk_glue::native_activity() + .asset_manager() + .open(&full_path) + .ok_or(std::io::ErrorKind::NotFound)?; + let mut buf = Vec::with_capacity(len as usize); + file.read_to_end(&mut buf)?; + Ok(buf) + } +} + +impl AssetOpen for AndroidAssetLoaderIo { + fn load(&self, asset_path: &AssetPath) -> BoxedFuture<'_, std::io::Result>> { + let resolved_path = self.resolve_path(asset_path); + Box::pin(blocking::unblock(move || self.open(resolved_path))) + } +} diff --git a/crates/assets-loader/src/platform/fs.rs b/crates/assets-loader/src/platform/fs.rs new file mode 100644 index 0000000..b8f9333 --- /dev/null +++ b/crates/assets-loader/src/platform/fs.rs @@ -0,0 +1,102 @@ +use std::{ + fs::{File, Metadata}, + io::Read, + path::PathBuf, +}; + +use super::{AssetOpen, BoxedFuture}; +use crate::path::{AssetPath, Component}; + +pub struct FileSystemAssetLoaderIo { + dirs: Vec, +} + +fn join_path(base_path: &mut PathBuf, asset_path: &AssetPath) { + // TODO: check for invalid characters in path components (except fragment) ([a-zA-Z0-9._-]) + base_path.reserve_exact(asset_path.as_str().len()); + let mut num_comps = 0; // count components to not escape from `base_path` + for comp in asset_path.components() { + match comp { + Component::Directory(dir) | Component::File(dir) => { + num_comps += 1; + base_path.push(dir); + } + Component::ParentDir => { + if num_comps > 0 { + num_comps -= 1; + base_path.pop(); + } + } + Component::RootDir | Component::Fragment(_) => {} + } + } +} + +impl FileSystemAssetLoaderIo { + pub const fn new() -> Self { + Self { dirs: Vec::new() } + } + + #[inline] + pub fn with_directory(mut self, path: impl Into) -> Self { + self._push_directory(path.into()); + self + } + + #[inline] + pub fn push_directory(&mut self, path: impl Into) -> &mut Self { + self._push_directory(path.into()); + self + } + + fn _push_directory(&mut self, path: PathBuf) { + self.dirs.push(path); + } + + async fn resolve_path(&self, asset_path: &AssetPath) -> Option<(PathBuf, Metadata)> { + for base in &self.dirs { + let mut path = base.to_owned(); + join_path(&mut path, asset_path); + if let Some(r) = blocking::unblock(move || { + let metadata = path.metadata().ok()?; + Some((path, metadata)) + }) + .await + { + return Some(r); + } + } + None + } + + async fn open(&self, asset_path: &AssetPath) -> std::io::Result> { + let (full_path, metadata) = self + .resolve_path(asset_path) + .await + .ok_or(std::io::ErrorKind::NotFound)?; + let len = metadata.len(); + if len >= usize::MAX as u64 { + // TODO: smaller max file size? + // TODO: use this when stabelized + // return Err(std::io::ErrorKind::FileTooLarge.into()); + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "file to large", + )); + } + blocking::unblock(move || { + let mut file = File::open(full_path)?; + let mut buf = Vec::with_capacity(len as usize); + file.read_to_end(&mut buf)?; + Ok(buf) + }) + .await + } +} + +impl AssetOpen for FileSystemAssetLoaderIo { + fn load(&self, asset_path: &AssetPath) -> BoxedFuture<'_, std::io::Result>> { + let asset_path = asset_path.to_owned(); + Box::pin(async move { self.open(&asset_path).await }) + } +} diff --git a/crates/assets-loader/src/platform/mod.rs b/crates/assets-loader/src/platform/mod.rs new file mode 100644 index 0000000..1484bcf --- /dev/null +++ b/crates/assets-loader/src/platform/mod.rs @@ -0,0 +1,31 @@ +use std::{future::Future, pin::Pin}; + +use crate::path::AssetPath; + +#[cfg(not(target_arch = "wasm32"))] +pub mod fs; + +#[cfg(target_os = "android")] +pub mod android; + +#[cfg(target_arch = "wasm32")] +pub mod wasm; + +type BoxedFuture<'l, O> = Pin + Send + Sync + 'l>>; + +pub trait AssetOpen { + fn load(&self, asset_path: &AssetPath) -> BoxedFuture<'_, std::io::Result>>; +} + +pub fn default_platform_io() -> impl AssetOpen { + #[cfg(not(any(target_os = "android", target_arch = "wasm32")))] + let io = fs::FileSystemAssetLoaderIo::new(); + + #[cfg(target_os = "android")] + let io = android::AndroidAssetLoaderIo::new(); + + #[cfg(target_arch = "wasm32")] + let io = wasm::WasmFetchAssetLoaderIo::new(); + + io +} diff --git a/crates/assets-loader/src/platform/wasm.rs b/crates/assets-loader/src/platform/wasm.rs new file mode 100644 index 0000000..d46168a --- /dev/null +++ b/crates/assets-loader/src/platform/wasm.rs @@ -0,0 +1,60 @@ +use std::io::Cursor; + +use js_sys::Uint8Array; +use wasm_bindgen_futures::JsFuture; + +pub struct WasmFetchAssetLoaderIo { + base: Cow<'static, str>, +} + +impl WasmFetchAssetLoaderIo { + pub const fn new() -> Self { + Self { + base: Cow::Borrowed("."), + } + } + + #[inline] + pub fn with_base(mut self, base: impl Into>) -> Self { + self.set_base(base); + self + } + + #[inline] + pub fn set_base(&mut self, base: impl Into>) -> &mut Self { + self.base = base.into(); + self + } + + fn resolve_url(&self, asset_path: &AssetPath) -> String { + // make absolute & normalize + let norm_asset_path = AssetPathBuf::with_capacity(asset_path.as_str().len() + 1); + norm_asset_path.push("/"); + norm_asset_path.push(asset_path); + + let base = self.base.trim_end_matches('/'); + let result = String::with_capacity(base.len() + asset_path.len()); + result.push_str(base); + result.push_str(&norm_asset_path); + result + } + + async fn open(&self, resolved_url: &str) -> std::io::Result> { + let window = web_sys::window().ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::Other, "window not available") + })?; + let resp: Response = JsFuture::from(window.fetch_with_str(resolved_url)) + .await? + .dyn_into()?; + let buffer = JsFuture::from(resp.array_buffer()?).await?; + let data = Uint8Array::new(&buffer).to_vec(); + Ok(data) + } +} + +impl AssetOpen for WasmFetchAssetLoaderIo { + fn open(&self, asset_path: AssetPathBuf) -> BoxedFuture<'_, std::io::Result>> { + let resolved_url = self.resolve_url(asset_path); + Box::pin(blocking::unblock(move || self.open(&resolved_url))) + } +} diff --git a/crates/assets/CHANGELOG.md b/crates/assets/CHANGELOG.md new file mode 100644 index 0000000..852b81f --- /dev/null +++ b/crates/assets/CHANGELOG.md @@ -0,0 +1,6 @@ +# `pulz-assets` Changelog +All notable changes to this crate will be documented in this file. + +## Unreleased (DATE) + + * Initial version diff --git a/crates/assets/Cargo.toml b/crates/assets/Cargo.toml new file mode 100644 index 0000000..8ed52f1 --- /dev/null +++ b/crates/assets/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "pulz-assets" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true +readme = "README.md" + +[dependencies] +pulz-schedule = { path = "../schedule" } + +slotmap = { workspace = true } diff --git a/crates/assets/README.md b/crates/assets/README.md new file mode 100644 index 0000000..6c2cb1e --- /dev/null +++ b/crates/assets/README.md @@ -0,0 +1,34 @@ +# `pulz-assets` + + + +[![Crates.io](https://img.shields.io/crates/v/pulz-assets.svg?label=pulz-assets)](https://crates.io/crates/pulz-assets) +[![docs.rs](https://docs.rs/pulz-assets/badge.svg)](https://docs.rs/pulz-assets/) +[![license: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](#license) +[![Rust CI](https://github.com/HellButcher/pulz/actions/workflows/rust.yml/badge.svg)](https://github.com/HellButcher/pulz/actions/workflows/rust.yml) + + +**TODO** + +## Example + + +**TODO** + +## License + +[license]: #license + +This project is licensed under either of + +* MIT license ([LICENSE-MIT] or ) +* Apache License, Version 2.0, ([LICENSE-APACHE] or ) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[LICENSE-MIT]: ../../LICENSE-MIT +[LICENSE-APACHE]: ../../LICENSE-APACHE diff --git a/crates/assets/src/lib.rs b/crates/assets/src/lib.rs new file mode 100644 index 0000000..71682d3 --- /dev/null +++ b/crates/assets/src/lib.rs @@ -0,0 +1,248 @@ +#![warn( + // missing_docs, + // rustdoc::missing_doc_code_examples, + future_incompatible, + rust_2018_idioms, + unused, + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_qualifications, + unused_crate_dependencies, + clippy::cargo, + clippy::multiple_crate_versions, + clippy::empty_line_after_outer_attr, + clippy::fallible_impl_from, + clippy::redundant_pub_crate, + clippy::use_self, + clippy::suspicious_operation_groupings, + clippy::useless_let_if_seq, + // clippy::missing_errors_doc, + // clippy::missing_panics_doc, + clippy::wildcard_imports +)] +#![doc(html_logo_url = "https://raw.githubusercontent.com/HellButcher/pulz/master/docs/logo.png")] +#![doc(html_no_source)] +#![doc = include_str!("../README.md")] + +use std::{ + fmt::Debug, + hash::{Hash, Hasher}, + marker::PhantomData, +}; + +use pulz_schedule::{ + define_label_enum, + event::{EventWriter, Events}, + label::{CoreSystemPhase, SystemPhase}, + prelude::*, +}; +use slotmap::{Key, KeyData, SlotMap}; + +#[repr(transparent)] +pub struct Handle(KeyData, PhantomData T>); + +impl Copy for Handle {} +impl Clone for Handle { + #[inline] + fn clone(&self) -> Self { + Self(self.0, PhantomData) + } +} +impl Default for Handle { + #[inline] + fn default() -> Self { + Self(KeyData::default(), PhantomData) + } +} +impl Debug for Handle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple(&format!("Handle<{}>", ::std::any::type_name::())) + .field(&self.0) + .finish() + } +} +impl PartialEq for Handle { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} +impl Eq for Handle {} +impl PartialOrd for Handle { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} +impl Ord for Handle { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.cmp(&other.0) + } +} +impl Hash for Handle { + fn hash(&self, state: &mut H) { + self.0.hash(state) + } +} +impl From for Handle { + #[inline] + fn from(k: KeyData) -> Self { + Self(k, PhantomData) + } +} +unsafe impl Key for Handle { + #[inline] + fn data(&self) -> KeyData { + self.0 + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum AssetEvent { + Created(Handle), + Modified(Handle), + Removed(Handle), +} + +struct AssetEntry { + asset: T, + changed_since_last_update: bool, +} + +pub struct Assets { + map: SlotMap, AssetEntry>, + events: Vec>, +} + +impl Assets { + pub fn new() -> Self { + Self { + map: SlotMap::with_key(), + events: Vec::new(), + } + } + #[inline] + pub fn capacity(&self) -> usize { + self.map.capacity() + } + + #[inline] + pub fn reserve(&mut self, additional_capacity: usize) { + self.map.reserve(additional_capacity) + } + + #[inline] + pub fn len(&self) -> usize { + self.map.len() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.map.is_empty() + } + + pub fn clear(&mut self) { + for (handle, _) in self.map.drain() { + self.events.push(AssetEvent::Removed(handle)) + } + } + + #[inline] + pub fn insert(&mut self, asset: T) -> Handle { + let handle = self.map.insert(AssetEntry { + asset, + changed_since_last_update: true, + }); + self.events.push(AssetEvent::Created(handle)); + handle + } + + #[inline] + pub fn contains(&self, handle: Handle) -> bool { + self.map.contains_key(handle) + } + + #[inline] + pub fn get(&self, handle: Handle) -> Option<&T> { + Some(&self.map.get(handle)?.asset) + } + + #[inline] + pub fn get_mut(&mut self, handle: Handle) -> Option<&mut T> { + let entry = self.map.get_mut(handle)?; + if !entry.changed_since_last_update { + entry.changed_since_last_update = true; + self.events.push(AssetEvent::Modified(handle)); + } + Some(&mut entry.asset) + } + + #[inline] + pub fn remove(&mut self, handle: Handle) -> Option { + let entry = self.map.remove(handle)?; + self.events.push(AssetEvent::Removed(handle)); + Some(entry.asset) + } + + pub fn update(&mut self, mut events_writer: EventWriter<'_, AssetEvent>) { + for (_, entry) in self.map.iter_mut() { + entry.changed_since_last_update = false; + } + events_writer.send_batch(self.events.drain(..)) + } + + pub fn update_system( + mut assets: ResMut<'_, Self>, + events_writer: EventWriter<'_, AssetEvent>, + ) { + assets.update(events_writer) + } + + pub fn install_into(res: &mut Resources) + where + T: Send + Sync + 'static, + { + if res.try_init::().is_ok() { + Events::>::install_into(res); + let mut schedule = res.borrow_res_mut::().unwrap(); + // update assets after FIRST(events), and before UPDATE + schedule.add_phase_chain([ + AssetSystemPhase::LoadAssets.as_label(), + AssetSystemPhase::UpdateAssets.as_label(), + CoreSystemPhase::Update.as_label(), + ]); + schedule + .add_system(Self::update_system) + .into_phase(AssetSystemPhase::UpdateAssets); + } + } +} + +define_label_enum! { + pub enum AssetSystemPhase: SystemPhase { + LoadAssets, + UpdateAssets, + } +} + +impl Default for Assets { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl std::ops::Index> for Assets { + type Output = T; + #[inline] + fn index(&self, handle: Handle) -> &T { + self.get(handle).expect("invalid handle") + } +} + +impl std::ops::IndexMut> for Assets { + #[inline] + fn index_mut(&mut self, handle: Handle) -> &mut T { + self.get_mut(handle).expect("invalid handle") + } +} diff --git a/crates/input/CHANGELOG.md b/crates/input/CHANGELOG.md new file mode 100644 index 0000000..5ab2eec --- /dev/null +++ b/crates/input/CHANGELOG.md @@ -0,0 +1,6 @@ +# `pulz-input` Changelog +All notable changes to this crate will be documented in this file. + +## Unreleased (DATE) + + * Initial version diff --git a/crates/input/Cargo.toml b/crates/input/Cargo.toml new file mode 100644 index 0000000..65901b9 --- /dev/null +++ b/crates/input/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "pulz-input" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true +readme = "README.md" + +[dependencies] +pulz-ecs = { path = "../ecs" } diff --git a/crates/input/README.md b/crates/input/README.md new file mode 100644 index 0000000..417d95b --- /dev/null +++ b/crates/input/README.md @@ -0,0 +1,34 @@ +# `pulz-input` + + + +[![Crates.io](https://img.shields.io/crates/v/pulz-input.svg?label=pulz-input)](https://crates.io/crates/pulz-input) +[![docs.rs](https://docs.rs/pulz-input/badge.svg)](https://docs.rs/pulz-input/) +[![license: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](#license) +[![Rust CI](https://github.com/HellButcher/pulz/actions/workflows/rust.yml/badge.svg)](https://github.com/HellButcher/pulz/actions/workflows/rust.yml) + + +**TODO** + +## Example + + +**TODO** + +## License + +[license]: #license + +This project is licensed under either of + +* MIT license ([LICENSE-MIT] or ) +* Apache License, Version 2.0, ([LICENSE-APACHE] or ) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[LICENSE-MIT]: ../../LICENSE-MIT +[LICENSE-APACHE]: ../../LICENSE-APACHE diff --git a/crates/input/src/lib.rs b/crates/input/src/lib.rs new file mode 100644 index 0000000..c5cdfb4 --- /dev/null +++ b/crates/input/src/lib.rs @@ -0,0 +1,35 @@ +#![warn( + // missing_docs, + // rustdoc::missing_doc_code_examples, + future_incompatible, + rust_2018_idioms, + unused, + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_qualifications, + unused_crate_dependencies, + clippy::cargo, + clippy::multiple_crate_versions, + clippy::empty_line_after_outer_attr, + clippy::fallible_impl_from, + clippy::redundant_pub_crate, + clippy::use_self, + clippy::suspicious_operation_groupings, + clippy::useless_let_if_seq, + // clippy::missing_errors_doc, + // clippy::missing_panics_doc, + clippy::wildcard_imports +)] +#![doc(html_logo_url = "https://raw.githubusercontent.com/HellButcher/pulz/master/docs/logo.png")] +#![doc(html_no_source)] +#![doc = include_str!("../README.md")] + +#[cfg(test)] +mod tests { + + #[test] + fn it_works() { + assert_eq!(2, 1 + 1); + } +} diff --git a/crates/render-wgpu/CHANGELOG.md b/crates/render-wgpu/CHANGELOG.md new file mode 100644 index 0000000..79ef115 --- /dev/null +++ b/crates/render-wgpu/CHANGELOG.md @@ -0,0 +1,6 @@ +# `pulz-render-wgpu` Changelog +All notable changes to this crate will be documented in this file. + +## Unreleased (DATE) + + * Initial version diff --git a/crates/render-wgpu/Cargo.toml b/crates/render-wgpu/Cargo.toml new file mode 100644 index 0000000..c8c203b --- /dev/null +++ b/crates/render-wgpu/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "pulz-render-wgpu" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true +readme = "README.md" + +[dependencies] +pulz-ecs = { path = "../ecs" } +pulz-window = { path = "../window" } +pulz-render = { path = "../render" } + +thiserror = { workspace = true} +tracing = { workspace = true} +wgpu = "0.14" +raw-window-handle = { workspace = true, features = ["alloc"] } + +[dev-dependencies] +anyhow = { workspace = true} +naga = "0.10" +pulz-window-winit = { path = "../window-winit" } + +[target.'cfg(not(target_os = "unknown"))'.dev-dependencies] +tracing-subscriber = { workspace = true } +async-std = { workspace = true } + +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +wgpu = { version = "0.13" , features = ["webgl"] } +tracing-wasm = { workspace = true } +tracing-log = { workspace = true } +console_error_panic_hook = { workspace = true } +wasm-bindgen = { workspace = true } +web-sys = { workspace = true, features = ["HtmlCanvasElement"] } +wasm-bindgen-futures = { workspace = true } diff --git a/crates/render-wgpu/README.md b/crates/render-wgpu/README.md new file mode 100644 index 0000000..4d14d17 --- /dev/null +++ b/crates/render-wgpu/README.md @@ -0,0 +1,34 @@ +# `pulz-render-wgpu` + + + +[![Crates.io](https://img.shields.io/crates/v/pulz-render-wgpu.svg?label=pulz-render-wgpu)](https://crates.io/crates/pulz-render-wgpu) +[![docs.rs](https://docs.rs/pulz-render-wgpu/badge.svg)](https://docs.rs/pulz-render-wgpu/) +[![license: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](#license) +[![Rust CI](https://github.com/HellButcher/pulz/actions/workflows/rust.yml/badge.svg)](https://github.com/HellButcher/pulz/actions/workflows/rust.yml) + + +**TODO** + +## Example + + +**TODO** + +## License + +[license]: #license + +This project is licensed under either of + +* MIT license ([LICENSE-MIT] or ) +* Apache License, Version 2.0, ([LICENSE-APACHE] or ) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[LICENSE-MIT]: ../../LICENSE-MIT +[LICENSE-APACHE]: ../../LICENSE-APACHE diff --git a/crates/render-wgpu/examples/render-wgpu-demo.rs b/crates/render-wgpu/examples/render-wgpu-demo.rs new file mode 100644 index 0000000..1229678 --- /dev/null +++ b/crates/render-wgpu/examples/render-wgpu-demo.rs @@ -0,0 +1,71 @@ +use std::rc::Rc; + +use pulz_ecs::prelude::*; +use pulz_render_wgpu::WgpuRendererBuilder; +use pulz_window::WindowDescriptor; +use pulz_window_winit::{ + winit::{event_loop::EventLoop, window::Window}, + WinitWindowModule, WinitWindowSystem, +}; +use tracing::info; + +async fn init() -> (Resources, EventLoop<()>, Rc, WinitWindowSystem) { + info!("Initializing..."); + let mut resources = Resources::new(); + + let event_loop = EventLoop::new(); + let (window_system, window_id, window) = + WinitWindowModule::new(WindowDescriptor::default(), &event_loop) + .unwrap() + .install(&mut resources); + + // TODO: SAFETY + unsafe { + WgpuRendererBuilder::new() + .with_window(window_id) + .install(&mut resources) + .await + .unwrap() + }; + + (resources, event_loop, window, window_system) +} + +#[cfg(not(target_arch = "wasm32"))] +#[async_std::main] +async fn main() { + // todo: run blocking! + use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter}; + let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); + tracing_subscriber::fmt() + .with_env_filter(env_filter) + .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE) + .init(); + + let (resources, event_loop, _window, window_system) = init().await; + window_system.run(resources, event_loop); +} + +#[cfg(target_arch = "wasm32")] +fn main() { + use wasm_bindgen::prelude::*; + use winit::platform::web::WindowExtWebSys; + + console_error_panic_hook::set_once(); + tracing_log::LogTracer::init().expect("unable to create log-tracer"); + tracing_wasm::set_as_global_default(); + + wasm_bindgen_futures::spawn_local(async move { + let (resources, event_loop, window, window_system) = init().await; + + let canvas = window.canvas(); + canvas.style().set_css_text("background-color: teal;"); + web_sys::window() + .and_then(|win| win.document()) + .and_then(|doc| doc.body()) + .and_then(|body| body.append_child(&web_sys::Element::from(canvas)).ok()) + .expect("couldn't append canvas to document body"); + + window_system.spawn(resources, event_loop); + }) +} diff --git a/crates/render-wgpu/src/backend.rs b/crates/render-wgpu/src/backend.rs new file mode 100644 index 0000000..dad1baf --- /dev/null +++ b/crates/render-wgpu/src/backend.rs @@ -0,0 +1,32 @@ +use pulz_render::backend::RenderBackend; +use pulz_window::WindowId; + +use crate::{Error, WgpuRendererBackend}; + +pub enum BackendTexture { + Texture { + texture: wgpu::Texture, + view: wgpu::TextureView, + }, + Surface { + window: WindowId, + view: wgpu::TextureView, + }, +} + +impl BackendTexture { + #[inline] + pub fn view(&self) -> &wgpu::TextureView { + match self { + Self::Texture { view, .. } => view, + Self::Surface { view, .. } => view, + } + } +} + +impl RenderBackend for WgpuRendererBackend { + type Error = Error; + type Buffer = wgpu::Buffer; + type Texture = BackendTexture; + type ShaderModule = wgpu::ShaderModule; +} diff --git a/crates/render-wgpu/src/convert.rs b/crates/render-wgpu/src/convert.rs new file mode 100644 index 0000000..9b18ba4 --- /dev/null +++ b/crates/render-wgpu/src/convert.rs @@ -0,0 +1,314 @@ +use std::ops::Deref; + +use pulz_render::{ + buffer::{BufferDescriptor, BufferUsage}, + color::Srgba, + math::USize3, + pipeline::{Face, FrontFace, IndexFormat, PrimitiveTopology, VertexFormat}, + shader::{ShaderModule, ShaderModuleDescriptor, ShaderSource}, + texture::{ + ImageDataLayout, Texture, TextureDescriptor, TextureDimensions, TextureFormat, TextureUsage, + }, +}; +use thiserror::Error; + +#[derive(Error, Debug)] +#[non_exhaustive] +pub enum ConversionError { + #[error("the texture format {0:?} is not supported!")] + UnsupportedTextureFormat(TextureFormat), + + #[error("the shader-module {0:?} is not available!")] + ShaderModuleNotAvailable(ShaderModule), + + #[error("the texture {0:?} is not available!")] + TextureNotAvailable(Texture), +} + +pub type Result = std::result::Result; + +#[inline] +pub fn convert_buffer_descriptor(val: &BufferDescriptor) -> wgpu::BufferDescriptor<'_> { + wgpu::BufferDescriptor { + label: None, + size: val.size as u64, + usage: convert_buffer_usage(val.usage), + mapped_at_creation: true, + } +} + +#[inline] +fn convert_buffer_usage(val: BufferUsage) -> wgpu::BufferUsages { + let mut result = wgpu::BufferUsages::empty(); + if val.contains(BufferUsage::TRANSFER_SRC) { + result |= wgpu::BufferUsages::COPY_SRC; + } + if val.contains(BufferUsage::TRANSFER_DST) { + result |= wgpu::BufferUsages::COPY_DST; + } + if val.contains(BufferUsage::INDEX) { + result |= wgpu::BufferUsages::INDEX; + } + if val.contains(BufferUsage::UNIFORM) { + result |= wgpu::BufferUsages::UNIFORM; + } + if val.contains(BufferUsage::STORAGE) { + result |= wgpu::BufferUsages::STORAGE; + } + if val.contains(BufferUsage::INDIRECT) { + result |= wgpu::BufferUsages::INDIRECT; + } + result +} + +#[inline] +pub fn convert_texture_descriptor(val: &TextureDescriptor) -> Result> { + Ok(wgpu::TextureDescriptor { + label: None, + size: convert_extents(val.dimensions.extents()), + mip_level_count: val.mip_level_count, + sample_count: val.sample_count, + dimension: convert_texture_dimensions(&val.dimensions), + format: convert_texture_format(val.format)?, + usage: convert_texture_usages(val.usage), + }) +} + +#[inline] +pub fn convert_texture_view_descriptor(val: &TextureDescriptor) -> wgpu::TextureViewDescriptor<'_> { + wgpu::TextureViewDescriptor { + dimension: Some(convert_texture_view_dimensions(&val.dimensions)), + ..Default::default() + } +} + +#[inline] +pub fn convert_image_data_layout(image: &ImageDataLayout) -> wgpu::ImageDataLayout { + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: std::num::NonZeroU32::new(image.bytes_per_row), + rows_per_image: std::num::NonZeroU32::new(image.rows_per_image), + } +} + +#[inline] +fn convert_texture_view_dimensions(val: &TextureDimensions) -> wgpu::TextureViewDimension { + match val { + TextureDimensions::D1(_) => wgpu::TextureViewDimension::D1, + TextureDimensions::D2 { .. } => wgpu::TextureViewDimension::D2, + TextureDimensions::D2Array { .. } => wgpu::TextureViewDimension::D2Array, + TextureDimensions::Cube { .. } => wgpu::TextureViewDimension::Cube, + TextureDimensions::CubeArray { .. } => wgpu::TextureViewDimension::CubeArray, + TextureDimensions::D3 { .. } => wgpu::TextureViewDimension::D3, + } +} + +#[inline] +fn convert_texture_dimensions(val: &TextureDimensions) -> wgpu::TextureDimension { + match val { + TextureDimensions::D1(_) => wgpu::TextureDimension::D1, + TextureDimensions::D2 { .. } + | TextureDimensions::D2Array { .. } + | TextureDimensions::Cube { .. } + | TextureDimensions::CubeArray { .. } => wgpu::TextureDimension::D2, + TextureDimensions::D3 { .. } => wgpu::TextureDimension::D3, + } +} + +#[inline] +fn convert_extents(val: USize3) -> wgpu::Extent3d { + wgpu::Extent3d { + width: val.x, + height: val.y, + depth_or_array_layers: val.z, + } +} + +#[inline] +fn convert_texture_format(val: TextureFormat) -> Result { + Ok(match val { + // 8-bit formats + TextureFormat::R8Unorm => wgpu::TextureFormat::R8Unorm, + TextureFormat::R8Snorm => wgpu::TextureFormat::R8Snorm, + TextureFormat::R8Uint => wgpu::TextureFormat::R8Uint, + TextureFormat::R8Sint => wgpu::TextureFormat::R8Sint, + + // 16-bit formats + TextureFormat::R16Uint => wgpu::TextureFormat::R16Uint, + TextureFormat::R16Sint => wgpu::TextureFormat::R16Sint, + TextureFormat::R16Float => wgpu::TextureFormat::R16Float, + TextureFormat::Rg8Unorm => wgpu::TextureFormat::Rg8Unorm, + TextureFormat::Rg8Snorm => wgpu::TextureFormat::Rg8Snorm, + TextureFormat::Rg8Uint => wgpu::TextureFormat::Rg8Uint, + TextureFormat::Rg8Sint => wgpu::TextureFormat::Rg8Sint, + + // 32-bit formats + TextureFormat::R32Uint => wgpu::TextureFormat::R32Uint, + TextureFormat::R32Sint => wgpu::TextureFormat::R32Sint, + TextureFormat::R32Float => wgpu::TextureFormat::R32Float, + TextureFormat::Rg16Uint => wgpu::TextureFormat::Rg16Uint, + TextureFormat::Rg16Sint => wgpu::TextureFormat::Rg16Sint, + TextureFormat::Rg16Float => wgpu::TextureFormat::Rg16Float, + TextureFormat::Rgba8Unorm => wgpu::TextureFormat::Rgba8Unorm, + TextureFormat::Rgba8UnormSrgb => wgpu::TextureFormat::Rgba8UnormSrgb, + TextureFormat::Rgba8Snorm => wgpu::TextureFormat::Rgba8Snorm, + TextureFormat::Rgba8Uint => wgpu::TextureFormat::Rgba8Uint, + TextureFormat::Rgba8Sint => wgpu::TextureFormat::Rgba8Sint, + TextureFormat::Bgra8Unorm => wgpu::TextureFormat::Bgra8Unorm, + TextureFormat::Bgra8UnormSrgb => wgpu::TextureFormat::Bgra8UnormSrgb, + + // Packed 32-bit formats + TextureFormat::Rgb9E5Ufloat => wgpu::TextureFormat::Rgb9e5Ufloat, + TextureFormat::Rgb10A2Unorm => wgpu::TextureFormat::Rgb10a2Unorm, + TextureFormat::Rg11B10Float => wgpu::TextureFormat::Rg11b10Float, + + // 64-bit formats + TextureFormat::Rg32Uint => wgpu::TextureFormat::Rg32Uint, + TextureFormat::Rg32Sint => wgpu::TextureFormat::Rg32Sint, + TextureFormat::Rg32Float => wgpu::TextureFormat::Rg32Float, + TextureFormat::Rgba16Uint => wgpu::TextureFormat::Rgba16Uint, + TextureFormat::Rgba16Sint => wgpu::TextureFormat::Rgba16Sint, + TextureFormat::Rgba16Float => wgpu::TextureFormat::Rgba16Float, + + // 128-bit formats + TextureFormat::Rgba32Uint => wgpu::TextureFormat::Rgba32Uint, + TextureFormat::Rgba32Sint => wgpu::TextureFormat::Rgba32Sint, + TextureFormat::Rgba32Float => wgpu::TextureFormat::Rgba32Float, + + // Depth and stencil formats + // TextureFormat::Stencil8 => wgpu::TextureFormat::Stencil8, + // TextureFormat::Depth16Unorm => wgpu::TextureFormat::Depth16Unorm, + TextureFormat::Depth24Plus => wgpu::TextureFormat::Depth24Plus, + TextureFormat::Depth24PlusStencil8 => wgpu::TextureFormat::Depth24PlusStencil8, + TextureFormat::Depth32Float => wgpu::TextureFormat::Depth32Float, + + _ => return Err(ConversionError::UnsupportedTextureFormat(val)), + }) +} + +#[inline] +fn convert_vertex_format(val: VertexFormat) -> Result { + Ok(match val { + VertexFormat::Uint8x2 => wgpu::VertexFormat::Uint8x2, + VertexFormat::Uint8x4 => wgpu::VertexFormat::Uint8x4, + VertexFormat::Sint8x2 => wgpu::VertexFormat::Sint8x2, + VertexFormat::Sint8x4 => wgpu::VertexFormat::Sint8x4, + VertexFormat::Unorm8x2 => wgpu::VertexFormat::Unorm8x2, + VertexFormat::Unorm8x4 => wgpu::VertexFormat::Unorm8x4, + VertexFormat::Snorm8x2 => wgpu::VertexFormat::Snorm8x2, + VertexFormat::Snorm8x4 => wgpu::VertexFormat::Snorm8x4, + VertexFormat::Uint16x2 => wgpu::VertexFormat::Uint16x2, + VertexFormat::Uint16x4 => wgpu::VertexFormat::Uint16x4, + VertexFormat::Sint16x2 => wgpu::VertexFormat::Sint16x2, + VertexFormat::Sint16x4 => wgpu::VertexFormat::Sint16x4, + VertexFormat::Unorm16x2 => wgpu::VertexFormat::Unorm16x2, + VertexFormat::Unorm16x4 => wgpu::VertexFormat::Unorm16x4, + VertexFormat::Snorm16x2 => wgpu::VertexFormat::Snorm16x2, + VertexFormat::Snorm16x4 => wgpu::VertexFormat::Snorm16x4, + VertexFormat::Uint32 => wgpu::VertexFormat::Uint32, + VertexFormat::Uint32x2 => wgpu::VertexFormat::Uint32x2, + VertexFormat::Uint32x3 => wgpu::VertexFormat::Uint32x3, + VertexFormat::Uint32x4 => wgpu::VertexFormat::Uint32x4, + VertexFormat::Sint32 => wgpu::VertexFormat::Sint32, + VertexFormat::Sint32x2 => wgpu::VertexFormat::Sint32x2, + VertexFormat::Sint32x3 => wgpu::VertexFormat::Sint32x3, + VertexFormat::Sint32x4 => wgpu::VertexFormat::Sint32x4, + //VertexFormat::Float16 => wgpu::VertexFormat::Float16, + VertexFormat::Float16x2 => wgpu::VertexFormat::Float16x2, + VertexFormat::Float16x4 => wgpu::VertexFormat::Float16x4, + VertexFormat::Float32 => wgpu::VertexFormat::Float32, + VertexFormat::Float32x2 => wgpu::VertexFormat::Float32x2, + VertexFormat::Float32x3 => wgpu::VertexFormat::Float32x3, + VertexFormat::Float32x4 => wgpu::VertexFormat::Float32x4, + VertexFormat::Float64 => wgpu::VertexFormat::Float64, + VertexFormat::Float64x2 => wgpu::VertexFormat::Float64x2, + VertexFormat::Float64x3 => wgpu::VertexFormat::Float64x3, + VertexFormat::Float64x4 => wgpu::VertexFormat::Float64x4, + + _ => panic!("unsupported vertex format: {:?}", val), + }) +} + +#[inline] +fn convert_texture_usages(val: TextureUsage) -> wgpu::TextureUsages { + let mut result = wgpu::TextureUsages::empty(); + if val.contains(TextureUsage::TRANSFER_SRC) { + result |= wgpu::TextureUsages::COPY_SRC; + } + if val.contains(TextureUsage::TRANSFER_DST) { + result |= wgpu::TextureUsages::COPY_DST; + } + if val.contains(TextureUsage::TEXTURE_BINDING) { + result |= wgpu::TextureUsages::TEXTURE_BINDING; + } + if val.contains(TextureUsage::STORAGE_BINDING) { + result |= wgpu::TextureUsages::STORAGE_BINDING; + } + if val.contains(TextureUsage::COLOR_ATTACHMENT) + || val.contains(TextureUsage::DEPTH_STENCIL_ATTACHMENT) + { + result |= wgpu::TextureUsages::RENDER_ATTACHMENT; + } + result +} + +pub fn convert_shader_module_descriptor<'a>( + val: &'a ShaderModuleDescriptor<'a>, +) -> wgpu::ShaderModuleDescriptor<'a> { + let source = match &val.source { + ShaderSource::Wgsl(s) => wgpu::ShaderSource::Wgsl(s.deref().into()), + // ShaderSource::Glsl(s) => wgpu::ShaderSource::Glsl(s.deref().into()), + // ShaderSource::SpirV(s) => wgpu::ShaderSource::SpirV(s.deref().into()), + #[allow(unreachable_patterns)] + _ => panic!("unsupported shader type in shader {:?}", val.label), + }; + wgpu::ShaderModuleDescriptor { + label: val.label, + source, + } +} + +#[inline] +fn convert_primitive_topology(val: PrimitiveTopology) -> wgpu::PrimitiveTopology { + match val { + PrimitiveTopology::PointList => wgpu::PrimitiveTopology::PointList, + PrimitiveTopology::LineList => wgpu::PrimitiveTopology::LineList, + PrimitiveTopology::LineStrip => wgpu::PrimitiveTopology::LineStrip, + PrimitiveTopology::TriangleList => wgpu::PrimitiveTopology::TriangleList, + PrimitiveTopology::TriangleStrip => wgpu::PrimitiveTopology::TriangleStrip, + } +} + +#[inline] +fn convert_front_face(val: FrontFace) -> wgpu::FrontFace { + match val { + FrontFace::CounterClockwise => wgpu::FrontFace::Ccw, + FrontFace::Clockwise => wgpu::FrontFace::Cw, + } +} + +#[inline] +fn convert_face(val: Face) -> wgpu::Face { + match val { + Face::Front => wgpu::Face::Front, + Face::Back => wgpu::Face::Back, + } +} + +#[inline] +fn convert_index_format(val: IndexFormat) -> wgpu::IndexFormat { + match val { + IndexFormat::Uint16 => wgpu::IndexFormat::Uint16, + IndexFormat::Uint32 => wgpu::IndexFormat::Uint32, + } +} + +#[inline] +fn convert_color(color: Srgba) -> wgpu::Color { + wgpu::Color { + r: color.red as f64, + g: color.green as f64, + b: color.blue as f64, + a: color.alpha as f64, + } +} diff --git a/crates/render-wgpu/src/lib.rs b/crates/render-wgpu/src/lib.rs new file mode 100644 index 0000000..04dffca --- /dev/null +++ b/crates/render-wgpu/src/lib.rs @@ -0,0 +1,293 @@ +#![warn( + // missing_docs, + // rustdoc::missing_doc_code_examples, + future_incompatible, + rust_2018_idioms, + unused, + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_qualifications, + unused_crate_dependencies, + clippy::cargo, + clippy::multiple_crate_versions, + clippy::empty_line_after_outer_attr, + clippy::fallible_impl_from, + clippy::redundant_pub_crate, + clippy::use_self, + clippy::suspicious_operation_groupings, + clippy::useless_let_if_seq, + // clippy::missing_errors_doc, + // clippy::missing_panics_doc, + clippy::wildcard_imports +)] +#![doc(html_logo_url = "https://raw.githubusercontent.com/HellButcher/pulz/master/docs/logo.png")] +#![doc(html_no_source)] +#![doc = include_str!("../README.md")] + +use std::{ + ops::{Deref, DerefMut}, + rc::Rc, +}; + +use pulz_ecs::prelude::*; +use pulz_render::{RenderModule, RenderSystemPhase}; +use pulz_window::{RawWindow, RawWindowHandles, Window, WindowId, Windows, WindowsMirror}; +use surface::Surface; +use thiserror::Error; +use tracing::info; + +mod backend; +mod convert; +mod surface; + +#[derive(Error, Debug)] +#[non_exhaustive] +pub enum Error { + #[error("No suitable GPU adapters found on the system!")] + NoAdapter, + + #[error("Unable to request a suitable device!")] + NoDevice, + + #[error("The window is not available, or it has no raw-window-handle")] + WindowNotAvailable, + + #[error("unknown renderer error")] + Unknown, +} + +impl From for Error { + fn from(_: wgpu::RequestDeviceError) -> Self { + Self::NoDevice + } +} + +pub type Result = std::result::Result; + +pub struct WgpuRenderer { + instance: wgpu::Instance, + backend: WgpuRendererBackend, + surfaces: WindowsMirror, + tmp_surface_textures: Vec, +} + +pub struct WgpuRendererBackend { + adapter: wgpu::Adapter, + device: wgpu::Device, + queue: wgpu::Queue, +} + +impl Deref for WgpuRenderer { + type Target = WgpuRendererBackend; + #[inline] + fn deref(&self) -> &Self::Target { + &self.backend + } +} + +impl DerefMut for WgpuRenderer { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.backend + } +} + +fn backend_bits_from_env_or_default() -> wgpu::Backends { + wgpu::util::backend_bits_from_env().unwrap_or(wgpu::Backends::PRIMARY | wgpu::Backends::GL) +} + +async fn initialize_adapter_from_env_or_default( + instance: &wgpu::Instance, + backend_bits: wgpu::Backends, + compatible_surface: Option<&wgpu::Surface>, +) -> Option { + match wgpu::util::initialize_adapter_from_env(instance, backend_bits) { + Some(a) => Some(a), + None => { + let power_preference = wgpu::util::power_preference_from_env().unwrap_or_default(); + instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference, + force_fallback_adapter: false, + compatible_surface, + }) + .await + } + } +} + +impl WgpuRenderer { + pub async fn new() -> Result { + let backends = backend_bits_from_env_or_default(); + let instance = wgpu::Instance::new(backends); + let adapter = initialize_adapter_from_env_or_default(&instance, backends, None) + .await + .ok_or(Error::NoAdapter)?; + Self::for_adapter(instance, adapter).await + } + + /// # Unsafe + /// Raw Window Handle must be a valid object to create a surface + /// upon and must remain valid for the lifetime of the surface. + pub async fn for_window( + window_id: WindowId, + window: &Window, + window_handle: Rc, + ) -> Result { + let backends = backend_bits_from_env_or_default(); + let instance = wgpu::Instance::new(backends); + let surface = Surface::create(&instance, window, window_handle); + let adapter = initialize_adapter_from_env_or_default(&instance, backends, Some(&surface)) + .await + .ok_or(Error::NoAdapter)?; + let mut renderer = Self::for_adapter(instance, adapter).await?; + renderer.surfaces.insert(window_id, surface); + Ok(renderer) + } + + pub async fn for_adapter(instance: wgpu::Instance, adapter: wgpu::Adapter) -> Result { + let trace_dir = std::env::var("WGPU_TRACE"); + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + features: wgpu::Features::empty(), + // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain. + limits: wgpu::Limits::downlevel_defaults().using_resolution(adapter.limits()), + }, + trace_dir.ok().as_ref().map(std::path::Path::new), + ) + .await?; + + Ok(Self { + instance, + backend: WgpuRendererBackend { + adapter, + device, + queue, + }, + surfaces: WindowsMirror::new(), + tmp_surface_textures: Vec::new(), + }) + } + + #[inline] + pub fn backend(&self) -> &WgpuRendererBackend { + &self.backend + } + + #[inline] + pub fn backend_mut(&mut self) -> &mut WgpuRendererBackend { + &mut self.backend + } + + fn reconfigure_surfaces(&mut self, windows: &Windows) { + for (window_id, surface) in self.surfaces.iter_mut() { + if let Some(window) = windows.get(window_id) { + if surface.update(window) { + surface.configure(&self.backend); + } + } + } + } + + fn aquire_swapchain_images(&mut self) { + let _ = tracing::trace_span!("AquireImages").entered(); + assert_eq!(0, self.tmp_surface_textures.len()); + + self.tmp_surface_textures + .reserve_exact(self.surfaces.capacity()); + + for (_window, surface) in self.surfaces.iter_mut() { + // TODO: only affected/updated surfaces/windows + let tex = match surface.get_current_texture() { + Ok(t) => t, + Err(wgpu::SurfaceError::Outdated | wgpu::SurfaceError::Lost) => { + info!("reconfigure surface (outdated)"); + surface.configure(&self.backend); + surface + .get_current_texture() + .expect("Failed to acquire next surface texture!") + } + Err(e) => panic!("unable to aquire next frame: {}", e), // TODO: better error handling + }; + self.tmp_surface_textures.push(tex); + } + } + + fn present_swapchain_images(&mut self) { + let _ = tracing::trace_span!("Present").entered(); + + for surface_texture in self.tmp_surface_textures.drain(..) { + surface_texture.present(); + } + } + + fn run_render_system(mut renderer: ResMut<'_, Self>, windows: Res<'_, Windows>) { + renderer.reconfigure_surfaces(&windows); + + renderer.aquire_swapchain_images(); + + // TODO: render + + renderer.present_swapchain_images(); + } +} + +impl ModuleWithOutput for WgpuRenderer { + type Output<'l> = &'l mut Self; + + fn install_modules(&self, res: &mut Resources) { + res.install(RenderModule); + } + + fn install_resources(self, res: &mut Resources) -> &mut Self { + let resource_id = res.insert_unsend(self); + res.get_mut_id(resource_id).unwrap() + } + + fn install_systems(schedule: &mut Schedule) { + schedule + .add_system(Self::run_render_system) + .into_phase(RenderSystemPhase::Render); + } +} + +pub struct WgpuRendererBuilder { + window: Option, +} + +impl WgpuRendererBuilder { + #[inline] + pub const fn new() -> Self { + Self { window: None } + } + + /// # Unsafe + /// Raw Window Handle must be a valid object to create a surface + /// upon and must remain valid for the lifetime of the surface. + #[inline] + pub unsafe fn with_window(mut self, window_id: WindowId) -> Self { + self.window = Some(window_id); + self + } + + pub async fn install(self, res: &mut Resources) -> Result<&mut WgpuRenderer> { + let renderer = if let Some(window_id) = self.window { + let windows = res.borrow_res::().unwrap(); + // TODO: make not dependent on descriptor. + // add size-method to RawWindow + let descriptor = &windows[window_id]; + let raw_window_handles = res.borrow_res::().unwrap(); + let handle = raw_window_handles + .get(window_id) + .and_then(|h| h.upgrade()) + .ok_or(Error::WindowNotAvailable)?; + WgpuRenderer::for_window(window_id, descriptor, handle).await? + } else { + WgpuRenderer::new().await? + }; + Ok(res.install(renderer)) + } +} diff --git a/crates/render-wgpu/src/surface.rs b/crates/render-wgpu/src/surface.rs new file mode 100644 index 0000000..f690f7d --- /dev/null +++ b/crates/render-wgpu/src/surface.rs @@ -0,0 +1,98 @@ +use std::{ + ops::{Deref, DerefMut}, + rc::Rc, +}; + +use pulz_window::{RawWindow, Size2, Window}; +use tracing::info; + +use crate::WgpuRendererBackend; + +pub struct Surface { + surface: wgpu::Surface, + window_handle: Rc, // holds reference to window to ensure sufface is still valid until destruction + size: Size2, + vsync: bool, + format: wgpu::TextureFormat, +} + +impl Surface { + pub fn create( + instance: &wgpu::Instance, + window: &Window, + window_handle: Rc, + ) -> Self { + let surface = unsafe { instance.create_surface(&window_handle) }; + Self { + surface, + window_handle, + size: window.size, + vsync: window.vsync, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + } + } + + #[inline] + pub fn size(&self) -> Size2 { + self.size + } + + #[inline] + pub fn format(&self) -> wgpu::TextureFormat { + self.format + } + + pub fn update(&mut self, window: &Window) -> bool { + let mut changed = false; + if self.vsync != window.vsync { + info!("window vsync changed: {} => {}", self.vsync, window.vsync); + self.vsync = window.vsync; + changed = true; + } + if self.size != window.size { + info!("window size changed: {} => {}", self.size, window.size); + self.size = window.size; + changed = true; + } + changed + } + + pub fn configure(&mut self, backend: &WgpuRendererBackend) { + // TODO: also reconfigure on resize, and when presenting results in `Outdated/Lost` + self.format = self + .surface + .get_supported_formats(&backend.adapter) + .first() + .copied() + .expect("surface not compatible"); + let present_mode = if self.vsync { + wgpu::PresentMode::Fifo + } else { + wgpu::PresentMode::Immediate + }; + let surface_config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: self.format, + width: self.size.x, + height: self.size.y, + present_mode, + alpha_mode: wgpu::CompositeAlphaMode::Auto, + }; + self.surface.configure(&backend.device, &surface_config); + } +} + +impl Deref for Surface { + type Target = wgpu::Surface; + #[inline] + fn deref(&self) -> &Self::Target { + &self.surface + } +} + +impl DerefMut for Surface { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.surface + } +} diff --git a/crates/render/CHANGELOG.md b/crates/render/CHANGELOG.md new file mode 100644 index 0000000..6b5a9a2 --- /dev/null +++ b/crates/render/CHANGELOG.md @@ -0,0 +1,6 @@ +# `pulz-render` Changelog +All notable changes to this crate will be documented in this file. + +## Unreleased (DATE) + + * Initial version diff --git a/crates/render/Cargo.toml b/crates/render/Cargo.toml new file mode 100644 index 0000000..43091fe --- /dev/null +++ b/crates/render/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "pulz-render" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true +keywords = ["game", "game-engine"] +categories = ["game-engines", "game-development"] +readme = "README.md" + +[dependencies] +pulz-ecs = { path = "../ecs" } +pulz-assets = { path = "../assets" } +pulz-transform = { path = "../transform" } +pulz-window = { path = "../window" } +pulz-render-macros = { path = "macros" } + +palette = { workspace = true, features = ["std"], default-features = false } +image = { workspace = true, default-features = false } +slotmap = { workspace = true } +bytemuck = { workspace = true } +fnv = { workspace = true } +bitflags = { workspace = true } +thiserror = { workspace = true } +encase = { workspace = true } + diff --git a/crates/render/README.md b/crates/render/README.md new file mode 100644 index 0000000..3dbfb73 --- /dev/null +++ b/crates/render/README.md @@ -0,0 +1,34 @@ +# `pulz-render` + + + +[![Crates.io](https://img.shields.io/crates/v/pulz-render.svg?label=pulz-render)](https://crates.io/crates/pulz-render) +[![docs.rs](https://docs.rs/pulz-render/badge.svg)](https://docs.rs/pulz-render/) +[![license: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](#license) +[![Rust CI](https://github.com/HellButcher/pulz/actions/workflows/rust.yml/badge.svg)](https://github.com/HellButcher/pulz/actions/workflows/rust.yml) + + +**TODO** + +## Example + + +**TODO** + +## License + +[license]: #license + +This project is licensed under either of + +* MIT license ([LICENSE-MIT] or ) +* Apache License, Version 2.0, ([LICENSE-APACHE] or ) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[LICENSE-MIT]: ../../LICENSE-MIT +[LICENSE-APACHE]: ../../LICENSE-APACHE diff --git a/crates/render/macros/Cargo.toml b/crates/render/macros/Cargo.toml new file mode 100644 index 0000000..d1b76d5 --- /dev/null +++ b/crates/render/macros/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "pulz-render-macros" +description = "Proc-Macros for pulz-render" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true +keywords = ["ecs", "game"] +categories = ["data-structures", "game-engines", "game-development"] +readme = "../README.md" + +[features] +unstable = [] + +[lib] +proc-macro = true + +[dependencies] +darling = { workspace = true } +proc-macro2 = { workspace = true } +syn = { workspace = true } +quote = { workspace = true } +proc-macro-crate = { workspace = true } +encase_derive_impl = { workspace = true } diff --git a/crates/render/macros/src/binding_layout.rs b/crates/render/macros/src/binding_layout.rs new file mode 100644 index 0000000..7780b1c --- /dev/null +++ b/crates/render/macros/src/binding_layout.rs @@ -0,0 +1,247 @@ +use darling::{FromMeta, ToTokens}; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{quote, TokenStreamExt}; +use syn::{Attribute, DeriveInput, Error, Lit, Meta, NestedMeta, Result}; + +use crate::utils::resolve_render_crate; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum BindingType { + Uniform, + Texture, + Sampler, +} + +impl BindingType { + fn from_ident(ident: &Ident) -> Option { + if ident == "uniform" { + Some(Self::Uniform) + } else if ident == "texture" { + Some(Self::Texture) + } else if ident == "sampler" { + Some(Self::Sampler) + } else { + None + } + } +} + +impl ToTokens for BindingType { + fn to_tokens(&self, tokens: &mut TokenStream) { + let name = match self { + Self::Uniform => "uniform", + Self::Texture => "texture", + Self::Sampler => "sampler", + }; + tokens.append(Ident::new(name, Span::call_site())) + } +} + +pub fn derive_as_binding_layout(input: DeriveInput) -> Result { + let ident = input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let crate_render = resolve_render_crate()?; + + let mut layouts = Vec::new(); + + if let Some(BindingLayoutArgs { + binding_type, + binding_index, + options: _, + }) = BindingLayoutArgs::from_attributes(&input.attrs)? + { + if binding_type != BindingType::Uniform { + return Err(Error::new( + ident.span(), + "only uniform is allowed on struct", + )); + } + + layouts.push(quote! { + #crate_render::pipeline::BindGroupLayoutEntry { + binding: #binding_index, + binding_type: #binding_type, + stages: #crate_render::pipeline::ShaderStages::ALL, // TODO, + } + }); + } + + Ok(quote! { + impl #impl_generics #crate_render::pipeline::AsBindingLayout for #ident #ty_generics #where_clause { + + } + }) +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct BindingLayoutArgs { + binding_type: BindingType, + binding_index: Lit, + options: BindingLayoutOptions, +} + +#[derive(FromMeta, Default, Clone, Eq, PartialEq, Debug)] +pub struct BindingLayoutOptions {} + +impl BindingLayoutArgs { + fn from_attributes(attribs: &[Attribute]) -> Result> { + let mut binding_type = None; + let mut binding_index = None; + let mut options = BindingLayoutOptions::default(); + for attr in attribs { + if let Some(ident) = attr.path.get_ident() { + if let Some(t) = BindingType::from_ident(ident) { + if binding_type.is_some() { + return Err(Error::new( + ident.span(), + "only a single attribute is allowed", + )); + } + binding_type = Some(t); + match attr.parse_meta()? { + Meta::List(meta) => { + let path = meta.path; + let mut it = meta.nested.into_iter(); + if let Some(NestedMeta::Lit(lit)) = it.next() { + binding_index = Some(lit); + let more_args: Vec<_> = it.collect(); + options = BindingLayoutOptions::from_list(&more_args)?; + } else { + return Err(Error::new_spanned(path, "a binding-index is missing")); + } + } + Meta::NameValue(meta) => { + binding_index = Some(meta.lit); + } + Meta::Path(path) => { + return Err(Error::new_spanned(path, "a binding-index is missing")); + } + } + // TODO: parse index + } + } + } + if let Some(binding_type) = binding_type { + Ok(Some(BindingLayoutArgs { + binding_type, + binding_index: binding_index.unwrap(), + options, + })) + } else { + Ok(None) + } + } +} + +#[cfg(test)] +mod tests { + use syn::{DeriveInput, FieldsNamed, LitInt}; + + use super::*; + + #[test] + fn test_empty() { + // with specified MyAttr: + let derive_input = syn::parse_str( + r#" + #[derive(AsBindingLayout)] + struct Foo; + "#, + ) + .unwrap(); + let _result = derive_as_binding_layout(derive_input).unwrap(); + // TODO: assert + } + + #[test] + fn test_newtype() { + let derive_input = syn::parse_str( + r#" + #[derive(AsBindingLayout)] + struct Foo(Bar); + "#, + ) + .unwrap(); + let _result = derive_as_binding_layout(derive_input).unwrap(); + // TODO: assert + } + + #[test] + fn test_fail_tuple() { + let derive_input = syn::parse_str( + r#" + #[derive(AsBindingLayout)] + struct Foo(Alice,Bob); + "#, + ) + .unwrap(); + let result = derive_as_binding_layout(derive_input); + match result { + Ok(tokens) => { + panic!("Expected error, got: {}", tokens); + } + Err(_err) => { + // TODO: assert + } + } + } + + #[test] + fn test_fail_enum() { + let derive_input = syn::parse_str( + r#" + #[derive(AsBindingLayout)] + enum Foo{Bar} + "#, + ) + .unwrap(); + let result = derive_as_binding_layout(derive_input); + match result { + Ok(tokens) => { + panic!("Expected error, got: {}", tokens); + } + Err(_err) => { + // TODO: assert + } + } + } + + #[test] + fn test_struct() { + // with no MyAttr: + let derive_input: DeriveInput = syn::parse_str( + r#" + #[derive(AsBindingLayout)] + #[myderive()] + struct Foo{ + a: Bar, + b: Blub, + } + "#, + ) + .unwrap(); + let _result = derive_as_binding_layout(derive_input).unwrap(); + // TODO: assert + } + + #[test] + fn test_parse_args() { + let fields: FieldsNamed = syn::parse_str( + r#"{ + #[uniform(2)] + pub foo: u32, + }"#, + ) + .unwrap(); + let args = BindingLayoutArgs::from_attributes(&fields.named[0].attrs).unwrap(); + assert_eq!( + args, + Some(BindingLayoutArgs { + binding_type: BindingType::Uniform, + binding_index: Lit::Int(LitInt::new("2", Span::call_site())), + options: BindingLayoutOptions {}, + }) + ); + } +} diff --git a/crates/render/macros/src/compile_shader.rs b/crates/render/macros/src/compile_shader.rs new file mode 100644 index 0000000..f393976 --- /dev/null +++ b/crates/render/macros/src/compile_shader.rs @@ -0,0 +1,24 @@ +use darling::FromMeta; +use proc_macro2::TokenStream; +use syn::{AttributeArgs, LitStr, Result}; + +pub fn compile_shader_int(args: AttributeArgs) -> Result { + let args = CompileShaderArgs::from_list(&args)?; + compile_shader_with_args(args) +} + +pub fn compile_shader_with_args(args: CompileShaderArgs) -> Result { + panic!("TODO: implement: {:#?}", args); +} + +#[derive(FromMeta, PartialEq, Eq, Debug)] +pub enum TargetFormat { + Wgsl, + SpirV, +} + +#[derive(FromMeta, PartialEq, Eq, Debug)] +pub struct CompileShaderArgs { + pub target_format: TargetFormat, + pub source: LitStr, +} diff --git a/crates/render/macros/src/lib.rs b/crates/render/macros/src/lib.rs new file mode 100644 index 0000000..3e01e9b --- /dev/null +++ b/crates/render/macros/src/lib.rs @@ -0,0 +1,35 @@ +#![cfg_attr(feature = "unstable", feature(proc_macro_span))] + +use proc_macro::TokenStream; +use syn::{parse_macro_input, DeriveInput}; +use utils::resolve_render_crate; + +#[macro_use] +mod utils; +mod binding_layout; + +#[cfg(feature = "unstable")] +mod compile_shader; + +#[proc_macro_derive(AsBindingLayout, attributes(uniform, texture, sampler))] +pub fn derive_as_binding_layout(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + binding_layout::derive_as_binding_layout(input) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} + +encase_derive_impl::implement! {{ + let crate_render = resolve_render_crate().unwrap(); + encase_derive_impl::syn::parse_quote!(#crate_render::shader) +}} + +/// requires #![feature(proc_macro_span)] +#[cfg(feature = "unstable")] +#[proc_macro] +pub fn compile_shader_int(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as syn::AttributeArgs); + compile_shader::compile_shader_int(input) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} diff --git a/crates/render/macros/src/utils.rs b/crates/render/macros/src/utils.rs new file mode 100644 index 0000000..4f2059b --- /dev/null +++ b/crates/render/macros/src/utils.rs @@ -0,0 +1,24 @@ +use proc_macro2::{Ident, Span}; +use proc_macro_crate::FoundCrate; +use syn::{Error, Path, Result, Token}; + +#[cfg(test)] +pub fn resolve_render_crate() -> Result { + Ok(Path::from(Ident::new("pulz_render", Span::call_site()))) +} + +#[cfg(not(test))] +pub fn resolve_render_crate() -> Result { + resolve_crate("pulz-render") +} + +pub fn resolve_crate(name: &str) -> Result { + match proc_macro_crate::crate_name(name).map_err(|e| Error::new(Span::call_site(), e))? { + FoundCrate::Itself => Ok(Path::from(Ident::new("crate", Span::call_site()))), + FoundCrate::Name(name) => { + let mut path: Path = Ident::new(&name, Span::call_site()).into(); + path.leading_colon = Some(Token![::](Span::call_site())); + Ok(path) + } + } +} diff --git a/crates/render/src/backend.rs b/crates/render/src/backend.rs new file mode 100644 index 0000000..c1cfd75 --- /dev/null +++ b/crates/render/src/backend.rs @@ -0,0 +1,28 @@ +pub trait GpuResource: slotmap::Key { + type Descriptor<'l>; +} + +macro_rules! define_gpu_resource { + ($type_name:ident, $collection_type_name:ident, $descriptor_type:ident $(<$life:tt>)?) => { + ::slotmap::new_key_type!{ + pub struct $type_name; + } + + pub type $collection_type_name = ::slotmap::basic::SlotMap<$type_name, V>; + + impl $crate::backend::GpuResource for $type_name { + type Descriptor<'l> = $descriptor_type $(<$life>)?; + } + }; +} + +// export macro to crate +pub(crate) use define_gpu_resource; + +pub trait RenderBackend { + type Error: std::error::Error; + + type Buffer; + type Texture; + type ShaderModule; +} diff --git a/crates/render/src/buffer.rs b/crates/render/src/buffer.rs new file mode 100644 index 0000000..20d748e --- /dev/null +++ b/crates/render/src/buffer.rs @@ -0,0 +1,40 @@ +use bitflags::bitflags; + +crate::backend::define_gpu_resource!(Buffer, Buffers, BufferDescriptor); + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct BufferDescriptor { + pub size: usize, + pub usage: BufferUsage, +} + +impl BufferDescriptor { + pub const fn new() -> Self { + Self { + size: 0, + usage: BufferUsage::empty(), + } + } +} + +impl Default for BufferDescriptor { + fn default() -> Self { + Self::new() + } +} + +bitflags! { + pub struct BufferUsage: u32 { + const MAP_READ = 1; + const MAP_WRITE = 2; + const TRANSFER_SRC = 4; + const TRANSFER_DST = 8; + const INDEX = 16; + const VERTEX = 32; + const UNIFORM = 64; + const STORAGE = 128; + const INDIRECT = 256; + + const NONE = 0; + } +} diff --git a/crates/render/src/camera.rs b/crates/render/src/camera.rs new file mode 100644 index 0000000..0b36818 --- /dev/null +++ b/crates/render/src/camera.rs @@ -0,0 +1,314 @@ +use pulz_assets::{Assets, Handle}; +use pulz_ecs::prelude::*; +use pulz_transform::math::{vec2, Mat4, Size2, USize2}; +use pulz_window::{Window, WindowId, Windows}; + +use crate::texture::Image; + +trait AsProjectionMatrix { + fn as_projection_matrix(&self) -> Mat4; + fn update_viewport(&mut self, size: Size2) -> bool; + fn far(&self) -> f32; + fn zorder_optimization(&self) -> bool; +} + +#[derive(Component)] +pub enum Projection { + Perspective(PerspectiveProjection), + Orthographic(OrthographicProjection), +} + +impl AsProjectionMatrix for Projection { + #[inline] + fn as_projection_matrix(&self) -> Mat4 { + match self { + Self::Perspective(p) => p.as_projection_matrix(), + Self::Orthographic(p) => p.as_projection_matrix(), + } + } + #[inline] + fn update_viewport(&mut self, size: Size2) -> bool { + match self { + Self::Perspective(p) => p.update_viewport(size), + Self::Orthographic(p) => p.update_viewport(size), + } + } + #[inline] + fn far(&self) -> f32 { + match self { + Self::Perspective(p) => p.far(), + Self::Orthographic(p) => p.far(), + } + } + #[inline] + fn zorder_optimization(&self) -> bool { + match self { + Self::Perspective(p) => p.zorder_optimization(), + Self::Orthographic(p) => p.zorder_optimization(), + } + } +} + +impl Default for Projection { + #[inline] + fn default() -> Self { + Self::Perspective(Default::default()) + } +} + +pub struct PerspectiveProjection { + pub fov: f32, + pub aspect_ratio: f32, + pub near: f32, + pub far: f32, +} + +impl AsProjectionMatrix for PerspectiveProjection { + #[inline] + fn as_projection_matrix(&self) -> Mat4 { + Mat4::perspective_rh(self.fov, self.aspect_ratio, self.near, self.far) + } + #[inline] + fn update_viewport(&mut self, size: Size2) -> bool { + let new_aspect_ratio = size.x / size.y; + if self.aspect_ratio != new_aspect_ratio { + self.aspect_ratio = new_aspect_ratio; + true + } else { + false + } + } + #[inline] + fn far(&self) -> f32 { + self.far + } + fn zorder_optimization(&self) -> bool { + false + } +} + +impl Default for PerspectiveProjection { + fn default() -> Self { + Self { + fov: std::f32::consts::PI / 4.0, + near: 0.1, + far: 1000.0, + aspect_ratio: 1.0, + } + } +} + +pub enum OrthographicOrigin { + Center, + BottomLeft, +} + +pub enum OrthographicScalingMode { + // use manually specified values of left/right/bottom/top as they are. + // the image will stretch wit the window! + Manual, + // use the window size + WindowSize, + // fits the given rect inside the window while keeping the aspect-ratio of the window. + AutoFit(Size2), +} + +pub struct OrthographicProjection { + pub left: f32, + pub right: f32, + pub bottom: f32, + pub top: f32, + pub near: f32, + pub far: f32, + pub scaling_mode: OrthographicScalingMode, + pub origin: OrthographicOrigin, +} + +impl AsProjectionMatrix for OrthographicProjection { + fn as_projection_matrix(&self) -> Mat4 { + Mat4::orthographic_rh( + self.left, + self.right, + self.bottom, + self.top, + self.near, + self.far, + ) + } + fn update_viewport(&mut self, size: Size2) -> bool { + let adjusted_size = match self.scaling_mode { + OrthographicScalingMode::Manual => return false, + OrthographicScalingMode::WindowSize => size, + OrthographicScalingMode::AutoFit(min) => { + if size.x * min.y > min.x * size.y { + vec2(size.x * min.y / size.y, min.y) + } else { + vec2(min.x, size.y * min.x / size.x) + } + } + }; + match self.origin { + OrthographicOrigin::Center => { + let half = adjusted_size / 2.0; + self.left = -half.x; + self.right = half.x; + self.bottom = -half.y; + self.top = half.y; + if let OrthographicScalingMode::WindowSize = self.scaling_mode { + self.left = self.left.floor(); + self.right = self.right.floor(); + self.bottom = self.bottom.floor(); + self.top = self.top.floor(); + } + } + OrthographicOrigin::BottomLeft => { + self.left = 0.0; + self.right = adjusted_size.x; + self.bottom = 0.0; + self.top = adjusted_size.y; + } + } + true + } + fn far(&self) -> f32 { + self.far + } + fn zorder_optimization(&self) -> bool { + true + } +} + +impl Default for OrthographicProjection { + #[inline] + fn default() -> Self { + Self { + left: -1.0, + right: 1.0, + bottom: -1.0, + top: 1.0, + near: 0.0, + far: 1000.0, + scaling_mode: OrthographicScalingMode::WindowSize, + origin: OrthographicOrigin::Center, + } + } +} + +impl From for Projection { + fn from(p: PerspectiveProjection) -> Self { + Self::Perspective(p) + } +} + +impl From for Projection { + fn from(p: OrthographicProjection) -> Self { + Self::Orthographic(p) + } +} + +#[derive(Component)] +pub struct Camera { + pub order: isize, + zorder_optimization: bool, + pub projection_matrix: Mat4, + target_info: Option, +} + +impl Camera { + pub const fn new() -> Self { + Self { + order: 0, + zorder_optimization: false, + projection_matrix: Mat4::IDENTITY, + target_info: None, + } + } + pub fn to_logical_size(&self, physical_size: USize2) -> Size2 { + let scale_factor = self.target_info.as_ref().map_or(1.0, |t| t.scale_factor); + (physical_size.as_dvec2() / scale_factor).as_vec2() + } + + #[inline] + pub fn logical_target_size(&self) -> Option { + self.target_info + .as_ref() + .map(|t| self.to_logical_size(t.physical_size)) + } + + #[inline] + pub fn physical_target_size(&self) -> Option { + self.target_info.as_ref().map(|t| t.physical_size) + } +} + +#[derive(Component)] +pub enum RenderTarget { + Window(WindowId), + Image(Handle), +} + +#[derive(Copy, Clone, PartialEq)] +struct RenderTargetInfo { + pub physical_size: USize2, + pub scale_factor: f64, +} + +impl RenderTargetInfo { + #[inline] + fn from_window(window: &Window) -> Self { + Self { + physical_size: window.size, + scale_factor: window.scale_factor, + } + } + + #[inline] + fn from_image(image: &Image) -> Self { + Self { + physical_size: image.descriptor.dimensions.subimage_extents(), + scale_factor: 1.0, + } + } +} + +pub fn update_cameras( + windows: Res<'_, Windows>, + images: Res<'_, Assets>, + mut cameras: Query< + '_, + ( + &'_ mut Camera, + Option<&'_ mut Projection>, + Option<&'_ RenderTarget>, + ), + >, +) { + for (camera, projection, render_target) in cameras.iter() { + let target_info = match render_target { + None => None, + Some(&RenderTarget::Window(window_id)) => { + windows.get(window_id).map(RenderTargetInfo::from_window) + } + Some(&RenderTarget::Image(image_handle)) => { + images.get(image_handle).map(RenderTargetInfo::from_image) + } + }; + let changed = target_info != camera.target_info; + if changed { + camera.target_info = target_info; + + if let Some(target_info) = target_info { + let logical_size = + (target_info.physical_size.as_dvec2() / target_info.scale_factor).as_vec2(); + // TODO: viewport size? + + // update projection + if let Some(projection) = projection { + projection.update_viewport(logical_size); + camera.zorder_optimization = projection.zorder_optimization(); + camera.projection_matrix = projection.as_projection_matrix(); + } + } + } + } +} diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs new file mode 100644 index 0000000..c5346b1 --- /dev/null +++ b/crates/render/src/lib.rs @@ -0,0 +1,77 @@ +#![warn( + // missing_docs, + // rustdoc::missing_doc_code_examples, + future_incompatible, + rust_2018_idioms, + unused, + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_qualifications, + unused_crate_dependencies, + clippy::cargo, + clippy::multiple_crate_versions, + clippy::empty_line_after_outer_attr, + clippy::fallible_impl_from, + clippy::redundant_pub_crate, + clippy::use_self, + clippy::suspicious_operation_groupings, + clippy::useless_let_if_seq, + // clippy::missing_errors_doc, + // clippy::missing_panics_doc, + clippy::wildcard_imports +)] +#![doc(html_logo_url = "https://raw.githubusercontent.com/HellButcher/pulz/master/docs/logo.png")] +#![doc(html_no_source)] +#![doc = include_str!("../README.md")] + +use camera::{Camera, RenderTarget}; +use pulz_assets::Assets; +use pulz_ecs::{define_label_enum, label::SystemPhase, prelude::*}; + +pub mod backend; +pub mod buffer; +pub mod camera; +pub mod mesh; +pub mod pipeline; +pub mod shader; +pub mod texture; +pub mod view; + +pub use pulz_window as window; + +pub mod color { + pub use palette::{Hsla, LinSrgba, Srgba}; +} + +#[doc(hidden)] +pub use ::encase as __encase; +pub use pulz_transform::math; + +define_label_enum! { + pub enum RenderSystemPhase: SystemPhase { + Sorting, + UpdateGraph, + Render, + } +} + +pub struct RenderModule; + +impl Module for RenderModule { + fn install_once(&self, res: &mut Resources) { + Assets::::install_into(res); + + let mut world = res.world_mut(); + world.init::(); + world.init::(); + } + + fn install_systems(schedule: &mut Schedule) { + schedule.add_phase_chain([ + RenderSystemPhase::Sorting, + RenderSystemPhase::UpdateGraph, + RenderSystemPhase::Render, + ]); + } +} diff --git a/crates/render/src/mesh/mod.rs b/crates/render/src/mesh/mod.rs new file mode 100644 index 0000000..01dede6 --- /dev/null +++ b/crates/render/src/mesh/mod.rs @@ -0,0 +1,137 @@ +use crate::pipeline::{IndexFormat, PrimitiveTopology, VertexFormat}; + +pub struct Mesh { + primitive_topology: PrimitiveTopology, + indices: Option, +} + +impl Mesh { + pub const ATTRIBUTE_POSITION: MeshVertexAttribute = + MeshVertexAttribute("Vertex_Position", 0, VertexFormat::Float32x3); + pub const ATTRIBUTE_NORMAL: MeshVertexAttribute = + MeshVertexAttribute("Vertex_Normal", 1, VertexFormat::Float32x3); + pub const ATTRIBUTE_UV_0: MeshVertexAttribute = + MeshVertexAttribute("Vertex_Uv0", 2, VertexFormat::Float32x2); + pub const ATTRIBUTE_UV_1: MeshVertexAttribute = + MeshVertexAttribute("Vertex_Uv1", 3, VertexFormat::Float32x2); + pub const ATTRIBUTE_TANGENT: MeshVertexAttribute = + MeshVertexAttribute("Vertex_Tangent", 4, VertexFormat::Float32x4); + pub const ATTRIBUTE_COLOR_0: MeshVertexAttribute = + MeshVertexAttribute("Vertex_Color0", 5, VertexFormat::Float32x4); + pub const ATTRIBUTE_COLOR_1: MeshVertexAttribute = + MeshVertexAttribute("Vertex_Color1", 6, VertexFormat::Float32x4); + pub const ATTRIBUTE_JOINT_WEIGHT: MeshVertexAttribute = + MeshVertexAttribute("Vertex_JointWeight", 7, VertexFormat::Float32x4); + pub const ATTRIBUTE_JOINT_INDEX: MeshVertexAttribute = + MeshVertexAttribute("Vertex_JointIndex", 8, VertexFormat::Uint16x4); + + pub const fn new(primitive_topology: PrimitiveTopology) -> Self { + Self { + primitive_topology, + indices: None, + } + } + + #[inline] + pub fn primitive_topology(&self) -> PrimitiveTopology { + self.primitive_topology + } + + #[inline] + pub fn indices(&self) -> Option<&Indices> { + self.indices.as_ref() + } +} + +#[derive(Debug, Clone)] +pub enum Indices { + U16(Vec), + U32(Vec), +} + +impl Indices { + pub fn format(&self) -> IndexFormat { + match self { + Self::U16(_) => IndexFormat::Uint16, + Self::U32(_) => IndexFormat::Uint32, + } + } +} + +pub struct MeshVertexAttribute { + pub name: &'static str, + pub id: u32, + pub format: VertexFormat, +} + +#[allow(non_snake_case)] +#[inline] +pub const fn MeshVertexAttribute( + name: &'static str, + id: u32, + format: VertexFormat, +) -> MeshVertexAttribute { + MeshVertexAttribute { name, id, format } +} + +impl MeshVertexAttribute { + #[inline] + pub const fn new(name: &'static str, id: u32, format: VertexFormat) -> Self { + Self { name, id, format } + } + + #[inline] + pub const fn at(&self, shader_location: u32) -> MeshVertexAttributeLocation { + MeshVertexAttributeLocation { + attribute_id: self.id, + shader_location, + } + } +} + +pub struct MeshVertexAttributeLocation { + pub attribute_id: u32, + pub shader_location: u32, +} + +impl VertexFormat { + pub fn size(self) -> usize { + match self { + Self::Uint8x2 => 2, + Self::Uint8x4 => 4, + Self::Sint8x2 => 2, + Self::Sint8x4 => 4, + Self::Unorm8x2 => 2, + Self::Unorm8x4 => 4, + Self::Snorm8x2 => 2, + Self::Snorm8x4 => 4, + Self::Uint16x2 => 4, + Self::Uint16x4 => 8, + Self::Sint16x2 => 4, + Self::Sint16x4 => 8, + Self::Unorm16x2 => 4, + Self::Unorm16x4 => 8, + Self::Snorm16x2 => 4, + Self::Snorm16x4 => 8, + Self::Float16 => 2, + Self::Float16x2 => 4, + Self::Float16x4 => 8, + Self::Float32 => 4, + Self::Float32x2 => 8, + Self::Float32x3 => 12, + Self::Float32x4 => 16, + Self::Float64 => 8, + Self::Float64x2 => 16, + Self::Float64x3 => 24, + Self::Float64x4 => 32, + Self::Uint32 => 4, + Self::Uint32x2 => 8, + Self::Uint32x3 => 12, + Self::Uint32x4 => 16, + Self::Sint32 => 4, + Self::Sint32x2 => 8, + Self::Sint32x3 => 12, + Self::Sint32x4 => 16, + } + } +} diff --git a/crates/render/src/pipeline.rs b/crates/render/src/pipeline.rs new file mode 100644 index 0000000..03efda0 --- /dev/null +++ b/crates/render/src/pipeline.rs @@ -0,0 +1,78 @@ +#[derive(Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub enum PolygonMode { + #[default] + Fill, + Line, + Point, +} + +#[derive(Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub enum PrimitiveTopology { + PointList, + LineList, + LineStrip, + #[default] + TriangleList, + TriangleStrip, +} + +#[derive(Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +#[non_exhaustive] +pub enum VertexFormat { + Uint8x2, + Uint8x4, + Sint8x2, + Sint8x4, + Unorm8x2, + Unorm8x4, + Snorm8x2, + Snorm8x4, + Uint16x2, + Uint16x4, + Sint16x2, + Sint16x4, + Unorm16x2, + Unorm16x4, + Snorm16x2, + Snorm16x4, + Float16, + Float16x2, + Float16x4, + Float32, + Float32x2, + Float32x3, + #[default] + Float32x4, + Float64, + Float64x2, + Float64x3, + Float64x4, + Uint32, + Uint32x2, + Uint32x3, + Uint32x4, + Sint32, + Sint32x2, + Sint32x3, + Sint32x4, +} + +#[derive(Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub enum IndexFormat { + Uint16, + #[default] + Uint32, +} + +#[derive(Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub enum FrontFace { + #[default] + CounterClockwise, + Clockwise, +} + +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub enum Face { + Front, + Back, +} diff --git a/crates/render/src/shader/mod.rs b/crates/render/src/shader/mod.rs new file mode 100644 index 0000000..e4f4911 --- /dev/null +++ b/crates/render/src/shader/mod.rs @@ -0,0 +1,102 @@ +use std::borrow::Cow; + +mod preprocessor; + +pub use ::encase::*; +pub use ::pulz_render_macros::ShaderType; + +crate::backend::define_gpu_resource!(ShaderModule, ShaderModules, ShaderModuleDescriptor<'l>); + +pub struct ShaderModuleDescriptor<'a> { + pub label: Option<&'a str>, + pub source: ShaderSource<'a>, +} + +#[non_exhaustive] +pub enum ShaderSource<'a> { + Wgsl(Cow<'a, str>), + // Glsl(Cow<'a, str>), + SpirV(Cow<'a, [u32]>), +} + +// impl<'a> ShaderSource<'a> { +// pub fn from_spirv_bytes(data: &'a [u8]) -> Self { +// Self::SpirV(spirv_raw_from_bytes(data)) +// } +// } + +// fn spirv_raw_from_bytes(data: &[u8]) -> Cow<'_, [u32]> { +// const MAGIC_NUMBER: u32 = 0x0723_0203; + +// assert!(data.len() % 4 == 0, "data size is not a multiple of 4"); + +// let (pre, words, post) = unsafe { data.align_to::() }; +// let words = if pre.is_empty() { +// // is already aligned +// debug_assert!(post.is_empty()); +// Cow::Borrowed(words) +// } else { +// // copy into aligned Vec +// let mut words = vec![0u32; data.len() / 4]; +// unsafe { +// std::ptr::copy_nonoverlapping(data.as_ptr(), words.as_mut_ptr() as *mut u8, data.len()); +// } +// Cow::from(words) +// }; + +// assert_eq!(words[0], MAGIC_NUMBER, "wrong magic word {:x}. Not a SPIRV file.", words[0]); + +// words +// } + +// #[macro_export] +// macro_rules! compile_shader { +// ($filename:literal) => { +// $crate::shader::ShaderModuleDescriptor { +// label: Some($filename), +// #[cfg(not(target_arch = "wasm32"))] +// source: $crate::shader::ShaderSource::SpirV(::std::borrow::Cow::Borrowed($crate::shader::__compile_shader_int!( +// target_format = "SpirV", +// source = $filename, +// ))), +// #[cfg(target_arch = "wasm32")] +// source: $crate::shader::ShaderSource::Wgsl(::std::borrow::Cow::Borrowed($crate::shader::__compile_shader_int!( +// target_format = "Wgsl", +// source = $filename, +// ))), +// } +// }; +// } + +/// Macro to load a WGSL shader module statically. +#[macro_export] +macro_rules! include_wgsl { + ($file:literal) => { + &$crate::shader::ShaderModuleDescriptor { + label: Some($file), + source: $crate::shader::ShaderSource::Wgsl(::std::borrow::Cow::Borrowed(include_str!( + $file + ))), + } + }; +} + +// #[macro_export] +// macro_rules! include_glsl { +// ($file:literal) => { +// &$crate::shader::ShaderModuleDescriptor { +// label: Some($file), +// source: $crate::shader::ShaderSource::Glsl(::std::borrow::Cow::Borrowed(include_str!($file))), +// } +// }; +// } + +// #[macro_export] +// macro_rules! include_spirv { +// ($file:literal) => { +// &$crate::shader::ShaderModuleDescriptor { +// label: Some($file), +// source: $crate::shader::ShaderSource::from_spirv_bytes(include_bytes!($file)), +// } +// }; +// } diff --git a/crates/render/src/shader/preprocessor.rs b/crates/render/src/shader/preprocessor.rs new file mode 100644 index 0000000..0e051c6 --- /dev/null +++ b/crates/render/src/shader/preprocessor.rs @@ -0,0 +1,279 @@ +use std::{borrow::Cow, collections::VecDeque}; + +use fnv::FnvHashMap; + +pub struct Preprocessor<'a> { + input: &'a str, + current_start: usize, + current: usize, + line: usize, + ifdef_state: VecDeque, + next_buffered: Option<&'a str>, + defines: FnvHashMap<&'a str, &'a str>, +} + +#[derive(PartialEq, Eq, Copy, Clone)] +enum PreprocessorState { + Neutral, + String, + Char, + Ident, + DirectiveStart, + DirectiveName(usize), + DirectiveArg(usize, usize), + Other, +} + +enum Token<'a> { + Ident(&'a str), + Directive(&'a str, &'a str), + Other, +} + +impl<'a> Preprocessor<'a> { + pub fn new(input: &'a str) -> Self { + Self { + input, + current_start: 0, + current: 0, + line: 0, + next_buffered: None, + ifdef_state: VecDeque::new(), + defines: FnvHashMap::default(), + } + } + + pub fn define(&mut self, key: &'a str, value: &'a str) -> &mut Self { + self.defines.insert(key, value); + self + } + + pub fn process(mut self) -> Cow<'a, str> { + let Some(first) = self.next() else { + return Cow::Borrowed(""); + }; + let Some(more) = self.next() else { + return Cow::Borrowed(first); + }; + let mut owned = first.to_string(); + owned.push_str(more); + for more in self { + owned.push_str(more); + } + Cow::Owned(owned) + } + + fn next_token(&mut self) -> Option> { + use self::PreprocessorState::*; + self.current_start = self.current; + let mut state = Neutral; + let bytes = self.input.as_bytes(); + while let Some(&c) = bytes.get(self.current) { + match state { + Neutral => { + self.current += 1; + if c == b'#' { + state = DirectiveStart; + } else if c == b'"' { + state = String; + } else if c == b'\'' { + state = Char; + } else if matches!(c, b'A'..=b'Z' | b'a'..=b'z' | b'_') { + state = Ident; + } else if c == b'\n' { + self.line += 1; + state = Other; + } else { + state = Other; + } + } + String | Char => { + self.current += 1; + if state == String && c == b'"' || state == Char && c == b'\'' { + state = Other; + } else if c == b'\\' { + // next character escaped + self.current += 1; + } + } + Ident => { + if matches!(c, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'_') { + self.current += 1; + } else { + break; + } + } + DirectiveStart => { + if matches!(c, b' ' | b'\t' | b'\r' | b'\x0C') { + self.current += 1; + } else { + state = DirectiveName(self.current); + } + } + DirectiveName(name_start) => { + if matches!(c, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'_') { + self.current += 1; + } else { + state = DirectiveArg(name_start, self.current); + } + } + DirectiveArg(_, _) => { + if c == b'\n' { + break; + } else { + self.current += 1; + } + } + Other => { + if c == b'\n' { + self.line += 1; + self.current += 1; + } else if matches!(c, b'A'..=b'Z' | b'a'..=b'z' | b'_' | b'#' | b'"' | b'\'') { + break; + } else { + self.current += 1; + } + } + } + } + match state { + Neutral => None, + Ident => Some(Token::Ident(&self.input[self.current_start..self.current])), + DirectiveStart => Some(Token::Directive("", "")), + DirectiveName(name_start) => Some(Token::Directive( + self.input[name_start..self.current].trim_start(), + "", + )), + DirectiveArg(name_start, arg_start) => Some(Token::Directive( + self.input[name_start..arg_start].trim_start(), + self.input[arg_start..self.current].trim(), + )), + String | Char | Other => Some(Token::Other), + } + } + + fn evaluate_ifdef(&self, arg: &str) -> bool { + self.defines.contains_key(arg) + } + + fn push_ifdef_state(&mut self, eval: F) -> bool + where + F: FnOnce(&Self) -> bool, + { + let mut state = self.ifdef_state.back().copied().unwrap_or(true); + if state { + state = eval(self); + } + self.ifdef_state.push_back(state); + state + } + + fn pop_ifdef_state(&mut self) -> bool { + self.ifdef_state.pop_back().unwrap_or(true) + } + + fn ifdef_state(&self) -> bool { + self.ifdef_state.back().copied().unwrap_or(true) + } +} + +impl<'a> Iterator for Preprocessor<'a> { + type Item = &'a str; + fn next(&mut self) -> Option<&'a str> { + use self::Token::*; + if let Some(next_buffered) = self.next_buffered.take() { + return Some(next_buffered); + } + + let mut start = None; + let mut end = self.current; + while let Some(token) = self.next_token() { + match token { + Directive("ifdef", arg) => { + self.push_ifdef_state(|s| s.evaluate_ifdef(arg)); + if let Some(start) = start { + return Some(&self.input[start..end]); + } + } + Directive("ifndef", arg) => { + self.push_ifdef_state(|s| !s.evaluate_ifdef(arg)); + if let Some(start) = start { + return Some(&self.input[start..end]); + } + } + Directive("elifdef", arg) => { + let old_state = self.pop_ifdef_state(); + self.push_ifdef_state(|s| !old_state && s.evaluate_ifdef(arg)); + if let Some(start) = start { + return Some(&self.input[start..end]); + } + } + Directive("elifndef", arg) => { + let old_state = self.pop_ifdef_state(); + self.push_ifdef_state(|s| !old_state && !s.evaluate_ifdef(arg)); + if let Some(start) = start { + return Some(&self.input[start..end]); + } + } + Directive("else", _) => { + let old_state = self.pop_ifdef_state(); + self.push_ifdef_state(|_| !old_state); + if let Some(start) = start { + return Some(&self.input[start..end]); + } + } + Directive("endif", _) => { + self.pop_ifdef_state(); + if let Some(start) = start { + return Some(&self.input[start..end]); + } + } + Directive("define", arg) => { + let mut args = arg.splitn(2, |c: char| c.is_ascii_whitespace()); + if let Some(key) = args.next() { + let value = args.next().unwrap_or(""); + self.defines.insert(key, value); + } + if let Some(start) = start { + return Some(&self.input[start..end]); + } + } + Ident(ident) => { + if self.ifdef_state() { + if let Some(value) = self.defines.get(ident) { + if let Some(start) = start { + self.next_buffered = Some(value); + return Some(&self.input[start..end]); + } else { + return Some(value); + } + } else { + if start.is_none() { + start = Some(self.current_start); + } + end = self.current; + } + } else if let Some(start) = start { + return Some(&self.input[start..end]); + } + } + _ => { + // pass unknown directives, and other tokens + if self.ifdef_state() { + if start.is_none() { + start = Some(self.current_start); + } + end = self.current; + } else if let Some(start) = start { + return Some(&self.input[start..end]); + } + } + } + } + if let Some(start) = start { + Some(&self.input[start..end]) + } else { + None + } + } +} diff --git a/crates/render/src/texture/descriptor.rs b/crates/render/src/texture/descriptor.rs new file mode 100644 index 0000000..79e1da8 --- /dev/null +++ b/crates/render/src/texture/descriptor.rs @@ -0,0 +1,296 @@ +use bitflags::bitflags; + +use crate::math::{uvec2, uvec3, USize2, USize3}; + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct TextureDescriptor { + pub dimensions: TextureDimensions, + pub mip_level_count: u32, + pub sample_count: u32, + pub format: TextureFormat, + pub aspects: TextureAspects, + pub usage: TextureUsage, +} + +impl TextureDescriptor { + pub const fn new() -> Self { + Self { + dimensions: TextureDimensions::D2(USize2::ONE), + mip_level_count: 1, + sample_count: 1, + format: TextureFormat::DEFAULT, + aspects: TextureAspects::DEFAULT, + usage: TextureUsage::ALL_READ, + } + } + + pub fn aspects(&self) -> TextureAspects { + if self.aspects.is_empty() { + self.format.aspects() + } else { + self.aspects + } + } + + #[inline] + pub fn data_layout(&self) -> Option { + ImageDataLayout::from_format(self.dimensions.subimage_extents(), self.format) + } +} + +impl Default for TextureDescriptor { + fn default() -> Self { + Self::new() + } +} + +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +pub enum TextureDimensions { + D1(u32), + D2(USize2), + D2Array { size: USize2, array_len: u32 }, + Cube(USize2), + CubeArray { size: USize2, array_len: u32 }, + D3(USize3), +} + +impl TextureDimensions { + #[inline] + pub fn extents(&self) -> USize3 { + match *self { + Self::D1(len) => uvec3(len, 1, 1), + Self::D2(size) => uvec3(size.x, size.y, 1), + Self::D2Array { size, array_len } => uvec3(size.x, size.y, array_len), + Self::Cube(size) => uvec3(size.x, size.y, 6), + Self::CubeArray { size, array_len } => uvec3(size.x, size.y, array_len * 6), + Self::D3(size) => size, + } + } + + #[inline] + pub fn subimage_extents(&self) -> USize2 { + match *self { + Self::D1(len) => uvec2(len, 1), + Self::D2(size) => size, + Self::D2Array { size, .. } => size, + Self::Cube(size) => size, + Self::CubeArray { size, .. } => size, + Self::D3(size) => uvec2(size.x, size.y), + } + } +} + +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +#[non_exhaustive] +pub enum TextureFormat { + // 8-bit formats + R8Unorm = 0, + R8Snorm = 1, + R8Uint = 2, + R8Sint = 3, + + // 16-bit formats + R16Uint = 4, + R16Sint = 5, + R16Float = 6, + Rg8Unorm = 7, + Rg8Snorm = 8, + Rg8Uint = 9, + Rg8Sint = 10, + + // 32-bit formats + R32Uint = 11, + R32Sint = 12, + R32Float = 13, + Rg16Uint = 14, + Rg16Sint = 15, + Rg16Float = 16, + Rgba8Unorm = 17, + Rgba8UnormSrgb = 18, + Rgba8Snorm = 19, + Rgba8Uint = 20, + Rgba8Sint = 21, + Bgra8Unorm = 22, + Bgra8UnormSrgb = 23, + + // Packed 32-bit formats + Rgb9E5Ufloat = 24, + Rgb10A2Unorm = 25, + Rg11B10Float = 26, + + // 64-bit formats + Rg32Uint = 27, + Rg32Sint = 28, + Rg32Float = 29, + Rgba16Uint = 30, + Rgba16Sint = 31, + Rgba16Float = 32, + + // 128-bit formats + Rgba32Uint = 33, + Rgba32Sint = 34, + Rgba32Float = 35, + + // Depth and stencil formats + // https://gpuweb.github.io/gpuweb/#depth-formats + Depth16Unorm = 36, // depth, 2 bytes per pixel + Depth24Plus = 37, // depth, (3-)4 bytes per pixel!!! + Depth32Float = 38, // depth 4 bytes per pixel + + // // these can have a variable size! + Stencil8 = 39, // stencil, 1-4 bytes per pixel + Depth24PlusStencil8 = 40, // depth+stencil, 4-8 bytes per pixel +} + +impl TextureFormat { + pub const DEFAULT: Self = Self::Rgba8UnormSrgb; + + pub fn num_components(self) -> u8 { + use self::TextureFormat::*; + match self { + // 8-bit formats + R8Unorm | R8Snorm | R8Uint | R8Sint => 1, + + // 16-bit formats + R16Uint | R16Sint | R16Float => 1, + Rg8Unorm | Rg8Snorm | Rg8Uint | Rg8Sint => 2, + + // 32-bit formats + R32Uint | R32Sint | R32Float => 1, + Rg16Uint | Rg16Sint | Rg16Float => 2, + Rgba8Unorm | Rgba8UnormSrgb | Rgba8Snorm | Rgba8Uint | Rgba8Sint | Bgra8Unorm + | Bgra8UnormSrgb => 4, + + // Packed 32-bit formats + Rgb9E5Ufloat => 3, + Rgb10A2Unorm => 4, + Rg11B10Float => 3, + + // 64-bit formats + Rg32Uint | Rg32Sint | Rg32Float => 2, + Rgba16Uint | Rgba16Sint | Rgba16Float => 4, + + // 128-bit formats + Rgba32Uint | Rgba32Sint | Rgba32Float => 4, + + // Depth and stencil formats + Depth32Float => 1, + Depth16Unorm => 1, + + // // these can have a variable size! + Stencil8 => 1, + Depth24Plus => 1, + Depth24PlusStencil8 => 2, + } + } + + pub fn bytes_per_pixel(self) -> Option { + use self::TextureFormat::*; + Some(match self { + // 8-bit formats + R8Unorm | R8Snorm | R8Uint | R8Sint => 1, + + // 16-bit formats + R16Uint | R16Sint | R16Float | Rg8Unorm | Rg8Snorm | Rg8Uint | Rg8Sint => 2, + + // 32-bit formats + R32Uint | R32Sint | R32Float | Rg16Uint | Rg16Sint | Rg16Float | Rgba8Unorm + | Rgba8UnormSrgb | Rgba8Snorm | Rgba8Uint | Rgba8Sint | Bgra8Unorm | Bgra8UnormSrgb => { + 4 + } + + // Packed 32-bit formats + Rgb9E5Ufloat | Rgb10A2Unorm | Rg11B10Float => 4, + + // 64-bit formats + Rg32Uint | Rg32Sint | Rg32Float | Rgba16Uint | Rgba16Sint | Rgba16Float => 8, + + // 128-bit formats + Rgba32Uint | Rgba32Sint | Rgba32Float => 16, + + // Depth and stencil formats + Depth16Unorm => 2, + Depth32Float => 4, + + // these can have a variable size! + // https://gpuweb.github.io/gpuweb/#depth-formats + // TODO: provide a way to query these + Stencil8 | Depth24Plus | Depth24PlusStencil8 => { + return None; + } + }) + } +} + +impl TextureFormat { + pub fn aspects(self) -> TextureAspects { + match self { + Self::Stencil8 => TextureAspects::STENCIL, + Self::Depth16Unorm | Self::Depth24Plus | Self::Depth32Float => TextureAspects::DEPTH, + Self::Depth24PlusStencil8 => TextureAspects::DEPTH | TextureAspects::STENCIL, + + _ => TextureAspects::COLOR, + } + } +} + +impl Default for TextureFormat { + #[inline] + fn default() -> Self { + Self::DEFAULT + } +} + +bitflags! { + pub struct TextureAspects: u32 { + const COLOR = 1; + const DEPTH = 2; + const STENCIL = 4; + + const DEFAULT = 0; + } +} + +impl Default for TextureAspects { + #[inline] + fn default() -> Self { + Self::DEFAULT + } +} + +bitflags! { + pub struct TextureUsage: u32 { + const TRANSFER_SRC = 1; + const TRANSFER_DST = 2; + const TEXTURE_BINDING = 4; + const STORAGE_BINDING = 8; + const COLOR_ATTACHMENT = 16; + const DEPTH_STENCIL_ATTACHMENT = 32; + const INPUT_ATTACHMENT = 64; + + // modifiers + const BY_REGION = 128; + + const NONE = 0; + const ALL_READ = Self::TRANSFER_SRC.bits | Self::TEXTURE_BINDING.bits | Self::INPUT_ATTACHMENT.bits; + const ALL_WRITE = Self::TRANSFER_DST.bits | Self::STORAGE_BINDING.bits | Self::COLOR_ATTACHMENT.bits | Self::DEPTH_STENCIL_ATTACHMENT.bits; + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)] +pub struct ImageDataLayout { + pub offset: usize, + pub bytes_per_row: u32, + pub rows_per_image: u32, +} + +impl ImageDataLayout { + pub fn from_format(extents: USize2, format: TextureFormat) -> Option { + let bytes_per_pixel = format.bytes_per_pixel()?; + Some(Self { + offset: 0, + bytes_per_row: extents.x * bytes_per_pixel as u32, + rows_per_image: extents.y, + }) + } +} diff --git a/crates/render/src/texture/image.rs b/crates/render/src/texture/image.rs new file mode 100644 index 0000000..bb7fec0 --- /dev/null +++ b/crates/render/src/texture/image.rs @@ -0,0 +1,154 @@ +use image::{buffer::ConvertBuffer, ImageFormat}; +use thiserror::Error; + +use super::{ImageDataLayout, TextureDescriptor, TextureDimensions, TextureFormat}; +use crate::math::uvec2; + +pub struct Image { + pub data: Vec, + pub layout: ImageDataLayout, + pub descriptor: TextureDescriptor, +} + +#[derive(Debug, Error)] +#[non_exhaustive] +pub enum ImageError { + #[error("Unable to detect image format")] + UnknownFormat, + + #[error("Unsupported image layout")] + UnsupportedImageLayout, + + #[error("unable to load image")] + ImageError(#[from] image::ImageError), +} + +// impl RenderAsset for Image { +// type Target = Texture; + +// fn prepare(&self, backend: &mut B) -> Self::Target { +// let texture = backend.create_texture(&self.descriptor).unwrap(); +// backend.write_image(&texture, self); +// texture +// } +// } + +impl Image { + fn from_image_buffer_with_format

( + format: TextureFormat, + image: image::ImageBuffer>, + ) -> Self + where + P: image::Pixel + 'static, + P::Subpixel: bytemuck::Pod, + { + let (width, height) = image.dimensions(); + let bytes_per_pixel = std::mem::size_of::

(); + let bytes_per_row = bytes_per_pixel as u32 * width; + let data = image.into_raw(); + let bytes: Vec = bytemuck::cast_vec(data); + Self { + data: bytes, + layout: ImageDataLayout { + offset: 0, + bytes_per_row, + rows_per_image: height, + }, + descriptor: TextureDescriptor { + format, + dimensions: TextureDimensions::D2(uvec2(width, height)), + ..Default::default() + }, + } + } + + pub fn load( + file_ext: Option<&str>, + mimetype: Option<&str>, + data: &[u8], + ) -> Result { + let format = mimetype + .and_then(format_from_mimetype) + .or_else(|| file_ext.and_then(ImageFormat::from_extension)) + .or_else(|| image::guess_format(data).ok()) + .ok_or(ImageError::UnknownFormat)?; + let image = image::load_from_memory_with_format(data, format)?; + image.try_into() + } +} + +fn format_from_mimetype(mime: &str) -> Option { + // STRIP: ("image/"|"application/")(-x)?EXTENSION(:.*) + let mut ext = mime + .strip_prefix("image/") + .or_else(|| mime.strip_prefix("application/"))?; + if let Some(offset) = ext.find(':') { + ext = &ext[0..offset]; + }; + match ext { + // map special names + "x-icon" | "vnd.microsoft.icon" => Some(ImageFormat::Ico), + "vnd.radiance" => Some(ImageFormat::Hdr), + "x-portable-bitmap" | "x-portable-pixmap" => Some(ImageFormat::Pnm), + // map names by extension + _ => { + if let Some(rest) = ext.strip_prefix("x-") { + ext = rest; + } + ImageFormat::from_extension(ext) + } + } +} + +impl TryFrom for Image { + type Error = ImageError; + fn try_from(image: image::DynamicImage) -> Result { + use image::DynamicImage; + Ok(match image { + DynamicImage::ImageLuma8(buffer) => buffer.into(), + DynamicImage::ImageLumaA8(buffer) => buffer.into(), + DynamicImage::ImageRgb8(buffer) => buffer.into(), + DynamicImage::ImageRgba8(buffer) => buffer.into(), + DynamicImage::ImageLuma16(buffer) => buffer.into(), + DynamicImage::ImageLumaA16(buffer) => buffer.into(), + DynamicImage::ImageRgb16(buffer) => buffer.into(), + DynamicImage::ImageRgba16(buffer) => buffer.into(), + DynamicImage::ImageRgb32F(buffer) => buffer.into(), + DynamicImage::ImageRgba32F(buffer) => buffer.into(), + _ => { + return Err(ImageError::UnsupportedImageLayout); + } + }) + } +} + +macro_rules! impl_from_imagebuffer { + ($( + $imgform:ident <$prim:ty> $(as $imgconv:ident <$convprim:ty>)? => $texform:ident ; + )*) => {$( + impl From, Vec<$prim>>> for Image { + #[inline] + fn from(image: image::ImageBuffer, Vec<$prim>>) -> Self { + $( + let image: image::ImageBuffer, Vec<$convprim>> = image.convert(); + )? + Self::from_image_buffer_with_format(TextureFormat::$texform, image) + } + } + )*}; +} + +impl_from_imagebuffer! { + Luma => R8Unorm; + LumaA => Rg8Unorm; + Rgb as Rgba => Rgba8UnormSrgb; + Rgba => Rgba8UnormSrgb; + Luma as Luma => R32Float; + LumaA as LumaA => Rg32Float; + Rgb as Rgba => Rgba32Float; + Rgba as Rgba => Rgba32Float; + Luma => R32Float; + LumaA => Rg32Float; + Rgb as Rgba => Rgba32Float; + Rgba => Rgba32Float; +} diff --git a/crates/render/src/texture/mod.rs b/crates/render/src/texture/mod.rs new file mode 100644 index 0000000..7d8cce4 --- /dev/null +++ b/crates/render/src/texture/mod.rs @@ -0,0 +1,6 @@ +mod descriptor; +mod image; + +pub use self::{descriptor::*, image::*}; + +crate::backend::define_gpu_resource!(Texture, Textures, TextureDescriptor); diff --git a/crates/render/src/view.rs b/crates/render/src/view.rs new file mode 100644 index 0000000..7868872 --- /dev/null +++ b/crates/render/src/view.rs @@ -0,0 +1,76 @@ +use std::ops::Range; + +use pulz_ecs::Component; +use pulz_transform::{ + components::GlobalTransform, + math::{USize2, UVec2, Vec2, Vec3}, +}; + +use crate::{math::Mat4, shader::ShaderType, texture::Texture}; + +pub struct View { + pub projection: Mat4, + pub transform: GlobalTransform, + pub size: USize2, +} + +#[derive(Clone, ShaderType)] +struct ViewUniform { + view_proj: Mat4, + inverse_view_projection: Mat4, + view: Mat4, + inverse_view: Mat4, + projection: Mat4, + inverse_projection: Mat4, + world_position: Vec3, + size: Vec2, +} + +impl View { + #[inline] + fn to_uniform(&self) -> ViewUniform { + let projection = self.projection; + let inverse_projection = projection.inverse(); + let view = self.transform.to_matrix(); + let inverse_view = view.inverse(); + let view_proj = projection * inverse_view; + let inverse_view_projection = view * inverse_projection; + let world_position = self.transform.translation; + let size = [self.size.x as f32, self.size.y as f32]; + ViewUniform { + view_proj, + inverse_view_projection, + view, + inverse_view, + projection, + inverse_projection, + world_position, + size: size.into(), + } + } +} + +#[derive(Component)] +pub struct ViewTarget { + pub target: Texture, + pub sampled: Option, + pub depth: Option, +} + +#[derive(Component)] +pub struct Viewport { + pub position: UVec2, + pub size: USize2, + pub depth: Range, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct Msaa { + pub samples: u32, +} + +impl Default for Msaa { + fn default() -> Self { + Self { samples: 4 } + } +} diff --git a/crates/transform/CHANGELOG.md b/crates/transform/CHANGELOG.md new file mode 100644 index 0000000..7fda291 --- /dev/null +++ b/crates/transform/CHANGELOG.md @@ -0,0 +1,6 @@ +# `pulz-transform` Changelog +All notable changes to this crate will be documented in this file. + +## Unreleased (DATE) + + * Initial version diff --git a/crates/transform/Cargo.toml b/crates/transform/Cargo.toml new file mode 100644 index 0000000..bb8c9ab --- /dev/null +++ b/crates/transform/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "pulz-transform" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true +readme = "README.md" + +[dependencies] +pulz-ecs = { path = "../ecs" } + +glam = { workspace = true, features = ["mint", "bytemuck", "serde"] } +serde = { workspace = true, features = ["derive"] } diff --git a/crates/transform/README.md b/crates/transform/README.md new file mode 100644 index 0000000..fb0bb1b --- /dev/null +++ b/crates/transform/README.md @@ -0,0 +1,34 @@ +# `pulz-transform` + + + +[![Crates.io](https://img.shields.io/crates/v/pulz-transform.svg?label=pulz-transform)](https://crates.io/crates/pulz-transform) +[![docs.rs](https://docs.rs/pulz-transform/badge.svg)](https://docs.rs/pulz-transform/) +[![license: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](#license) +[![Rust CI](https://github.com/HellButcher/pulz/actions/workflows/rust.yml/badge.svg)](https://github.com/HellButcher/pulz/actions/workflows/rust.yml) + + +**TODO** + +## Example + + +**TODO** + +## License + +[license]: #license + +This project is licensed under either of + +* MIT license ([LICENSE-MIT] or ) +* Apache License, Version 2.0, ([LICENSE-APACHE] or ) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[LICENSE-MIT]: ../../LICENSE-MIT +[LICENSE-APACHE]: ../../LICENSE-APACHE diff --git a/crates/transform/src/components/mod.rs b/crates/transform/src/components/mod.rs new file mode 100644 index 0000000..6efac93 --- /dev/null +++ b/crates/transform/src/components/mod.rs @@ -0,0 +1,5 @@ +mod parent; +mod transform; + +pub use parent::*; +pub use transform::*; diff --git a/crates/transform/src/components/parent.rs b/crates/transform/src/components/parent.rs new file mode 100644 index 0000000..6857703 --- /dev/null +++ b/crates/transform/src/components/parent.rs @@ -0,0 +1,18 @@ +use pulz_ecs::prelude::*; + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Component)] +pub struct Parent(pub Entity); + +impl std::ops::Deref for Parent { + type Target = Entity; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for Parent { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/crates/transform/src/components/transform.rs b/crates/transform/src/components/transform.rs new file mode 100644 index 0000000..852d1f8 --- /dev/null +++ b/crates/transform/src/components/transform.rs @@ -0,0 +1,176 @@ +use pulz_ecs::prelude::*; + +use crate::math::{Mat3, Mat4, Quat, Vec3}; + +macro_rules! define_transform { + ($Transform:ident) => { + #[derive(Debug, PartialEq, Clone, Copy, Component)] + pub struct $Transform { + pub translation: Vec3, + pub rotation: Quat, + pub scale: Vec3, + } + + impl $Transform { + pub const IDENTITY: Self = Self::identity(); + + #[inline] + pub fn from_xyz(x: f32, y: f32, z: f32) -> Self { + Self::from_translation(Vec3::new(x, y, z)) + } + + #[inline] + pub const fn identity() -> Self { + Self { + translation: Vec3::ZERO, + rotation: Quat::IDENTITY, + scale: Vec3::ONE, + } + } + + #[inline] + pub fn from_matrix(matrix: Mat4) -> Self { + let (scale, rotation, translation) = matrix.to_scale_rotation_translation(); + Self { + translation, + rotation, + scale, + } + } + + #[inline] + pub const fn from_translation(translation: Vec3) -> Self { + Self { + translation, + ..Self::identity() + } + } + + #[inline] + pub const fn from_rotation(rotation: Quat) -> Self { + Self { + rotation, + ..Self::identity() + } + } + + #[inline] + pub const fn from_scale(scale: Vec3) -> Self { + Self { + scale, + ..Self::identity() + } + } + + #[inline] + pub fn looking_at(mut self, target: Vec3, up: Vec3) -> Self { + self.look_at(target, up); + self + } + + #[inline] + pub fn to_matrix(&self) -> Mat4 { + Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation) + } + + #[inline] + pub fn rotate(&mut self, rotation: Quat) { + self.rotation *= rotation; + } + + #[inline] + pub fn look_at(&mut self, target: Vec3, up: Vec3) { + let forward = Vec3::normalize(self.translation - target); + let right = up.cross(forward).normalize(); + let up = forward.cross(right); + self.rotation = Quat::from_mat3(&Mat3::from_cols(right, up, forward)); + } + } + + impl Default for $Transform { + #[inline] + fn default() -> Self { + Self::identity() + } + } + + impl std::ops::Mul for $Transform { + type Output = Self; + + #[inline] + fn mul(self, transform: Self) -> Self::Output { + let translation = self * transform.translation; + let rotation = self.rotation * transform.rotation; + let scale = self.scale * transform.scale; + Self { + translation, + rotation, + scale, + } + } + } + + impl std::ops::Mul for &$Transform { + type Output = $Transform; + + #[inline] + fn mul(self, transform: Self) -> Self::Output { + let translation = self * transform.translation; + let rotation = self.rotation * transform.rotation; + let scale = self.scale * transform.scale; + $Transform { + translation, + rotation, + scale, + } + } + } + + impl std::ops::Mul for $Transform { + type Output = Vec3; + + #[inline] + fn mul(self, mut value: Vec3) -> Self::Output { + value = self.rotation * value; + value = self.scale * value; + value += self.translation; + value + } + } + + impl std::ops::Mul for &$Transform { + type Output = Vec3; + + #[inline] + fn mul(self, mut value: Vec3) -> Self::Output { + value = self.rotation * value; + value = self.scale * value; + value += self.translation; + value + } + } + }; +} + +define_transform!(Transform); +define_transform!(GlobalTransform); + +impl From for Transform { + fn from(transform: GlobalTransform) -> Self { + Self { + translation: transform.translation, + rotation: transform.rotation, + scale: transform.scale, + } + } +} + +impl From for GlobalTransform { + fn from(transform: Transform) -> Self { + Self { + translation: transform.translation, + rotation: transform.rotation, + scale: transform.scale, + } + } +} diff --git a/crates/transform/src/lib.rs b/crates/transform/src/lib.rs new file mode 100644 index 0000000..7e7d67b --- /dev/null +++ b/crates/transform/src/lib.rs @@ -0,0 +1,37 @@ +#![warn( + // missing_docs, + // rustdoc::missing_doc_code_examples, + future_incompatible, + rust_2018_idioms, + unused, + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_qualifications, + unused_crate_dependencies, + clippy::cargo, + clippy::multiple_crate_versions, + clippy::empty_line_after_outer_attr, + clippy::fallible_impl_from, + clippy::redundant_pub_crate, + clippy::use_self, + clippy::suspicious_operation_groupings, + clippy::useless_let_if_seq, + // clippy::missing_errors_doc, + // clippy::missing_panics_doc, + clippy::wildcard_imports +)] +#![doc(html_logo_url = "https://raw.githubusercontent.com/HellButcher/pulz/master/docs/logo.png")] +#![doc(html_no_source)] +#![doc = include_str!("../README.md")] + +pub mod math { + pub use glam::*; + pub type Point2 = Vec2; + pub type Size2 = Vec2; + pub type Point3 = Vec3; + pub type Size3 = Vec3; + pub type USize2 = UVec2; + pub type USize3 = UVec3; +} +pub mod components; diff --git a/crates/window-winit/CHANGELOG.md b/crates/window-winit/CHANGELOG.md new file mode 100644 index 0000000..a251215 --- /dev/null +++ b/crates/window-winit/CHANGELOG.md @@ -0,0 +1,6 @@ +# `pulz-window-winit` Changelog +All notable changes to this crate will be documented in this file. + +## Unreleased (DATE) + + * Initial version diff --git a/crates/window-winit/Cargo.toml b/crates/window-winit/Cargo.toml new file mode 100644 index 0000000..a5a59a4 --- /dev/null +++ b/crates/window-winit/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "pulz-window-winit" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true +readme = "README.md" + +[features] +default = ["winit/x11", "winit/wayland"] +x11 = ["winit/x11"] +wayland = ["winit/wayland"] + +[dependencies] +pulz-ecs = { path = "../ecs" } +pulz-window = { path = "../window" } +pulz-input = { path = "../input" } + +fnv = { workspace = true } +tracing = { workspace = true } +winit = { version = "0.27", default-features = false } + +[target.'cfg(not(target_os = "unknown"))'.dev-dependencies] +tracing-subscriber = { workspace = true } + +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +tracing-wasm = { workspace = true } +tracing-log = { workspace = true } +console_error_panic_hook = { workspace = true } +wasm-bindgen = { workspace = true } +web-sys = { workspace = true, features = ["HtmlCanvasElement"] } diff --git a/crates/window-winit/README.md b/crates/window-winit/README.md new file mode 100644 index 0000000..8aae1f4 --- /dev/null +++ b/crates/window-winit/README.md @@ -0,0 +1,34 @@ +# `pulz-window-winit` + + + +[![Crates.io](https://img.shields.io/crates/v/pulz-window-winit.svg?label=pulz-window-winit)](https://crates.io/crates/pulz-window-winit) +[![docs.rs](https://docs.rs/pulz-window-winit/badge.svg)](https://docs.rs/pulz-window-winit/) +[![license: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](#license) +[![Rust CI](https://github.com/HellButcher/pulz/actions/workflows/rust.yml/badge.svg)](https://github.com/HellButcher/pulz/actions/workflows/rust.yml) + + +**TODO** + +## Example + + +**TODO** + +## License + +[license]: #license + +This project is licensed under either of + +* MIT license ([LICENSE-MIT] or ) +* Apache License, Version 2.0, ([LICENSE-APACHE] or ) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[LICENSE-MIT]: ../../LICENSE-MIT +[LICENSE-APACHE]: ../../LICENSE-APACHE diff --git a/crates/window-winit/examples/window-winit-demo.rs b/crates/window-winit/examples/window-winit-demo.rs new file mode 100644 index 0000000..6e99bd0 --- /dev/null +++ b/crates/window-winit/examples/window-winit-demo.rs @@ -0,0 +1,57 @@ +use std::rc::Rc; + +use pulz_ecs::prelude::*; +use pulz_window::WindowDescriptor; +use pulz_window_winit::{WinitWindowModule, WinitWindowSystem}; +use tracing::*; +use winit::{event_loop::EventLoop, window::Window}; + +fn init() -> (Resources, EventLoop<()>, Rc, WinitWindowSystem) { + info!("Initializing..."); + let mut resources = Resources::new(); + + let event_loop = EventLoop::new(); + + let (window_system, _window_id, window) = + WinitWindowModule::new(WindowDescriptor::default(), &event_loop) + .unwrap() + .install(&mut resources); + + (resources, event_loop, window, window_system) +} + +#[cfg(not(target_arch = "wasm32"))] +fn main() { + use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter}; + let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); + tracing_subscriber::fmt() + .with_env_filter(env_filter) + .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE) + .init(); + + let (resources, event_loop, _window, window_system) = init(); + + window_system.run(resources, event_loop); +} + +#[cfg(target_arch = "wasm32")] +fn main() { + use wasm_bindgen::prelude::*; + use winit::platform::web::WindowExtWebSys; + + console_error_panic_hook::set_once(); + tracing_log::LogTracer::init().expect("unable to create log-tracer"); + tracing_wasm::set_as_global_default(); + + let (resources, event_loop, window, window_system) = init(); + + let canvas = window.canvas(); + canvas.style().set_css_text("background-color: teal;"); + web_sys::window() + .and_then(|win| win.document()) + .and_then(|doc| doc.body()) + .and_then(|body| body.append_child(&web_sys::Element::from(canvas)).ok()) + .expect("couldn't append canvas to document body"); + + window_system.spawn(resources, event_loop); +} diff --git a/crates/window-winit/src/lib.rs b/crates/window-winit/src/lib.rs new file mode 100644 index 0000000..e22c0ac --- /dev/null +++ b/crates/window-winit/src/lib.rs @@ -0,0 +1,448 @@ +#![warn( + // missing_docs, + // rustdoc::missing_doc_code_examples, + future_incompatible, + rust_2018_idioms, + unused, + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_qualifications, + unused_crate_dependencies, + clippy::cargo, + clippy::multiple_crate_versions, + clippy::empty_line_after_outer_attr, + clippy::fallible_impl_from, + clippy::redundant_pub_crate, + clippy::use_self, + clippy::suspicious_operation_groupings, + clippy::useless_let_if_seq, + // clippy::missing_errors_doc, + // clippy::missing_panics_doc, + clippy::wildcard_imports +)] +#![doc(html_logo_url = "https://raw.githubusercontent.com/HellButcher/pulz/master/docs/logo.png")] +#![doc(html_no_source)] +#![doc = include_str!("../README.md")] + +use std::{ops::Deref, rc::Rc}; + +use fnv::FnvHashMap; +use pulz_ecs::prelude::*; +use pulz_window::{ + RawWindow, RawWindowHandles, Size2, WindowDescriptor, WindowId, Windows, WindowsMirror, +}; +use tracing::{debug, info, warn}; +pub use winit; +use winit::{ + dpi::PhysicalSize, + error::OsError, + event::{Event, StartCause, WindowEvent}, + event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, + window::{Window as WinitWindow, WindowId as WinitWindowId}, +}; + +#[derive(Default)] +pub struct WinitWindows { + windows: WindowsMirror>, + window_id_map: FnvHashMap, +} + +impl WinitWindows { + fn builder_for_descriptor(descriptor: &WindowDescriptor) -> winit::window::WindowBuilder { + let mut builder = + winit::window::WindowBuilder::new().with_title(descriptor.title.to_owned()); + + #[cfg(target_os = "windows")] + { + use winit::platform::windows::WindowBuilderExtWindows; + builder = builder.with_drag_and_drop(false); + } + + if descriptor.size != Size2::ZERO { + builder = + builder.with_inner_size(PhysicalSize::new(descriptor.size.x, descriptor.size.y)); + } + + builder + } + + fn create( + &mut self, + window_id: WindowId, + window: &mut WindowDescriptor, + event_loop: &EventLoopWindowTarget, + ) -> Result, OsError> { + let builder = Self::builder_for_descriptor(window); + let winit_window = builder.build(event_loop)?; + Self::update_window_descriptor(window, &winit_window); + debug!( + "created window {:?} with {:?}, {:?}", + window_id, + winit_window.id(), + winit_window.inner_size(), + ); + + Ok(self.insert(window_id, winit_window)) + } + + fn insert(&mut self, window_id: WindowId, winit_window: WinitWindow) -> Rc { + let winit_window = Rc::new(winit_window); + self.windows.insert(window_id, winit_window.clone()); + self.window_id_map.insert(winit_window.id(), window_id); + winit_window + } + + #[inline] + pub fn len(&self) -> usize { + self.windows.len() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.windows.is_empty() + } + + #[inline] + pub fn get(&self, id: WindowId) -> Option<&WinitWindow> { + self.windows.get(id).map(Deref::deref) + } + + fn update_window_descriptor( + window_descriptor: &mut WindowDescriptor, + winit_window: &winit::window::Window, + ) { + window_descriptor.scale_factor = winit_window.scale_factor(); + let phys_size: [u32; 2] = winit_window.inner_size().into(); + window_descriptor.size = phys_size.into(); + } + + fn close(&mut self, window_id: WindowId) -> bool { + let Some(window) = self.windows.remove(window_id) else { + return false; + }; + self.window_id_map.remove(&window.id()); + window.set_visible(false); + drop(window); + true + } +} + +impl std::ops::Index for WinitWindows { + type Output = WinitWindow; + #[inline] + fn index(&self, id: WindowId) -> &Self::Output { + &self.windows[id] + } +} + +pub struct WinitWindowSystem { + windows_id: ResourceId, + winit_windows_id: ResourceId, + raw_window_handles_id: ResourceId, + active: bool, +} + +pub struct WinitWindowSystemMut<'l> { + windows: ResMut<'l, Windows>, + winit_windows: ResMut<'l, WinitWindows>, + raw_window_handles: ResMut<'l, RawWindowHandles>, +} + +pub struct WinitWindowModule { + descriptor: WindowDescriptor, + window: WinitWindow, +} + +impl WinitWindowModule { + pub fn new( + mut descriptor: WindowDescriptor, + event_loop: &EventLoopWindowTarget, + ) -> Result { + let builder = WinitWindows::builder_for_descriptor(&descriptor); + let window = builder.build(event_loop)?; + WinitWindows::update_window_descriptor(&mut descriptor, &window); + Ok(Self { descriptor, window }) + } + + pub fn from_window(window: WinitWindow) -> Self { + let mut descriptor = WindowDescriptor::default(); + WinitWindows::update_window_descriptor(&mut descriptor, &window); + Self { descriptor, window } + } +} + +impl ModuleWithOutput for WinitWindowModule { + type Output<'l> = (WinitWindowSystem, WindowId, Rc); + fn install_resources(self, resources: &mut Resources) -> Self::Output<'_> { + let sys = WinitWindowSystem::install(resources); + let mut sys_mut = sys.as_mut(resources); + let (window_id, window) = + sys_mut.add_winit_window_with_descriptor(self.descriptor, self.window); + (sys, window_id, window) + } +} + +impl WinitWindowSystemMut<'_> { + pub fn add_window( + &mut self, + descriptor: WindowDescriptor, + event_loop: &EventLoopWindowTarget, + ) -> Result<(WindowId, Rc), OsError> { + let window_id = self.windows.create(descriptor); + let winit_window = + self.winit_windows + .create(window_id, &mut self.windows[window_id], event_loop)?; + let raw_window: Rc = winit_window.clone(); + self.raw_window_handles + .insert(window_id, Rc::downgrade(&raw_window)); + Ok((window_id, winit_window)) + } + + pub fn add_winit_window(&mut self, window: WinitWindow) -> WindowId { + let mut descriptor = WindowDescriptor::default(); + WinitWindows::update_window_descriptor(&mut descriptor, &window); + self.add_winit_window_with_descriptor(descriptor, window).0 + } + + fn add_winit_window_with_descriptor( + &mut self, + descriptor: WindowDescriptor, + window: WinitWindow, + ) -> (WindowId, Rc) { + let window_id = self.windows.create(descriptor); + let window = self.winit_windows.insert(window_id, window); + let raw_window: Rc = window.clone(); + self.raw_window_handles + .insert(window_id, Rc::downgrade(&raw_window)); + debug!( + "Added window {:?} with {:?}, {:?}", + window_id, + window.id(), + window.inner_size() + ); + (window_id, window) + } + + pub fn update_windows( + &mut self, + event_loop: &EventLoopWindowTarget, + ) -> Result<(), OsError> { + for (window_id, window) in self.windows.iter_mut() { + if self.winit_windows.windows.get(window_id).is_none() { + // create missing window + let winit_window: Rc = + self.winit_windows.create(window_id, window, event_loop)?; + self.raw_window_handles + .insert(window_id, Rc::downgrade(&winit_window)); + } + + // handle commands + // TODO + } + Ok(()) + } + + fn handle_window_event(&mut self, window_id: WinitWindowId, event: WindowEvent<'_>) { + if let Some(&window_id) = self.winit_windows.window_id_map.get(&window_id) { + if matches!(event, WindowEvent::Destroyed) { + self.windows.close(window_id); + self.winit_windows.close(window_id); + self.raw_window_handles.remove(window_id); + } else if let Some(window) = self.windows.get_mut(window_id) { + match event { + WindowEvent::CloseRequested => { + window.close_requested = true; + } + WindowEvent::Resized(size) => { + let phys_size: [u32; 2] = size.into(); + window.size = phys_size.into(); + } + WindowEvent::ScaleFactorChanged { + scale_factor, + new_inner_size, + } => { + let phys_size: [u32; 2] = (*new_inner_size).into(); + window.scale_factor = scale_factor; + window.size = phys_size.into(); + } + _ => {} + } + } + } + } + + fn handle_close(&mut self) -> bool { + let mut to_close = Vec::new(); + for (window_id, _) in self.winit_windows.windows.iter() { + match self.windows.get(window_id) { + None => to_close.push(window_id), + Some(w) if w.close_requested => to_close.push(window_id), + _ => {} + } + } + if !to_close.is_empty() { + debug!("Closing {} windows", to_close.len()); + for window_id in to_close { + self.windows.close(window_id); + self.raw_window_handles.remove(window_id); + self.winit_windows.close(window_id); + } + } + self.winit_windows.windows.is_empty() // all windows closed + } +} + +impl WinitWindowSystem { + fn install(res: &mut Resources) -> Self { + let windows_id = res.init::(); + let winit_windows_id = res.init_unsend::(); + let raw_window_handles_id = res.init_unsend::(); + Self { + windows_id, + winit_windows_id, + raw_window_handles_id, + active: false, + } + } + + fn as_mut<'l>(&self, res: &'l mut Resources) -> WinitWindowSystemMut<'l> { + WinitWindowSystemMut { + windows: res.borrow_res_mut_id(self.windows_id).unwrap(), + winit_windows: res.borrow_res_mut_id(self.winit_windows_id).unwrap(), + raw_window_handles: res.borrow_res_mut_id(self.raw_window_handles_id).unwrap(), + } + } + + pub fn handle_event( + &mut self, + resources: &mut Resources, + schedule: &mut Schedule, + event: Event<'_, T>, + event_loop: &EventLoopWindowTarget, + control_flow: &mut ControlFlow, + ) { + *control_flow = winit::event_loop::ControlFlow::Poll; + + match event { + Event::NewEvents(StartCause::Init) => { + info!("event loop started..."); + self.as_mut(resources).update_windows(event_loop).unwrap(); + self.active = true; + } + Event::WindowEvent { window_id, event } => { + self.as_mut(resources).handle_window_event(window_id, event); + } + Event::Suspended => { + info!("suspended"); + self.active = false; + // TODO: ON ANDROID: all surfaces need to be destroyed, and re-created on RESUME + *control_flow = winit::event_loop::ControlFlow::Wait; + } + Event::Resumed => { + info!("resumed"); + self.active = true; + // TODO: ON ANDROID: surface-creation needs to be delayed until this Event + // TODO: clever way how to link that to the render-system, and delay creation of device + *control_flow = winit::event_loop::ControlFlow::Poll; + } + Event::MainEventsCleared => { + self.as_mut(resources).update_windows(event_loop).unwrap(); + if self.active { + schedule.run(resources); + } + if self.as_mut(resources).handle_close() { + // all windows closed + *control_flow = winit::event_loop::ControlFlow::Exit; + } + } + Event::LoopDestroyed => { + info!("event loop ended"); + self.active = false; + } + _ => {} + } + } + + pub fn run(mut self, mut resources: Resources, event_loop: EventLoop) -> ! { + let schedule_id = resources.init_unsend::(); + let mut schedule = resources.remove_id(schedule_id).unwrap(); + + let event_loop_span = tracing::trace_span!("EventLoop"); + + event_loop.run(move |event, event_loop, control_flow| { + let span = event_loop_span.enter(); + self.handle_event( + &mut resources, + &mut schedule, + event, + event_loop, + control_flow, + ); + drop(span); + }) + } + + #[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "android", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + pub fn run_return( + &mut self, + resources: &mut Resources, + event_loop: &mut EventLoop, + ) -> i32 { + use winit::platform::run_return::EventLoopExtRunReturn; + + let schedule_id = resources.init_unsend::(); + let mut schedule = resources.remove_id(schedule_id).unwrap(); + + let event_loop_span = tracing::trace_span!("EventLoop"); + + let result = event_loop.run_return(|event, event_loop, control_flow| { + let span = event_loop_span.enter(); + self.handle_event(resources, &mut schedule, event, event_loop, control_flow); + drop(span); + }); + + resources.insert_again(schedule); + + result + } + + #[cfg(target_arch = "wasm32")] + pub fn spawn(mut self, mut resources: Resources, event_loop: EventLoop) { + use winit::platform::web::EventLoopExtWebSys; + + let schedule_id = resources.init_unsend::(); + let mut schedule = resources.remove_id(schedule_id).unwrap(); + + let event_loop_span = tracing::trace_span!("EventLoop"); + + event_loop.spawn(move |event, event_loop, control_flow| { + let span = event_loop_span.enter(); + self.handle_event( + &mut resources, + &mut schedule, + event, + event_loop, + control_flow, + ); + drop(span); + }) + } +} + +impl ModuleWithOutput for WinitWindowSystem { + type Output<'l> = Self; + + fn install_resources(self, resources: &mut Resources) -> Self { + Self::install(resources) + } +} diff --git a/crates/window/CHANGELOG.md b/crates/window/CHANGELOG.md new file mode 100644 index 0000000..8758e92 --- /dev/null +++ b/crates/window/CHANGELOG.md @@ -0,0 +1,6 @@ +# `pulz-window` Changelog +All notable changes to this crate will be documented in this file. + +## Unreleased (DATE) + + * Initial version diff --git a/crates/window/Cargo.toml b/crates/window/Cargo.toml new file mode 100644 index 0000000..e747de1 --- /dev/null +++ b/crates/window/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "pulz-window" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true +readme = "README.md" + +[dependencies] +pulz-ecs = { path = "../ecs" } + +slotmap = { workspace = true } +glam = { workspace = true, features = ["mint", "bytemuck"] } +raw-window-handle = { workspace = true } diff --git a/crates/window/README.md b/crates/window/README.md new file mode 100644 index 0000000..24154db --- /dev/null +++ b/crates/window/README.md @@ -0,0 +1,34 @@ +# `pulz-window` + + + +[![Crates.io](https://img.shields.io/crates/v/pulz-window.svg?label=pulz-window)](https://crates.io/crates/pulz-window) +[![docs.rs](https://docs.rs/pulz-window/badge.svg)](https://docs.rs/pulz-window/) +[![license: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](#license) +[![Rust CI](https://github.com/HellButcher/pulz/actions/workflows/rust.yml/badge.svg)](https://github.com/HellButcher/pulz/actions/workflows/rust.yml) + + +**TODO** + +## Example + + +**TODO** + +## License + +[license]: #license + +This project is licensed under either of + +* MIT license ([LICENSE-MIT] or ) +* Apache License, Version 2.0, ([LICENSE-APACHE] or ) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[LICENSE-MIT]: ../../LICENSE-MIT +[LICENSE-APACHE]: ../../LICENSE-APACHE diff --git a/crates/window/src/event.rs b/crates/window/src/event.rs new file mode 100644 index 0000000..fec49e1 --- /dev/null +++ b/crates/window/src/event.rs @@ -0,0 +1,21 @@ +use crate::{Point2, Size2, WindowId}; + +#[derive(Debug, Clone)] +pub struct WindowResized { + pub id: WindowId, + pub size: Size2, +} + +#[derive(Debug, Clone)] +pub enum WindowEvent { + Created(WindowId), + CloseRequested(WindowId), + Closed(WindowId), +} + +#[derive(Debug, Clone)] +pub enum CursorEvent { + Enter(WindowId), + Move(WindowId, Point2), + Leave(WindowId), +} diff --git a/crates/window/src/lib.rs b/crates/window/src/lib.rs new file mode 100644 index 0000000..79eb1c8 --- /dev/null +++ b/crates/window/src/lib.rs @@ -0,0 +1,38 @@ +#![warn( + // missing_docs, + // rustdoc::missing_doc_code_examples, + future_incompatible, + rust_2018_idioms, + unused, + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_qualifications, + unused_crate_dependencies, + clippy::cargo, + clippy::multiple_crate_versions, + clippy::empty_line_after_outer_attr, + clippy::fallible_impl_from, + clippy::redundant_pub_crate, + clippy::use_self, + clippy::suspicious_operation_groupings, + clippy::useless_let_if_seq, + // clippy::missing_errors_doc, + // clippy::missing_panics_doc, + clippy::wildcard_imports +)] +#![doc(html_logo_url = "https://raw.githubusercontent.com/HellButcher/pulz/master/docs/logo.png")] +#![doc(html_no_source)] +#![doc = include_str!("../README.md")] + +pub mod event; +mod window; + +pub type Point2 = glam::IVec2; +pub type Size2 = glam::UVec2; + +pub use raw_window_handle::{ + HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, +}; + +pub use crate::window::*; diff --git a/crates/window/src/window.rs b/crates/window/src/window.rs new file mode 100644 index 0000000..21b307f --- /dev/null +++ b/crates/window/src/window.rs @@ -0,0 +1,159 @@ +use std::{ + borrow::Cow, + collections::VecDeque, + ops::{Deref, DerefMut}, + rc::Weak, +}; + +use pulz_ecs::Component; +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; +use slotmap::{new_key_type, SlotMap}; + +use crate::Size2; + +new_key_type! { + #[derive(Component)] + pub struct WindowId; +} + +pub type Iter<'a, T = Window> = slotmap::basic::Iter<'a, WindowId, T>; +pub type IterMut<'a, T = Window> = slotmap::basic::IterMut<'a, WindowId, T>; +pub type WindowsMirror = slotmap::SecondaryMap; + +#[derive(Debug)] +pub struct WindowDescriptor { + pub size: Size2, + pub scale_factor: f64, + pub title: Cow<'static, str>, + pub vsync: bool, +} + +pub struct Window { + descriptor: WindowDescriptor, + pub close_requested: bool, + command_queue: VecDeque, +} + +pub struct Windows(SlotMap); + +impl WindowDescriptor { + pub const DEFAULT_TITLE: &'static str = + concat!(env!("CARGO_PKG_NAME"), ": ", env!("CARGO_PKG_VERSION")); + #[inline] + pub fn new() -> Self { + Self { + size: Size2::ZERO, + scale_factor: 1.0, + title: Cow::Borrowed(Self::DEFAULT_TITLE), + vsync: true, + } + } +} + +impl Default for WindowDescriptor { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl Deref for Window { + type Target = WindowDescriptor; + fn deref(&self) -> &Self::Target { + &self.descriptor + } +} + +impl DerefMut for Window { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.descriptor + } +} + +impl Windows { + pub fn new() -> Self { + Self(SlotMap::with_key()) + } + + #[inline] + pub fn create(&mut self, descriptor: WindowDescriptor) -> WindowId { + let window = Window { + descriptor, + close_requested: false, + command_queue: VecDeque::new(), + }; + + //self.new_windows.push_back(id); + self.0.insert(window) + } + + #[inline] + pub fn len(&self) -> usize { + self.0.len() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + #[inline] + pub fn get(&self, id: WindowId) -> Option<&Window> { + self.0.get(id) + } + + #[inline] + pub fn get_mut(&mut self, id: WindowId) -> Option<&mut Window> { + self.0.get_mut(id) + } + + #[inline] + pub fn close(&mut self, id: WindowId) -> bool { + self.0.remove(id).is_some() + } + + #[inline] + pub fn iter(&self) -> Iter<'_> { + self.0.iter() + } + + #[inline] + pub fn iter_mut(&mut self) -> IterMut<'_> { + self.0.iter_mut() + } +} + +impl Default for Windows { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl std::ops::Index for Windows { + type Output = Window; + #[inline] + fn index(&self, id: WindowId) -> &Self::Output { + &self.0[id] + } +} + +impl std::ops::IndexMut for Windows { + #[inline] + fn index_mut(&mut self, id: WindowId) -> &mut Self::Output { + &mut self.0[id] + } +} + +#[derive(Debug)] +#[non_exhaustive] +pub enum WindowCommand { + SetTitle(Cow<'static, String>), + Close, +} + +pub trait RawWindow: HasRawWindowHandle + HasRawDisplayHandle {} + +impl RawWindow for W where W: HasRawWindowHandle + HasRawDisplayHandle {} + +pub type RawWindowHandles = WindowsMirror>; diff --git a/run-wasm/Cargo.toml b/run-wasm/Cargo.toml new file mode 100644 index 0000000..b96e090 --- /dev/null +++ b/run-wasm/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "run-wasm" +publish = false +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true + +[dependencies] +cargo-run-wasm = "0.3.0" diff --git a/run-wasm/src/main.rs b/run-wasm/src/main.rs new file mode 100644 index 0000000..6961358 --- /dev/null +++ b/run-wasm/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + cargo_run_wasm::run_wasm_with_css("body { margin: 0px; }"); +} From 37da3d839d6086808d090a21168ca549dfc4c136 Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sat, 7 Jan 2023 19:14:23 +0100 Subject: [PATCH 02/36] move pipeline descriptors --- crates/render/src/{pipeline.rs => pipeline/descriptor.rs} | 1 + crates/render/src/pipeline/mod.rs | 3 +++ 2 files changed, 4 insertions(+) rename crates/render/src/{pipeline.rs => pipeline/descriptor.rs} (99%) create mode 100644 crates/render/src/pipeline/mod.rs diff --git a/crates/render/src/pipeline.rs b/crates/render/src/pipeline/descriptor.rs similarity index 99% rename from crates/render/src/pipeline.rs rename to crates/render/src/pipeline/descriptor.rs index 03efda0..7297f72 100644 --- a/crates/render/src/pipeline.rs +++ b/crates/render/src/pipeline/descriptor.rs @@ -76,3 +76,4 @@ pub enum Face { Front, Back, } + diff --git a/crates/render/src/pipeline/mod.rs b/crates/render/src/pipeline/mod.rs new file mode 100644 index 0000000..68a64c0 --- /dev/null +++ b/crates/render/src/pipeline/mod.rs @@ -0,0 +1,3 @@ +mod descriptor; + +pub use self::descriptor::*; From d80527a22307691fe506eae729e5de8ac5e2ee51 Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Mon, 9 Jan 2023 00:45:09 +0100 Subject: [PATCH 03/36] fixes for window --- Cargo.toml | 1 + crates/render/src/backend.rs | 4 +--- crates/render/src/buffer.rs | 2 +- crates/render/src/shader/mod.rs | 4 ++-- crates/render/src/texture/mod.rs | 2 +- crates/window-winit/Cargo.toml | 4 ++-- crates/window/Cargo.toml | 2 +- 7 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c171b6b..8e5599b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ fnv = "1.0" radsort = "0.1" bytemuck = "1.12" downcast-rs = "1.2" +dynsequence = { version = "0.1.0-alpha.3" } ambassador = "0.3" blocking = "1.3" threadpool = "1.8" diff --git a/crates/render/src/backend.rs b/crates/render/src/backend.rs index c1cfd75..6c628e9 100644 --- a/crates/render/src/backend.rs +++ b/crates/render/src/backend.rs @@ -3,13 +3,11 @@ pub trait GpuResource: slotmap::Key { } macro_rules! define_gpu_resource { - ($type_name:ident, $collection_type_name:ident, $descriptor_type:ident $(<$life:tt>)?) => { + ($type_name:ident, $descriptor_type:ident $(<$life:tt>)?) => { ::slotmap::new_key_type!{ pub struct $type_name; } - pub type $collection_type_name = ::slotmap::basic::SlotMap<$type_name, V>; - impl $crate::backend::GpuResource for $type_name { type Descriptor<'l> = $descriptor_type $(<$life>)?; } diff --git a/crates/render/src/buffer.rs b/crates/render/src/buffer.rs index 20d748e..852cd23 100644 --- a/crates/render/src/buffer.rs +++ b/crates/render/src/buffer.rs @@ -1,6 +1,6 @@ use bitflags::bitflags; -crate::backend::define_gpu_resource!(Buffer, Buffers, BufferDescriptor); +crate::backend::define_gpu_resource!(Buffer, BufferDescriptor); #[derive(Debug, Clone, Eq, PartialEq)] pub struct BufferDescriptor { diff --git a/crates/render/src/shader/mod.rs b/crates/render/src/shader/mod.rs index e4f4911..fc47918 100644 --- a/crates/render/src/shader/mod.rs +++ b/crates/render/src/shader/mod.rs @@ -5,7 +5,7 @@ mod preprocessor; pub use ::encase::*; pub use ::pulz_render_macros::ShaderType; -crate::backend::define_gpu_resource!(ShaderModule, ShaderModules, ShaderModuleDescriptor<'l>); +crate::backend::define_gpu_resource!(ShaderModule, ShaderModuleDescriptor<'l>); pub struct ShaderModuleDescriptor<'a> { pub label: Option<&'a str>, @@ -15,7 +15,7 @@ pub struct ShaderModuleDescriptor<'a> { #[non_exhaustive] pub enum ShaderSource<'a> { Wgsl(Cow<'a, str>), - // Glsl(Cow<'a, str>), + Glsl(Cow<'a, str>), SpirV(Cow<'a, [u32]>), } diff --git a/crates/render/src/texture/mod.rs b/crates/render/src/texture/mod.rs index 7d8cce4..896f689 100644 --- a/crates/render/src/texture/mod.rs +++ b/crates/render/src/texture/mod.rs @@ -3,4 +3,4 @@ mod image; pub use self::{descriptor::*, image::*}; -crate::backend::define_gpu_resource!(Texture, Textures, TextureDescriptor); +crate::backend::define_gpu_resource!(Texture, TextureDescriptor); diff --git a/crates/window-winit/Cargo.toml b/crates/window-winit/Cargo.toml index a5a59a4..e5d4ffc 100644 --- a/crates/window-winit/Cargo.toml +++ b/crates/window-winit/Cargo.toml @@ -8,9 +8,9 @@ repository.workspace = true readme = "README.md" [features] -default = ["winit/x11", "winit/wayland"] +default = ["x11", "wayland"] x11 = ["winit/x11"] -wayland = ["winit/wayland"] +wayland = ["winit/wayland", "winit/wayland-dlopen", "winit/wayland-csd-adwaita"] [dependencies] pulz-ecs = { path = "../ecs" } diff --git a/crates/window/Cargo.toml b/crates/window/Cargo.toml index e747de1..928cefc 100644 --- a/crates/window/Cargo.toml +++ b/crates/window/Cargo.toml @@ -12,4 +12,4 @@ pulz-ecs = { path = "../ecs" } slotmap = { workspace = true } glam = { workspace = true, features = ["mint", "bytemuck"] } -raw-window-handle = { workspace = true } +raw-window-handle = { workspace = true, features = ["alloc"]} From d94e695d4d355f271f831aae7fdb380ae6755bcc Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Mon, 9 Jan 2023 00:47:31 +0100 Subject: [PATCH 04/36] `pulz-render` improvements; render-graph, core-pipeline --- crates/render-pipeline-core/CHANGELOG.md | 6 + crates/render-pipeline-core/Cargo.toml | 16 + crates/render-pipeline-core/README.md | 34 ++ crates/render-pipeline-core/src/common.rs | 64 +++ .../render-pipeline-core/src/core_3d/mod.rs | 65 +++ .../src/deferred_3d/mod.rs | 142 ++++++ crates/render-pipeline-core/src/lib.rs | 30 ++ crates/render-wgpu/Cargo.toml | 12 +- .../render-wgpu/examples/render-wgpu-demo.rs | 24 +- crates/render-wgpu/src/backend.rs | 257 ++++++++++- crates/render-wgpu/src/convert.rs | 414 +++++++++++++++++- crates/render-wgpu/src/graph.rs | 26 ++ crates/render-wgpu/src/lib.rs | 89 ++-- crates/render-wgpu/src/resources.rs | 213 +++++++++ crates/render-wgpu/src/surface.rs | 8 +- crates/render/Cargo.toml | 9 + crates/render/src/backend.rs | 10 +- crates/render/src/draw.rs | 324 ++++++++++++++ crates/render/src/graph/access.rs | 332 ++++++++++++++ crates/render/src/graph/builder.rs | 167 +++++++ crates/render/src/graph/deps.rs | 128 ++++++ crates/render/src/graph/mod.rs | 206 +++++++++ crates/render/src/graph/pass/builder.rs | 208 +++++++++ crates/render/src/graph/pass/mod.rs | 99 +++++ crates/render/src/graph/pass/run.rs | 131 ++++++ crates/render/src/graph/resources.rs | 320 ++++++++++++++ crates/render/src/lib.rs | 26 +- crates/render/src/pipeline/binding.rs | 19 + crates/render/src/pipeline/descriptor.rs | 385 ++++++++++++++++ crates/render/src/pipeline/mod.rs | 10 +- crates/render/src/pipeline/pipeline_layout.rs | 6 + 31 files changed, 3703 insertions(+), 77 deletions(-) create mode 100644 crates/render-pipeline-core/CHANGELOG.md create mode 100644 crates/render-pipeline-core/Cargo.toml create mode 100644 crates/render-pipeline-core/README.md create mode 100644 crates/render-pipeline-core/src/common.rs create mode 100644 crates/render-pipeline-core/src/core_3d/mod.rs create mode 100644 crates/render-pipeline-core/src/deferred_3d/mod.rs create mode 100644 crates/render-pipeline-core/src/lib.rs create mode 100644 crates/render-wgpu/src/graph.rs create mode 100644 crates/render-wgpu/src/resources.rs create mode 100644 crates/render/src/draw.rs create mode 100644 crates/render/src/graph/access.rs create mode 100644 crates/render/src/graph/builder.rs create mode 100644 crates/render/src/graph/deps.rs create mode 100644 crates/render/src/graph/mod.rs create mode 100644 crates/render/src/graph/pass/builder.rs create mode 100644 crates/render/src/graph/pass/mod.rs create mode 100644 crates/render/src/graph/pass/run.rs create mode 100644 crates/render/src/graph/resources.rs create mode 100644 crates/render/src/pipeline/binding.rs create mode 100644 crates/render/src/pipeline/pipeline_layout.rs diff --git a/crates/render-pipeline-core/CHANGELOG.md b/crates/render-pipeline-core/CHANGELOG.md new file mode 100644 index 0000000..77e12af --- /dev/null +++ b/crates/render-pipeline-core/CHANGELOG.md @@ -0,0 +1,6 @@ +# `pulz-render-core-pipeline` Changelog +All notable changes to this crate will be documented in this file. + +## Unreleased (DATE) + + * Initial version diff --git a/crates/render-pipeline-core/Cargo.toml b/crates/render-pipeline-core/Cargo.toml new file mode 100644 index 0000000..80d54f5 --- /dev/null +++ b/crates/render-pipeline-core/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "pulz-render-pipeline-core" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true +keywords = ["game", "game-engine"] +categories = ["game-engines", "game-development"] +readme = "README.md" + +[dependencies] +pulz-ecs = { path = "../ecs" } +pulz-render = { path = "../render" } + +radsort = { workspace = true } diff --git a/crates/render-pipeline-core/README.md b/crates/render-pipeline-core/README.md new file mode 100644 index 0000000..e05d7a4 --- /dev/null +++ b/crates/render-pipeline-core/README.md @@ -0,0 +1,34 @@ +# `pulz-render-core-pipeline` + + + +[![Crates.io](https://img.shields.io/crates/v/pulz-render-core-pipeline.svg?label=pulz-render-core-pipeline)](https://crates.io/crates/pulz-render-core-pipeline) +[![docs.rs](https://docs.rs/pulz-render-core-pipeline/badge.svg)](https://docs.rs/pulz-render-core-pipeline/) +[![license: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](#license) +[![Rust CI](https://github.com/HellButcher/pulz/actions/workflows/rust.yml/badge.svg)](https://github.com/HellButcher/pulz/actions/workflows/rust.yml) + + +**TODO** + +## Example + + +**TODO** + +## License + +[license]: #license + +This project is licensed under either of + +* MIT license ([LICENSE-MIT] or ) +* Apache License, Version 2.0, ([LICENSE-APACHE] or ) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[LICENSE-MIT]: ../../LICENSE-MIT +[LICENSE-APACHE]: ../../LICENSE-APACHE diff --git a/crates/render-pipeline-core/src/common.rs b/crates/render-pipeline-core/src/common.rs new file mode 100644 index 0000000..4bacf98 --- /dev/null +++ b/crates/render-pipeline-core/src/common.rs @@ -0,0 +1,64 @@ +use pulz_ecs::{prelude::Module, Entity}; +use pulz_render::{ + draw::{PhaseItem, PhaseModule}, + RenderModule, +}; + +pub struct Opaque { + distance: f32, + entity: Entity, +} + +impl PhaseItem for Opaque { + type TargetKey = Entity; + fn sort(items: &mut [E]) + where + E: std::ops::Deref, + { + // front to back + radsort::sort_by_key(items, |item| item.distance); + } +} + +pub struct OpaqueAlpha { + distance: f32, + entity: Entity, +} + +impl PhaseItem for OpaqueAlpha { + type TargetKey = Entity; + fn sort(items: &mut [E]) + where + E: std::ops::Deref, + { + // front to back + radsort::sort_by_key(items, |item| item.distance); + } +} + +pub struct Transparent { + distance: f32, + entity: Entity, +} + +impl PhaseItem for Transparent { + type TargetKey = Entity; + fn sort(items: &mut [E]) + where + E: std::ops::Deref, + { + // back to front + radsort::sort_by_key(items, |item| -item.distance); + } +} + +pub struct CorePipelineCommonModule; + +impl Module for CorePipelineCommonModule { + fn install_modules(&self, res: &mut pulz_ecs::resource::Resources) { + res.install(RenderModule); + res.install(PhaseModule::::new()); + res.install(PhaseModule::::new()); + res.install(PhaseModule::::new()); + } +} diff --git a/crates/render-pipeline-core/src/core_3d/mod.rs b/crates/render-pipeline-core/src/core_3d/mod.rs new file mode 100644 index 0000000..fbec086 --- /dev/null +++ b/crates/render-pipeline-core/src/core_3d/mod.rs @@ -0,0 +1,65 @@ +use pulz_ecs::prelude::*; +use pulz_render::{ + camera::{Camera, RenderTarget}, + graph::{ + pass::{builder::PassBuilder, run::PassExec, Graphics, Pass}, + resources::{Texture, WriteSlot}, + RenderGraphBuilder, + }, + math::Mat4, + RenderSystemPhase, +}; + +pub use crate::common::*; + +pub struct CoreShadingModule; + +impl CoreShadingModule { + fn build_graph_system( + mut builder: ResMut<'_, RenderGraphBuilder>, + cams_qry: Query<'_, (&Camera, &RenderTarget, Entity)>, + ) { + for (camera, render_target, entity) in cams_qry { + let output = builder.add_pass(CoreShadingPass { + view_camera: entity, + projection: camera.projection_matrix, + }); + + builder.export_texture(output.read(), render_target); + } + } +} + +impl Module for CoreShadingModule { + fn install_modules(&self, res: &mut Resources) { + res.install(CorePipelineCommonModule); + } + + fn install_systems(schedule: &mut Schedule) { + schedule + .add_system(Self::build_graph_system) + .into_phase(RenderSystemPhase::BuildGraph); + } +} +pub struct CoreShadingPass { + view_camera: Entity, + projection: Mat4, +} + +impl Pass for CoreShadingPass { + type Output = WriteSlot; + + fn build(self, mut builder: PassBuilder<'_, Graphics>) -> (Self::Output, PassExec) { + let color = builder.creates_color_attachment(); + builder.creates_depth_stencil_attachment(); + + ( + color, + PassExec::new_fn(move |mut ctx| { + ctx.draw_phase_items::(self.view_camera); + ctx.draw_phase_items::(self.view_camera); + ctx.draw_phase_items::(self.view_camera); + }), + ) + } +} diff --git a/crates/render-pipeline-core/src/deferred_3d/mod.rs b/crates/render-pipeline-core/src/deferred_3d/mod.rs new file mode 100644 index 0000000..241f186 --- /dev/null +++ b/crates/render-pipeline-core/src/deferred_3d/mod.rs @@ -0,0 +1,142 @@ +use pulz_ecs::prelude::*; +use pulz_render::{ + camera::{Camera, RenderTarget}, + graph::{ + pass::{ + builder::{PassBuilder, PassGroupBuilder}, + run::PassExec, + Graphics, Pass, PassGroup, + }, + resources::{Slot, Texture, WriteSlot}, + RenderGraphBuilder, + }, + RenderSystemPhase, +}; + +pub use crate::common::*; +pub struct DeferredShadingModule; + +impl DeferredShadingModule { + fn build_graph_system( + mut builder: ResMut<'_, RenderGraphBuilder>, + cams_qry: Query<'_, (&Camera, &RenderTarget, Entity)>, + ) { + for (_camera, render_target, entity) in cams_qry { + let output = builder.add_pass(DeferredShadingPass { + view_camera: entity, + }); + + builder.export_texture(output.read(), render_target); + } + } +} +impl Module for DeferredShadingModule { + fn install_modules(&self, res: &mut Resources) { + res.install(CorePipelineCommonModule); + } + + fn install_systems(schedule: &mut Schedule) { + schedule + .add_system(Self::build_graph_system) + .into_phase(RenderSystemPhase::BuildGraph); + } +} + +pub struct DeferredShadingPass { + view_camera: Entity, +} + +impl PassGroup for DeferredShadingPass { + type Output = WriteSlot; + + fn build(self, mut build: PassGroupBuilder<'_, Graphics>) -> Self::Output { + let gbuffer = build.sub_pass(GBuffer { + view_camera: self.view_camera, + }); + let output = build.sub_pass(Composition { + albedo: gbuffer.albedo.read(), + position: gbuffer.position.read(), + normal: gbuffer.normal.read(), + }); + + build.sub_pass(Transparency { + view_camera: self.view_camera, + output, + depth: gbuffer.depth, + }) + } +} + +struct GBuffer { + view_camera: Entity, +} + +struct GBufferOutput { + albedo: WriteSlot, + position: WriteSlot, + normal: WriteSlot, + depth: WriteSlot, +} +struct Composition { + albedo: Slot, + position: Slot, + normal: Slot, +} +struct Transparency { + view_camera: Entity, + output: WriteSlot, + depth: WriteSlot, +} + +impl Pass for GBuffer { + type Output = GBufferOutput; + + fn build(self, mut build: PassBuilder<'_, Graphics>) -> (Self::Output, PassExec) { + let albedo = build.creates_color_attachment(); + let position = build.creates_color_attachment(); + let normal = build.creates_color_attachment(); + let depth = build.creates_depth_stencil_attachment(); + ( + GBufferOutput { + albedo, + position, + normal, + depth, + }, + PassExec::new_fn(move |mut ctx| { + ctx.draw_phase_items::(self.view_camera); + ctx.draw_phase_items::(self.view_camera); + }), + ) + } +} + +impl Pass for Composition { + type Output = WriteSlot; + + fn build(self, mut build: PassBuilder<'_, Graphics>) -> (Self::Output, PassExec) { + build.input_attachment(self.albedo); + build.input_attachment(self.position); + build.input_attachment(self.normal); + let output = build.creates_color_attachment(); + (output, PassExec::noop()) + } +} + +impl Pass for Transparency { + type Output = WriteSlot; + + fn build( + self, + mut build: PassBuilder<'_, Graphics>, + ) -> (WriteSlot, PassExec) { + build.depth_stencil_attachment(self.depth); + let output = build.color_attachment(self.output); + ( + output, + PassExec::new_fn(move |mut ctx| { + ctx.draw_phase_items::(self.view_camera); + }), + ) + } +} diff --git a/crates/render-pipeline-core/src/lib.rs b/crates/render-pipeline-core/src/lib.rs new file mode 100644 index 0000000..559afac --- /dev/null +++ b/crates/render-pipeline-core/src/lib.rs @@ -0,0 +1,30 @@ +#![warn( + // missing_docs, + // rustdoc::missing_doc_code_examples, + future_incompatible, + rust_2018_idioms, + unused, + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_qualifications, + unused_crate_dependencies, + clippy::cargo, + clippy::multiple_crate_versions, + clippy::empty_line_after_outer_attr, + clippy::fallible_impl_from, + clippy::redundant_pub_crate, + clippy::use_self, + clippy::suspicious_operation_groupings, + clippy::useless_let_if_seq, + // clippy::missing_errors_doc, + // clippy::missing_panics_doc, + clippy::wildcard_imports +)] +#![doc(html_logo_url = "https://raw.githubusercontent.com/HellButcher/pulz/master/docs/logo.png")] +#![doc(html_no_source)] +#![doc = include_str!("../README.md")] + +pub mod common; +pub mod core_3d; +pub mod deferred_3d; diff --git a/crates/render-wgpu/Cargo.toml b/crates/render-wgpu/Cargo.toml index c8c203b..c858b7e 100644 --- a/crates/render-wgpu/Cargo.toml +++ b/crates/render-wgpu/Cargo.toml @@ -12,22 +12,24 @@ pulz-ecs = { path = "../ecs" } pulz-window = { path = "../window" } pulz-render = { path = "../render" } -thiserror = { workspace = true} -tracing = { workspace = true} +thiserror = { workspace = true } +tracing = { workspace = true } +slotmap = { workspace = true } wgpu = "0.14" -raw-window-handle = { workspace = true, features = ["alloc"] } +raw-window-handle = { workspace = true } [dev-dependencies] -anyhow = { workspace = true} +anyhow = { workspace = true } naga = "0.10" pulz-window-winit = { path = "../window-winit" } +pulz-render-pipeline-core = { path = "../render-pipeline-core" } [target.'cfg(not(target_os = "unknown"))'.dev-dependencies] tracing-subscriber = { workspace = true } async-std = { workspace = true } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wgpu = { version = "0.13" , features = ["webgl"] } +wgpu = { version = "0.14" , features = ["webgl"] } tracing-wasm = { workspace = true } tracing-log = { workspace = true } console_error_panic_hook = { workspace = true } diff --git a/crates/render-wgpu/examples/render-wgpu-demo.rs b/crates/render-wgpu/examples/render-wgpu-demo.rs index 1229678..e1f1c14 100644 --- a/crates/render-wgpu/examples/render-wgpu-demo.rs +++ b/crates/render-wgpu/examples/render-wgpu-demo.rs @@ -1,17 +1,20 @@ use std::rc::Rc; use pulz_ecs::prelude::*; +use pulz_render::camera::{Camera, RenderTarget}; use pulz_render_wgpu::WgpuRendererBuilder; -use pulz_window::WindowDescriptor; +use pulz_render_pipeline_core::core_3d::CoreShadingModule; +use pulz_window::{WindowDescriptor, WindowId}; use pulz_window_winit::{ winit::{event_loop::EventLoop, window::Window}, WinitWindowModule, WinitWindowSystem, }; -use tracing::info; +use tracing::*; async fn init() -> (Resources, EventLoop<()>, Rc, WinitWindowSystem) { info!("Initializing..."); let mut resources = Resources::new(); + resources.install(CoreShadingModule); let event_loop = EventLoop::new(); let (window_system, window_id, window) = @@ -28,9 +31,25 @@ async fn init() -> (Resources, EventLoop<()>, Rc, WinitWindowSystem) { .unwrap() }; + // let mut schedule = resources.remove::().unwrap(); + // schedule.init(&mut resources); + // schedule.debug_dump_if_env(None).unwrap(); + // resources.insert_again(schedule); + + setup_demo_scene(&mut resources, window_id); + (resources, event_loop, window, window_system) } +fn setup_demo_scene(resources: &mut Resources, window: WindowId) { + let mut world = resources.world_mut(); + + world + .spawn() + .insert(Camera::new()) + .insert(RenderTarget::Window(window)); +} + #[cfg(not(target_arch = "wasm32"))] #[async_std::main] async fn main() { @@ -43,6 +62,7 @@ async fn main() { .init(); let (resources, event_loop, _window, window_system) = init().await; + window_system.run(resources, event_loop); } diff --git a/crates/render-wgpu/src/backend.rs b/crates/render-wgpu/src/backend.rs index dad1baf..d1320c4 100644 --- a/crates/render-wgpu/src/backend.rs +++ b/crates/render-wgpu/src/backend.rs @@ -1,7 +1,7 @@ -use pulz_render::backend::RenderBackend; -use pulz_window::WindowId; +use std::ops::{Deref, DerefMut}; -use crate::{Error, WgpuRendererBackend}; +use pulz_render::backend::CommandEncoder; +use pulz_window::WindowId; pub enum BackendTexture { Texture { @@ -24,9 +24,250 @@ impl BackendTexture { } } -impl RenderBackend for WgpuRendererBackend { - type Error = Error; - type Buffer = wgpu::Buffer; - type Texture = BackendTexture; - type ShaderModule = wgpu::ShaderModule; +// impl RenderBackend for WgpuRendererBackend { +// type Error = ConversionError; +// fn create_buffer(&mut self, desc: &BufferDescriptor) -> Result { +// let desc = convert_buffer_descriptor(desc); +// let buffer = self.device.create_buffer(&desc); +// self.resources.buffers.insert(buffer) +// } +// fn create_texture(&mut self, desc: &TextureDescriptor) -> Result { +// let tex_desc = convert_texture_descriptor(desc); +// let view_desc = convert_texture_view_descriptor(desc); +// let texture = self.device.create_texture(&tex_desc); +// let view = texture.create_view(&view_desc); +// self.resources +// .textures +// .insert(BackendTexture::Texture { texture, view }) +// } +// fn create_shader_module(&mut self, desc: &ShaderModuleDescriptor<'_>) -> ShaderModuleId { +// debug!("creating shader module `{:?}`", desc.label); +// let desc = convert_shader_module_descriptor(desc); +// let shader_module = self.device.create_shader_module(&desc); +// self.resources.shader_modules.insert(shader_module) +// } +// fn create_bind_group_layout( +// &mut self, +// desc: &BindGroupLayoutDescriptor<'_>, +// ) -> BindGroupLayoutId { +// let mut tmp1 = Vec::new(); +// let desc = convert_bind_group_layout_descriptor(desc, &mut tmp1); +// let bind_group_layout = self.device.create_bind_group_layout(&desc); +// self.resources.bind_group_layouts.insert(bind_group_layout) +// } +// fn create_pipeline_layout(&mut self, desc: &PipelineLayoutDescriptor<'_>) -> PipelineLayoutId { +// let mut tmp1 = Vec::new(); +// let desc = convert_pipeline_layout_descriptor(self.resources(), desc, &mut tmp1); +// let pipeline_layout = self.device.create_pipeline_layout(&desc); +// self.resources.pipeline_layouts.insert(pipeline_layout) +// } +// fn create_compute_pipeline( +// &mut self, +// desc: &ComputePipelineDescriptor<'_>, +// ) -> ComputePipelineId { +// let desc = convert_compute_pipeline_descriptor(self.resources(), desc).unwrap(); +// let compute_pipeline = self.device.create_compute_pipeline(&desc); +// self.resources.compute_pipelines.insert(compute_pipeline) +// } +// fn create_graphics_pipeline( +// &mut self, +// desc: &GraphicsPipelineDescriptor<'_>, +// ) -> GraphicsPipelineId { +// let mut tmp1 = Vec::new(); +// let mut tmp2 = Vec::new(); +// let mut tmp3 = Vec::new(); +// let desc = convert_graphics_pipeline_descriptor( +// self.resources(), +// desc, +// &mut tmp1, +// &mut tmp2, +// &mut tmp3, +// ) +// .unwrap(); +// let graphics_pipeline = self.device.create_render_pipeline(&desc); +// self.resources.graphics_pipelines.insert(graphics_pipeline) +// } + +// fn write_image(&self, texture: TextureId, image: &render::texture::Image) { +// let texture = self +// .resources +// .textures +// .get(texture) +// .expect("invalid texture handle"); +// let BackendTexture::Texture { texture, .. } = texture else { +// panic!("trying to write to surface texture"); +// }; +// self.queue.write_texture( +// ImageCopyTexture { +// texture, +// mip_level: 1, +// origin: Origin3d::ZERO, +// aspect: wgpu::TextureAspect::All, +// }, +// &image.data, +// image.descriptor.wgpu_into(), +// image.descriptor.wgpu_into(), +// ); +// } + +// fn destroy_buffer(&mut self, id: BufferId) { +// self.resources.buffers.remove(id); +// } +// fn destroy_texture(&mut self, id: TextureId) { +// self.resources.textures.remove(id); +// } +// fn destroy_shader_module(&mut self, id: ShaderModuleId) { +// self.resources.shader_modules.remove(id); +// } +// } + +// impl RenderResourceBackend for WgpuRendererBackend { +// type Error = (); +// type Target = Buffer; + +// fn create( +// &mut self, +// descriptor: &render_resource::BufferDescriptor, +// ) -> Result { +// let desc = convert_buffer_descriptor(desc); +// let buffer = self.device.create_buffer(&desc); +// Ok(buffer) +// } + +// fn destroy(&mut self, buffer: Buffer) { +// self.device.destroy_buffer(buffer) +// } +// } + +// impl RenderResourceBackend for WgpuRendererBackend { +// type Error = (); +// type Target = BackendTexture; + +// fn create( +// &mut self, +// descriptor: &render_resource::TextureDescriptor, +// ) -> Result { +// let tex_desc = convert_texture_descriptor(desc); +// let view_desc = convert_texture_view_descriptor(desc); +// let texture = self.device.create_texture(&tex_desc); +// let view = texture.create_view(&view_desc); +// Ok(BackendTexture::Texture { texture, view }) +// } + +// fn destroy(&mut self, texture: BackendTexture) { +// self.device.destroy_texture(texture) +// } +// } + +// impl RenderResourceBackend for WgpuRendererBackend { +// type Error = (); +// type Target = ShaderModule; + +// fn create( +// &mut self, +// descriptor: &render_resource::ShaderModuleDescriptor, +// ) -> Result { +// debug!("creating shader module `{:?}`", desc.label); +// let desc = convert_shader_module_descriptor(desc); +// let shader_module = self.device.create_shader_module(&desc); +// Ok(shader_module) +// } + +// fn destroy(&mut self, module: ShaderModule) { +// self.device.destroy_shader_module(module) +// } +// } + +// impl RenderBackendTypes for WgpuRendererBackend { +// type Buffer = Buffer; +// type Texture = BackendTexture; +// type ShaderModule = ShaderModule; +// type BindGroupLayout = BindGroupLayout; +// type PipelineLayout = PipelineLayout; +// type GraphicsPipeline = RenderPipeline; +// type ComputePipeline = ComputePipeline; + +// #[inline] +// fn resources(&self) -> &RenderBackendResources { +// &self.resources +// } + +// #[inline] +// fn resources_mut(&mut self) -> &mut RenderBackendResources { +// &mut self.resources +// } +// } + +pub struct WgpuCommandEncoder(pub T); + +impl Deref for WgpuCommandEncoder { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + &self.0 + } +} + +impl DerefMut for WgpuCommandEncoder { + #[inline] + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl WgpuCommandEncoder { + pub fn finish(self) -> wgpu::CommandBuffer { + self.0.finish() + } +} + +impl CommandEncoder for WgpuCommandEncoder { + // fn graphics_pass( + // &mut self, + // desc: &render::pass::GraphicsPassDescriptor<'_>, + // pass_fn: &mut dyn FnMut(&mut dyn render::draw::DrawCommandEncoder), + // ) { + // let mut tmp1 = Vec::new(); + // let desc = convert_render_pass(self.1, desc, &mut tmp1).unwrap(); + // let pass = self.0.begin_render_pass(&desc); + // let mut pass_encoder = CommandEncoder(pass, self.1); + // pass_fn(&mut pass_encoder); + // } + + fn insert_debug_marker(&mut self, label: &str) { + self.0.insert_debug_marker(label) + } + + fn push_debug_group(&mut self, label: &str) { + self.0.push_debug_group(label) + } + + fn pop_debug_group(&mut self) { + self.0.pop_debug_group(); + } +} + +impl<'a> CommandEncoder for WgpuCommandEncoder> { + // fn set_pipeline(&mut self, pipeline: GraphicsPipelineId) { + // self.0.set_pipeline(&self.1.graphics_pipelines[pipeline]) + // } + // fn draw_indexed(&mut self, indices: Range, base_vertex: i32, instances: Range) { + // self.0.draw_indexed(indices, base_vertex, instances) + // } + // fn draw(&mut self, vertices: Range, instances: Range) { + // self.0.draw(vertices, instances) + // } + + fn insert_debug_marker(&mut self, label: &str) { + self.0.insert_debug_marker(label) + } + + fn push_debug_group(&mut self, label: &str) { + self.0.push_debug_group(label) + } + + fn pop_debug_group(&mut self) { + self.0.pop_debug_group(); + } } diff --git a/crates/render-wgpu/src/convert.rs b/crates/render-wgpu/src/convert.rs index 9b18ba4..7fb577d 100644 --- a/crates/render-wgpu/src/convert.rs +++ b/crates/render-wgpu/src/convert.rs @@ -4,7 +4,7 @@ use pulz_render::{ buffer::{BufferDescriptor, BufferUsage}, color::Srgba, math::USize3, - pipeline::{Face, FrontFace, IndexFormat, PrimitiveTopology, VertexFormat}, + pipeline::{Face, FrontFace, IndexFormat, PrimitiveTopology, VertexFormat, PipelineLayout, BindGroupLayoutEntry, VertexAttribute, BindGroupLayoutDescriptor, GraphicsPipelineDescriptor, ComputePipelineDescriptor, PipelineLayoutDescriptor, VertexState, FragmentState, ColorTargetState, BlendState, BlendComponent, PrimitiveState, DepthStencilState, StencilFaceState, ColorWrite, BlendOperation, BlendFactor, CompareFunction, StencilOperation}, shader::{ShaderModule, ShaderModuleDescriptor, ShaderSource}, texture::{ ImageDataLayout, Texture, TextureDescriptor, TextureDimensions, TextureFormat, TextureUsage, @@ -12,6 +12,8 @@ use pulz_render::{ }; use thiserror::Error; +use crate::resources::WgpuResources; + #[derive(Error, Debug)] #[non_exhaustive] pub enum ConversionError { @@ -23,6 +25,9 @@ pub enum ConversionError { #[error("the texture {0:?} is not available!")] TextureNotAvailable(Texture), + + #[error("the pipeline layout {0:?} is not available!")] + PipelineLayoutNotAvailable(PipelineLayout), } pub type Result = std::result::Result; @@ -252,6 +257,18 @@ fn convert_texture_usages(val: TextureUsage) -> wgpu::TextureUsages { result } +fn convert_bind_group_layout_entry(_val: BindGroupLayoutEntry) -> wgpu::BindGroupLayoutEntry { + todo!() // TODO +} + +fn convert_vertex_attribute(index: usize, attr: VertexAttribute) -> Result { + Ok(wgpu::VertexAttribute { + format: convert_vertex_format(attr.format)?, + offset: attr.offset as u64, + shader_location: index as u32, + }) +} + pub fn convert_shader_module_descriptor<'a>( val: &'a ShaderModuleDescriptor<'a>, ) -> wgpu::ShaderModuleDescriptor<'a> { @@ -268,6 +285,257 @@ pub fn convert_shader_module_descriptor<'a>( } } +pub fn convert_bind_group_layout_descriptor<'l>( + desc: &BindGroupLayoutDescriptor<'l>, + entries_tmp: &'l mut Vec, +) -> wgpu::BindGroupLayoutDescriptor<'l> { + entries_tmp.reserve_exact(desc.entries.len()); + for entry in desc.entries.iter().copied() { + entries_tmp.push(convert_bind_group_layout_entry(entry)); + } + wgpu::BindGroupLayoutDescriptor { + label: desc.label, + entries: entries_tmp, + } +} + +pub fn convert_pipeline_layout_descriptor<'l>( + _res: &'l WgpuResources, + desc: &PipelineLayoutDescriptor<'l>, + layouts_tmp: &'l mut Vec<&'l wgpu::BindGroupLayout>, +) -> wgpu::PipelineLayoutDescriptor<'l> { + wgpu::PipelineLayoutDescriptor { + label: desc.label, + bind_group_layouts: layouts_tmp, + push_constant_ranges: &[], // TODO + } +} + +pub fn convert_compute_pipeline_descriptor<'l>( + res: &'l WgpuResources, + desc: &ComputePipelineDescriptor<'l>, +) -> Result> { + let layout = if let Some(layout) = desc.layout { + Some( + res.pipeline_layouts + .get(layout) + .ok_or(ConversionError::PipelineLayoutNotAvailable(layout))?, + ) + } else { + None + }; + + let module = res + .shader_modules + .get(desc.module) + .ok_or(ConversionError::ShaderModuleNotAvailable(desc.module))?; + + Ok(wgpu::ComputePipelineDescriptor { + label: desc.label, + layout, + module, + entry_point: desc.entry_point, + }) +} + +pub fn convert_graphics_pipeline_descriptor<'l>( + res: &'l WgpuResources, + desc: &GraphicsPipelineDescriptor<'l>, + buffers_tmp: &'l mut Vec>, + attribs_tmp: &'l mut Vec, + targets_tmp: &'l mut Vec>, +) -> Result> { + let layout = if let Some(layout) = desc.layout { + Some( + res.pipeline_layouts + .get(layout) + .ok_or(ConversionError::PipelineLayoutNotAvailable(layout))?, + ) + } else { + None + }; + + let vertex = convert_vertex_state(res, &desc.vertex, buffers_tmp, attribs_tmp)?; + + let depth_stencil = if let Some(ref state) = desc.depth_stencil { + Some(convert_depth_stencil_state(state)?) + } else { + None + }; + + let fragment = if let Some(ref fragment) = desc.fragment { + Some(convert_fragment_state(res, fragment, targets_tmp)?) + } else { + None + }; + + Ok(wgpu::RenderPipelineDescriptor { + label: desc.label, + layout, + vertex, + primitive: convert_primitive_state(&desc.primitive), + depth_stencil, + multisample: wgpu::MultisampleState { + count: desc.samples, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment, + multiview: None, + }) +} + +fn convert_vertex_state<'l>( + res: &'l WgpuResources, + state: &VertexState<'l>, + buffers_tmp: &'l mut Vec>, + attributes_tmp: &'l mut Vec, +) -> Result> { + let module = res + .shader_modules + .get(state.module) + .ok_or(ConversionError::ShaderModuleNotAvailable(state.module))?; + + attributes_tmp.reserve_exact(state.buffers.iter().map(|l| l.attributes.len()).sum()); + for (i, attr) in state + .buffers + .iter() + .flat_map(|l| l.attributes) + .copied() + .enumerate() + { + attributes_tmp.push(convert_vertex_attribute(i, attr)?); + } + + buffers_tmp.reserve_exact(state.buffers.len()); + let mut offset = 0; + for layout in state.buffers { + let next_offset = offset + layout.attributes.len(); + buffers_tmp.push(wgpu::VertexBufferLayout { + array_stride: layout.array_stride as u64, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &attributes_tmp[offset..next_offset], + }); + offset = next_offset; + } + + Ok(wgpu::VertexState { + module, + entry_point: state.entry_point, + buffers: buffers_tmp, + }) +} + +fn convert_fragment_state<'l>( + res: &'l WgpuResources, + state: &FragmentState<'l>, + targets_tmp: &'l mut Vec>, +) -> Result> { + let module = res + .shader_modules + .get(state.module) + .ok_or(ConversionError::ShaderModuleNotAvailable(state.module))?; + + targets_tmp.reserve_exact(state.targets.len()); + for target in state.targets { + targets_tmp.push(convert_color_target_state(&target)?); + } + + Ok(wgpu::FragmentState { + module, + entry_point: state.entry_point, + targets: targets_tmp, + }) +} + +#[inline] +fn convert_color_target_state(val: &ColorTargetState) -> Result> { + Ok(Some(wgpu::ColorTargetState { + format: convert_texture_format(val.format)?, + blend: val.blend.map(convert_blent_state), + write_mask: convert_color_write(val.write_mask), + })) +} + +#[inline] +fn convert_blent_state(val: BlendState) -> wgpu::BlendState { + wgpu::BlendState { + color: convert_blent_component(val.color), + alpha: convert_blent_component(val.alpha), + } +} + +#[inline] +fn convert_blent_component(val: BlendComponent) -> wgpu::BlendComponent { + wgpu::BlendComponent { + operation: convert_blend_operation(val.operation), + src_factor: convert_blend_factor(val.src_factor), + dst_factor: convert_blend_factor(val.dst_factor), + } +} + +#[inline] +fn convert_primitive_state(val: &PrimitiveState) -> wgpu::PrimitiveState { + wgpu::PrimitiveState { + topology: convert_primitive_topology(val.topology), + strip_index_format: None, + front_face: convert_front_face(val.front_face), + cull_mode: val.cull_mode.map(convert_face), + + polygon_mode: wgpu::PolygonMode::Fill, // TODO: + unclipped_depth: false, // TODO, + conservative: false, // TODO + } +} + +#[inline] +fn convert_depth_stencil_state(val: &DepthStencilState) -> Result { + Ok(wgpu::DepthStencilState { + format: convert_texture_format(val.format)?, + depth_write_enabled: val.depth.write_enabled, + depth_compare: convert_compare_function(val.depth.compare), + stencil: wgpu::StencilState { + front: convert_stencil_face_state(&val.stencil.front), + back: convert_stencil_face_state(&val.stencil.back), + read_mask: val.stencil.read_mask, + write_mask: val.stencil.write_mask, + }, + bias: wgpu::DepthBiasState { + constant: val.depth.bias, + slope_scale: val.depth.bias_slope_scale, + clamp: val.depth.bias_clamp, + }, + }) +} + +#[inline] +fn convert_stencil_face_state(val: &StencilFaceState) -> wgpu::StencilFaceState { + wgpu::StencilFaceState { + compare: convert_compare_function(val.compare), + fail_op: convert_stencil_operation(val.fail_op), + depth_fail_op: convert_stencil_operation(val.depth_fail_op), + pass_op: convert_stencil_operation(val.pass_op), + } +} + +#[inline] +fn convert_color_write(val: ColorWrite) -> wgpu::ColorWrites { + let mut result = wgpu::ColorWrites::empty(); + if val.contains(ColorWrite::RED) { + result |= wgpu::ColorWrites::RED; + } + if val.contains(ColorWrite::GREEN) { + result |= wgpu::ColorWrites::GREEN; + } + if val.contains(ColorWrite::BLUE) { + result |= wgpu::ColorWrites::BLUE; + } + if val.contains(ColorWrite::ALPHA) { + result |= wgpu::ColorWrites::ALPHA; + } + result +} + #[inline] fn convert_primitive_topology(val: PrimitiveTopology) -> wgpu::PrimitiveTopology { match val { @@ -295,6 +563,36 @@ fn convert_face(val: Face) -> wgpu::Face { } } +#[inline] +fn convert_blend_operation(val: BlendOperation) -> wgpu::BlendOperation { + match val { + BlendOperation::Add => wgpu::BlendOperation::Add, + BlendOperation::Subtract => wgpu::BlendOperation::Subtract, + BlendOperation::ReverseSubtract => wgpu::BlendOperation::ReverseSubtract, + BlendOperation::Min => wgpu::BlendOperation::Min, + BlendOperation::Max => wgpu::BlendOperation::Max, + } +} + +#[inline] +fn convert_blend_factor(val: BlendFactor) -> wgpu::BlendFactor { + match val { + BlendFactor::Zero => wgpu::BlendFactor::Zero, + BlendFactor::One => wgpu::BlendFactor::One, + BlendFactor::Src => wgpu::BlendFactor::Src, + BlendFactor::OneMinusSrc => wgpu::BlendFactor::OneMinusSrc, + BlendFactor::SrcAlpha => wgpu::BlendFactor::SrcAlpha, + BlendFactor::OneMinusSrcAlpha => wgpu::BlendFactor::OneMinusSrcAlpha, + BlendFactor::Dst => wgpu::BlendFactor::Dst, + BlendFactor::OneMinusDst => wgpu::BlendFactor::OneMinusDst, + BlendFactor::DstAlpha => wgpu::BlendFactor::DstAlpha, + BlendFactor::OneMinusDstAlpha => wgpu::BlendFactor::OneMinusDstAlpha, + BlendFactor::SrcAlphaSaturated => wgpu::BlendFactor::SrcAlphaSaturated, + BlendFactor::Constant => wgpu::BlendFactor::Constant, + BlendFactor::OneMinusConstant => wgpu::BlendFactor::OneMinusConstant, + } +} + #[inline] fn convert_index_format(val: IndexFormat) -> wgpu::IndexFormat { match val { @@ -303,6 +601,56 @@ fn convert_index_format(val: IndexFormat) -> wgpu::IndexFormat { } } +#[inline] +fn convert_compare_function(val: CompareFunction) -> wgpu::CompareFunction { + match val { + CompareFunction::Never => wgpu::CompareFunction::Never, + CompareFunction::Less => wgpu::CompareFunction::Less, + CompareFunction::Equal => wgpu::CompareFunction::Equal, + CompareFunction::LessEqual => wgpu::CompareFunction::LessEqual, + CompareFunction::Greater => wgpu::CompareFunction::Greater, + CompareFunction::NotEqual => wgpu::CompareFunction::NotEqual, + CompareFunction::GreaterEqual => wgpu::CompareFunction::GreaterEqual, + CompareFunction::Always => wgpu::CompareFunction::Always, + } +} + +#[inline] +fn convert_stencil_operation(val: StencilOperation) -> wgpu::StencilOperation { + match val { + StencilOperation::Keep => wgpu::StencilOperation::Keep, + StencilOperation::Zero => wgpu::StencilOperation::Zero, + StencilOperation::Replace => wgpu::StencilOperation::Replace, + StencilOperation::Invert => wgpu::StencilOperation::Invert, + StencilOperation::IncrementClamp => wgpu::StencilOperation::IncrementClamp, + StencilOperation::DecrementClamp => wgpu::StencilOperation::DecrementClamp, + StencilOperation::IncrementWrap => wgpu::StencilOperation::IncrementWrap, + StencilOperation::DecrementWrap => wgpu::StencilOperation::DecrementWrap, + } +} + +// #[inline] +// fn convert_color_operations(val: Operations) -> wgpu::Operations { +// wgpu::Operations { +// load: match val.load { +// LoadOp::Clear(clear) => wgpu::LoadOp::Clear(convert_color(clear)), +// LoadOp::Load => wgpu::LoadOp::Load, +// }, +// store: val.store, +// } +// } + +// #[inline] +// fn convert_operations(val: Operations) -> wgpu::Operations { +// wgpu::Operations { +// load: match val.load { +// LoadOp::Clear(clear) => wgpu::LoadOp::Clear(clear), +// LoadOp::Load => wgpu::LoadOp::Load, +// }, +// store: val.store, +// } +// } + #[inline] fn convert_color(color: Srgba) -> wgpu::Color { wgpu::Color { @@ -312,3 +660,67 @@ fn convert_color(color: Srgba) -> wgpu::Color { a: color.alpha as f64, } } + +// pub fn convert_render_pass<'l>( +// res: &'l RenderBackendResources, +// desc: &GraphicsPassDescriptor<'l>, +// tmp_color: &'l mut Vec>, +// ) -> Result> { +// tmp_color.reserve_exact(desc.color_attachments.len()); +// for a in desc.color_attachments { +// tmp_color.push(convert_color_attachment(res, a)?); +// } + +// let depth_stencil_attachment = if let Some(a) = &desc.depth_stencil_attachment { +// Some(convert_depth_stencil_attachment(res, a)?) +// } else { +// None +// }; +// Ok(wgpu::RenderPassDescriptor { +// label: desc.label, +// color_attachments: tmp_color, +// depth_stencil_attachment, +// }) +// } + +// pub fn convert_color_attachment<'l>( +// res: &'l RenderBackendResources, +// desc: &ColorAttachment, +// ) -> Result> { +// let view = res +// .textures +// .get(desc.texture) +// .ok_or(ConversionError::TextureNotAvailable(desc.texture))? +// .view(); +// let resolve_target = if let Some(resolve) = desc.resolve_target { +// Some( +// res.textures +// .get(resolve) +// .ok_or(ConversionError::TextureNotAvailable(resolve))? +// .view(), +// ) +// } else { +// None +// }; +// Ok(wgpu::RenderPassColorAttachment { +// view, +// resolve_target, +// ops: convert_color_operations(desc.ops), +// }) +// } + +// pub fn convert_depth_stencil_attachment<'l>( +// res: &'l RenderBackendResources, +// desc: &DepthStencilAttachment, +// ) -> Result> { +// let view = res +// .textures +// .get(desc.texture) +// .ok_or(ConversionError::TextureNotAvailable(desc.texture))? +// .view(); +// Ok(wgpu::RenderPassDepthStencilAttachment { +// view, +// depth_ops: desc.depth_ops.map(convert_operations), +// stencil_ops: desc.stencil_ops.map(convert_operations), +// }) +// } diff --git a/crates/render-wgpu/src/graph.rs b/crates/render-wgpu/src/graph.rs new file mode 100644 index 0000000..63272c7 --- /dev/null +++ b/crates/render-wgpu/src/graph.rs @@ -0,0 +1,26 @@ +use pulz_render::graph::RenderGraph; + +use crate::backend::WgpuCommandEncoder; + +pub struct WgpuRenderGraph; + +impl WgpuRenderGraph { + pub fn new() -> Self { + Self + } + + pub fn update(&mut self, src_graph: &RenderGraph) { + todo!() + } + + pub fn execute( + &self, + src_graph: &RenderGraph, + encoder: wgpu::CommandEncoder, + ) -> [wgpu::CommandBuffer; 1] { + let mut encoder = WgpuCommandEncoder(encoder); + todo!(); + // TODO + [encoder.finish()] + } +} diff --git a/crates/render-wgpu/src/lib.rs b/crates/render-wgpu/src/lib.rs index 04dffca..f51bbda 100644 --- a/crates/render-wgpu/src/lib.rs +++ b/crates/render-wgpu/src/lib.rs @@ -25,20 +25,22 @@ #![doc(html_no_source)] #![doc = include_str!("../README.md")] -use std::{ - ops::{Deref, DerefMut}, - rc::Rc, -}; +use std::rc::Rc; +use convert::ConversionError; +use graph::WgpuRenderGraph; use pulz_ecs::prelude::*; -use pulz_render::{RenderModule, RenderSystemPhase}; +use pulz_render::{graph::RenderGraph, RenderModule, RenderSystemPhase}; use pulz_window::{RawWindow, RawWindowHandles, Window, WindowId, Windows, WindowsMirror}; +use resources::WgpuResources; use surface::Surface; use thiserror::Error; use tracing::info; mod backend; mod convert; +mod graph; +mod resources; mod surface; #[derive(Error, Debug)] @@ -53,6 +55,9 @@ pub enum Error { #[error("The window is not available, or it has no raw-window-handle")] WindowNotAvailable, + #[error("Unable to convert objects")] + ConversionError(#[from] ConversionError), + #[error("unknown renderer error")] Unknown, } @@ -67,30 +72,13 @@ pub type Result = std::result::Result; pub struct WgpuRenderer { instance: wgpu::Instance, - backend: WgpuRendererBackend, - surfaces: WindowsMirror, - tmp_surface_textures: Vec, -} - -pub struct WgpuRendererBackend { adapter: wgpu::Adapter, device: wgpu::Device, queue: wgpu::Queue, -} - -impl Deref for WgpuRenderer { - type Target = WgpuRendererBackend; - #[inline] - fn deref(&self) -> &Self::Target { - &self.backend - } -} - -impl DerefMut for WgpuRenderer { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.backend - } + resources: WgpuResources, + surfaces: WindowsMirror, + graph: WgpuRenderGraph, + tmp_surface_textures: Vec, } fn backend_bits_from_env_or_default() -> wgpu::Backends { @@ -162,31 +150,21 @@ impl WgpuRenderer { Ok(Self { instance, - backend: WgpuRendererBackend { - adapter, - device, - queue, - }, + adapter, + device, + queue, + resources: WgpuResources::new(), surfaces: WindowsMirror::new(), tmp_surface_textures: Vec::new(), + graph: WgpuRenderGraph::new(), }) } - #[inline] - pub fn backend(&self) -> &WgpuRendererBackend { - &self.backend - } - - #[inline] - pub fn backend_mut(&mut self) -> &mut WgpuRendererBackend { - &mut self.backend - } - fn reconfigure_surfaces(&mut self, windows: &Windows) { for (window_id, surface) in self.surfaces.iter_mut() { if let Some(window) = windows.get(window_id) { if surface.update(window) { - surface.configure(&self.backend); + surface.configure(&self.adapter, &self.device); } } } @@ -205,7 +183,7 @@ impl WgpuRenderer { Ok(t) => t, Err(wgpu::SurfaceError::Outdated | wgpu::SurfaceError::Lost) => { info!("reconfigure surface (outdated)"); - surface.configure(&self.backend); + surface.configure(&self.adapter, &self.device); surface .get_current_texture() .expect("Failed to acquire next surface texture!") @@ -224,13 +202,28 @@ impl WgpuRenderer { } } - fn run_render_system(mut renderer: ResMut<'_, Self>, windows: Res<'_, Windows>) { - renderer.reconfigure_surfaces(&windows); + fn run_graph(&mut self, src_graph: &RenderGraph) { + if self.tmp_surface_textures.is_empty() { + // skip + return; + } + let _ = tracing::trace_span!("RunGraph").entered(); + let encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + let cmds = self.graph.execute(src_graph, encoder); + self.queue.submit(cmds); + } + fn run_render_system( + mut renderer: ResMut<'_, Self>, + windows: Res<'_, Windows>, + src_graph: Res<'_, RenderGraph>, + ) { + renderer.reconfigure_surfaces(&windows); + renderer.graph.update(&src_graph); renderer.aquire_swapchain_images(); - - // TODO: render - + renderer.run_graph(&src_graph); renderer.present_swapchain_images(); } } diff --git a/crates/render-wgpu/src/resources.rs b/crates/render-wgpu/src/resources.rs new file mode 100644 index 0000000..2f8e85b --- /dev/null +++ b/crates/render-wgpu/src/resources.rs @@ -0,0 +1,213 @@ +use crate::convert as c; +use crate::Result; +use pulz_render::pipeline::{BindGroupLayout, ComputePipeline, GraphicsPipeline, PipelineLayout}; +use pulz_render::shader::ShaderModule; +use pulz_render::{backend::GpuResource, buffer::Buffer, texture::Texture}; +use slotmap::SlotMap; + +pub trait WgpuResource: GpuResource { + type Wgpu; + + fn create( + device: &wgpu::Device, + res: &WgpuResources, + descriptor: &Self::Descriptor<'_>, + ) -> Result; + + fn create_many( + device: &wgpu::Device, + res: &WgpuResources, + descriptors: &[Self::Descriptor<'_>], + ) -> Result> { + descriptors + .iter() + .map(|d| Self::create(device, res, d)) + .collect() + } +} + +impl WgpuResource for Buffer { + type Wgpu = wgpu::Buffer; + + fn create( + device: &wgpu::Device, + _res: &WgpuResources, + descr: &Self::Descriptor<'_>, + ) -> Result { + let descr = c::convert_buffer_descriptor(descr); + let raw = device.create_buffer(&descr); + Ok(raw) + } +} + +impl WgpuResource for Texture { + type Wgpu = (wgpu::Texture, wgpu::TextureView); + + fn create( + device: &wgpu::Device, + _res: &WgpuResources, + descr: &Self::Descriptor<'_>, + ) -> Result { + let tex_descr = c::convert_texture_descriptor(descr)?; + let tex = device.create_texture(&tex_descr); + let view_descr = c::convert_texture_view_descriptor(descr); + let view = tex.create_view(&view_descr); + Ok((tex, view)) + } +} + +impl WgpuResource for ShaderModule { + type Wgpu = wgpu::ShaderModule; + + fn create( + device: &wgpu::Device, + _res: &WgpuResources, + descr: &Self::Descriptor<'_>, + ) -> Result { + let descr = c::convert_shader_module_descriptor(descr); + let raw = device.create_shader_module(descr); + Ok(raw) + } +} + +impl WgpuResource for BindGroupLayout { + type Wgpu = wgpu::BindGroupLayout; + + fn create( + device: &wgpu::Device, + _res: &WgpuResources, + descr: &Self::Descriptor<'_>, + ) -> Result { + let mut tmp = Vec::new(); + let descr = c::convert_bind_group_layout_descriptor(descr, &mut tmp); + let raw = device.create_bind_group_layout(&descr); + Ok(raw) + } +} + +impl WgpuResource for PipelineLayout { + type Wgpu = wgpu::PipelineLayout; + + fn create( + device: &wgpu::Device, + res: &WgpuResources, + descr: &Self::Descriptor<'_>, + ) -> Result { + let mut tmp = Vec::new(); + let descr = c::convert_pipeline_layout_descriptor(res, descr, &mut tmp); + let raw = device.create_pipeline_layout(&descr); + Ok(raw) + } +} + +impl WgpuResource for GraphicsPipeline { + type Wgpu = wgpu::RenderPipeline; + + fn create( + device: &wgpu::Device, + res: &WgpuResources, + descr: &Self::Descriptor<'_>, + ) -> Result { + let mut tmp1 = Vec::new(); + let mut tmp2 = Vec::new(); + let mut tmp3 = Vec::new(); + let descr = + c::convert_graphics_pipeline_descriptor(res, descr, &mut tmp1, &mut tmp2, &mut tmp3)?; + let raw = device.create_render_pipeline(&descr); + Ok(raw) + } +} + +impl WgpuResource for ComputePipeline { + type Wgpu = wgpu::ComputePipeline; + + fn create( + device: &wgpu::Device, + res: &WgpuResources, + descr: &Self::Descriptor<'_>, + ) -> Result { + let descr = c::convert_compute_pipeline_descriptor(res, descr)?; + let raw = device.create_compute_pipeline(&descr); + Ok(raw) + } +} + +macro_rules! define_resources { + ( + $v:vis struct $name:ident { + $( + $vfield:vis $namefield:ident<$keytype:ty, $ashtype:ty> + ),* + $(,)? + } + ) => { + $v struct $name { + $( + $vfield $namefield: ::slotmap::basic::SlotMap<$keytype, $ashtype> + ),* + } + + impl $name { + pub fn new() -> Self { + Self { + $( + $namefield: ::slotmap::basic::SlotMap::with_key(), + )* + } + } + + } + + $( + impl AsRef<::slotmap::basic::SlotMap<$keytype,$ashtype>> for $name { + fn as_ref(&self) -> &::slotmap::basic::SlotMap<$keytype,$ashtype> { + &self.$namefield + } + } + + impl AsMut<::slotmap::basic::SlotMap<$keytype,$ashtype>> for $name { + fn as_mut(&mut self) -> &mut ::slotmap::basic::SlotMap<$keytype,$ashtype> { + &mut self.$namefield + } + } + )* + }; +} + +define_resources! { + pub struct WgpuResources { + pub buffers, + pub textures, + pub shader_modules, + pub bind_group_layouts, + pub pipeline_layouts, + pub render_pipelines, + pub compute_pipelines, + } +} + +impl WgpuResources { + pub fn create(&mut self, device: &wgpu::Device, descriptor: &R::Descriptor<'_>) -> Result + where + R: WgpuResource, + Self: AsMut>, + { + let raw = unsafe { R::create(device, self, descriptor)? }; + let key = self.as_mut().insert(raw); + Ok(key) + } + + pub fn create_many( + &mut self, + device: &wgpu::Device, + descriptors: &[R::Descriptor<'_>], + ) -> Result> + where + R: WgpuResource, + Self: AsMut>, + { + let raw = unsafe { R::create_many(device, self, descriptors)? }; + let keys = raw.into_iter().map(|r| self.as_mut().insert(r)).collect(); + Ok(keys) + } +} diff --git a/crates/render-wgpu/src/surface.rs b/crates/render-wgpu/src/surface.rs index f690f7d..fa28b10 100644 --- a/crates/render-wgpu/src/surface.rs +++ b/crates/render-wgpu/src/surface.rs @@ -6,8 +6,6 @@ use std::{ use pulz_window::{RawWindow, Size2, Window}; use tracing::info; -use crate::WgpuRendererBackend; - pub struct Surface { surface: wgpu::Surface, window_handle: Rc, // holds reference to window to ensure sufface is still valid until destruction @@ -57,11 +55,11 @@ impl Surface { changed } - pub fn configure(&mut self, backend: &WgpuRendererBackend) { + pub fn configure(&mut self, adapter: &wgpu::Adapter, device:&wgpu::Device) { // TODO: also reconfigure on resize, and when presenting results in `Outdated/Lost` self.format = self .surface - .get_supported_formats(&backend.adapter) + .get_supported_formats(adapter) .first() .copied() .expect("surface not compatible"); @@ -78,7 +76,7 @@ impl Surface { present_mode, alpha_mode: wgpu::CompositeAlphaMode::Auto, }; - self.surface.configure(&backend.device, &surface_config); + self.surface.configure(device, &surface_config); } } diff --git a/crates/render/Cargo.toml b/crates/render/Cargo.toml index 43091fe..23ef0a3 100644 --- a/crates/render/Cargo.toml +++ b/crates/render/Cargo.toml @@ -10,12 +10,18 @@ categories = ["game-engines", "game-development"] readme = "README.md" [dependencies] +pulz-bitset = { path = "../bitset" } pulz-ecs = { path = "../ecs" } pulz-assets = { path = "../assets" } pulz-transform = { path = "../transform" } pulz-window = { path = "../window" } pulz-render-macros = { path = "macros" } +pulz-functional-utils = { version = "0.1.0-alpha", path = "../functional-utils" } +atomic_refcell = { workspace = true } +ambassador = { workspace = true } +typemap = { workspace = true } +dynsequence = { workspace = true } palette = { workspace = true, features = ["std"], default-features = false } image = { workspace = true, default-features = false } slotmap = { workspace = true } @@ -23,5 +29,8 @@ bytemuck = { workspace = true } fnv = { workspace = true } bitflags = { workspace = true } thiserror = { workspace = true } +tracing = { workspace = true } +downcast-rs = { workspace = true } +crossbeam-queue = { workspace = true } encase = { workspace = true } diff --git a/crates/render/src/backend.rs b/crates/render/src/backend.rs index 6c628e9..378a453 100644 --- a/crates/render/src/backend.rs +++ b/crates/render/src/backend.rs @@ -17,10 +17,8 @@ macro_rules! define_gpu_resource { // export macro to crate pub(crate) use define_gpu_resource; -pub trait RenderBackend { - type Error: std::error::Error; - - type Buffer; - type Texture; - type ShaderModule; +pub trait CommandEncoder { + fn insert_debug_marker(&mut self, label: &str); + fn push_debug_group(&mut self, label: &str); + fn pop_debug_group(&mut self); } diff --git a/crates/render/src/draw.rs b/crates/render/src/draw.rs new file mode 100644 index 0000000..961af1c --- /dev/null +++ b/crates/render/src/draw.rs @@ -0,0 +1,324 @@ +use std::{hash::Hash, marker::PhantomData, ops::Deref}; + +use atomic_refcell::AtomicRefCell; +use dynsequence::{dyn_sequence, DynSequence}; +use pulz_ecs::{ + prelude::*, + resource::ResourceAccess, + system::param::{SystemParam, SystemParamState}, +}; + +use crate::{backend::CommandEncoder, RenderSystemPhase}; + +type HashMap = std::collections::HashMap; + +pub type DrawContext<'a> = &'a mut (dyn CommandEncoder + 'a); + +pub trait Drawable { + fn draw(&self, cmds: DrawContext<'_>); +} +impl Drawable for &D { + #[inline] + fn draw(&self, cmds: DrawContext<'_>) { + D::draw(self, cmds) + } +} +impl Drawable for &mut D { + #[inline] + fn draw(&self, cmds: DrawContext<'_>) { + D::draw(self, cmds) + } +} +impl Drawable for [D] { + #[inline] + fn draw(&self, cmds: DrawContext<'_>) { + for d in self { + D::draw(d, cmds) + } + } +} +impl Drawable for Box { + #[inline] + fn draw(&self, cmds: DrawContext<'_>) { + D::draw(self.as_ref(), cmds) + } +} +impl Drawable for Vec { + #[inline] + fn draw(&self, cmds: DrawContext<'_>) { + <[D]>::draw(self.as_slice(), cmds) + } +} + +impl Drawable for DynSequence { + #[inline] + fn draw(&self, cmds: DrawContext<'_>) { + <[&D]>::draw(self.as_slice(), cmds) + } +} + +pub type DynDrawables = DynSequence; + +pub trait PhaseItem: Send + Sync + Sized + 'static { + type TargetKey: Copy + Clone + Hash + Ord + Eq + Send + Sync; + fn sort(items: &mut [E]) + where + E: Deref; +} + +struct DrawQueue(crossbeam_queue::SegQueue<(I::TargetKey, PhaseData)>); + +struct KeyType(PhantomData); +impl typemap::Key for KeyType { + type Value = AtomicRefCell>>; +} + +pub struct PhaseData { + drawables: DynDrawables, + items: Vec>, +} + +struct PhaseDataItem { + item: I, + draw_offset: usize, + draw_count: usize, +} + +impl Deref for PhaseDataItem { + type Target = I; + #[inline] + fn deref(&self) -> &Self::Target { + &self.item + } +} + +impl PhaseData { + #[inline] + const fn new() -> Self { + Self { + drawables: DynDrawables::new(), + items: Vec::new(), + } + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.items.is_empty() + } + + #[inline] + pub fn len(&self) -> usize { + self.items.len() + } + + pub fn push(&mut self, item: I) -> PhaseDraw<'_> { + let draw_offset = self.drawables.len(); + let index = self.items.len(); + self.items.push(PhaseDataItem { + draw_offset, + draw_count: 0, + item, + }); + let item = &mut self.items[index]; + PhaseDraw { + drawables: &mut self.drawables, + count: &mut item.draw_count, + } + } + + fn clear(&mut self) { + self.drawables.clear(); + self.items.clear(); + } + + fn extend(&mut self, mut other: Self) { + let drawables_offset = self.drawables.len(); + self.drawables + .extend_dynsequence(std::mem::take(&mut other.drawables)); + let mut other_items = std::mem::take(&mut other.items); + for other_item in &mut other_items { + other_item.draw_offset += drawables_offset; + } + self.items.extend(other_items); + } + + fn sort(&mut self) { + I::sort(self.items.as_mut_slice()); + } + + pub(crate) fn draw(&self, cmds: DrawContext<'_>) { + for item in &self.items { + for draw in + &self.drawables.as_slice()[item.draw_offset..item.draw_offset + item.draw_count] + { + draw.draw(cmds); + } + } + } +} + +pub struct PhaseDraw<'l> { + drawables: &'l mut DynDrawables, + count: &'l mut usize, +} + +impl PhaseDraw<'_> { + pub fn draw(&mut self, draw: D) + where + D: Drawable + Send + Sync + 'static, + { + dyn_sequence![dyn Drawable + Send + Sync + 'static | &mut self.drawables => { + push(draw); + }]; + *self.count += 1; + } +} + +pub struct DrawPhases(typemap::ShareMap); +impl Default for DrawPhases { + #[inline] + fn default() -> Self { + Self(typemap::ShareMap::custom()) + } +} + +impl DrawPhases { + pub fn get( + &self, + target_key: I::TargetKey, + ) -> Option>> { + self.0 + .get::>() + .and_then(|v| atomic_refcell::AtomicRef::filter_map(v.borrow(), |v| v.get(&target_key))) + } + + fn register(&mut self) { + self.0 + .entry::>() + .or_insert_with(Default::default); + } +} + +pub struct Draw<'l, I: PhaseItem> { + destination: Res<'l, DrawQueue>, +} + +impl Draw<'_, I> { + #[inline] + pub fn draw(&mut self, target_key: I::TargetKey) -> DrawTarget<'_, I> { + DrawTarget { + draw: self, + data: PhaseData::new(), + target_key, + } + } +} + +pub struct DrawTarget<'l, I: PhaseItem> { + draw: &'l Draw<'l, I>, + data: PhaseData, + target_key: I::TargetKey, +} + +impl Default for DrawQueue { + #[inline] + fn default() -> Self { + Self(crossbeam_queue::SegQueue::new()) + } +} + +fn collect_and_sort_draws_system( + mut queue: ResMut<'_, DrawQueue>, + phases: Res<'_, DrawPhases>, +) { + let mut phase_map = phases.0.get::>().unwrap().borrow_mut(); + + // clear sequences + for phase_data in phase_map.values_mut() { + phase_data.clear(); + } + + // TODO: optimize with a variant of merge-sort with pre-sorted chunks. + // pre-sort chunks inside Draw::flush, where it could utilize other threads. + for (target_key, chunk) in std::mem::take(&mut queue.0) { + phase_map + .entry(target_key) + .or_insert_with(PhaseData::new) + .extend(chunk); + } + + // remove empty sequences + phase_map.retain(|_, v| !v.items.is_empty()); + + // sort remaining sequences + for phase_data in phase_map.values_mut() { + phase_data.sort(); + } +} + +impl DrawTarget<'_, I> { + pub fn flush(&mut self) { + if !self.data.is_empty() { + // move commands into queue + self.draw.destination.0.push(( + self.target_key, + std::mem::replace(&mut self.data, PhaseData::new()), + )); + } + } + pub fn push(&mut self, item: I) -> PhaseDraw<'_> { + if self.data.len() >= 64 { + self.flush(); + } + self.data.push(item) + } +} + +impl Drop for DrawTarget<'_, I> { + fn drop(&mut self) { + self.flush(); + } +} + +unsafe impl SystemParam for Draw<'_, I> { + type State = DrawState; +} +pub struct DrawState(ResourceId>); + +unsafe impl SystemParamState for DrawState { + type Item<'r> = Draw<'r, I>; + fn init(resources: &mut Resources) -> Self { + Self(resources.init::>()) + } + fn update_access(&self, _resources: &Resources, access: &mut ResourceAccess) { + access.add_shared_checked(self.0); + } + fn fetch<'r>(&'r mut self, resources: &'r Resources) -> Self::Item<'r> { + Draw { + destination: resources.borrow_res_id(self.0).unwrap(), + } + } +} + +pub struct PhaseModule(PhantomData); + +impl PhaseModule { + #[inline] + pub const fn new() -> Self { + Self(PhantomData) + } +} + +impl Module for PhaseModule { + fn install_once(&self, res: &mut Resources) { + let phases = res.init::(); + res.init::>(); + res.get_mut_id(phases).unwrap().register::(); + } + + fn install_systems(schedule: &mut Schedule) { + schedule + .add_system(collect_and_sort_draws_system::) + .into_phase(RenderSystemPhase::Sorting); + } +} diff --git a/crates/render/src/graph/access.rs b/crates/render/src/graph/access.rs new file mode 100644 index 0000000..d339230 --- /dev/null +++ b/crates/render/src/graph/access.rs @@ -0,0 +1,332 @@ +use std::{ + fmt::Debug, + hash::Hash, + ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, RangeBounds, Sub}, +}; + +use bitflags::bitflags; + +use super::resources::{Texture, TextureUsage}; +use crate::buffer::{Buffer, BufferUsage}; + +pub trait ResourceAccess { + // Bitflags! + type Usage: Copy + + Clone + + Debug + + Eq + + BitOr + + BitOrAssign + + BitAnd + + BitAndAssign + + BitXor + + BitXorAssign + + Not + + Sub + + Hash; +} + +impl ResourceAccess for Texture { + type Usage = TextureUsage; +} + +impl ResourceAccess for Buffer { + type Usage = BufferUsage; +} + +bitflags! { + pub struct Stage: u32 { + // const TOP_OF_PIPE = 0x00000001; + const DRAW_INDIRECT = 0x00000002; + const VERTEX_INPUT = 0x00000004; + const VERTEX_SHADER = 0x00000008; + const TESSELLATION_CONTROL_SHADER = 0x00000010; + const TESSELLATION_EVALUATION_SHADER = 0x00000020; + const GEOMETRY_SHADER = 0x00000040; + const FRAGMENT_SHADER = 0x00000080; + const EARLY_FRAGMENT_TESTS = 0x00000100; + const LATE_FRAGMENT_TESTS = 0x00000200; + const FRAGMENT_TESTS = 0x00000300; // EARLY_FRAGMENT_TESTS | LATE_FRAGMENT_TESTS + + const COLOR_ATTACHMENT_OUTPUT = 0x00000400; + const COMPUTE_SHADER = 0x00000800; + const TRANSFER = 0x00001000; + // const BOTTOM_OF_PIPE = 0x00002000; + const HOST = 0x00004000; + // const ALL_GRAPHICS = 0x00008000; + + const ACCELERATION_STRUCTURE_BUILD = 0x02000000; + const RAY_TRACING_SHADER = 0x00200000; + + const NONE = 0; + } +} + +bitflags! { + pub struct Access: u32 { + // const INDIRECT_COMMAND_READ = 0x00000001; + const INDEX_READ = 0x00000002; + const VERTEX_ATTRIBUTE_READ = 0x00000004; + const UNIFORM_READ = 0x00000008; + const INPUT_ATTACHMENT_READ = 0x00000010; + const SHADER_READ = 0x00000020; + const SHADER_WRITE = 0x00000040; + const COLOR_ATTACHMENT_READ = 0x00000080; + const COLOR_ATTACHMENT_WRITE = 0x00000100; + const DEPTH_STENCIL_ATTACHMENT_READ = 0x00000200; + const DEPTH_STENCIL_ATTACHMENT_WRITE = 0x00000400; + const TRANSFER_READ = 0x00000800; + const TRANSFER_WRITE = 0x00001000; + const HOST_READ = 0x00002000; + const HOST_WRITE = 0x00004000; + // const MEMORY_READ = 0x00008000; + // const MEMORY_WRITE = 0x00010000; + + const ACCELERATION_STRUCTURE_READ = 0x00200000; + const ACCELERATION_STRUCTURE_WRITE = 0x00400000; + + const NONE = 0; + } +} + +bitflags! { + pub struct BufferReadAccess: u32 { + // const IndirectCommand = 0x01; + const INDEX = 0x0002; + const VERTEX_ATTRIBUTE = 0x0004; + const TRANSFER = 0x0800; + const HOST = 0x2000; + + const VERTEX_SHADER_UNIFORM = 0x00010000; + const VERTEX_SHADER_STORAGE = 0x00020000; + const TESS_CTRL_SHADER_UNIFORM = 0x00040000; + const TESS_CTRL_SHADER_STORAGE = 0x00080000; + const TESS_EVAL_SHADER_UNIFORM = 0x00100000; + const TESS_EVAL_SHADER_STORAGE = 0x00200000; + const GEOMETRY_SHADER_UNIFORM = 0x00400000; + const GEOMETRY_SHADER_STORAGE = 0x00800000; + const FRAGMENT_SHADER_UNIFORM = 0x01000000; + const FRAGMENT_SHADER_STORAGE = 0x02000000; + const COMPUTE_SHADER_UNIFORM = 0x04000000; + const COMPUTE_SHADER_STORAGE = 0x08000000; + } +} + +bitflags! { + pub struct BufferWriteAccess: u32 { + const TRANSFER = 0x1000; + const HOST = 0x4000; + + const VERTEX_SHADER_STORAGE = 0x00020000; + const TESS_CTRL_SHADER_STORAGE = 0x00080000; + const TESS_EVAL_SHADER_STORAGE = 0x00200000; + const GEOMETRY_SHADER_STORAGE = 0x00800000; + const FRAGMENT_SHADER_STORAGE = 0x02000000; + const COMPUTE_SHADER_STORAGE = 0x08000000; + } +} + +bitflags! { + pub struct TextureReadAccess: u32 { + const INPUT_ATTACHMENT = 0x0010; + const COLOR_ATTACHMENT = 0x0080; + const DEPTH_STENCIL_ATTACHMENT = 0x0200; + const TRANSFER = 0x0800; + const HOST = 0x2000; + + const VERTEX_SHADER = 0x00020000; + const TESS_CTRL_SHADER = 0x00080000; + const TESS_EVAL_SHADER = 0x00200000; + const GEOMETRY_SHADER = 0x00800000; + const FRAGMENT_SHADER = 0x02000000; + const COMPUTE_SHADER = 0x08000000; + } +} + +bitflags! { + pub struct TextureWriteAccess: u32 { + const COLOR_ATTACHMENT = 0x0100; + const DEPTH_STENCIL_ATTACHMENT = 0x0400; + const TRANSFER = 0x1000; + const HOST = 0x4000; + + const VERTEX_SHADER = 0x00020000; + const TESS_CTRL_SHADER = 0x00080000; + const TESS_EVAL_SHADER = 0x00200000; + const GEOMETRY_SHADER = 0x00800000; + const FRAGMENT_SHADER = 0x02000000; + const COMPUTE_SHADER = 0x08000000; + } +} + +trait AccessStage: Copy { + fn stage(self) -> Stage; +} + +impl AccessStage for BufferReadAccess { + #[inline] + fn stage(self) -> Stage { + let mut stage = Stage::empty(); + //if self.contains(Self::IndirectCommand) { stage |= Stage::DRAW_INDIRECT; } + if self.contains(Self::INDEX) { + stage |= Stage::VERTEX_INPUT; + } + if self.contains(Self::VERTEX_ATTRIBUTE) { + stage |= Stage::VERTEX_INPUT; + } + if self.contains(Self::TRANSFER) { + stage |= Stage::TRANSFER; + } + if self.contains(Self::HOST) { + stage |= Stage::HOST; + } + if self.contains(Self::VERTEX_SHADER_UNIFORM) { + stage |= Stage::VERTEX_SHADER; + } + if self.contains(Self::VERTEX_SHADER_STORAGE) { + stage |= Stage::VERTEX_SHADER; + } + if self.contains(Self::TESS_CTRL_SHADER_UNIFORM) { + stage |= Stage::TESSELLATION_CONTROL_SHADER; + } + if self.contains(Self::TESS_CTRL_SHADER_STORAGE) { + stage |= Stage::TESSELLATION_CONTROL_SHADER; + } + if self.contains(Self::TESS_EVAL_SHADER_UNIFORM) { + stage |= Stage::TESSELLATION_EVALUATION_SHADER; + } + if self.contains(Self::TESS_EVAL_SHADER_STORAGE) { + stage |= Stage::TESSELLATION_EVALUATION_SHADER; + } + if self.contains(Self::GEOMETRY_SHADER_UNIFORM) { + stage |= Stage::GEOMETRY_SHADER; + } + if self.contains(Self::GEOMETRY_SHADER_STORAGE) { + stage |= Stage::GEOMETRY_SHADER; + } + if self.contains(Self::FRAGMENT_SHADER_UNIFORM) { + stage |= Stage::FRAGMENT_SHADER; + } + if self.contains(Self::FRAGMENT_SHADER_STORAGE) { + stage |= Stage::FRAGMENT_SHADER; + } + if self.contains(Self::COMPUTE_SHADER_UNIFORM) { + stage |= Stage::COMPUTE_SHADER; + } + if self.contains(Self::COMPUTE_SHADER_STORAGE) { + stage |= Stage::COMPUTE_SHADER; + } + stage + } +} + +impl AccessStage for BufferWriteAccess { + #[inline] + fn stage(self) -> Stage { + let mut stage = Stage::empty(); + if self.contains(Self::TRANSFER) { + stage |= Stage::TRANSFER; + } + if self.contains(Self::HOST) { + stage |= Stage::HOST; + } + if self.contains(Self::VERTEX_SHADER_STORAGE) { + stage |= Stage::VERTEX_SHADER; + } + if self.contains(Self::TESS_CTRL_SHADER_STORAGE) { + stage |= Stage::TESSELLATION_CONTROL_SHADER; + } + if self.contains(Self::TESS_EVAL_SHADER_STORAGE) { + stage |= Stage::TESSELLATION_EVALUATION_SHADER; + } + if self.contains(Self::GEOMETRY_SHADER_STORAGE) { + stage |= Stage::GEOMETRY_SHADER; + } + if self.contains(Self::FRAGMENT_SHADER_STORAGE) { + stage |= Stage::FRAGMENT_SHADER; + } + if self.contains(Self::COMPUTE_SHADER_STORAGE) { + stage |= Stage::COMPUTE_SHADER; + } + stage + } +} + +impl AccessStage for TextureReadAccess { + #[inline] + fn stage(self) -> Stage { + let mut stage = Stage::empty(); + if self.contains(Self::INPUT_ATTACHMENT) { + stage |= Stage::FRAGMENT_SHADER; + } + if self.contains(Self::COLOR_ATTACHMENT) { + stage |= Stage::COLOR_ATTACHMENT_OUTPUT; + } + if self.contains(Self::DEPTH_STENCIL_ATTACHMENT) { + stage |= Stage::FRAGMENT_TESTS; + } + if self.contains(Self::TRANSFER) { + stage |= Stage::TRANSFER; + } + if self.contains(Self::HOST) { + stage |= Stage::HOST; + } + if self.contains(Self::VERTEX_SHADER) { + stage |= Stage::VERTEX_SHADER; + } + if self.contains(Self::TESS_CTRL_SHADER) { + stage |= Stage::TESSELLATION_CONTROL_SHADER; + } + if self.contains(Self::TESS_EVAL_SHADER) { + stage |= Stage::TESSELLATION_EVALUATION_SHADER; + } + if self.contains(Self::GEOMETRY_SHADER) { + stage |= Stage::GEOMETRY_SHADER; + } + if self.contains(Self::FRAGMENT_SHADER) { + stage |= Stage::FRAGMENT_SHADER; + } + if self.contains(Self::COMPUTE_SHADER) { + stage |= Stage::COMPUTE_SHADER; + } + stage + } +} + +impl AccessStage for TextureWriteAccess { + #[inline] + fn stage(self) -> Stage { + let mut stage = Stage::empty(); + if self.contains(Self::COLOR_ATTACHMENT) { + stage |= Stage::COLOR_ATTACHMENT_OUTPUT; + } + if self.contains(Self::DEPTH_STENCIL_ATTACHMENT) { + stage |= Stage::FRAGMENT_TESTS; + } + if self.contains(Self::TRANSFER) { + stage |= Stage::TRANSFER; + } + if self.contains(Self::HOST) { + stage |= Stage::HOST; + } + if self.contains(Self::VERTEX_SHADER) { + stage |= Stage::VERTEX_SHADER; + } + if self.contains(Self::TESS_CTRL_SHADER) { + stage |= Stage::TESSELLATION_CONTROL_SHADER; + } + if self.contains(Self::TESS_EVAL_SHADER) { + stage |= Stage::TESSELLATION_EVALUATION_SHADER; + } + if self.contains(Self::GEOMETRY_SHADER) { + stage |= Stage::GEOMETRY_SHADER; + } + if self.contains(Self::FRAGMENT_SHADER) { + stage |= Stage::FRAGMENT_SHADER; + } + if self.contains(Self::COMPUTE_SHADER) { + stage |= Stage::COMPUTE_SHADER; + } + stage + } +} diff --git a/crates/render/src/graph/builder.rs b/crates/render/src/graph/builder.rs new file mode 100644 index 0000000..f14c734 --- /dev/null +++ b/crates/render/src/graph/builder.rs @@ -0,0 +1,167 @@ +use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, +}; + +use pulz_ecs::prelude::*; +use tracing::{debug, trace, Callsite}; + +use super::{ + access::ResourceAccess, + deps::DependencyMatrix, + resources::{Slot, Texture}, + RenderGraph, RenderGraphBuilder, +}; +use crate::buffer::Buffer; + +pub trait GraphImport { + type Resource: ResourceAccess; +} + +pub trait GraphExport { + type Resource: ResourceAccess; +} + +impl RenderGraphBuilder { + pub fn import_texture(&mut self, _import_from: &I) -> Slot + where + I: GraphImport, + { + // TODO: associate resource + self.textures.import() + } + + pub fn import_buffer(&mut self, _import_from: &I) -> Slot + where + I: GraphImport, + { + // TODO: associate resource + self.buffers.import() + } + + pub fn export_texture(&mut self, slot: Slot, _export_to: &E) + where + E: GraphExport, + { + // TODO: associate resource + self.textures.export(slot) + } + + pub fn export_buffer(&mut self, slot: Slot, _export_to: &E) + where + E: GraphExport, + { + // TODO: associate resource + self.buffers.export(slot) + } + + fn hash(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + Hash::hash(&self.textures, &mut hasher); + Hash::hash(&self.buffers, &mut hasher); + Hash::hash(&self.passes, &mut hasher); + Hash::hash(&self.groups, &mut hasher); + hasher.finish() + } + + fn reset(&mut self) { + self.is_reset = true; + self.textures.reset(); + self.buffers.reset(); + self.passes.clear(); + self.passes_run.clear(); + self.groups.clear(); + } + + pub(crate) fn build_graph_system( + mut builder: ResMut<'_, Self>, + mut graph: ResMut<'_, RenderGraph>, + ) { + debug_assert!(builder.is_reset); + builder.is_reset = false; + + let builder_hash = builder.hash(); + if graph.init + && builder_hash == graph.hash + && builder.textures.len() == graph.textures.len() + && builder.buffers.len() == graph.buffers.len() + && builder.passes.len() == graph.passes.len() + && builder.passes_run.len() == graph.passes_exec.len() + && builder.groups.len() == graph.groups.len() + { + // graph not changed: swap data from builder (rest stays the same) + graph.was_updated = false; + swap_graph_data(&mut builder, &mut graph); + trace!("RenderGraph not changed"); + return; + } + + debug!( + "Updating RenderGraph with {} passes...", + builder.passes.len() + ); + + graph.reset(); + swap_graph_data(&mut builder, &mut graph); + graph.hash = builder_hash; + + // TODO: detect unused nodes / dead-stripping + + let mut m = graph.build_group_dependency_matrix(); + m.remove_self_references(); + + graph.topo_order = m.into_topological_order(); + + debug!("Topological order: {:?}", graph.topo_order); + + // TODO: resource aliasing (e.g. share Image resource when ) + + graph.init = true; + } + + pub(crate) fn reset_graph_builder(mut builder: ResMut<'_, Self>) { + debug_assert!(!builder.is_reset); + builder.reset() + } +} + +impl RenderGraph { + fn reset(&mut self) { + self.init = false; + self.was_updated = true; + self.textures.reset(); + self.buffers.reset(); + self.passes.clear(); + self.passes_exec.clear(); + self.groups.clear(); + self.topo_order.clear(); + } + + fn build_pass_dependency_matrix(&self) -> DependencyMatrix { + let mut m = DependencyMatrix::new(self.passes.len()); + // TODO: only mark used nodes + for p in &self.passes { + p.textures.mark_pass_dependency_matrix(&mut m, p.index); + p.buffers.mark_pass_dependency_matrix(&mut m, p.index); + } + m + } + + fn build_group_dependency_matrix(&self) -> DependencyMatrix { + let mut m = DependencyMatrix::new(self.passes.len()); + // TODO: only mark used nodes + for p in &self.passes { + p.textures + .mark_group_dependency_matrix(&mut m, &self.passes, p.group_index); + } + m + } +} + +fn swap_graph_data(builder: &mut RenderGraphBuilder, dest: &mut RenderGraph) { + std::mem::swap(&mut builder.textures, &mut dest.textures); + std::mem::swap(&mut builder.buffers, &mut dest.buffers); + std::mem::swap(&mut builder.passes, &mut dest.passes); + std::mem::swap(&mut builder.passes_run, &mut dest.passes_exec); + std::mem::swap(&mut builder.groups, &mut dest.groups); +} diff --git a/crates/render/src/graph/deps.rs b/crates/render/src/graph/deps.rs new file mode 100644 index 0000000..d57026b --- /dev/null +++ b/crates/render/src/graph/deps.rs @@ -0,0 +1,128 @@ +use pulz_bitset::{BitSet, BitSetIter}; + +pub struct DependencyMatrix { + num_dependencies: Vec, + num_dependents: Vec, + matrix: BitSet, +} + +impl DependencyMatrix { + pub fn new(num_nodes: usize) -> Self { + let mut num_dependencies = Vec::new(); + let mut num_dependents = Vec::new(); + num_dependencies.resize(num_nodes, 0); + num_dependents.resize(num_nodes, 0); + let deps = BitSet::with_capacity_for(num_nodes * num_nodes); + Self { + num_dependencies, + num_dependents, + matrix: deps, + } + } + + #[inline] + pub fn num_nodes(&self) -> usize { + self.num_dependencies.len() + } + + #[inline] + pub fn num_dependencies(&self, index: usize) -> usize { + self.num_dependencies.get(index).copied().unwrap_or(0) + } + + #[inline] + pub fn num_dependents(&self, index: usize) -> usize { + self.num_dependencies.get(index).copied().unwrap_or(0) + } + + pub fn dependents(&self, from: usize) -> BitSetIter<'_> { + let len = self.num_nodes(); + let start_index = len * from; + self.matrix.iter_range(start_index..start_index + len) + } + + #[inline] + fn index(&self, from: usize, to: usize) -> usize { + self.num_nodes() * from + to + } + + pub fn insert(&mut self, from: usize, to: usize) -> bool { + if self.matrix.insert(self.index(from, to)) { + self.num_dependents[from] += 1; + self.num_dependencies[to] += 1; + true + } else { + false + } + } + + pub fn contains(&self, from: usize, to: usize) -> bool { + self.matrix.contains(self.index(from, to)) + } + + pub fn remove(&mut self, from: usize, to: usize) -> bool { + if self.matrix.remove(self.index(from, to)) { + self.num_dependents[from] -= 1; + self.num_dependencies[to] -= 1; + true + } else { + false + } + } + + pub fn remove_self_references(&mut self) { + for i in 0..self.num_nodes() { + self.remove(i, i); + } + } + + pub fn remove_from_dependents(&mut self, from: usize) { + for _i in 0..self.num_nodes() { + let len = self.num_nodes(); + let start_index = len * from; + for d in self.matrix.drain(start_index..start_index + len) { + self.num_dependents[d] -= 1; + } + self.num_dependents[from] = 0; + } + } + + pub fn clear(&mut self) { + self.matrix.clear(); + for e in &mut self.num_dependencies { + *e = 0; + } + for e in &mut self.num_dependents { + *e = 0; + } + } + + pub fn into_topological_order(mut self) -> Vec> { + let mut todo = BitSet::from_range(0..self.num_nodes()); + let mut result = Vec::new(); + loop { + let mut new_nodes = Vec::new(); + let mut total_deps = 0; + for i in &todo { + let num_deps = self.num_dependencies[i]; + total_deps += num_deps; + if num_deps == 0 { + new_nodes.push(i); + } + } + + if new_nodes.is_empty() { + assert_eq!(0, total_deps, "cycle detected"); + break; + } + + for i in new_nodes.iter().copied() { + todo.remove(i); + self.remove_from_dependents(i); + } + + result.push(new_nodes); + } + result + } +} diff --git a/crates/render/src/graph/mod.rs b/crates/render/src/graph/mod.rs new file mode 100644 index 0000000..8b0b761 --- /dev/null +++ b/crates/render/src/graph/mod.rs @@ -0,0 +1,206 @@ +use self::{ + pass::{run::PassExec, PipelineBindPoint}, + resources::{Buffer, ResourceDeps, ResourceSet, SlotAccess, Texture}, +}; +use crate::draw::{DrawContext, DrawPhases}; + +pub mod access; +#[macro_use] +pub mod resources; +pub mod builder; +pub mod deps; +pub mod pass; + +#[derive(Hash)] +pub struct PassDescription { + index: usize, + group_index: usize, + name: &'static str, + bind_point: PipelineBindPoint, + textures: ResourceDeps, + buffers: ResourceDeps, + color_attachments: Vec, + depth_stencil_attachments: Option, + input_attachments: Vec, +} + +#[derive(Hash)] +pub struct PassGroupDescription { + index: usize, + name: &'static str, + bind_point: PipelineBindPoint, + begin_passes: usize, + end_passes: usize, // exclusive! +} + +pub struct RenderGraph { + init: bool, + hash: u64, + was_updated: bool, + textures: ResourceSet, + buffers: ResourceSet, + passes: Vec, + passes_exec: Vec>, + groups: Vec, + topo_order: Vec>, +} + +pub struct RenderGraphBuilder { + is_reset: bool, + textures: ResourceSet, + buffers: ResourceSet, + passes: Vec, + passes_run: Vec>, + groups: Vec, +} + +impl RenderGraph { + #[inline] + const fn new() -> Self { + Self { + init: false, + hash: 0, + was_updated: false, + textures: ResourceSet::new(), + buffers: ResourceSet::new(), + passes: Vec::new(), + passes_exec: Vec::new(), + groups: Vec::new(), + topo_order: Vec::new(), + } + } + + #[inline] + pub const fn was_updated(&self) -> bool { + self.was_updated + } + + #[inline] + pub const fn hash(&self) -> u64 { + self.hash + } + + #[inline] + pub fn get_num_topological_groups(&self) -> usize { + self.topo_order.len() + } + + pub fn get_topological_group( + &self, + group: usize, + ) -> impl Iterator + '_ { + self.topo_order + .get(group) + .into_iter() + .flatten() + .map(|g| &self.groups[*g]) + } + + #[inline] + pub fn get_num_passes(&self) -> usize { + self.passes.len() + } + + pub fn get_pass(&self, pass: usize) -> Option<&PassDescription> { + self.passes.get(pass) + } + + #[inline] + pub fn get_num_pass_groups(&self) -> usize { + self.groups.len() + } + + pub fn get_pass_group(&self, pass: usize) -> Option<&PassGroupDescription> { + self.groups.get(pass) + } + + pub fn execute_pass( + &self, + pass: usize, + draw_context: DrawContext<'_>, + draw_phases: &DrawPhases, + ) { + self.passes_exec[pass].execute(draw_context, draw_phases) + } +} + +impl RenderGraphBuilder { + #[inline] + pub const fn new() -> Self { + Self { + is_reset: false, + textures: ResourceSet::new(), + buffers: ResourceSet::new(), + passes: Vec::new(), + passes_run: Vec::new(), + groups: Vec::new(), + } + } +} + +impl Default for RenderGraphBuilder { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl Default for RenderGraph { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl PassDescription { + #[inline] + pub const fn index(&self) -> usize { + self.index + } + + #[inline] + pub const fn group_index(&self) -> usize { + self.group_index + } + + #[inline] + pub const fn name(&self) -> &'static str { + self.name + } + + #[inline] + pub const fn bind_point(&self) -> PipelineBindPoint { + self.bind_point + } + + #[inline] + pub const fn textures(&self) -> &ResourceDeps { + &self.textures + } + + #[inline] + pub const fn buffers(&self) -> &ResourceDeps { + &self.buffers + } +} + +impl PassGroupDescription { + #[inline] + pub const fn group_index(&self) -> usize { + self.index + } + #[inline] + pub const fn name(&self) -> &'static str { + self.name + } + + #[inline] + pub const fn bind_point(&self) -> PipelineBindPoint { + self.bind_point + } + + #[inline] + pub fn range(&self) -> std::ops::Range { + self.begin_passes..self.end_passes + } +} diff --git a/crates/render/src/graph/pass/builder.rs b/crates/render/src/graph/pass/builder.rs new file mode 100644 index 0000000..f746590 --- /dev/null +++ b/crates/render/src/graph/pass/builder.rs @@ -0,0 +1,208 @@ +use std::marker::PhantomData; + +use super::{Graphics, Pass, PassDescription, PassGroup, PipelineType}; +use crate::graph::{ + access::Stage, + resources::{Buffer, BufferUsage, Slot, SlotAccess, Texture, TextureUsage, WriteSlot}, + PassGroupDescription, RenderGraphBuilder, +}; + +impl RenderGraphBuilder { + pub fn add_pass>(&mut self, pass_group: P) -> P::Output { + debug_assert!(self.is_reset); + let begin_passes = self.passes.len(); + let group_index = self.groups.len(); + self.groups.push(PassGroupDescription { + index: group_index, + name: pass_group.type_name(), + bind_point: Q::BIND_POINT, + begin_passes, + end_passes: begin_passes, + }); + + let output = pass_group.build(PassGroupBuilder { + base: self, + group_index, + _pipeline_type: PhantomData, + }); + + // update end marker + let end_passes = self.passes.len(); + if begin_passes == end_passes { + // group was empty, remove it! + self.groups.pop(); + } else { + self.groups[group_index].end_passes = end_passes; + } + output + } +} + +pub struct PassGroupBuilder<'a, Q> { + base: &'a mut RenderGraphBuilder, + group_index: usize, + _pipeline_type: PhantomData, +} +impl PassGroupBuilder<'_, Q> { + #[inline] + pub fn creates_texture(&mut self) -> WriteSlot { + self.base.textures.create() + } + + #[inline] + pub fn creates_buffer(&mut self) -> WriteSlot { + self.base.buffers.create() + } +} + +impl PassGroupBuilder<'_, Q> { + #[inline] + pub(super) fn push_pass>(&mut self, pass: P) -> P::Output { + let index = self.base.passes.len(); + let mut descr = + PassDescription::new(index, self.group_index, pass.type_name(), Q::BIND_POINT); + let (output, run) = pass.build(PassBuilder { + base: self.base, + pass: &mut descr, + _pipeline_type: PhantomData, + }); + self.base.passes.push(descr); + self.base.passes_run.push(run.erased()); + output + } +} + +impl PassGroupBuilder<'_, Graphics> { + #[inline] + pub fn sub_pass>(&mut self, pass: P) -> P::Output { + self.push_pass(pass) + } +} + +pub struct PassBuilder<'a, Q> { + base: &'a mut RenderGraphBuilder, + pass: &'a mut PassDescription, + _pipeline_type: PhantomData, +} + +impl PassBuilder<'_, Q> { + #[inline] + pub fn creates_texture(&mut self, usage: TextureUsage, stages: Stage) -> WriteSlot { + let slot = self.base.textures.create(); + self.writes_texture(slot, stages, usage) + } + + #[inline] + pub fn writes_or_creates_texture( + &mut self, + slot: Option>, + stages: Stage, + usage: TextureUsage, + ) -> WriteSlot { + let slot = slot.unwrap_or_else(|| self.base.textures.create()); + self.writes_texture(slot, stages, usage) + } + + pub fn writes_texture( + &mut self, + slot: WriteSlot, + stages: Stage, + usage: TextureUsage, + ) -> WriteSlot { + self.pass.textures.access(&slot, true, stages, usage); + let last_written_by_pass = slot.last_written_by_pass as usize; + if last_written_by_pass != self.pass.index { + return self.base.textures.writes(slot, self.pass.index); + } + slot + } + + #[inline] + pub fn reads_texture(&mut self, slot: Slot, stages: Stage, usage: TextureUsage) { + self.pass.textures.access(&slot, false, stages, usage); + let last_written_by_pass = slot.last_written_by_pass as usize; + if last_written_by_pass != self.pass.index { + self.base.textures.reads(slot); + } + } + + #[inline] + pub fn creates_buffer(&mut self, usage: BufferUsage, stages: Stage) -> WriteSlot { + let slot = self.base.buffers.create(); + self.writes_buffer(slot, stages, usage) + } + + #[inline] + pub fn writes_or_creates_buffer( + &mut self, + slot: Option>, + stages: Stage, + usage: BufferUsage, + ) -> WriteSlot { + let slot = slot.unwrap_or_else(|| self.base.buffers.create()); + self.writes_buffer(slot, stages, usage) + } + + pub fn writes_buffer( + &mut self, + slot: WriteSlot, + stages: Stage, + usage: BufferUsage, + ) -> WriteSlot { + self.pass.buffers.access(&slot, true, stages, usage); + let last_written_by_pass = slot.last_written_by_pass as usize; + if last_written_by_pass != self.pass.index { + return self.base.buffers.writes(slot, self.pass.index); + } + slot + } + + pub fn reads_buffer(&mut self, slot: Slot, stages: Stage, usage: BufferUsage) { + self.pass.buffers.access(&slot, false, stages, usage); + let last_written_by_pass = slot.last_written_by_pass as usize; + if last_written_by_pass != self.pass.index { + self.base.buffers.reads(slot); + } + } +} + +impl PassBuilder<'_, Graphics> { + #[inline] + pub fn creates_color_attachment(&mut self) -> WriteSlot { + let slot = self.base.textures.create(); + self.color_attachment(slot) + } + + pub fn color_attachment(&mut self, texture: WriteSlot) -> WriteSlot { + self.pass.color_attachments.push(texture.index()); + self.writes_texture( + texture, + Stage::COLOR_ATTACHMENT_OUTPUT, + TextureUsage::COLOR_ATTACHMENT, + ) + } + + #[inline] + pub fn creates_depth_stencil_attachment(&mut self) -> WriteSlot { + let slot = self.base.textures.create(); + self.depth_stencil_attachment(slot) + } + pub fn depth_stencil_attachment(&mut self, texture: WriteSlot) -> WriteSlot { + self.pass.depth_stencil_attachments.replace(texture.index()); + // TODO: support early & late fragment tests + self.writes_texture( + texture, + Stage::FRAGMENT_TESTS, + TextureUsage::DEPTH_STENCIL_ATTACHMENT, + ) + } + + pub fn input_attachment(&mut self, texture: Slot) { + self.pass.input_attachments.push(texture.index()); + self.reads_texture( + texture, + Stage::FRAGMENT_SHADER, + TextureUsage::INPUT_ATTACHMENT, + ) + } +} diff --git a/crates/render/src/graph/pass/mod.rs b/crates/render/src/graph/pass/mod.rs new file mode 100644 index 0000000..3906c84 --- /dev/null +++ b/crates/render/src/graph/pass/mod.rs @@ -0,0 +1,99 @@ +use self::{ + builder::{PassBuilder, PassGroupBuilder}, + run::PassExec, +}; +use super::{resources::ResourceDeps, PassDescription}; + +pub mod builder; +pub mod run; + +impl PassDescription { + const fn new( + index: usize, + group_index: usize, + name: &'static str, + bind_point: PipelineBindPoint, + ) -> Self { + Self { + index, + group_index, + name, + bind_point, + textures: ResourceDeps::new(), + buffers: ResourceDeps::new(), + color_attachments: Vec::new(), + depth_stencil_attachments: None, + input_attachments: Vec::new(), + } + } +} + +pub trait PipelineType: 'static { + const BIND_POINT: PipelineBindPoint; +} +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum PipelineBindPoint { + Graphics, + Compute, + RayTracing, +} + +// use the types Graphics, Compute and RayTracing also as a PipelineBindPoint Value +pub use self::PipelineBindPoint::*; + +pub enum Graphics {} +impl PipelineType for Graphics { + const BIND_POINT: PipelineBindPoint = Graphics; +} + +pub enum Compute {} +impl PipelineType for Compute { + const BIND_POINT: PipelineBindPoint = Compute; +} + +pub enum RayTracing {} +impl PipelineType for RayTracing { + const BIND_POINT: PipelineBindPoint = RayTracing; +} + +pub trait Pass { + type Output: 'static; + fn build(self, builder: PassBuilder<'_, Q>) -> (Self::Output, PassExec); + + fn type_name(&self) -> &'static str { + std::any::type_name::() + } +} + +pub trait PassGroup { + type Output; + fn build(self, builder: PassGroupBuilder<'_, Q>) -> Self::Output; + + fn type_name(&self) -> &'static str { + std::any::type_name::() + } +} + +impl> PassGroup for P { + type Output = P::Output; + #[inline] + fn build(self, mut builder: PassGroupBuilder<'_, Q>) -> Self::Output { + builder.push_pass(self) + } + + fn type_name(&self) -> &'static str { + Pass::type_name(self) + } +} + +impl Pass for F +where + F: FnOnce(PassBuilder<'_, Q>) -> (O, PassExec), + O: PipelineType, +{ + type Output = O; + #[inline] + fn build(self, builder: PassBuilder<'_, Q>) -> (Self::Output, PassExec) { + self(builder) + } +} diff --git a/crates/render/src/graph/pass/run.rs b/crates/render/src/graph/pass/run.rs new file mode 100644 index 0000000..3f3917f --- /dev/null +++ b/crates/render/src/graph/pass/run.rs @@ -0,0 +1,131 @@ +use std::{ + marker::PhantomData, + ops::{Deref, DerefMut}, +}; + +use super::{Graphics, PipelineType}; +use crate::{ + backend::CommandEncoder, + draw::{DrawContext, DrawPhases, PhaseItem}, +}; + +pub trait PassRun: Send + Sync + 'static { + fn run(&self, _ctx: PassContext<'_, Q>); +} + +trait PassRunAny: Send + Sync + 'static { + fn run(&self, draw_context: DrawContext<'_>, draw_phases: &DrawPhases); +} + +struct PassRunAnyNoop; +struct PassRunAnyWrapper(R, PhantomData); + +impl PassRunAny for PassRunAnyNoop { + #[inline] + fn run(&self, _draw_context: DrawContext<'_>, _draw_phases: &DrawPhases) {} +} + +impl PassRunAny for PassRunAnyWrapper +where + R: PassRun, + Q: PipelineType, +{ + #[inline] + fn run(&self, draw_context: DrawContext<'_>, draw_phases: &DrawPhases) { + let ctx = PassContext::<'_, Q> { + draw_context, + draw_phases, + _pipeline_type: PhantomData, + }; + PassRun::::run(&self.0, ctx); + } +} +pub struct PassExec { + run: Box, + _phantom: PhantomData, +} + +impl PassExec { + pub fn noop() -> Self { + Self { + run: Box::new(PassRunAnyNoop), + _phantom: PhantomData, + } + } + + #[inline] + pub(crate) fn execute(&self, draw_context: DrawContext<'_>, draw_phases: &DrawPhases) { + self.run.run(draw_context, draw_phases) + } +} + +impl PassExec { + #[inline] + pub fn new_fn(run: F) -> Self + where + F: Fn(PassContext<'_, Q>) + Send + Sync + 'static, + { + Self::new(run) + } + + pub fn new(run: R) -> Self + where + R: PassRun, + { + let boxed = Box::new(PassRunAnyWrapper::(run, PhantomData)); + Self { + run: boxed, + _phantom: PhantomData, + } + } + + #[inline] + pub(crate) fn erased(self) -> PassExec<()> { + PassExec { + run: self.run, + _phantom: PhantomData, + } + } +} + +impl PassRun for F +where + F: Fn(PassContext<'_, Q>) + Send + Sync + 'static, +{ + #[inline] + fn run(&self, ctx: PassContext<'_, Q>) { + self(ctx) + } +} + +pub struct PassContext<'a, Q = Graphics> { + draw_context: DrawContext<'a>, + draw_phases: &'a DrawPhases, + _pipeline_type: PhantomData, +} + +impl PassContext<'_, Graphics> { + pub fn draw_phase_items(&mut self, target_key: I::TargetKey) + where + I: PhaseItem, + { + if let Some(phase) = self.draw_phases.get::(target_key) { + phase.draw(self.draw_context); + } + } +} + +// TODO: ambassador +impl<'a, Q> Deref for PassContext<'a, Q> { + type Target = dyn CommandEncoder + 'a; + #[inline] + fn deref(&self) -> &Self::Target { + self.draw_context + } +} +impl DerefMut for PassContext<'_, Q> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + self.draw_context + } +} diff --git a/crates/render/src/graph/resources.rs b/crates/render/src/graph/resources.rs new file mode 100644 index 0000000..69b67a4 --- /dev/null +++ b/crates/render/src/graph/resources.rs @@ -0,0 +1,320 @@ +use std::{ + hash::{self, Hash}, + marker::PhantomData, + ops::Deref, +}; + +use pulz_assets::Handle; +use pulz_window::WindowId; + +use super::{ + access::{ResourceAccess, Stage}, + builder::{GraphExport, GraphImport}, + deps::DependencyMatrix, + PassDescription, +}; +pub use crate::{ + buffer::{Buffer, BufferUsage}, + texture::{Texture, TextureUsage}, +}; +use crate::{camera::RenderTarget, texture::Image}; + +#[derive(Copy, Clone)] +pub struct Slot { + pub(crate) index: u16, + pub(crate) last_written_by_pass: u16, + _phantom: PhantomData R>, +} + +// Not Copy by intention! +pub struct WriteSlot(Slot); + +pub trait SlotAccess { + const WRITE: bool; + fn index(&self) -> u16; +} + +impl SlotAccess for Slot { + const WRITE: bool = false; + #[inline] + fn index(&self) -> u16 { + self.index + } +} + +impl SlotAccess for WriteSlot { + const WRITE: bool = true; + #[inline] + fn index(&self) -> u16 { + self.0.index + } +} + +impl Deref for WriteSlot { + type Target = Slot; + #[inline] + fn deref(&self) -> &Slot { + &self.0 + } +} + +impl Slot { + const fn new(index: usize, last_written_by_pass: u16) -> Self { + Self { + index: index as u16, + last_written_by_pass, + _phantom: PhantomData, + } + } +} + +impl WriteSlot { + #[inline] + const fn new(index: usize, last_writing_pass: u16) -> Self { + Self(Slot::new(index, last_writing_pass)) + } + #[inline] + pub const fn read(self) -> Slot { + self.0 + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum ResourceVariant { + Transient, + Import, + Export, +} + +pub(super) struct ResourceSet { + last_written: Vec, + first_written: Vec, + variant: Vec, + _phantom: PhantomData R>, +} + +impl ResourceSet { + pub fn len(&self) -> usize { + self.last_written.len() + } +} + +impl Hash for ResourceSet { + fn hash(&self, state: &mut H) { + Hash::hash_slice(&self.last_written, state); + Hash::hash_slice(&self.first_written, state); + Hash::hash_slice(&self.variant, state); + } +} + +pub struct ResourceDeps { + deps: Vec>, +} + +impl Hash for ResourceDeps { + fn hash(&self, state: &mut H) { + Hash::hash_slice(&self.deps, state); + } +} + +#[derive(Hash)] +pub struct ResourceDep { + index: u16, + last_written_by_pass: u16, + write_access: bool, + stages: Stage, + usage: U, +} + +impl ResourceSet { + #[inline] + pub(super) const fn new() -> Self { + Self { + last_written: Vec::new(), + first_written: Vec::new(), + variant: Vec::new(), + _phantom: PhantomData, + } + } + + pub(super) fn reset(&mut self) { + self.last_written.clear(); + self.first_written.clear(); + self.variant.clear(); + } + + pub(super) fn create(&mut self) -> WriteSlot { + let index = self.last_written.len(); + self.last_written.push(!0); + self.first_written.push(!0); + self.variant.push(ResourceVariant::Transient); + WriteSlot::new(index, !0) + } + + pub(super) fn writes(&mut self, slot: WriteSlot, new_pass: usize) -> WriteSlot { + let new_pass = new_pass as u16; + let index = slot.0.index as usize; + let last_written_by_pass = self.last_written[index]; + assert_eq!( + last_written_by_pass, slot.0.last_written_by_pass, + "resource also written by an other pass (slot out of sync)" + ); + if new_pass != last_written_by_pass { + self.last_written[index] = new_pass; + if self.first_written[index] == !0 { + self.first_written[index] = new_pass + } + } + WriteSlot::new(index, new_pass) + } + + pub(super) fn reads(&mut self, slot: Slot) { + assert_ne!( + slot.last_written_by_pass, !0, + "resource was not yet written!" + ); + let index = slot.index as usize; + let last_written_by_pass = self.last_written[index]; + // TODO: allow usage of older slots for reading + assert_eq!( + last_written_by_pass, slot.last_written_by_pass, + "resource also written by an other pass (slot out of sync), TODO!" + ); + } + + pub(super) fn import(&mut self) -> Slot { + let s = self.create(); + let index = s.index as usize; + self.variant[index] = ResourceVariant::Import; + s.read() + } + + pub(super) fn export(&mut self, slot: Slot) { + let index = slot.index as usize; + assert_eq!(ResourceVariant::Transient, self.variant[index]); + self.variant[index] = ResourceVariant::Export; + } +} + +impl ResourceDeps { + #[inline] + pub fn deps(&self) -> &[ResourceDep] { + &self.deps + } + + pub fn find_by_resource_index(&self, resource_index: usize) -> Option<&ResourceDep> { + if let Ok(i) = self + .deps + .binary_search_by_key(&resource_index, |d| d.index as usize) + { + Some(&self.deps[i]) + } else { + None + } + } + + #[inline] + pub(super) const fn new() -> Self { + Self { deps: Vec::new() } + } + + pub(super) fn mark_pass_dependency_matrix(&self, m: &mut DependencyMatrix, to_pass: usize) { + for dep in &self.deps { + let pass_index = dep.src_pass(); + if pass_index != !0 { + m.insert(pass_index, to_pass); + } + } + } + + pub(super) fn mark_group_dependency_matrix( + &self, + m: &mut DependencyMatrix, + passes: &[PassDescription], + to_group: usize, + ) { + for dep in &self.deps { + let pass_index = dep.src_pass(); + if pass_index != !0 { + m.insert(passes[pass_index].group_index, to_group); + } + } + } + + pub(super) fn access( + &mut self, + slot: &Slot, + write_access: bool, + stages: Stage, + usage: R::Usage, + ) { + match self.deps.binary_search_by_key(&slot.index, |e| e.index) { + Ok(i) => { + let entry = &mut self.deps[i]; + assert_eq!(entry.last_written_by_pass, slot.last_written_by_pass); + entry.write_access |= write_access; + entry.stages |= stages; + entry.usage |= usage; + } + Err(i) => { + self.deps.insert( + i, + ResourceDep { + index: slot.index, + last_written_by_pass: slot.last_written_by_pass, + write_access, + stages, + usage, + }, + ); + } + } + } +} + +impl ResourceDep { + #[inline] + pub fn resource_index(&self) -> usize { + self.index as usize + } + + #[inline] + pub fn src_pass(&self) -> usize { + if self.last_written_by_pass == !0 { + !0 + } else { + self.last_written_by_pass as usize + } + } + + #[inline] + pub fn stages(&self) -> Stage { + self.stages + } + + #[inline] + pub fn usage(&self) -> U { + self.usage + } + + #[inline] + pub fn write_access(&self) -> bool { + self.write_access + } +} + +impl GraphImport for Handle { + type Resource = Texture; +} + +impl GraphExport for Handle { + type Resource = Texture; +} + +impl GraphExport for WindowId { + type Resource = Texture; +} + +impl GraphExport for RenderTarget { + type Resource = Texture; +} diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index c5346b1..b4abd2b 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -26,12 +26,19 @@ #![doc = include_str!("../README.md")] use camera::{Camera, RenderTarget}; +use graph::{RenderGraph, RenderGraphBuilder}; use pulz_assets::Assets; -use pulz_ecs::{define_label_enum, label::SystemPhase, prelude::*}; +use pulz_ecs::{ + define_label_enum, + label::{CoreSystemPhase, SystemPhase}, + prelude::*, +}; pub mod backend; pub mod buffer; pub mod camera; +pub mod draw; +pub mod graph; pub mod mesh; pub mod pipeline; pub mod shader; @@ -51,6 +58,7 @@ pub use pulz_transform::math; define_label_enum! { pub enum RenderSystemPhase: SystemPhase { Sorting, + BuildGraph, UpdateGraph, Render, } @@ -62,6 +70,12 @@ impl Module for RenderModule { fn install_once(&self, res: &mut Resources) { Assets::::install_into(res); + res.init::(); + res.init::(); + // TODO: + //res.init::(); + //render_graph::graph::RenderGraph::install_into(res, schedule); + let mut world = res.world_mut(); world.init::(); world.init::(); @@ -70,8 +84,18 @@ impl Module for RenderModule { fn install_systems(schedule: &mut Schedule) { schedule.add_phase_chain([ RenderSystemPhase::Sorting, + RenderSystemPhase::BuildGraph, RenderSystemPhase::UpdateGraph, RenderSystemPhase::Render, ]); + // SORTING after update UPDATE + schedule.add_phase_dependency(CoreSystemPhase::Update, RenderSystemPhase::Sorting); + schedule + .add_system(RenderGraphBuilder::reset_graph_builder) + .before(RenderSystemPhase::BuildGraph); + schedule + .add_system(RenderGraphBuilder::build_graph_system) + .after(RenderSystemPhase::BuildGraph) + .before(RenderSystemPhase::UpdateGraph); } } diff --git a/crates/render/src/pipeline/binding.rs b/crates/render/src/pipeline/binding.rs new file mode 100644 index 0000000..76b2771 --- /dev/null +++ b/crates/render/src/pipeline/binding.rs @@ -0,0 +1,19 @@ +pub struct BindGroupLayoutDescriptor<'a> { + pub label: Option<&'a str>, + pub entries: &'a [BindGroupLayoutEntry], +} + +#[derive(Copy, Clone)] +pub struct BindGroupLayoutEntry { + pub binding: u32, + // pub visibility: ShaderStages, + // pub ty: BindingType, + // TODO: + pub count: u32, +} + +pub use pulz_render_macros::AsBindingLayout; + +pub trait AsBindingLayout { + // TODO (also macro) +} diff --git a/crates/render/src/pipeline/descriptor.rs b/crates/render/src/pipeline/descriptor.rs index 7297f72..185f136 100644 --- a/crates/render/src/pipeline/descriptor.rs +++ b/crates/render/src/pipeline/descriptor.rs @@ -1,3 +1,313 @@ +use bitflags::bitflags; + +use super::PipelineLayout; +use crate::{shader::ShaderModule, texture::TextureFormat}; + +#[derive(Debug, Clone, PartialEq)] +pub struct ComputePipelineDescriptor<'a> { + pub label: Option<&'a str>, + pub layout: Option, + pub module: ShaderModule, + pub entry_point: &'a str, + pub specialization: SpecializationInfo<'a>, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct GraphicsPipelineDescriptor<'a> { + pub label: Option<&'a str>, + pub layout: Option, + pub vertex: VertexState<'a>, + pub primitive: PrimitiveState, + pub depth_stencil: Option, + pub fragment: Option>, + pub samples: u32, + pub specialization: SpecializationInfo<'a>, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct RayTracingPipelineDescriptor<'a> { + pub label: Option<&'a str>, + pub layout: Option, + pub modules: Vec>, + pub groups: Vec, + pub max_recursion_depth: u32, + pub specialization: SpecializationInfo<'a>, +} + +pub type SpecializationInfo<'a> = Vec>; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct SpecializationMapEntry<'a> { + pub constant_id: u32, + pub name: &'a str, + pub value: PipelineConstantValue, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum PipelineConstantValue { + Bool(bool), + Float(f32), + Sint(i32), + Uint(u32), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VertexState<'a> { + pub module: ShaderModule, + pub entry_point: &'a str, + pub buffers: &'a [VertexBufferLayout<'a>], +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct VertexBufferLayout<'a> { + pub array_stride: usize, + pub attributes: &'a [VertexAttribute], +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct VertexAttribute { + pub format: VertexFormat, + pub offset: usize, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct PrimitiveState { + pub topology: PrimitiveTopology, + pub polygon_mode: PolygonMode, + pub front_face: FrontFace, + pub cull_mode: Option, + pub line_width: f32, +} + +impl PrimitiveState { + pub const DEFAULT: Self = Self { + topology: PrimitiveTopology::TriangleList, + polygon_mode: PolygonMode::Fill, + front_face: FrontFace::CounterClockwise, + cull_mode: None, + line_width: 0.0, + }; +} + +impl Default for PrimitiveState { + fn default() -> Self { + Self::DEFAULT + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct DepthStencilState { + pub format: TextureFormat, + pub depth: DepthState, + pub stencil: StencilState, +} + +impl DepthStencilState { + pub const DEFAULT: Self = Self { + format: TextureFormat::DEFAULT, + depth: DepthState::DEFAULT, + stencil: StencilState::DEFAULT, + }; + + pub fn is_depth_enabled(&self) -> bool { + self.depth.compare != CompareFunction::Always || self.depth.write_enabled + } + /// Returns true if the state doesn't mutate either depth or stencil of the target. + pub fn is_read_only(&self) -> bool { + !self.depth.write_enabled && self.stencil.is_read_only() + } +} + +impl Default for DepthStencilState { + fn default() -> Self { + Self::DEFAULT + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct DepthState { + pub write_enabled: bool, + pub compare: CompareFunction, + + pub bias: i32, + pub bias_slope_scale: f32, + pub bias_clamp: f32, +} + +impl DepthState { + pub const DEFAULT: Self = Self { + write_enabled: false, + compare: CompareFunction::Always, + + bias: 0, + bias_slope_scale: 0.0, + bias_clamp: 0.0, + }; +} + +impl Default for DepthState { + fn default() -> Self { + Self::DEFAULT + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct StencilState { + pub front: StencilFaceState, + pub back: StencilFaceState, + + pub read_mask: u32, + pub write_mask: u32, +} + +impl StencilState { + pub const DEFAULT: Self = Self { + front: StencilFaceState::IGNORE, + back: StencilFaceState::IGNORE, + + read_mask: u32::MAX, + write_mask: u32::MAX, + }; + + pub fn is_enabled(&self) -> bool { + (self.front != StencilFaceState::IGNORE || self.back != StencilFaceState::IGNORE) + && (self.read_mask != 0 || self.write_mask != 0) + } + /// Returns true if the state doesn't mutate the target values. + pub fn is_read_only(&self) -> bool { + self.write_mask == 0 + } +} + +impl Default for StencilState { + fn default() -> Self { + Self::DEFAULT + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct StencilFaceState { + pub compare: CompareFunction, + pub fail_op: StencilOperation, + pub depth_fail_op: StencilOperation, + pub pass_op: StencilOperation, +} + +impl StencilFaceState { + pub const IGNORE: Self = Self { + compare: CompareFunction::Always, + fail_op: StencilOperation::Keep, + depth_fail_op: StencilOperation::Keep, + pass_op: StencilOperation::Keep, + }; +} + +impl Default for StencilFaceState { + fn default() -> Self { + Self::IGNORE + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct FragmentState<'a> { + pub module: ShaderModule, + pub entry_point: &'a str, + pub targets: &'a [ColorTargetState], +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct ColorTargetState { + pub format: TextureFormat, + pub blend: Option, + pub write_mask: ColorWrite, +} + +impl ColorTargetState { + pub const DEFAULT: Self = Self { + format: TextureFormat::DEFAULT, + blend: None, + write_mask: ColorWrite::ALL, + }; +} + +impl Default for ColorTargetState { + fn default() -> Self { + Self::DEFAULT + } +} + +impl From for ColorTargetState { + fn from(format: TextureFormat) -> Self { + Self { + format, + blend: None, + write_mask: ColorWrite::ALL, + } + } +} + +#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)] +pub struct BlendState { + pub color: BlendComponent, + pub alpha: BlendComponent, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct BlendComponent { + pub operation: BlendOperation, + pub src_factor: BlendFactor, + pub dst_factor: BlendFactor, +} + +impl BlendComponent { + pub const DEFAULT: Self = Self { + operation: BlendOperation::Add, + src_factor: BlendFactor::One, + dst_factor: BlendFactor::Zero, + }; +} + +impl Default for BlendComponent { + fn default() -> Self { + Self::DEFAULT + } +} + +#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)] +pub struct RayTracingShaderGroup { + pub group_type: RayTracingGroupType, + pub general_shader: u32, + pub closest_hit_shader: u32, + pub any_hit_shader: u32, + pub intersection_shader: u32, +} + +#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)] +pub struct RayTracingShaderModule<'a> { + pub stage: RayTracingStage, + pub module: ShaderModule, + pub entry_point: &'a str, +} + +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)] +pub enum RayTracingStage { + #[default] + Raygen, + AnyHit, + ClosestHit, + Miss, + Intersection, + Callable, +} + +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)] +pub enum RayTracingGroupType { + #[default] + General, + TrianglesHitGroup, + ProceduralHitGroup, +} + #[derive(Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] pub enum PolygonMode { #[default] @@ -16,6 +326,13 @@ pub enum PrimitiveTopology { TriangleStrip, } +#[derive(Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub enum VertexStepMode { + #[default] + Vertex, + Instance, +} + #[derive(Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] #[non_exhaustive] pub enum VertexFormat { @@ -77,3 +394,71 @@ pub enum Face { Back, } +#[derive(Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub enum CompareFunction { + Never, + Less, + Equal, + LessEqual, + Greater, + NotEqual, + GreaterEqual, + #[default] + Always, +} + +#[derive(Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub enum StencilOperation { + #[default] + Keep, + Zero, + Replace, + Invert, + IncrementClamp, + DecrementClamp, + IncrementWrap, + DecrementWrap, +} + +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub enum BlendOperation { + Add, + Subtract, + ReverseSubtract, + Min, + Max, +} + +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub enum BlendFactor { + Zero, + One, + Src, + OneMinusSrc, + SrcAlpha, + OneMinusSrcAlpha, + Dst, + OneMinusDst, + DstAlpha, + OneMinusDstAlpha, + SrcAlphaSaturated, + Constant, + OneMinusConstant, +} + +bitflags! { + pub struct ColorWrite: u32 { + const RED = 1; + const GREEN = 2; + const BLUE = 4; + const ALPHA = 8; + + const ALL = 0xF; + } +} + +impl Default for ColorWrite { + fn default() -> Self { + Self::ALL + } +} diff --git a/crates/render/src/pipeline/mod.rs b/crates/render/src/pipeline/mod.rs index 68a64c0..371028d 100644 --- a/crates/render/src/pipeline/mod.rs +++ b/crates/render/src/pipeline/mod.rs @@ -1,3 +1,11 @@ +mod binding; mod descriptor; +mod pipeline_layout; -pub use self::descriptor::*; +pub use self::{binding::*, descriptor::*, pipeline_layout::*}; + +crate::backend::define_gpu_resource!(BindGroupLayout, BindGroupLayoutDescriptor<'l>); +crate::backend::define_gpu_resource!(PipelineLayout, PipelineLayoutDescriptor<'l>); +crate::backend::define_gpu_resource!(ComputePipeline, ComputePipelineDescriptor<'l>); +crate::backend::define_gpu_resource!(GraphicsPipeline, GraphicsPipelineDescriptor<'l>); +crate::backend::define_gpu_resource!(RayTracingPipeline, RayTracingPipelineDescriptor<'l>); diff --git a/crates/render/src/pipeline/pipeline_layout.rs b/crates/render/src/pipeline/pipeline_layout.rs new file mode 100644 index 0000000..bed25bd --- /dev/null +++ b/crates/render/src/pipeline/pipeline_layout.rs @@ -0,0 +1,6 @@ +use super::BindGroupLayout; + +pub struct PipelineLayoutDescriptor<'a> { + pub label: Option<&'a str>, + pub bind_group_layouts: &'a [BindGroupLayout], +} From 75fb33a0aa3f2262c4bee1223a48e757728b3812 Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Mon, 9 Jan 2023 00:47:42 +0100 Subject: [PATCH 05/36] pulz-render-ash --- crates/render-ash/CHANGELOG.md | 6 + crates/render-ash/Cargo.toml | 31 + crates/render-ash/README.md | 34 + crates/render-ash/examples/render-ash-demo.rs | 85 ++ crates/render-ash/src/attachments.rs | 90 ++ crates/render-ash/src/convert.rs | 1014 +++++++++++++++++ crates/render-ash/src/debug_utils.rs | 322 ++++++ crates/render-ash/src/device.rs | 447 ++++++++ crates/render-ash/src/drop_guard.rs | 196 ++++ crates/render-ash/src/encoder.rs | 379 ++++++ crates/render-ash/src/graph.rs | 343 ++++++ crates/render-ash/src/instance.rs | 205 ++++ crates/render-ash/src/lib.rs | 535 +++++++++ crates/render-ash/src/resources.rs | 409 +++++++ crates/render-ash/src/shader.rs | 15 + crates/render-ash/src/swapchain.rs | 809 +++++++++++++ crates/render-ash/src/utils.rs | 1 + 17 files changed, 4921 insertions(+) create mode 100644 crates/render-ash/CHANGELOG.md create mode 100644 crates/render-ash/Cargo.toml create mode 100644 crates/render-ash/README.md create mode 100644 crates/render-ash/examples/render-ash-demo.rs create mode 100644 crates/render-ash/src/attachments.rs create mode 100644 crates/render-ash/src/convert.rs create mode 100644 crates/render-ash/src/debug_utils.rs create mode 100644 crates/render-ash/src/device.rs create mode 100644 crates/render-ash/src/drop_guard.rs create mode 100644 crates/render-ash/src/encoder.rs create mode 100644 crates/render-ash/src/graph.rs create mode 100644 crates/render-ash/src/instance.rs create mode 100644 crates/render-ash/src/lib.rs create mode 100644 crates/render-ash/src/resources.rs create mode 100644 crates/render-ash/src/shader.rs create mode 100644 crates/render-ash/src/swapchain.rs create mode 100644 crates/render-ash/src/utils.rs diff --git a/crates/render-ash/CHANGELOG.md b/crates/render-ash/CHANGELOG.md new file mode 100644 index 0000000..71d66ea --- /dev/null +++ b/crates/render-ash/CHANGELOG.md @@ -0,0 +1,6 @@ +# `pulz-render-ash` Changelog +All notable changes to this crate will be documented in this file. + +## Unreleased (DATE) + + * Initial version diff --git a/crates/render-ash/Cargo.toml b/crates/render-ash/Cargo.toml new file mode 100644 index 0000000..4bbf083 --- /dev/null +++ b/crates/render-ash/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "pulz-render-ash" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true +readme = "README.md" + +[dependencies] +pulz-ecs = { path = "../ecs" } +pulz-window = { path = "../window" } +pulz-render = { path = "../render" } + +thiserror = { workspace = true } +tracing = { workspace = true } +bitflags = { workspace = true } +slotmap = { workspace = true } +raw-window-handle = { workspace = true } +ash = "0.37" +gpu-alloc = "0.5" +gpu-alloc-ash = "0.5" +gpu-descriptor = "0.2" +crossbeam-queue = { workspace = true } + +[dev-dependencies] +anyhow = { workspace = true } +pulz-window-winit = { path = "../window-winit" } +pulz-render-pipeline-core = { path = "../render-pipeline-core" } +tracing-subscriber = { workspace = true } +async-std = { workspace = true } diff --git a/crates/render-ash/README.md b/crates/render-ash/README.md new file mode 100644 index 0000000..2f869a9 --- /dev/null +++ b/crates/render-ash/README.md @@ -0,0 +1,34 @@ +# `pulz-render-ash` + + + +[![Crates.io](https://img.shields.io/crates/v/pulz-render-ash.svg?label=pulz-render-ash)](https://crates.io/crates/pulz-render-ash) +[![docs.rs](https://docs.rs/pulz-render-ash/badge.svg)](https://docs.rs/pulz-render-ash/) +[![license: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](#license) +[![Rust CI](https://github.com/HellButcher/pulz/actions/workflows/rust.yml/badge.svg)](https://github.com/HellButcher/pulz/actions/workflows/rust.yml) + + +**TODO** + +## Example + + +**TODO** + +## License + +[license]: #license + +This project is licensed under either of + +* MIT license ([LICENSE-MIT] or ) +* Apache License, Version 2.0, ([LICENSE-APACHE] or ) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[LICENSE-MIT]: ../../LICENSE-MIT +[LICENSE-APACHE]: ../../LICENSE-APACHE diff --git a/crates/render-ash/examples/render-ash-demo.rs b/crates/render-ash/examples/render-ash-demo.rs new file mode 100644 index 0000000..021e9ba --- /dev/null +++ b/crates/render-ash/examples/render-ash-demo.rs @@ -0,0 +1,85 @@ +use std::rc::Rc; + +use pulz_ecs::prelude::*; +use pulz_render::camera::{Camera, RenderTarget}; +use pulz_render_ash::AshRendererBuilder; +use pulz_render_pipeline_core::core_3d::CoreShadingModule; +use pulz_window::{WindowDescriptor, WindowId}; +use pulz_window_winit::{ + winit::{event_loop::EventLoop, window::Window}, + WinitWindowModule, WinitWindowSystem, +}; +use tracing::*; + +fn init() -> (Resources, EventLoop<()>, Rc, WinitWindowSystem) { + info!("Initializing..."); + let mut resources = Resources::new(); + resources.install(CoreShadingModule); + + let event_loop = EventLoop::new(); + let (window_system, window_id, window) = + WinitWindowModule::new(WindowDescriptor::default(), &event_loop) + .unwrap() + .install(&mut resources); + + unsafe { + AshRendererBuilder::new() + .with_window(window_id) + .install(&mut resources) + .unwrap(); + }; + + // let mut schedule = resources.remove::().unwrap(); + // schedule.init(&mut resources); + // schedule.debug_dump_if_env(None).unwrap(); + // resources.insert_again(schedule); + + setup_demo_scene(&mut resources, window_id); + + (resources, event_loop, window, window_system) +} + +fn setup_demo_scene(resources: &mut Resources, window: WindowId) { + let mut world = resources.world_mut(); + + world + .spawn() + .insert(Camera::new()) + .insert(RenderTarget::Window(window)); +} + +#[cfg(not(target_arch = "wasm32"))] +fn main() { + use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter}; + let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); + tracing_subscriber::fmt() + .with_env_filter(env_filter) + .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE) + .init(); + + let (resources, event_loop, _window, window_system) = init(); + + window_system.run(resources, event_loop); +} + +#[cfg(target_arch = "wasm32")] +fn main() { + use wasm_bindgen::prelude::*; + use winit::platform::web::WindowExtWebSys; + + console_error_panic_hook::set_once(); + tracing_log::LogTracer::init().expect("unable to create log-tracer"); + tracing_wasm::set_as_global_default(); + + let (resources, event_loop, window, window_system) = init(); + + let canvas = window.canvas(); + canvas.style().set_css_text("background-color: teal;"); + web_sys::window() + .and_then(|win| win.document()) + .and_then(|doc| doc.body()) + .and_then(|body| body.append_child(&web_sys::Element::from(canvas)).ok()) + .expect("couldn't append canvas to document body"); + + window_system.spawn(resources, event_loop); +} diff --git a/crates/render-ash/src/attachments.rs b/crates/render-ash/src/attachments.rs new file mode 100644 index 0000000..166dd38 --- /dev/null +++ b/crates/render-ash/src/attachments.rs @@ -0,0 +1,90 @@ +use ash::vk; +use bitflags::bitflags; + +bitflags!( + pub struct AttachmentOps: u8 { + const LOAD = 1 << 0; + const STORE = 1 << 1; + } +); + +impl AttachmentOps { + #[inline] + pub fn load_op(self) -> vk::AttachmentLoadOp { + if self.contains(Self::LOAD) { + vk::AttachmentLoadOp::LOAD + } else { + vk::AttachmentLoadOp::CLEAR + } + } + #[inline] + pub fn store_op(self) -> vk::AttachmentStoreOp { + if self.contains(Self::STORE) { + vk::AttachmentStoreOp::STORE + } else { + vk::AttachmentStoreOp::DONT_CARE + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ColorAttachmentKey { + pub format: vk::Format, + pub layout: vk::ImageLayout, + pub samples: u8, + pub ops: AttachmentOps, + pub resolve_ops: AttachmentOps, +} + +impl ColorAttachmentKey { + pub fn attachment_base_desc(&self) -> vk::AttachmentDescription { + vk::AttachmentDescription::builder() + .format(self.format) + .samples(vk::SampleCountFlags::from_raw(self.samples as u32)) + .load_op(self.ops.load_op()) + .store_op(self.ops.store_op()) + .initial_layout(self.layout) + .final_layout(self.layout) + .build() + } + pub fn attachment_resolve_desc(&self) -> Option { + if self.resolve_ops.is_empty() { + None + } else { + Some( + vk::AttachmentDescription::builder() + .format(self.format) + .samples(vk::SampleCountFlags::TYPE_1) + .load_op(self.resolve_ops.load_op()) + .store_op(self.resolve_ops.store_op()) + .initial_layout(self.layout) + .final_layout(self.layout) + .build(), + ) + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DepthStencilAttachmentKey { + pub format: vk::Format, + pub layout: vk::ImageLayout, + pub samples: u8, + pub depth_ops: AttachmentOps, + pub stencil_ops: AttachmentOps, +} + +impl DepthStencilAttachmentKey { + pub fn attachment_desc(&self) -> vk::AttachmentDescription { + vk::AttachmentDescription::builder() + .format(self.format) + .samples(vk::SampleCountFlags::from_raw(self.samples as u32)) + .load_op(self.depth_ops.load_op()) + .store_op(self.depth_ops.store_op()) + .stencil_load_op(self.stencil_ops.load_op()) + .stencil_store_op(self.stencil_ops.store_op()) + .initial_layout(self.layout) + .final_layout(self.layout) + .build() + } +} diff --git a/crates/render-ash/src/convert.rs b/crates/render-ash/src/convert.rs new file mode 100644 index 0000000..427d2b7 --- /dev/null +++ b/crates/render-ash/src/convert.rs @@ -0,0 +1,1014 @@ +use std::marker::PhantomData; + +use ash::vk; +use pulz_render::{ + buffer::{BufferDescriptor, BufferUsage}, + graph::{access::Stage, pass::PipelineBindPoint}, + math::{USize2, USize3}, + pipeline::{ + BindGroupLayoutDescriptor, BlendFactor, BlendOperation, CompareFunction, + ComputePipelineDescriptor, DepthStencilState, Face, FrontFace, GraphicsPipelineDescriptor, + IndexFormat, PipelineLayoutDescriptor, PrimitiveState, PrimitiveTopology, + RayTracingPipelineDescriptor, StencilFaceState, StencilOperation, VertexFormat, + }, + texture::{TextureAspects, TextureDescriptor, TextureDimensions, TextureFormat, TextureUsage}, +}; + +use crate::resources::AshResources; + +pub trait VkFrom { + fn from(val: &T) -> Self; +} + +pub trait VkInto { + fn vk_into(&self) -> U; +} + +impl VkInto for T +where + U: VkFrom, +{ + #[inline] + fn vk_into(&self) -> U { + U::from(self) + } +} + +impl, V> VkFrom> for Option { + #[inline] + fn from(val: &Option) -> Self { + val.as_ref().map(T::from) + } +} + +impl VkFrom for vk::BufferCreateInfo { + #[inline] + fn from(val: &BufferDescriptor) -> Self { + Self::builder() + .size(val.size as u64) + .usage(val.usage.vk_into()) + .sharing_mode(vk::SharingMode::EXCLUSIVE) + .build() + } +} + +impl VkFrom for vk::BufferUsageFlags { + #[inline] + fn from(val: &BufferUsage) -> Self { + let mut result = Self::empty(); + if val.contains(BufferUsage::TRANSFER_SRC) { + result |= Self::TRANSFER_SRC; + } + if val.contains(BufferUsage::TRANSFER_DST) { + result |= Self::TRANSFER_DST; + } + if val.contains(BufferUsage::INDEX) { + result |= Self::INDEX_BUFFER; + } + if val.contains(BufferUsage::UNIFORM) { + result |= Self::UNIFORM_BUFFER; + } + if val.contains(BufferUsage::STORAGE) { + result |= Self::STORAGE_BUFFER; + } + if val.contains(BufferUsage::INDIRECT) { + result |= Self::INDIRECT_BUFFER; + } + // if val.contains(BufferUsage::UNIFORM_TEXEL) { + // result |= Self::UNIFORM_TEXEL_BUFFER; + // } + // if val.contains(BufferUsage::STORAGE_TEXEL) { + // result |= Self::STORAGE_TEXEL_BUFFER; + // } + result + } +} + +fn get_array_layers(dimensions: &TextureDimensions) -> u32 { + match dimensions { + TextureDimensions::Cube(_) => 6, + TextureDimensions::D2Array { array_len, .. } => *array_len, + TextureDimensions::CubeArray { array_len, .. } => *array_len * 6, + _ => 1, + } +} + +impl VkFrom for vk::ImageCreateInfo { + fn from(val: &TextureDescriptor) -> Self { + Self::builder() + .image_type(val.dimensions.vk_into()) + .format(val.format.vk_into()) + .extent(val.dimensions.vk_into()) + .array_layers(get_array_layers(&val.dimensions)) + .mip_levels(val.mip_level_count) + .samples(vk::SampleCountFlags::from_raw(val.sample_count)) + .usage(val.usage.vk_into()) + .tiling(vk::ImageTiling::OPTIMAL) + .sharing_mode(vk::SharingMode::EXCLUSIVE) + .initial_layout(vk::ImageLayout::UNDEFINED) + .build() + } +} + +impl VkFrom for vk::ImageViewCreateInfo { + fn from(val: &TextureDescriptor) -> Self { + Self::builder() + .view_type(val.dimensions.vk_into()) + .format(val.format.vk_into()) + .subresource_range( + vk::ImageSubresourceRange::builder() + .aspect_mask(val.aspects().vk_into()) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(get_array_layers(&val.dimensions)) + .build(), + ) + .build() + } +} + +impl VkFrom for vk::Extent3D { + #[inline] + fn from(val: &TextureDescriptor) -> Self { + val.dimensions.vk_into() + } +} + +impl VkFrom for vk::Extent3D { + #[inline] + fn from(val: &TextureDimensions) -> Self { + match *val { + TextureDimensions::D1(len) => Self { + width: len, + height: 1, + depth: 1, + }, + TextureDimensions::D2(size) => Self { + width: size.x, + height: size.y, + depth: 1, + }, + TextureDimensions::D2Array { size, array_len: _ } => Self { + width: size.x, + height: size.y, + depth: 1, + }, + TextureDimensions::Cube(size) => Self { + width: size.x, + height: size.y, + depth: 1, + }, + TextureDimensions::CubeArray { size, array_len: _ } => Self { + width: size.x, + height: size.y, + depth: 1, + }, + TextureDimensions::D3(size) => Self { + width: size.x, + height: size.y, + depth: size.z, + }, + } + } +} + +impl VkFrom for vk::ImageAspectFlags { + #[inline] + fn from(val: &TextureAspects) -> Self { + let mut result = Self::empty(); + if val.contains(TextureAspects::COLOR) { + result |= Self::COLOR; + } + if val.contains(TextureAspects::DEPTH) { + result |= Self::DEPTH; + } + if val.contains(TextureAspects::STENCIL) { + result |= Self::STENCIL; + } + result + } +} + +impl VkFrom for vk::ImageViewType { + #[inline] + fn from(val: &TextureDimensions) -> Self { + match val { + TextureDimensions::D1(_) => Self::TYPE_1D, + TextureDimensions::D2 { .. } => Self::TYPE_2D, + TextureDimensions::D2Array { .. } => Self::TYPE_2D_ARRAY, + TextureDimensions::Cube { .. } => Self::CUBE, + TextureDimensions::CubeArray { .. } => Self::CUBE_ARRAY, + TextureDimensions::D3 { .. } => Self::TYPE_3D, + } + } +} + +impl VkFrom for vk::ImageType { + #[inline] + fn from(val: &TextureDimensions) -> Self { + match val { + TextureDimensions::D1(_) => Self::TYPE_1D, + TextureDimensions::D2 { .. } + | TextureDimensions::D2Array { .. } + | TextureDimensions::Cube { .. } + | TextureDimensions::CubeArray { .. } => Self::TYPE_2D, + TextureDimensions::D3 { .. } => Self::TYPE_3D, + } + } +} + +impl VkFrom for vk::Extent3D { + #[inline] + fn from(val: &USize3) -> Self { + Self { + width: val.x, + height: val.y, + depth: val.z, + } + } +} + +impl VkFrom<[u32; 3]> for vk::Extent3D { + #[inline] + fn from(val: &[u32; 3]) -> Self { + Self { + width: val[0], + height: val[1], + depth: val[2], + } + } +} + +impl VkFrom for vk::Extent2D { + #[inline] + fn from(val: &USize2) -> Self { + Self { + width: val.x, + height: val.y, + } + } +} + +impl VkFrom<[u32; 2]> for vk::Extent2D { + #[inline] + fn from(val: &[u32; 2]) -> Self { + Self { + width: val[0], + height: val[1], + } + } +} + +impl VkFrom for USize2 { + #[inline] + fn from(val: &vk::Extent2D) -> Self { + Self::new(val.width, val.height) + } +} + +impl VkFrom for vk::Format { + #[inline] + fn from(val: &TextureFormat) -> Self { + match val { + // 8-bit formats + TextureFormat::R8Unorm => Self::R8_UNORM, + TextureFormat::R8Snorm => Self::R8_SNORM, + TextureFormat::R8Uint => Self::R8_UINT, + TextureFormat::R8Sint => Self::R8_SINT, + + // 16-bit formats + TextureFormat::R16Uint => Self::R16_UINT, + TextureFormat::R16Sint => Self::R16_SINT, + TextureFormat::R16Float => Self::R16_SFLOAT, + TextureFormat::Rg8Unorm => Self::R8G8_UNORM, + TextureFormat::Rg8Snorm => Self::R8G8_SNORM, + TextureFormat::Rg8Uint => Self::R8G8_UINT, + TextureFormat::Rg8Sint => Self::R8G8_SINT, + + // 32-bit formats + TextureFormat::R32Uint => Self::R32_UINT, + TextureFormat::R32Sint => Self::R32_SINT, + TextureFormat::R32Float => Self::R32_SFLOAT, + TextureFormat::Rg16Uint => Self::R16G16_UINT, + TextureFormat::Rg16Sint => Self::R16G16_SINT, + TextureFormat::Rg16Float => Self::R16G16_SFLOAT, + TextureFormat::Rgba8Unorm => Self::R8G8B8A8_UNORM, + TextureFormat::Rgba8UnormSrgb => Self::R8G8B8A8_SRGB, + TextureFormat::Rgba8Snorm => Self::R8G8B8A8_SNORM, + TextureFormat::Rgba8Uint => Self::R8G8B8A8_UINT, + TextureFormat::Rgba8Sint => Self::R8G8B8A8_SINT, + TextureFormat::Bgra8Unorm => Self::B8G8R8A8_UNORM, + TextureFormat::Bgra8UnormSrgb => Self::B8G8R8A8_SRGB, + + // Packed 32-bit formats + TextureFormat::Rgb9E5Ufloat => Self::E5B9G9R9_UFLOAT_PACK32, + TextureFormat::Rgb10A2Unorm => Self::A2R10G10B10_UNORM_PACK32, + TextureFormat::Rg11B10Float => Self::B10G11R11_UFLOAT_PACK32, + + // 64-bit formats + TextureFormat::Rg32Uint => Self::R32G32_UINT, + TextureFormat::Rg32Sint => Self::R32G32_SINT, + TextureFormat::Rg32Float => Self::R32G32_SFLOAT, + TextureFormat::Rgba16Uint => Self::R16G16B16A16_UINT, + TextureFormat::Rgba16Sint => Self::R16G16B16A16_SINT, + TextureFormat::Rgba16Float => Self::R16G16B16A16_SFLOAT, + + // 128-bit formats + TextureFormat::Rgba32Uint => Self::R32G32B32A32_UINT, + TextureFormat::Rgba32Sint => Self::R32G32B32A32_SINT, + TextureFormat::Rgba32Float => Self::R32G32B32A32_SFLOAT, + + // Depth and stencil formats + TextureFormat::Stencil8 => Self::S8_UINT, + TextureFormat::Depth16Unorm => Self::D16_UNORM, + TextureFormat::Depth24Plus => Self::X8_D24_UNORM_PACK32, + TextureFormat::Depth24PlusStencil8 => Self::D24_UNORM_S8_UINT, + TextureFormat::Depth32Float => Self::D32_SFLOAT, + + _ => panic!("unsupported texture format: {val:?}"), + } + } +} + +impl VkFrom for TextureFormat { + #[inline] + fn from(val: &vk::Format) -> Self { + use vk::Format; + match *val { + // 8-bit formats + Format::R8_UNORM => Self::R8Unorm, + Format::R8_SNORM => Self::R8Snorm, + Format::R8_UINT => Self::R8Uint, + Format::R8_SINT => Self::R8Sint, + + // 16-bit formats + Format::R16_UINT => Self::R16Uint, + Format::R16_SINT => Self::R16Sint, + Format::R16_SFLOAT => Self::R16Float, + Format::R8G8_UNORM => Self::Rg8Unorm, + Format::R8G8_SNORM => Self::Rg8Snorm, + Format::R8G8_UINT => Self::Rg8Uint, + Format::R8G8_SINT => Self::Rg8Sint, + + // 32-bit formats + Format::R32_UINT => Self::R32Uint, + Format::R32_SINT => Self::R32Sint, + Format::R32_SFLOAT => Self::R32Float, + Format::R16G16_UINT => Self::Rg16Uint, + Format::R16G16_SINT => Self::Rg16Sint, + Format::R16G16_SFLOAT => Self::Rg16Float, + Format::R8G8B8A8_UNORM => Self::Rgba8Unorm, + Format::R8G8B8A8_SRGB => Self::Rgba8UnormSrgb, + Format::R8G8B8A8_SNORM => Self::Rgba8Snorm, + Format::R8G8B8A8_UINT => Self::Rgba8Uint, + Format::R8G8B8A8_SINT => Self::Rgba8Sint, + Format::B8G8R8A8_UNORM => Self::Bgra8Unorm, + Format::B8G8R8A8_SRGB => Self::Bgra8UnormSrgb, + + // Packed 32-bit formats + Format::E5B9G9R9_UFLOAT_PACK32 => Self::Rgb9E5Ufloat, + Format::A2R10G10B10_UNORM_PACK32 => Self::Rgb10A2Unorm, + Format::B10G11R11_UFLOAT_PACK32 => Self::Rg11B10Float, + + // 64-bit formats + Format::R32G32_UINT => Self::Rg32Uint, + Format::R32G32_SINT => Self::Rg32Sint, + Format::R32G32_SFLOAT => Self::Rg32Float, + Format::R16G16B16A16_UINT => Self::Rgba16Uint, + Format::R16G16B16A16_SINT => Self::Rgba16Sint, + Format::R16G16B16A16_SFLOAT => Self::Rgba16Float, + + // 128-bit formats + Format::R32G32B32A32_UINT => Self::Rgba32Uint, + Format::R32G32B32A32_SINT => Self::Rgba32Sint, + Format::R32G32B32A32_SFLOAT => Self::Rgba32Float, + + // Depth and stencil formats + Format::S8_UINT => Self::Stencil8, + Format::D16_UNORM => Self::Depth16Unorm, + Format::X8_D24_UNORM_PACK32 => Self::Depth24Plus, + Format::D24_UNORM_S8_UINT => Self::Depth24PlusStencil8, + Format::D32_SFLOAT => Self::Depth32Float, + + _ => panic!("unsupported texture format: {:x}", val.as_raw()), + } + } +} + +impl VkFrom for vk::Format { + #[inline] + fn from(val: &VertexFormat) -> Self { + match val { + VertexFormat::Uint8x2 => Self::R8G8_UINT, + VertexFormat::Uint8x4 => Self::R8G8B8A8_UINT, + VertexFormat::Sint8x2 => Self::R8G8_SINT, + VertexFormat::Sint8x4 => Self::R8G8B8A8_SINT, + VertexFormat::Unorm8x2 => Self::R8G8_UNORM, + VertexFormat::Unorm8x4 => Self::R8G8B8A8_UNORM, + VertexFormat::Snorm8x2 => Self::R8G8_SNORM, + VertexFormat::Snorm8x4 => Self::R8G8B8A8_SNORM, + VertexFormat::Uint16x2 => Self::R16G16_UINT, + VertexFormat::Uint16x4 => Self::R16G16B16A16_UINT, + VertexFormat::Sint16x2 => Self::R16G16_SINT, + VertexFormat::Sint16x4 => Self::R16G16B16A16_SINT, + VertexFormat::Unorm16x2 => Self::R16G16_UNORM, + VertexFormat::Unorm16x4 => Self::R16G16B16A16_UNORM, + VertexFormat::Snorm16x2 => Self::R16G16_SNORM, + VertexFormat::Snorm16x4 => Self::R16G16B16A16_SNORM, + VertexFormat::Uint32 => Self::R32_UINT, + VertexFormat::Uint32x2 => Self::R32G32_UINT, + VertexFormat::Uint32x3 => Self::R32G32B32_UINT, + VertexFormat::Uint32x4 => Self::R32G32B32A32_UINT, + VertexFormat::Sint32 => Self::R32_SINT, + VertexFormat::Sint32x2 => Self::R32G32_SINT, + VertexFormat::Sint32x3 => Self::R32G32B32_SINT, + VertexFormat::Sint32x4 => Self::R32G32B32A32_SINT, + VertexFormat::Float16 => Self::R16_SFLOAT, + VertexFormat::Float16x2 => Self::R16G16_SFLOAT, + VertexFormat::Float16x4 => Self::R16G16B16A16_SFLOAT, + VertexFormat::Float32 => Self::R32_SFLOAT, + VertexFormat::Float32x2 => Self::R32G32_SFLOAT, + VertexFormat::Float32x3 => Self::R32G32B32_SFLOAT, + VertexFormat::Float32x4 => Self::R32G32B32A32_SFLOAT, + VertexFormat::Float64 => Self::R64_SFLOAT, + VertexFormat::Float64x2 => Self::R64G64_SFLOAT, + VertexFormat::Float64x3 => Self::R64G64B64_SFLOAT, + VertexFormat::Float64x4 => Self::R64G64B64A64_SFLOAT, + + _ => panic!("unsupported vertex format: {val:?}"), + } + } +} + +impl VkFrom for vk::ImageUsageFlags { + fn from(val: &TextureUsage) -> Self { + let mut result = Self::empty(); + if val.contains(TextureUsage::TRANSFER_SRC) { + result |= Self::TRANSFER_SRC; + } + if val.contains(TextureUsage::TRANSFER_DST) { + result |= Self::TRANSFER_DST; + } + if val.contains(TextureUsage::TEXTURE_BINDING) { + result |= Self::SAMPLED; + } + if val.contains(TextureUsage::STORAGE_BINDING) { + result |= Self::STORAGE; + } + if val.contains(TextureUsage::COLOR_ATTACHMENT) { + result |= Self::COLOR_ATTACHMENT; + } + if val.contains(TextureUsage::DEPTH_STENCIL_ATTACHMENT) { + result |= Self::DEPTH_STENCIL_ATTACHMENT; + } + if val.contains(TextureUsage::INPUT_ATTACHMENT) { + result |= Self::INPUT_ATTACHMENT; + } + result + } +} + +pub fn into_texture_usage_read_access(usage: TextureUsage) -> vk::AccessFlags { + let mut result = vk::AccessFlags::empty(); + if usage.contains(TextureUsage::TRANSFER_SRC) | usage.contains(TextureUsage::TRANSFER_DST) { + result |= vk::AccessFlags::TRANSFER_READ; + } + if usage.contains(TextureUsage::TEXTURE_BINDING) + || usage.contains(TextureUsage::STORAGE_BINDING) + { + result |= vk::AccessFlags::SHADER_READ; + } + if usage.contains(TextureUsage::COLOR_ATTACHMENT) { + result |= vk::AccessFlags::COLOR_ATTACHMENT_READ; + } + if usage.contains(TextureUsage::DEPTH_STENCIL_ATTACHMENT) { + result |= vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ; + } + if usage.contains(TextureUsage::INPUT_ATTACHMENT) { + result |= vk::AccessFlags::INPUT_ATTACHMENT_READ; + } + result +} + +pub fn into_texture_usage_write_access(usage: TextureUsage) -> vk::AccessFlags { + let mut result = vk::AccessFlags::empty(); + if usage.contains(TextureUsage::TRANSFER_SRC) | usage.contains(TextureUsage::TRANSFER_DST) { + result |= vk::AccessFlags::TRANSFER_WRITE; + } + if usage.contains(TextureUsage::TEXTURE_BINDING) + || usage.contains(TextureUsage::STORAGE_BINDING) + { + result |= vk::AccessFlags::SHADER_WRITE; + } + if usage.contains(TextureUsage::COLOR_ATTACHMENT) { + result |= vk::AccessFlags::COLOR_ATTACHMENT_WRITE; + } + if usage.contains(TextureUsage::DEPTH_STENCIL_ATTACHMENT) { + result |= vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE; + } + result +} + +pub fn into_buffer_usage_read_access(usage: BufferUsage) -> vk::AccessFlags { + let mut result = vk::AccessFlags::empty(); + if usage.contains(BufferUsage::MAP_READ) | usage.contains(BufferUsage::MAP_WRITE) { + result |= vk::AccessFlags::HOST_READ; + } + if usage.contains(BufferUsage::TRANSFER_SRC) | usage.contains(BufferUsage::TRANSFER_DST) { + result |= vk::AccessFlags::TRANSFER_READ; + } + if usage.contains(BufferUsage::INDEX) { + result |= vk::AccessFlags::INDEX_READ; + } + if usage.contains(BufferUsage::VERTEX) { + result |= vk::AccessFlags::VERTEX_ATTRIBUTE_READ; + } + if usage.contains(BufferUsage::UNIFORM) { + result |= vk::AccessFlags::UNIFORM_READ; + } + if usage.contains(BufferUsage::STORAGE) { + result |= vk::AccessFlags::SHADER_READ; + } + if usage.contains(BufferUsage::INDIRECT) { + result |= vk::AccessFlags::INDIRECT_COMMAND_READ; + } + result +} + +pub fn into_buffer_usage_write_access(usage: BufferUsage) -> vk::AccessFlags { + let mut result = vk::AccessFlags::empty(); + if usage.contains(BufferUsage::MAP_READ) | usage.contains(BufferUsage::MAP_WRITE) { + result |= vk::AccessFlags::HOST_WRITE; + } + if usage.contains(BufferUsage::TRANSFER_SRC) | usage.contains(BufferUsage::TRANSFER_DST) { + result |= vk::AccessFlags::TRANSFER_WRITE; + } + if usage.contains(BufferUsage::STORAGE) { + result |= vk::AccessFlags::SHADER_WRITE; + } + result +} + +impl VkFrom for vk::PipelineBindPoint { + #[inline] + fn from(val: &PipelineBindPoint) -> Self { + match val { + PipelineBindPoint::Graphics => Self::GRAPHICS, + PipelineBindPoint::Compute => Self::COMPUTE, + PipelineBindPoint::RayTracing => Self::RAY_TRACING_KHR, + } + } +} + +impl VkFrom for vk::PipelineStageFlags { + #[inline] + fn from(val: &Stage) -> Self { + let mut result = Self::empty(); + if val.contains(Stage::DRAW_INDIRECT) { + result |= Self::DRAW_INDIRECT; + } + if val.contains(Stage::VERTEX_INPUT) { + result |= Self::VERTEX_INPUT; + } + if val.contains(Stage::VERTEX_SHADER) { + result |= Self::VERTEX_SHADER; + } + if val.contains(Stage::TESSELLATION_CONTROL_SHADER) { + result |= Self::TESSELLATION_CONTROL_SHADER; + } + if val.contains(Stage::TESSELLATION_EVALUATION_SHADER) { + result |= Self::TESSELLATION_EVALUATION_SHADER; + } + if val.contains(Stage::GEOMETRY_SHADER) { + result |= Self::GEOMETRY_SHADER; + } + if val.contains(Stage::FRAGMENT_SHADER) { + result |= Self::FRAGMENT_SHADER; + } + if val.contains(Stage::EARLY_FRAGMENT_TESTS) { + result |= Self::EARLY_FRAGMENT_TESTS; + } + if val.contains(Stage::LATE_FRAGMENT_TESTS) { + result |= Self::LATE_FRAGMENT_TESTS; + } + if val.contains(Stage::COLOR_ATTACHMENT_OUTPUT) { + result |= Self::COLOR_ATTACHMENT_OUTPUT; + } + if val.contains(Stage::COMPUTE_SHADER) { + result |= Self::COMPUTE_SHADER; + } + if val.contains(Stage::ACCELERATION_STRUCTURE_BUILD) { + result |= Self::ACCELERATION_STRUCTURE_BUILD_KHR; + } + if val.contains(Stage::RAY_TRACING_SHADER) { + result |= Self::RAY_TRACING_SHADER_KHR; + } + if val.contains(Stage::TRANSFER) { + result |= Self::TRANSFER; + } + if val.contains(Stage::HOST) { + result |= Self::HOST; + } + result + } +} + +impl VkFrom for vk::PipelineInputAssemblyStateCreateInfo { + #[inline] + fn from(val: &PrimitiveState) -> Self { + Self::builder().topology(val.topology.vk_into()).build() + } +} + +impl VkFrom for vk::PipelineRasterizationStateCreateInfo { + #[inline] + fn from(val: &PrimitiveState) -> Self { + let mut builder = Self::builder() + .polygon_mode(vk::PolygonMode::FILL) + .front_face(val.front_face.vk_into()) + .line_width(1.0); + if let Some(cull_mode) = val.cull_mode { + builder = builder.cull_mode(cull_mode.vk_into()) + } + builder.build() + } +} + +impl VkFrom for vk::PipelineDepthStencilStateCreateInfo { + #[inline] + fn from(val: &DepthStencilState) -> Self { + let mut builder = Self::builder(); + if val.is_depth_enabled() { + builder = builder + .depth_test_enable(true) + .depth_write_enable(val.depth.write_enabled) + .depth_compare_op(val.depth.compare.vk_into()); + } + if val.stencil.is_enabled() { + builder = builder + .stencil_test_enable(true) + .front(val.stencil.front.vk_into()) + .back(val.stencil.back.vk_into()); + } + builder.build() + } +} + +impl VkFrom for vk::StencilOpState { + #[inline] + fn from(val: &StencilFaceState) -> Self { + Self { + compare_op: val.compare.vk_into(), + fail_op: val.fail_op.vk_into(), + depth_fail_op: val.depth_fail_op.vk_into(), + pass_op: val.pass_op.vk_into(), + compare_mask: !0, + write_mask: !0, + reference: !0, + } + } +} + +impl VkFrom for vk::PrimitiveTopology { + #[inline] + fn from(val: &PrimitiveTopology) -> Self { + match val { + PrimitiveTopology::PointList => Self::POINT_LIST, + PrimitiveTopology::LineList => Self::LINE_LIST, + PrimitiveTopology::LineStrip => Self::LINE_STRIP, + PrimitiveTopology::TriangleList => Self::TRIANGLE_LIST, + PrimitiveTopology::TriangleStrip => Self::TRIANGLE_STRIP, + } + } +} + +impl VkFrom for vk::FrontFace { + #[inline] + fn from(val: &FrontFace) -> Self { + match val { + FrontFace::CounterClockwise => Self::COUNTER_CLOCKWISE, + FrontFace::Clockwise => Self::CLOCKWISE, + } + } +} + +impl VkFrom for vk::CullModeFlags { + #[inline] + fn from(val: &Face) -> Self { + match val { + Face::Front => Self::FRONT, + Face::Back => Self::BACK, + } + } +} + +impl VkFrom for vk::BlendOp { + #[inline] + fn from(val: &BlendOperation) -> Self { + match val { + BlendOperation::Add => Self::ADD, + BlendOperation::Subtract => Self::SUBTRACT, + BlendOperation::ReverseSubtract => Self::REVERSE_SUBTRACT, + BlendOperation::Min => Self::MIN, + BlendOperation::Max => Self::MAX, + } + } +} + +impl VkFrom for vk::BlendFactor { + #[inline] + fn from(val: &BlendFactor) -> Self { + match val { + BlendFactor::Zero => Self::ZERO, + BlendFactor::One => Self::ONE, + BlendFactor::Src => Self::SRC_COLOR, + BlendFactor::OneMinusSrc => Self::ONE_MINUS_SRC_COLOR, + BlendFactor::SrcAlpha => Self::SRC_ALPHA, + BlendFactor::OneMinusSrcAlpha => Self::ONE_MINUS_SRC_ALPHA, + BlendFactor::Dst => Self::DST_COLOR, + BlendFactor::OneMinusDst => Self::ONE_MINUS_DST_COLOR, + BlendFactor::DstAlpha => Self::DST_ALPHA, + BlendFactor::OneMinusDstAlpha => Self::ONE_MINUS_DST_ALPHA, + BlendFactor::SrcAlphaSaturated => Self::SRC_ALPHA_SATURATE, + BlendFactor::Constant => Self::CONSTANT_COLOR, + BlendFactor::OneMinusConstant => Self::ONE_MINUS_CONSTANT_COLOR, + } + } +} + +impl VkFrom for vk::IndexType { + #[inline] + fn from(val: &IndexFormat) -> Self { + match val { + IndexFormat::Uint16 => Self::UINT16, + IndexFormat::Uint32 => Self::UINT32, + } + } +} + +impl VkFrom for vk::CompareOp { + #[inline] + fn from(val: &CompareFunction) -> Self { + match val { + CompareFunction::Never => Self::NEVER, + CompareFunction::Less => Self::LESS, + CompareFunction::Equal => Self::EQUAL, + CompareFunction::LessEqual => Self::LESS_OR_EQUAL, + CompareFunction::Greater => Self::GREATER, + CompareFunction::NotEqual => Self::NOT_EQUAL, + CompareFunction::GreaterEqual => Self::GREATER_OR_EQUAL, + CompareFunction::Always => Self::ALWAYS, + } + } +} + +impl VkFrom for vk::StencilOp { + #[inline] + fn from(val: &StencilOperation) -> Self { + match val { + StencilOperation::Keep => Self::KEEP, + StencilOperation::Zero => Self::ZERO, + StencilOperation::Replace => Self::REPLACE, + StencilOperation::Invert => Self::INVERT, + StencilOperation::IncrementClamp => Self::INCREMENT_AND_CLAMP, + StencilOperation::DecrementClamp => Self::DECREMENT_AND_CLAMP, + StencilOperation::IncrementWrap => Self::INCREMENT_AND_WRAP, + StencilOperation::DecrementWrap => Self::DECREMENT_AND_WRAP, + } + } +} + +pub struct CreateInfoBuffer { + buf: *mut u8, + len_bytes: usize, + capacity: usize, + _phantom: PhantomData, +} + +impl CreateInfoBuffer { + #[inline] + pub const fn new() -> Self { + Self { + buf: std::ptr::null_mut(), + len_bytes: 0, + capacity: 0, + _phantom: PhantomData, + } + } + + fn reset_as(&mut self) -> &mut CreateInfoBuffer { + assert!(std::mem::align_of::() <= std::mem::align_of::()); + assert_ne!(0, std::mem::size_of::()); + self.len_bytes = 0; + unsafe { std::mem::transmute(self) } + } +} + +impl CreateInfoBuffer { + #[inline] + pub fn len(&self) -> usize { + self.len_bytes / std::mem::size_of::() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.len_bytes == 0 + } + + pub fn reserve(&mut self, _num_items: usize) { + let value_len = std::mem::size_of::(); + let new_len = self.len_bytes + value_len * 2; + if new_len > self.capacity { + let new_capacity = new_len.next_power_of_two(); + unsafe { + if self.buf.is_null() { + let layout = std::alloc::Layout::from_size_align( + new_capacity, + std::mem::align_of::(), + ) + .unwrap(); + self.buf = std::alloc::alloc(layout); + } else { + let layout = std::alloc::Layout::from_size_align( + self.capacity, + std::mem::align_of::(), + ) + .unwrap(); + self.buf = std::alloc::realloc(self.buf, layout, new_capacity) + } + } + self.capacity = new_capacity; + } + } + + pub fn push(&mut self, value: T) -> &mut T { + self.reserve(1); + let value_len = std::mem::size_of::(); + unsafe { + let ptr = self.buf.add(self.len_bytes) as *mut T; + std::ptr::write(ptr, value); + self.len_bytes += value_len; + &mut *ptr + } + } + + pub fn as_slice(&self) -> &[T] { + let len = self.len(); + if len == 0 { + return &[]; + } + unsafe { + let ptr = self.buf as *const T; + std::slice::from_raw_parts(ptr, len) + } + } + pub fn as_slice_mut(&mut self) -> &mut [T] { + let len = self.len(); + if len == 0 { + return &mut []; + } + unsafe { + let ptr = self.buf as *mut T; + std::slice::from_raw_parts_mut(ptr, len) + } + } +} + +impl Drop for CreateInfoBuffer { + fn drop(&mut self) { + if !self.buf.is_null() { + let layout = + std::alloc::Layout::from_size_align(self.capacity, std::mem::align_of::()) + .unwrap(); + unsafe { + std::alloc::dealloc(self.buf, layout); + } + self.buf = std::ptr::null_mut(); + } + } +} + +pub struct CreateInfoConverter(CreateInfoBuffer, CreateInfoBuffer); + +impl CreateInfoConverter { + #[inline] + pub const fn new() -> Self { + Self(CreateInfoBuffer::new(), CreateInfoBuffer::new()) + } + + pub fn bind_group_layout( + &mut self, + desc: &BindGroupLayoutDescriptor<'_>, + ) -> &vk::DescriptorSetLayoutCreateInfo { + let buf0 = self.0.reset_as::(); + buf0.reserve(desc.entries.len()); + for e in desc.entries { + buf0.push( + vk::DescriptorSetLayoutBinding::builder() + .binding(e.binding) + .descriptor_count(e.count) + .build(), + ); + // TODO: descriptor_type, stage_flags, immutable_samplers + todo!(); + } + + let buf = self.1.reset_as::(); + buf.reserve(1); + buf.push( + vk::DescriptorSetLayoutCreateInfo::builder() + .bindings(buf0.as_slice()) + .build(), + ); + &buf.as_slice()[0] + } + + pub fn pipeline_layout( + &mut self, + res: &AshResources, + desc: &PipelineLayoutDescriptor<'_>, + ) -> &vk::PipelineLayoutCreateInfo { + let buf0 = self.0.reset_as::(); + buf0.reserve(desc.bind_group_layouts.len()); + for bgl in desc.bind_group_layouts { + buf0.push(res.bind_group_layouts[*bgl]); + } + + let buf = self.1.reset_as::(); + buf.reserve(1); + buf.push( + vk::PipelineLayoutCreateInfo::builder() + .set_layouts(buf0.as_slice()) + .build(), + ); + &buf.as_slice()[0] + } + + pub fn graphics_pipeline_descriptor( + &mut self, + res: &AshResources, + descs: &[GraphicsPipelineDescriptor<'_>], + ) -> &[vk::GraphicsPipelineCreateInfo] { + let buf = self.0.reset_as::(); + buf.reserve(descs.len()); + for desc in descs { + let layout = desc + .layout + .map_or(vk::PipelineLayout::null(), |l| res.pipeline_layouts[l]); + buf.push( + vk::GraphicsPipelineCreateInfo::builder() + .layout(layout) + .build(), + ); + // TODO: vertex, primitive, depth_stencil, fragment, samples, specialization + todo!(" implement graphics_pipeline_descriptor"); + } + buf.as_slice() + } + + pub fn compute_pipeline_descriptor( + &mut self, + res: &AshResources, + descs: &[ComputePipelineDescriptor<'_>], + ) -> &[vk::ComputePipelineCreateInfo] { + let buf = self.0.reset_as::(); + buf.reserve(descs.len()); + for desc in descs { + let layout = desc + .layout + .map_or(vk::PipelineLayout::null(), |l| res.pipeline_layouts[l]); + // TODO: module, entry_point, specialization + buf.push( + vk::ComputePipelineCreateInfo::builder() + .layout(layout) + .build(), + ); + todo!(" implement compute_pipeline_descriptor"); + } + buf.as_slice() + } + + pub fn ray_tracing_pipeline_descriptor( + &mut self, + res: &AshResources, + descs: &[RayTracingPipelineDescriptor<'_>], + ) -> &[vk::RayTracingPipelineCreateInfoKHR] { + let buf = self.0.reset_as::(); + buf.reserve(descs.len()); + for desc in descs { + let layout = desc + .layout + .map_or(vk::PipelineLayout::null(), |l| res.pipeline_layouts[l]); + // TODO: modules, groups, specialization + buf.push( + vk::RayTracingPipelineCreateInfoKHR::builder() + .layout(layout) + .max_pipeline_ray_recursion_depth(desc.max_recursion_depth) + .build(), + ); + todo!(" implement ray_tracing_pipeline_descriptor"); + } + buf.as_slice() + } +} diff --git a/crates/render-ash/src/debug_utils.rs b/crates/render-ash/src/debug_utils.rs new file mode 100644 index 0000000..9ee7aee --- /dev/null +++ b/crates/render-ash/src/debug_utils.rs @@ -0,0 +1,322 @@ +use std::{ + ffi::{c_void, CStr}, + os::raw::c_char, +}; + +use ash::vk; +use tracing::{debug, error, info, warn}; + +unsafe fn c_str_from_ptr<'a>(str_ptr: *const c_char) -> &'a CStr { + if str_ptr.is_null() { + CStr::from_bytes_with_nul_unchecked(b"\0") + } else { + CStr::from_ptr(str_ptr) + } +} + +unsafe extern "system" fn debug_callback( + message_severity: vk::DebugUtilsMessageSeverityFlagsEXT, + message_type: vk::DebugUtilsMessageTypeFlagsEXT, + p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT, + _p_user_data: *mut c_void, +) -> vk::Bool32 { + use vk::DebugUtilsMessageSeverityFlagsEXT; + + if std::thread::panicking() { + return vk::FALSE; + } + + let message = c_str_from_ptr((*p_callback_data).p_message); + let message_id_name = c_str_from_ptr((*p_callback_data).p_message_id_name); + let message_id_number = (*p_callback_data).message_id_number; + + // TODO: queues, labels, objects, ... + + match message_severity { + DebugUtilsMessageSeverityFlagsEXT::VERBOSE => { + debug!( + ?message_type, + ?message_severity, + ?message_id_name, + ?message_id_number, + "Vulkan Message: {:?}", + message + ) + } + DebugUtilsMessageSeverityFlagsEXT::INFO => { + info!( + ?message_type, + ?message_severity, + ?message_id_name, + ?message_id_number, + "Vulkan Message: {:?}", + message + ) + } + DebugUtilsMessageSeverityFlagsEXT::WARNING => { + warn!( + ?message_type, + ?message_severity, + ?message_id_name, + ?message_id_number, + "Vulkan Message: {:?}", + message + ) + } + DebugUtilsMessageSeverityFlagsEXT::ERROR => { + error!( + ?message_type, + ?message_severity, + ?message_id_name, + ?message_id_number, + "Vulkan Message: {:?}", + message + ) + } + _ => { + warn!( + ?message_type, + ?message_severity, + ?message_id_name, + ?message_id_number, + "Vulkan Message: {:?}", + message + ) + } + }; + + vk::FALSE +} + +// stack-allocated buffer for keeping a copy of the object_name (for appending \0-byte) +// + optional Vector for allocations +struct CStrBuf { + buf: [u8; 64], + alloc: Vec, +} + +impl CStrBuf { + #[inline] + const fn new() -> Self { + Self { + buf: [0; 64], + alloc: Vec::new(), + } + } + + #[inline] + fn to_cstr<'a>(&'a mut self, s: &'a str) -> &'a CStr { + if s.ends_with('\0') { + // SAFETY: string always ends with 0-byte. + // Don't care, if there are 0-bytes before end. + unsafe { CStr::from_bytes_with_nul_unchecked(s.as_bytes()) } + } else { + let bytes = s.as_bytes(); + let len = bytes.len(); + if len < self.buf.len() { + self.buf[..len].copy_from_slice(bytes); + self.buf[len] = 0; + // SAFETY: string always ends with 0-byte. + // Don't care, if there are 0-bytes before end. + return unsafe { CStr::from_bytes_with_nul_unchecked(&self.buf[..len + 1]) }; + } + self.alloc.clear(); + self.alloc.reserve_exact(len + 1); + self.alloc.extend_from_slice(s.as_bytes()); + self.alloc.push(0); + // SAFETY: string always ends with 0-byte. + // Don't care, if there are 0-bytes before end. + unsafe { CStr::from_bytes_with_nul_unchecked(&self.alloc) } + } + } +} + +pub struct DebugUtils { + functions: ash::extensions::ext::DebugUtils, + utils_messenger: vk::DebugUtilsMessengerEXT, +} + +impl DebugUtils { + pub const fn name() -> &'static CStr { + ash::extensions::ext::DebugUtils::name() + } + + pub fn new( + entry: &ash::Entry, + instance: &ash::Instance, + message_severities: vk::DebugUtilsMessageSeverityFlagsEXT, + ) -> Self { + let functions = ash::extensions::ext::DebugUtils::new(entry, instance); + if message_severities.is_empty() { + Self { + functions, + utils_messenger: ash::vk::DebugUtilsMessengerEXT::null(), + } + } else { + let utils_messenger = unsafe { + functions + .create_debug_utils_messenger( + &vk::DebugUtilsMessengerCreateInfoEXT::builder() + .message_severity(message_severities) + .message_type( + vk::DebugUtilsMessageTypeFlagsEXT::GENERAL + | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION + | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE, + ) + .pfn_user_callback(Some(debug_callback)), + None, + ) + .expect("Debug Utils Callback") + }; + + Self { + functions, + utils_messenger, + } + } + } + + #[inline(always)] + pub fn is_messenger_enabled(&self) -> bool { + self.utils_messenger != vk::DebugUtilsMessengerEXT::null() + } + + #[inline(always)] + pub unsafe fn object_name( + &self, + device: vk::Device, + handle: H, + object_name: &str, + ) { + self._object_name(device, H::TYPE, handle.as_raw(), object_name) + } + #[inline(always)] + pub unsafe fn object_name_cstr( + &self, + device: vk::Device, + handle: H, + object_name: &CStr, + ) { + self._object_name_cstr(device, H::TYPE, handle.as_raw(), object_name) + } + + #[inline] + unsafe fn _object_name( + &self, + device: vk::Device, + object_type: vk::ObjectType, + object_handle: u64, + object_name: &str, + ) { + if object_handle == 0 { + return; + } + + let mut cstr_buf = CStrBuf::new(); + self._object_name_cstr( + device, + object_type, + object_handle, + cstr_buf.to_cstr(object_name), + ) + } + + unsafe fn _object_name_cstr( + &self, + device: vk::Device, + object_type: vk::ObjectType, + object_handle: u64, + object_name: &CStr, + ) { + if object_handle == 0 { + return; + } + let _result = self.functions.set_debug_utils_object_name( + device, + &vk::DebugUtilsObjectNameInfoEXT::builder() + .object_type(object_type) + .object_handle(object_handle) + .object_name(object_name), + ); + } + + #[inline] + pub unsafe fn cmd_insert_debug_label(&self, command_buffer: vk::CommandBuffer, label: &str) { + let mut cstr_buf = CStrBuf::new(); + self.cmd_insert_debug_label_cstr(command_buffer, cstr_buf.to_cstr(label)) + } + pub unsafe fn cmd_insert_debug_label_cstr( + &self, + command_buffer: vk::CommandBuffer, + label: &CStr, + ) { + self.functions.cmd_insert_debug_utils_label( + command_buffer, + &vk::DebugUtilsLabelEXT::builder().label_name(label), + ); + } + + #[inline] + pub unsafe fn cmd_begin_debug_label(&self, command_buffer: vk::CommandBuffer, label: &str) { + let mut cstr_buf = CStrBuf::new(); + self.cmd_begin_debug_label_cstr(command_buffer, cstr_buf.to_cstr(label)) + } + pub unsafe fn cmd_begin_debug_label_cstr( + &self, + command_buffer: vk::CommandBuffer, + label: &CStr, + ) { + self.functions.cmd_begin_debug_utils_label( + command_buffer, + &vk::DebugUtilsLabelEXT::builder().label_name(label), + ); + } + + #[inline] + pub unsafe fn cmd_end_debug_label(&self, command_buffer: vk::CommandBuffer) { + self.functions.cmd_end_debug_utils_label(command_buffer); + } + + #[inline] + pub unsafe fn queue_insert_debug_label(&self, queue: vk::Queue, label: &str) { + let mut cstr_buf = CStrBuf::new(); + self.queue_insert_debug_label_cstr(queue, cstr_buf.to_cstr(label)) + } + + pub unsafe fn queue_insert_debug_label_cstr(&self, queue: vk::Queue, label: &CStr) { + self.functions.queue_insert_debug_utils_label( + queue, + &vk::DebugUtilsLabelEXT::builder().label_name(label), + ); + } + + #[inline] + pub unsafe fn queue_begin_debug_label(&self, queue: vk::Queue, label: &str) { + let mut cstr_buf = CStrBuf::new(); + self.queue_begin_debug_label_cstr(queue, cstr_buf.to_cstr(label)) + } + + pub unsafe fn queue_begin_debug_label_cstr(&self, queue: vk::Queue, label: &CStr) { + self.functions.queue_begin_debug_utils_label( + queue, + &vk::DebugUtilsLabelEXT::builder().label_name(label), + ); + } + + #[inline] + pub unsafe fn queue_end_debug_label(&self, queue: vk::Queue) { + self.functions.queue_end_debug_utils_label(queue); + } +} + +impl Drop for DebugUtils { + fn drop(&mut self) { + if self.utils_messenger != vk::DebugUtilsMessengerEXT::null() { + let utils_messenger = std::mem::take(&mut self.utils_messenger); + unsafe { + self.functions + .destroy_debug_utils_messenger(utils_messenger, None); + } + } + } +} diff --git a/crates/render-ash/src/device.rs b/crates/render-ash/src/device.rs new file mode 100644 index 0000000..f63197c --- /dev/null +++ b/crates/render-ash/src/device.rs @@ -0,0 +1,447 @@ +use std::{ffi::CStr, ops::Deref, os::raw::c_char, sync::Arc}; + +use ash::{extensions::khr, vk}; +use pulz_render::graph::pass::PipelineBindPoint; +use tracing::{debug, info, warn}; + +use crate::{ + instance::{AshInstance, VK_API_VERSION}, + swapchain::Surface, + Error, ErrorNoExtension, Result, +}; + +pub type GpuAllocator = gpu_alloc::GpuAllocator; +pub type GpuMemoryBlock = gpu_alloc::MemoryBlock; +pub use gpu_alloc::AllocationError; + +pub struct AshDevice { + instance: Arc, + physical_device: vk::PhysicalDevice, + device_raw: ash::Device, + device_extensions: Vec<&'static CStr>, + gpu_allocator: GpuAllocator, + ext_swapchain: Option, + ext_sync2: Option, + ext_raytracing_pipeline: Option, + queues: Queues, +} + +impl Deref for AshDevice { + type Target = ash::Device; + #[inline] + fn deref(&self) -> &ash::Device { + &self.device_raw + } +} + +impl AshInstance { + pub(crate) fn new_device(self: &Arc) -> Result> { + let (physical_device, indices, device_extensions) = pick_physical_device(self, None)?; + AshDevice::new(self, physical_device, indices, device_extensions) + } + + pub(crate) fn new_device_for_surface( + self: &Arc, + surface: &Surface, + ) -> Result> { + let (physical_device, indices, device_extensions) = + pick_physical_device(self, Some(surface))?; + AshDevice::new(self, physical_device, indices, device_extensions) + } +} + +impl AshDevice { + fn new( + instance: &Arc, + physical_device: vk::PhysicalDevice, + indices: QueueFamilyIndices, + device_extensions: Vec<&'static CStr>, + ) -> Result> { + let gpu_alloc_props = + unsafe { gpu_alloc_ash::device_properties(instance, VK_API_VERSION, physical_device)? }; + + let (device_raw, queues) = create_logical_device( + instance, + physical_device, + indices, + device_extensions.iter().copied(), + )?; + + // TODO: Config + let gpu_alloc_config = gpu_alloc::Config::i_am_potato(); + + let mut device = Self { + instance: instance.clone(), + physical_device, + device_raw, + device_extensions, + gpu_allocator: GpuAllocator::new(gpu_alloc_config, gpu_alloc_props), + ext_swapchain: None, + ext_sync2: None, + ext_raytracing_pipeline: None, + queues, + }; + + if device.has_device_extension(khr::Swapchain::name()) { + device.ext_swapchain = Some(khr::Swapchain::new(instance, &device)); + } + if device.has_device_extension(khr::Synchronization2::name()) { + device.ext_sync2 = Some(khr::Synchronization2::new(instance, &device)) + } + if device.has_device_extension(khr::RayTracingPipeline::name()) { + device.ext_raytracing_pipeline = Some(khr::RayTracingPipeline::new(instance, &device)) + } + + Ok(Arc::new(device)) + } + + #[inline] + pub fn instance(&self) -> &AshInstance { + &self.instance + } + + #[inline] + pub fn physical_device(&self) -> vk::PhysicalDevice { + self.physical_device + } + + #[inline] + pub fn has_device_extension(&self, name: &CStr) -> bool { + self.device_extensions.contains(&name) + } + + #[inline] + pub fn queues(&self) -> &Queues { + &self.queues + } + + #[inline] + pub(crate) fn ext_swapchain(&self) -> Result<&khr::Swapchain, ErrorNoExtension> { + self.ext_swapchain + .as_ref() + .ok_or(ErrorNoExtension(khr::Swapchain::name())) + } + + #[inline] + pub(crate) fn ext_sync2(&self) -> Result<&khr::Synchronization2, ErrorNoExtension> { + self.ext_sync2 + .as_ref() + .ok_or(ErrorNoExtension(khr::Synchronization2::name())) + } + + #[inline] + pub(crate) fn ext_raytracing_pipeline( + &self, + ) -> Result<&khr::RayTracingPipeline, ErrorNoExtension> { + self.ext_raytracing_pipeline + .as_ref() + .ok_or(ErrorNoExtension(khr::RayTracingPipeline::name())) + } + + #[inline] + pub unsafe fn alloc( + &mut self, + request: gpu_alloc::Request, + ) -> Result { + self.gpu_allocator.alloc( + gpu_alloc_ash::AshMemoryDevice::wrap(&self.device_raw), + request, + ) + } + + #[inline] + pub unsafe fn alloc_with_dedicated( + &mut self, + request: gpu_alloc::Request, + dedicated: gpu_alloc::Dedicated, + ) -> Result { + self.gpu_allocator.alloc_with_dedicated( + gpu_alloc_ash::AshMemoryDevice::wrap(&self.device_raw), + request, + dedicated, + ) + } + + #[inline] + pub unsafe fn dealloc(&mut self, block: GpuMemoryBlock) { + self.gpu_allocator.dealloc( + gpu_alloc_ash::AshMemoryDevice::wrap(&self.device_raw), + block, + ) + } + + #[inline] + pub unsafe fn object_name(&self, handle: H, name: &str) { + if let Ok(debug_utils) = self.instance.ext_debug_utils() { + debug_utils.object_name(self.handle(), handle, name) + } + } +} + +impl Drop for AshDevice { + fn drop(&mut self) { + if self.device_raw.handle() != vk::Device::null() { + unsafe { + self.device_raw.destroy_device(None); + } + } + } +} + +fn get_device_extensions( + instance: &ash::Instance, + physical_device: vk::PhysicalDevice, +) -> Result> { + let available_extensions = + unsafe { instance.enumerate_device_extension_properties(physical_device)? }; + + let mut extensions = Vec::with_capacity(4); + extensions.push(khr::Swapchain::name()); + extensions.push(khr::Synchronization2::name()); + + // Only keep available extensions. + extensions.retain(|&ext| { + if available_extensions + .iter() + .any(|avail_ext| unsafe { CStr::from_ptr(avail_ext.extension_name.as_ptr()) == ext }) + { + debug!("Device extension ✅ YES {:?}", ext); + true + } else { + warn!("Device extension ❌ NO {:?}", ext); + false + } + }); + + Ok(extensions) +} + +fn pick_physical_device( + instance: &ash::Instance, + for_surface: Option<&Surface>, +) -> Result<(vk::PhysicalDevice, QueueFamilyIndices, Vec<&'static CStr>)> { + let physical_devices = unsafe { instance.enumerate_physical_devices()? }; + + info!( + "{} devices (GPU) found with vulkan support.", + physical_devices.len() + ); + + let mut result = None; + for (i, &physical_device) in physical_devices.iter().enumerate() { + log_device_infos(instance, physical_device, i); + + if let Some((indices, extensions)) = + check_physical_device_suitable(instance, physical_device, for_surface) + { + if result.is_none() { + result = Some((i, physical_device, indices, extensions)) + } + } + } + + match result { + Some((i, physical_device, indices, extensions)) => { + info!("Selected device: #{}", i); + Ok((physical_device, indices, extensions)) + } + None => { + warn!("Unable to find a suitable GPU!"); + Err(Error::NoAdapter) + } + } +} + +fn log_device_infos( + instance: &ash::Instance, + physical_device: vk::PhysicalDevice, + device_index: usize, +) { + let device_properties = unsafe { instance.get_physical_device_properties(physical_device) }; + let _device_features = unsafe { instance.get_physical_device_features(physical_device) }; + let device_queue_families = + unsafe { instance.get_physical_device_queue_family_properties(physical_device) }; + + let device_name = unsafe { CStr::from_ptr(device_properties.device_name.as_ptr()) }; + + info!( + "Device #{}\tName: {:?}, id: {:?}, type: {:?}", + device_index, device_name, device_properties.device_id, device_properties.device_type + ); + + info!("\tQueue Families: {}", device_queue_families.len()); + for (i, queue_family) in device_queue_families.iter().enumerate() { + info!( + "\t #{}:{:4} x {:?}", + i, queue_family.queue_count, queue_family.queue_flags + ); + } +} + +fn check_physical_device_suitable( + instance: &ash::Instance, + physical_device: vk::PhysicalDevice, + for_surface: Option<&Surface>, +) -> Option<(QueueFamilyIndices, Vec<&'static CStr>)> { + let indices = QueueFamilyIndices::from_physical_device(instance, physical_device, for_surface)?; + let extensions = get_device_extensions(instance, physical_device).ok()?; + + if let Some(surface) = for_surface { + if !extensions.contains(&khr::Swapchain::name()) + || surface.query_swapchain_support(physical_device).is_none() + { + return None; + } + } + + Some((indices, extensions)) +} + +#[inline] +fn create_logical_device<'a>( + instance: &ash::Instance, + physical_device: vk::PhysicalDevice, + indices: QueueFamilyIndices, + extensions: impl IntoIterator, +) -> Result<(ash::Device, Queues)> { + let extensions_ptr: Vec<_> = extensions.into_iter().map(CStr::as_ptr).collect(); + _create_logical_device(instance, physical_device, indices, &extensions_ptr) +} + +fn _create_logical_device( + instance: &ash::Instance, + physical_device: vk::PhysicalDevice, + indices: QueueFamilyIndices, + extensions_ptr: &[*const c_char], +) -> Result<(ash::Device, Queues)> { + let device = unsafe { + instance.create_device( + physical_device, + &vk::DeviceCreateInfo::builder() + .queue_create_infos(&[vk::DeviceQueueCreateInfo::builder() + .queue_family_index(indices.graphics_family) + .queue_priorities(&[1.0_f32]) + .build()]) + .enabled_extension_names(extensions_ptr), + // .enabled_features(&vk::PhysicalDeviceFeatures { + // ..Default::default() // default just enable no feature. + // }) + None, + )? + }; + + let queues = Queues::from_device(&device, indices); + + Ok((device, queues)) +} + +pub struct QueueFamilyIndices { + pub graphics_family: u32, + pub compute_family: u32, + pub present_family: u32, +} + +impl QueueFamilyIndices { + fn from_physical_device( + instance: &ash::Instance, + physical_device: vk::PhysicalDevice, + for_surface: Option<&Surface>, + ) -> Option { + let queue_families = + unsafe { instance.get_physical_device_queue_family_properties(physical_device) }; + + #[derive(Default)] + struct OptIndices { + graphics: Option, + compute: Option, + present: Option, + } + + impl OptIndices { + fn check_complete(&self) -> Option { + Some(QueueFamilyIndices { + graphics_family: self.graphics?, + compute_family: self.compute?, + present_family: self.present?, + }) + } + } + + let mut indices = OptIndices::default(); + for (i, queue_family) in queue_families.iter().enumerate() { + let i = i as u32; + if queue_family.queue_count == 0 { + continue; + } + if indices.graphics.is_none() + && queue_family.queue_flags.contains(vk::QueueFlags::GRAPHICS) + { + indices.graphics = Some(i); + if for_surface.is_none() { + indices.present = Some(i); + } + } + + if indices.compute.is_none() + && queue_family.queue_flags.contains(vk::QueueFlags::COMPUTE) + { + indices.compute = Some(i); + } + + if indices.present.is_none() + && for_surface.is_some() + && for_surface + .unwrap() + .get_physical_device_surface_support(physical_device, i) + { + indices.present = Some(i); + } + + if let Some(result) = indices.check_complete() { + return Some(result); + } + } + + None + } +} + +pub struct Queues { + pub indices: QueueFamilyIndices, + pub graphics: vk::Queue, + pub compute: vk::Queue, + pub present: vk::Queue, +} + +impl Queues { + pub fn from_device(device: &ash::Device, indices: QueueFamilyIndices) -> Self { + unsafe { + let graphics = device.get_device_queue(indices.graphics_family, 0); + let compute = device.get_device_queue(indices.compute_family, 0); + let present = device.get_device_queue(indices.present_family, 0); + Self { + indices, + graphics, + compute, + present, + } + } + } + + pub fn for_bind_point(&self, bind_point: PipelineBindPoint) -> (vk::Queue, u32) { + match bind_point { + PipelineBindPoint::Graphics | PipelineBindPoint::RayTracing => { + (self.graphics, self.indices.graphics_family) + } + PipelineBindPoint::Compute => (self.compute, self.indices.compute_family), + } + } +} + +impl Deref for Queues { + type Target = QueueFamilyIndices; + #[inline] + fn deref(&self) -> &QueueFamilyIndices { + &self.indices + } +} diff --git a/crates/render-ash/src/drop_guard.rs b/crates/render-ash/src/drop_guard.rs new file mode 100644 index 0000000..67e8d33 --- /dev/null +++ b/crates/render-ash/src/drop_guard.rs @@ -0,0 +1,196 @@ +use std::{collections::VecDeque, mem::ManuallyDrop}; + +use ash::vk; + +use crate::{instance::AshInstance, AshDevice, Result}; + +pub trait Destroy { + type Context; + unsafe fn destroy(self, context: &Self::Context); +} + +pub trait CreateWithInfo: Destroy + Sized { + type CreateInfo; + unsafe fn create(context: &Self::Context, create_info: &Self::CreateInfo) -> Result; +} + +impl AshDevice { + #[inline] + pub unsafe fn create(&self, create_info: &C::CreateInfo) -> Result> + where + C: CreateWithInfo, + { + Ok(Guard::new(self, unsafe { C::create(self, create_info)? })) + } + + #[inline] + pub unsafe fn destroy(&self, handle: D) + where + D: Destroy, + { + handle.destroy(self) + } + + #[inline] + pub(crate) fn hold(&self, item: D) -> Guard<'_, D> + where + D: Destroy, + { + Guard::new(self, item) + } +} + +impl AshInstance { + #[inline] + pub unsafe fn create(&self, create_info: &C::CreateInfo) -> Result> + where + C: CreateWithInfo, + { + Ok(Guard::new(self, unsafe { C::create(self, create_info)? })) + } + + #[inline] + pub unsafe fn destroy(&self, handle: D) + where + D: Destroy, + { + handle.destroy(self) + } + + #[inline] + pub(crate) fn hold(&self, item: D) -> Guard<'_, D> + where + D: Destroy, + { + Guard::new(self, item) + } +} + +pub struct Guard<'a, D: Destroy> { + item: ManuallyDrop, + context: &'a D::Context, +} + +impl<'a, D: Destroy> Guard<'a, D> { + #[inline] + pub fn new(context: &'a D::Context, item: D) -> Self { + Self { + item: ManuallyDrop::new(item), + context, + } + } + + #[inline] + pub fn take(mut self) -> D { + let item = unsafe { ManuallyDrop::take(&mut self.item) }; + std::mem::forget(self); + item + } + + #[inline] + pub fn as_ref(&self) -> &D { + &self.item + } +} + +impl<'a, I, D: Destroy + std::ops::Index> std::ops::Index for Guard<'a, D> { + type Output = D::Output; + #[inline] + fn index(&self, i: I) -> &D::Output { + &self.item[i] + } +} + +impl<'a, D: Destroy + Copy> Guard<'a, D> { + #[inline] + pub fn raw(&self) -> D { + *self.item + } +} + +impl Drop for Guard<'_, D> { + fn drop(&mut self) { + unsafe { + let item = ManuallyDrop::take(&mut self.item); + item.destroy(self.context); + } + } +} + +impl Destroy for Vec { + type Context = D::Context; + #[inline] + unsafe fn destroy(self, device: &D::Context) { + self.into_iter().for_each(|d| d.destroy(device)) + } +} + +impl Destroy for VecDeque { + type Context = D::Context; + #[inline] + unsafe fn destroy(self, device: &D::Context) { + self.into_iter().for_each(|d| d.destroy(device)) + } +} + +impl Destroy for std::vec::Drain<'_, D> { + type Context = D::Context; + #[inline] + unsafe fn destroy(self, device: &D::Context) { + self.for_each(|d| d.destroy(device)) + } +} + +impl Destroy for std::collections::vec_deque::Drain<'_, D> { + type Context = D::Context; + #[inline] + unsafe fn destroy(self, device: &D::Context) { + self.for_each(|d| d.destroy(device)) + } +} + +macro_rules! impl_create_destroy { + ($ctx:ty { + $( + $vktype:ty : ($destroy:ident $(, $create:ident $createinfo:ty)?) + ),* $(,)? + }) => { + $( + + impl Destroy for $vktype { + type Context = $ctx; + #[inline] + unsafe fn destroy(self, ctx: &Self::Context) { + ctx.$destroy(self, None); + } + } + + $( + impl CreateWithInfo for $vktype { + type CreateInfo = $createinfo; + #[inline] + unsafe fn create(ctx: &Self::Context, create_info: &Self::CreateInfo) -> Result { + Ok(ctx.$create(create_info, None)?) + } + } + )? + )* + }; +} + +impl_create_destroy! { + AshDevice { + vk::Fence : (destroy_fence, create_fence vk::FenceCreateInfo), + vk::Semaphore : (destroy_semaphore, create_semaphore vk::SemaphoreCreateInfo), + vk::Event : (destroy_event, create_event vk::EventCreateInfo), + vk::CommandPool : (destroy_command_pool, create_command_pool vk::CommandPoolCreateInfo), + vk::Image : (destroy_image, create_image vk::ImageCreateInfo), + vk::ImageView : (destroy_image_view, create_image_view vk::ImageViewCreateInfo), + vk::Framebuffer : (destroy_framebuffer, create_framebuffer vk::FramebufferCreateInfo), + vk::RenderPass : (destroy_render_pass, create_render_pass vk::RenderPassCreateInfo), + vk::ShaderModule : (destroy_shader_module, create_shader_module vk::ShaderModuleCreateInfo), + vk::DescriptorSetLayout : (destroy_descriptor_set_layout, create_descriptor_set_layout vk::DescriptorSetLayoutCreateInfo), + vk::PipelineLayout : (destroy_pipeline_layout, create_pipeline_layout vk::PipelineLayoutCreateInfo), + vk::Pipeline : (destroy_pipeline), + } +} diff --git a/crates/render-ash/src/encoder.rs b/crates/render-ash/src/encoder.rs new file mode 100644 index 0000000..f341db7 --- /dev/null +++ b/crates/render-ash/src/encoder.rs @@ -0,0 +1,379 @@ +use std::{cell::Cell, collections::VecDeque, sync::Arc}; + +use ash::{prelude::VkResult, vk}; +use pulz_render::backend::CommandEncoder; + +use crate::{device::AshDevice, Result}; + +pub struct AshCommandPool { + device: Arc, + queue_family_index: u32, + pool: vk::CommandPool, + fresh_buffers: VecDeque, + done_buffers: Vec, + new_allocation_count: u32, + semaphores_pool: VecDeque, + used_semaphores: Vec, // semaphores to return to pool after frame finished +} + +impl AshDevice { + pub(crate) fn new_command_pool( + self: &Arc, + queue_family_index: u32, + ) -> VkResult { + let pool = unsafe { + self.create_command_pool( + &vk::CommandPoolCreateInfo::builder() + .queue_family_index(queue_family_index) + .flags(vk::CommandPoolCreateFlags::TRANSIENT), + None, + )? + }; + Ok(AshCommandPool { + device: self.clone(), + queue_family_index, + pool, + fresh_buffers: VecDeque::new(), + done_buffers: Vec::new(), + new_allocation_count: 1, + semaphores_pool: VecDeque::new(), + used_semaphores: Vec::new(), + }) + } +} + +impl AshCommandPool { + #[inline] + pub fn device(&self) -> &AshDevice { + &self.device + } + + pub unsafe fn reset(&mut self) -> VkResult<()> { + self.device + .reset_command_pool(self.pool, vk::CommandPoolResetFlags::empty())?; + self.fresh_buffers.extend(self.done_buffers.drain(..)); + + // return all semaphores to pool + self.semaphores_pool.extend(self.used_semaphores.drain(..)); + + Ok(()) + } + + pub fn request_semaphore(&mut self) -> Result { + // TODO: drop guard + let s = if let Some(s) = self.semaphores_pool.pop_front() { + s + } else { + unsafe { + self.device + .create_semaphore(&vk::SemaphoreCreateInfo::builder().build(), None)? + } + }; + self.used_semaphores.push(s); + Ok(s) + } + + pub fn encoder(&mut self) -> Result, vk::Result> { + if self.fresh_buffers.is_empty() { + let new_buffers = unsafe { + self.device.allocate_command_buffers( + &vk::CommandBufferAllocateInfo::builder() + .command_pool(self.pool) + .level(vk::CommandBufferLevel::PRIMARY) + .command_buffer_count(self.new_allocation_count), + )? + }; + self.fresh_buffers.extend(new_buffers); + } + let buffer = self.fresh_buffers.pop_front().unwrap(); + unsafe { + self.device.begin_command_buffer( + buffer, + &vk::CommandBufferBeginInfo::builder() + .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), + )?; + } + Ok(AshCommandEncoder { + pool: self, + buffer, + debug_levels: Cell::new(0), + }) + } + + unsafe fn free_command_buffers(&self, buffers: &[vk::CommandBuffer]) { + if !buffers.is_empty() { + self.device.free_command_buffers(self.pool, buffers); + } + } +} + +impl Drop for AshCommandPool { + fn drop(&mut self) { + self.semaphores_pool.extend(self.used_semaphores.drain(..)); + for semaphore in self.semaphores_pool.drain(..) { + unsafe { + self.device.destroy_semaphore(semaphore, None); + } + } + + unsafe { + let (a, b) = self.fresh_buffers.as_slices(); + self.free_command_buffers(a); + self.free_command_buffers(b); + self.fresh_buffers.clear(); + self.free_command_buffers(&self.done_buffers); + self.done_buffers.clear(); + } + if self.pool != vk::CommandPool::null() { + unsafe { + self.device.destroy_command_pool(self.pool, None); + } + self.pool = vk::CommandPool::null(); + } + } +} +pub struct AshCommandEncoder<'l> { + pool: &'l mut AshCommandPool, + buffer: vk::CommandBuffer, + debug_levels: Cell, +} + +impl AshCommandEncoder<'_> { + pub fn submit(self, submission: &SubmissionGroup) -> VkResult<()> { + self.end_remaining_debug_labels(); + unsafe { + self.pool.device.end_command_buffer(self.buffer)?; + } + submission.queue.push(self.buffer); + Ok(()) + } + + #[inline] + pub fn request_semaphore(&mut self) -> Result { + self.pool.request_semaphore() + } + + pub fn insert_debug_label(&self, label: &str) { + if let Ok(debug_utils) = self.pool.device.instance().ext_debug_utils() { + unsafe { + debug_utils.cmd_begin_debug_label(self.buffer, label); + } + } + } + + pub fn begin_debug_label(&self, label: &str) { + if let Ok(debug_utils) = self.pool.device.instance().ext_debug_utils() { + unsafe { + debug_utils.cmd_begin_debug_label(self.buffer, label); + } + let debug_levels = self.debug_levels.get(); + self.debug_levels.set(debug_levels + 1); + } + } + + pub fn end_debug_label(&self) { + if let Ok(debug_utils) = self.pool.device.instance().ext_debug_utils() { + unsafe { + debug_utils.cmd_end_debug_label(self.buffer); + } + let debug_levels = self.debug_levels.get(); + if debug_levels > 0 { + self.debug_levels.set(debug_levels - 1); + } + } + } + + fn end_remaining_debug_labels(&self) { + let debug_levels = self.debug_levels.get(); + if debug_levels > 0 { + self.debug_levels.set(0); + if let Ok(debug_utils) = self.pool.device.instance().ext_debug_utils() { + for _i in 0..debug_levels { + unsafe { + debug_utils.cmd_end_debug_label(self.buffer); + } + } + } + } + } + + pub unsafe fn clear_color_image( + &self, + image: vk::Image, + image_layout: vk::ImageLayout, + clear_value: &vk::ClearColorValue, + ranges: &[vk::ImageSubresourceRange], + ) { + self.pool.device().cmd_clear_color_image( + self.buffer, + image, + image_layout, + clear_value, + ranges, + ) + } + + pub unsafe fn clear_depth_stencil_image( + &self, + image: vk::Image, + image_layout: vk::ImageLayout, + clear_value: &vk::ClearDepthStencilValue, + ranges: &[vk::ImageSubresourceRange], + ) { + self.pool.device().cmd_clear_depth_stencil_image( + self.buffer, + image, + image_layout, + clear_value, + ranges, + ) + } + + pub unsafe fn pipeline_barrier( + &self, + src_stage_mask: vk::PipelineStageFlags, + dst_stage_mask: vk::PipelineStageFlags, + memory_barriers: &[vk::MemoryBarrier], + buffer_memory_barriers: &[vk::BufferMemoryBarrier], + image_memory_barriers: &[vk::ImageMemoryBarrier], + ) { + self.pool.device().cmd_pipeline_barrier( + self.buffer, + src_stage_mask, + dst_stage_mask, + vk::DependencyFlags::empty(), + memory_barriers, + buffer_memory_barriers, + image_memory_barriers, + ) + } + + pub unsafe fn begin_render_pass( + &self, + create_info: &vk::RenderPassBeginInfo, + contents: vk::SubpassContents, + ) { + self.pool + .device() + .cmd_begin_render_pass(self.buffer, create_info, contents); + } + + pub unsafe fn next_subpass(&self, contents: vk::SubpassContents) { + self.pool.device().cmd_next_subpass(self.buffer, contents); + } + + pub unsafe fn end_render_pass(&self) { + self.pool.device().cmd_end_render_pass(self.buffer); + } +} + +impl CommandEncoder for AshCommandEncoder<'_> { + #[inline] + fn insert_debug_marker(&mut self, label: &str) { + self.insert_debug_label(label); + } + #[inline] + fn push_debug_group(&mut self, label: &str) { + self.begin_debug_label(label) + } + #[inline] + fn pop_debug_group(&mut self) { + self.end_debug_label(); + } +} + +impl Drop for AshCommandEncoder<'_> { + fn drop(&mut self) { + if self.buffer != vk::CommandBuffer::null() { + self.pool.done_buffers.push(self.buffer); + self.buffer = vk::CommandBuffer::null(); + } + } +} + +pub struct SubmissionGroup { + wait_semaphores: Vec, + wait_semaphores_dst_stages: Vec, + command_buffers: Vec, + signal_semaphores: Vec, + queue: crossbeam_queue::SegQueue, +} + +impl SubmissionGroup { + #[inline] + pub fn new() -> Self { + Self { + wait_semaphores: Vec::new(), + wait_semaphores_dst_stages: Vec::new(), + command_buffers: Vec::new(), + signal_semaphores: Vec::new(), + queue: crossbeam_queue::SegQueue::new(), + } + } + + #[inline] + pub fn wait(&mut self, sem: vk::Semaphore, dst_stages: vk::PipelineStageFlags) -> &mut Self { + self.wait_semaphores.push(sem); + self.wait_semaphores_dst_stages.push(dst_stages); + self + } + + #[inline] + pub(crate) fn get_wait_semaphores(&self) -> &[vk::Semaphore] { + &self.wait_semaphores + } + + #[inline] + pub fn push(&mut self, buf: vk::CommandBuffer) -> &mut Self { + self.command_buffers.push(buf); + self + } + + #[inline] + pub fn flush_queue(&mut self) -> &mut Self { + while let Some(buf) = self.queue.pop() { + self.command_buffers.push(buf); + } + self + } + + #[inline] + pub fn signal(&mut self, sem: vk::Semaphore) -> &mut Self { + self.signal_semaphores.push(sem); + self + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.command_buffers.is_empty() + } + + pub fn submit_info(&self) -> vk::SubmitInfoBuilder<'_> { + vk::SubmitInfo::builder() + .wait_semaphores(&self.wait_semaphores) + .wait_dst_stage_mask(&self.wait_semaphores_dst_stages) + .command_buffers(&self.command_buffers) + .signal_semaphores(&self.signal_semaphores) + } + + pub fn submit(&mut self, device: &AshDevice, fence: vk::Fence) -> VkResult<&mut Self> { + unsafe { + device.queue_submit( + device.queues().graphics, + &[self.submit_info().build()], + fence, + )?; + } + self.reset(); + Ok(self) + } + + #[inline] + pub fn reset(&mut self) -> &mut Self { + self.wait_semaphores.clear(); + self.wait_semaphores_dst_stages.clear(); + self.command_buffers.clear(); + self.signal_semaphores.clear(); + self + } +} diff --git a/crates/render-ash/src/graph.rs b/crates/render-ash/src/graph.rs new file mode 100644 index 0000000..da2ffa8 --- /dev/null +++ b/crates/render-ash/src/graph.rs @@ -0,0 +1,343 @@ +use std::sync::Arc; + +use ash::vk::{self, SubpassDependency}; +use pulz_render::{ + buffer::BufferUsage, + draw::DrawPhases, + graph::{ + pass::PipelineBindPoint, resources::ResourceDep, PassDescription, PassGroupDescription, + RenderGraph, + }, + texture::TextureUsage, +}; + +use crate::{ + convert::{ + into_buffer_usage_read_access, into_buffer_usage_write_access, + into_texture_usage_read_access, into_texture_usage_write_access, VkInto, + }, + device::AshDevice, + encoder::{AshCommandPool, SubmissionGroup}, + drop_guard::Guard, + Result, +}; + +pub struct AshRenderGraph { + device: Arc, + hash: u64, + topo: Vec, + barriers: Vec, +} + +#[derive(Default)] +pub struct TopoGroup { + render_passes: Vec<(usize, vk::RenderPass, vk::Framebuffer)>, // group-index + compute_passes: Vec, // pass-index + ray_tracing_passes: Vec, // pass-index +} + +pub struct Barrier { + image: Vec, + buffer: Vec, +} + +// implement Send+Sync manually, because vk::*MemoryBarrier have unused p_next pointers +// SAFETY: p_next pointers are not used +unsafe impl Send for Barrier {} +unsafe impl Sync for Barrier {} + +impl AshRenderGraph { + #[inline] + pub fn create(device: &Arc) -> Self { + Self { + device: device.clone(), + hash: 0, + topo: Vec::new(), + barriers: Vec::new(), + } + } + + fn reset(&mut self) { + // TODO: caching of render-passes: don't destroy & recreate on every update! + for topo in &mut self.topo { + for (_, pass, fb) in topo.render_passes.drain(..) { + unsafe { + self.device.destroy_framebuffer(fb, None); + self.device.destroy_render_pass(pass, None); + } + } + } + + self.topo.clear(); + self.barriers.clear(); + } + + pub fn update(&mut self, src_graph: &RenderGraph) { + if src_graph.was_updated() || self.hash != src_graph.hash() { + self.force_update(src_graph); + } + } + + fn create_render_pass<'d>( + device: &'d AshDevice, + src_graph: &RenderGraph, + group: &PassGroupDescription, + ) -> Result> { + let range = group.range(); + let attachments = Vec::new(); + let mut subpasses = Vec::with_capacity(range.len()); + let mut dependencies = Vec::new(); + + fn map_pass_index_to_subpass_index(group: &PassGroupDescription, pass_index: usize) -> u32 { + let range = group.range(); + if range.contains(&pass_index) { + (pass_index - range.start) as u32 + } else { + vk::SUBPASS_EXTERNAL + } + } + + fn get_subpass_dep<'l>( + deps: &'l mut Vec, + group: &PassGroupDescription, + src_pass: usize, + dst_pass: usize, + ) -> &'l mut SubpassDependency { + let src = map_pass_index_to_subpass_index(group, src_pass); + let dst = map_pass_index_to_subpass_index(group, dst_pass); + match deps.binary_search_by_key(&(src, dst), |d| (d.src_subpass, d.dst_subpass)) { + Ok(i) => &mut deps[i], + Err(i) => { + deps.insert( + i, + SubpassDependency::builder() + .src_subpass(src) + .dst_subpass(dst) + // use BY-REGION by default + .dependency_flags(vk::DependencyFlags::BY_REGION) + .build(), + ); + &mut deps[i] + } + } + } + + fn get_texture_access(dep: &ResourceDep, dst: bool) -> vk::AccessFlags { + let reads = dep.src_pass() != !0; // resource was written in a different pass + let writes = dep.write_access(); + let usage = dep.usage(); + let mut result = vk::AccessFlags::empty(); + if reads && (dst || !writes) { + result |= into_texture_usage_read_access(usage); + } + if writes { + result |= into_texture_usage_write_access(usage); + } + result + } + + fn get_buffer_access(dep: &ResourceDep, _dst: bool) -> vk::AccessFlags { + let reads = dep.src_pass() != !0; // resource was written in a different pass + let writes = dep.write_access(); + let usage = dep.usage(); + let mut result = vk::AccessFlags::empty(); + if reads { + result |= into_buffer_usage_read_access(usage); + } + if writes { + result |= into_buffer_usage_write_access(usage); + } + result + } + + fn add_subpass_deps( + src_graph: &RenderGraph, + deps: &mut Vec, + group: &PassGroupDescription, + pass: &PassDescription, + ) { + let dst_pass = pass.index(); + for tex_dep in pass.textures().deps() { + if tex_dep.src_pass() != !0 { + let usage = tex_dep.usage(); + let dst_dep = get_subpass_dep(deps, group, tex_dep.src_pass(), dst_pass); + if !usage.contains(TextureUsage::BY_REGION) { + // remove by-region + dst_dep.dependency_flags &= !vk::DependencyFlags::BY_REGION; + } + dst_dep.dst_stage_mask |= tex_dep.stages().vk_into(); + dst_dep.dst_access_mask |= get_texture_access(tex_dep, true); + + let tex_src = src_graph + .get_pass(tex_dep.src_pass()) + .unwrap() + .textures() + .find_by_resource_index(tex_dep.resource_index()) + .unwrap(); + dst_dep.src_stage_mask |= tex_src.stages().vk_into(); + dst_dep.src_access_mask |= get_texture_access(tex_src, false); + } + } + for buf_dep in pass.buffers().deps() { + if buf_dep.src_pass() != !0 { + let dst_dep = get_subpass_dep(deps, group, buf_dep.src_pass(), dst_pass); + dst_dep.dst_stage_mask |= buf_dep.stages().vk_into(); + dst_dep.dst_access_mask |= get_buffer_access(buf_dep, true); + + let buf_src = src_graph + .get_pass(buf_dep.src_pass()) + .unwrap() + .buffers() + .find_by_resource_index(buf_dep.resource_index()) + .unwrap(); + dst_dep.src_stage_mask |= buf_src.stages().vk_into(); + dst_dep.src_access_mask |= get_buffer_access(buf_src, false); + } + } + } + + for pass_index in range { + let pass = src_graph.get_pass(pass_index).unwrap(); + subpasses.push( + vk::SubpassDescription::builder() + .pipeline_bind_point(pass.bind_point().vk_into()) + // TODO: attachments + .build(), + ); + add_subpass_deps(src_graph, &mut dependencies, group, pass); + } + + let create_info = vk::RenderPassCreateInfo::builder() + .attachments(&attachments) + .subpasses(&subpasses) + .dependencies(&dependencies); + + unsafe { + let pass = device.create(&create_info.build())?; + if let Ok(debug_utils) = device.instance().ext_debug_utils() { + debug_utils.object_name(device.handle(), pass.raw(), group.name()); + } + Ok(pass) + } + } + + fn create_framebuffer<'d>( + device: &'d AshDevice, + _src_graph: &RenderGraph, + group: &PassGroupDescription, + render_pass: vk::RenderPass, + ) -> Result> { + let create_info = vk::FramebufferCreateInfo::builder() + .render_pass(render_pass) + // TODO + // .attachments() + .width(800) + .height(600) + .layers(1); + + unsafe { + let fb = device.create(&create_info.build())?; + if let Ok(debug_utils) = device.instance().ext_debug_utils() { + debug_utils.object_name(device.handle(), fb.raw(), group.name()); + } + Ok(fb) + } + } + + pub fn force_update(&mut self, src: &RenderGraph) -> Result<()> { + self.reset(); + self.hash = src.hash(); + + let num_topological_groups = src.get_num_topological_groups(); + self.topo + .resize_with(num_topological_groups, Default::default); + + for topo_index in 0..num_topological_groups { + let topo = &mut self.topo[topo_index]; + for group in src.get_topological_group(topo_index) { + match group.bind_point() { + PipelineBindPoint::Graphics => { + let pass = Self::create_render_pass(&self.device, src, group)?; + let fb = Self::create_framebuffer(&self.device, src, group, pass.raw())?; + topo.render_passes + .push((group.group_index(), pass.take(), fb.take())); + } + PipelineBindPoint::Compute => { + let range = group.range(); + assert_eq!(range.start + 1, range.end); + topo.compute_passes.push(range.start); + } + PipelineBindPoint::RayTracing => { + let range = group.range(); + assert_eq!(range.start + 1, range.end); + topo.ray_tracing_passes.push(range.start); + } + } + } + } + + Ok(()) + } + + pub fn execute( + &self, + src_graph: &RenderGraph, + submission_group: &mut SubmissionGroup, + command_pool: &mut AshCommandPool, + draw_phases: &DrawPhases, + ) -> Result<()> { + let mut encoder = command_pool.encoder()?; + for (topo_index, topo) in self.topo.iter().enumerate() { + // render-passes + for &(group_index, render_pass, fb) in &topo.render_passes { + let group = src_graph.get_pass_group(group_index).unwrap(); + let multi_pass = group.range().len() > 1; + if multi_pass { + encoder.begin_debug_label(group.name()); + } + unsafe { + // TODO: caching of render-pass & framebuffer + // TODO: clear-values, ... + encoder.begin_render_pass( + &vk::RenderPassBeginInfo::builder() + .render_pass(render_pass) + .framebuffer(fb) + .build(), + vk::SubpassContents::INLINE, + ); + let mut first = true; + for pass_index in group.range() { + if first { + first = false; + } else { + encoder.next_subpass(vk::SubpassContents::INLINE); + } + let pass = src_graph.get_pass(pass_index).unwrap(); + encoder.begin_debug_label(pass.name()); + src_graph.execute_pass(pass.index(), &mut encoder, draw_phases); + encoder.end_debug_label(); + } + encoder.end_render_pass(); + } + if multi_pass { + encoder.end_debug_label(); + } + } + // TODO: compute passes, raytracing-passes + + if let Some(_barrier) = self.barriers.get(topo_index) { + // TODO: add barriers + } + } + + encoder.submit(submission_group)?; + + Ok(()) + } +} + +impl Drop for AshRenderGraph { + fn drop(&mut self) { + self.reset() + } +} diff --git a/crates/render-ash/src/instance.rs b/crates/render-ash/src/instance.rs new file mode 100644 index 0000000..a737318 --- /dev/null +++ b/crates/render-ash/src/instance.rs @@ -0,0 +1,205 @@ +use std::{ffi::CStr, ops::Deref, os::raw::c_char, sync::Arc}; + +use ash::{ + extensions::{ext, khr}, + vk, +}; +use tracing::{debug, warn}; + +use crate::{debug_utils::DebugUtils, AshRendererFlags, ErrorNoExtension, Result}; + +pub const ENGINE_NAME: &[u8] = concat!(env!("CARGO_PKG_NAME"), "\0").as_bytes(); +pub const ENGINE_VERSION: u32 = parse_version(env!("CARGO_PKG_VERSION")); +pub const VK_API_VERSION: u32 = vk::API_VERSION_1_1; + +pub struct AshInstance { + entry: ash::Entry, + instance_raw: ash::Instance, + instance_extensions: Vec<&'static CStr>, + ext_debug_utils: Option, + ext_surface: Option, +} + +impl Deref for AshInstance { + type Target = ash::Instance; + #[inline] + fn deref(&self) -> &ash::Instance { + &self.instance_raw + } +} + +impl AshInstance { + pub(crate) fn new(flags: AshRendererFlags) -> Result> { + let entry = unsafe { ash::Entry::load()? }; + let instance_extensions = get_instance_extensions(&entry, flags)?; + let instance_raw = create_instance(&entry, instance_extensions.iter().copied())?; + + let mut instance = Self { + entry, + instance_raw, + instance_extensions, + ext_debug_utils: None, + ext_surface: None, + }; + + if instance.has_instance_extension(DebugUtils::name()) { + instance.ext_debug_utils = Some(DebugUtils::new( + instance.entry(), + &instance, + // TODO: filter activated severities + vk::DebugUtilsMessageSeverityFlagsEXT::ERROR + | vk::DebugUtilsMessageSeverityFlagsEXT::WARNING + | vk::DebugUtilsMessageSeverityFlagsEXT::INFO + | vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE, + )); + } + + if instance.has_instance_extension(khr::Surface::name()) { + instance.ext_surface = Some(khr::Surface::new(instance.entry(), &instance)); + } + + Ok(Arc::new(instance)) + } + + #[inline] + pub fn entry(&self) -> &ash::Entry { + &self.entry + } + + #[inline] + pub fn has_instance_extension(&self, name: &CStr) -> bool { + self.instance_extensions.contains(&name) + } + + #[inline] + pub(crate) fn ext_surface(&self) -> Result<&khr::Surface, ErrorNoExtension> { + self.ext_surface + .as_ref() + .ok_or(ErrorNoExtension(khr::Surface::name())) + } + + #[inline] + pub(crate) fn ext_debug_utils(&self) -> Result<&DebugUtils, ErrorNoExtension> { + self.ext_debug_utils + .as_ref() + .ok_or(ErrorNoExtension(ext::DebugUtils::name())) + } +} + +impl Drop for AshInstance { + fn drop(&mut self) { + self.ext_debug_utils = None; + unsafe { self.instance_raw.destroy_instance(None) } + } +} + +fn get_instance_extensions( + entry: &ash::Entry, + flags: AshRendererFlags, +) -> Result> { + let available_extensions = entry.enumerate_instance_extension_properties(None)?; + + let mut extensions = Vec::with_capacity(5); + extensions.push(ash::extensions::khr::Surface::name()); + + if cfg!(target_os = "windows") { + extensions.push(khr::Win32Surface::name()); + } else if cfg!(target_os = "android") { + extensions.push(khr::AndroidSurface::name()); + } else if cfg!(any(target_os = "macos", target_os = "ios")) { + extensions.push(ext::MetalSurface::name()); + } else if cfg!(unix) { + extensions.push(khr::XlibSurface::name()); + extensions.push(khr::XcbSurface::name()); + extensions.push(khr::WaylandSurface::name()); + } + + if flags.contains(AshRendererFlags::DEBUG) { + extensions.push(DebugUtils::name()); + } + + // Only keep available extensions. + extensions.retain(|&ext| { + if available_extensions + .iter() + .any(|avail_ext| unsafe { CStr::from_ptr(avail_ext.extension_name.as_ptr()) == ext }) + { + debug!("Instance extension ✅ YES {:?}", ext); + true + } else { + warn!("Instance extension ❌ NO {:?}", ext); + false + } + }); + + Ok(extensions) +} + +#[inline] +fn create_instance<'a>( + entry: &ash::Entry, + extensions: impl IntoIterator, +) -> Result { + let extensions_ptr: Vec<_> = extensions.into_iter().map(CStr::as_ptr).collect(); + _create_instance(entry, &extensions_ptr) +} + +fn _create_instance(entry: &ash::Entry, extensions_ptr: &[*const c_char]) -> Result { + let engine_name = unsafe { CStr::from_bytes_with_nul_unchecked(ENGINE_NAME) }; + + let instance = unsafe { + entry.create_instance( + &vk::InstanceCreateInfo::builder() + .application_info( + &vk::ApplicationInfo::builder() + .application_name(engine_name) + .application_version(ENGINE_VERSION) + .engine_name(engine_name) + .engine_version(ENGINE_VERSION) + .api_version(VK_API_VERSION), + ) + .enabled_extension_names(extensions_ptr), + None, + )? + }; + Ok(instance) +} + +macro_rules! parse_int_iteration { + ($value:ident += $input:ident[$pos:expr]) => { + if $input.len() <= $pos { + return ($value, $pos); + } + $value *= 10; + let c = $input[$pos]; + if c < '0' as u8 || c > '9' as u8 { + if c != '.' as u8 && c != '-' as u8 { + panic!("invalid character in version"); + } + return ($value, $pos + 1); + } + $value += c as u32 - '0' as u32; + }; +} + +#[inline] +const fn const_parse_decimal_u32(input: &[u8], offset: usize) -> (u32, usize) { + let mut value = 0; + // manual unroll of loop for const compability + parse_int_iteration!(value += input[offset]); + parse_int_iteration!(value += input[offset + 1]); + parse_int_iteration!(value += input[offset + 2]); + parse_int_iteration!(value += input[offset + 3]); + parse_int_iteration!(value += input[offset + 4]); + parse_int_iteration!(value += input[offset + 5]); + (value, offset + 6) +} + +#[inline] +const fn parse_version(version: &str) -> u32 { + let version = version.as_bytes(); + let (major, i) = const_parse_decimal_u32(version, 0); + let (minor, j) = const_parse_decimal_u32(version, i); + let (patch, _) = const_parse_decimal_u32(version, j); + vk::make_api_version(0, major, minor, patch) +} diff --git a/crates/render-ash/src/lib.rs b/crates/render-ash/src/lib.rs new file mode 100644 index 0000000..2826045 --- /dev/null +++ b/crates/render-ash/src/lib.rs @@ -0,0 +1,535 @@ +#![warn( + // missing_docs, + // rustdoc::missing_doc_code_examples, + future_incompatible, + rust_2018_idioms, + unused, + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_qualifications, + unused_crate_dependencies, + clippy::cargo, + clippy::multiple_crate_versions, + clippy::empty_line_after_outer_attr, + clippy::fallible_impl_from, + clippy::redundant_pub_crate, + clippy::use_self, + clippy::suspicious_operation_groupings, + clippy::useless_let_if_seq, + // clippy::missing_errors_doc, + // clippy::missing_panics_doc, + clippy::wildcard_imports +)] +#![doc(html_logo_url = "https://raw.githubusercontent.com/HellButcher/pulz/master/docs/logo.png")] +#![doc(html_no_source)] +#![doc = include_str!("../README.md")] + +use std::{ffi::CStr, sync::Arc}; + +use ash::vk::{self, PipelineStageFlags}; +use bitflags::bitflags; +use device::AshDevice; +use encoder::{AshCommandPool, SubmissionGroup}; +use graph::AshRenderGraph; +use instance::AshInstance; +use pulz_ecs::prelude::*; +use pulz_render::{draw::DrawPhases, graph::RenderGraph, RenderModule, RenderSystemPhase}; +use resources::AshResources; +use thiserror::Error; +use tracing::info; + +mod attachments; +mod convert; +mod debug_utils; +mod device; +mod drop_guard; +mod encoder; +mod graph; +mod instance; +mod resources; +mod shader; +mod swapchain; + +use pulz_window::{ + RawWindow, RawWindowHandles, WindowDescriptor, WindowId, Windows, WindowsMirror, +}; + +#[derive(Error, Debug)] +#[non_exhaustive] +pub enum Error { + #[error("library loading error")] + LoadingError(#[from] ash::LoadingError), + + #[error("Vulkan driver does not support {0:?}")] + ExtensionNotSupported(&'static CStr), + + #[error("The used Window-System is not supported")] + UnsupportedWindowSystem, + + #[error("The window is not available, or it has no raw-window-handle")] + WindowNotAvailable, + + #[error("No suitable GPU adapters found on the system!")] + NoAdapter, + + #[error("Device doesn't have swapchain support")] + NoSwapchainSupport, + + #[error("Swapchain supports {supported:?}, but {requested:?} was requested")] + SwapchainUsageNotSupported { + requested: vk::ImageUsageFlags, + supported: vk::ImageUsageFlags, + }, + + #[error("The surface was lost")] + SurfaceLost, + + #[error("A next swapchain image was already acquired without beeing presented.")] + SwapchainImageAlreadyAcquired, + + #[error("Vulkan Error")] + VkError(#[from] vk::Result), + + #[error("Allocation Error")] + AllocationError(#[from] gpu_alloc::AllocationError), + + #[error("unknown renderer error")] + Unknown, +} + +#[derive(Debug)] +pub struct ErrorNoExtension(pub &'static CStr); + +impl From for Error { + #[inline] + fn from(e: ErrorNoExtension) -> Self { + Self::ExtensionNotSupported(e.0) + } +} + +impl From<&vk::Result> for Error { + #[inline] + fn from(e: &vk::Result) -> Self { + Self::VkError(*e) + } +} + +pub type Result = std::result::Result; + +pub struct AshRenderer { + device: Arc, + res: AshResources, + frames: Vec, + current_frame: usize, + surfaces: WindowsMirror, + graph: AshRenderGraph, +} + +impl Drop for AshRenderer { + fn drop(&mut self) { + unsafe { + self.device.device_wait_idle().unwrap(); + } + self.frames.clear(); + self.res.destroy_all(&self.device); + } +} + +bitflags!( + /// Instance initialization flags. + pub struct AshRendererFlags: u32 { + /// Generate debug information in shaders and objects. + const DEBUG = 1 << 0; + } +); + +struct Frame { + // TODO: multi-threaded command recording: CommandPool per thread + command_pool: AshCommandPool, + finished_fence: vk::Fence, // signaled ad end of command-cueue, waited at beginning of frame + finished_semaphore: vk::Semaphore, // semaphore used for presenting to the swapchain + retired_swapchains: Vec, +} + +impl Frame { + pub const NUM_FRAMES_IN_FLIGHT: usize = 2; +} + +impl Frame { + unsafe fn create(device: &Arc) -> Result { + let command_pool = device.new_command_pool(device.queues().graphics_family)?; + let finished_fence = device.create( + &vk::FenceCreateInfo::builder() + .flags(vk::FenceCreateFlags::SIGNALED) + .build(), + )?; + let finished_semaphore = device.create(&vk::SemaphoreCreateInfo::builder().build())?; + Ok(Self { + command_pool, + finished_fence: finished_fence.take(), + finished_semaphore: finished_semaphore.take(), + retired_swapchains: Vec::new(), + }) + } + + unsafe fn reset(&mut self, device: &AshDevice) -> Result<(), vk::Result> { + if let Ok(ext_swapchain) = device.ext_swapchain() { + for swapchain in self.retired_swapchains.drain(..) { + ext_swapchain.destroy_swapchain(swapchain, None); + } + } + + self.command_pool.reset()?; + + Ok(()) + } +} + +impl Drop for Frame { + fn drop(&mut self) { + unsafe { + let device = self.command_pool.device(); + if let Ok(ext_swapchain) = device.ext_swapchain() { + for swapchain in self.retired_swapchains.drain(..) { + ext_swapchain.destroy_swapchain(swapchain, None); + } + } + if self.finished_fence != vk::Fence::null() { + device.destroy_fence(self.finished_fence, None); + } + if self.finished_semaphore != vk::Semaphore::null() { + device.destroy_semaphore(self.finished_semaphore, None); + } + } + } +} + +impl AshRenderer { + pub fn new(flags: AshRendererFlags) -> Result { + let instance = AshInstance::new(flags)?; + let device = instance.new_device()?; + Ok(Self::from_device(device)) + } + + pub fn for_window( + flags: AshRendererFlags, + window_id: WindowId, + window_descriptor: &WindowDescriptor, + // TODO: link lifetimes of HasRawWindowHandle and Surface! + raw_window: &dyn RawWindow, + ) -> Result { + let instance = AshInstance::new(flags)?; + let surface = instance.new_surface(raw_window)?; + let device = instance.new_device_for_surface(&surface)?; + let mut renderer = Self::from_device(device); + + let surface_swapchain = renderer.device.new_swapchain( + surface, + window_descriptor.size, + //TODO: ergonomics + if window_descriptor.vsync { 3 } else { 2 }, + if window_descriptor.vsync { + vk::PresentModeKHR::MAILBOX + } else { + vk::PresentModeKHR::IMMEDIATE + }, + )?; + renderer.surfaces.insert(window_id, surface_swapchain); + Ok(renderer) + } + + fn from_device(device: Arc) -> Self { + let graph = AshRenderGraph::create(&device); + Self { + device, + res: AshResources::new(), + frames: Vec::with_capacity(Frame::NUM_FRAMES_IN_FLIGHT), + current_frame: 0, + surfaces: WindowsMirror::new(), + graph, + } + } + + fn reconfigure_swapchains(&mut self, windows: &Windows) { + let mut to_remove = Vec::new(); + for (window_id, surface_swapchain) in self.surfaces.iter_mut() { + let Some(window) = windows.get(window_id) else { + to_remove.push(window_id); + continue; + }; + //TODO: re-create also the surface, when SURFACE_LOST was returned in earlier calls. + //TODO: better resize check (don't compare size, but use a 'dirty'-flag) + if window.size != surface_swapchain.size() { + info!( + "surface sized changed: {} => {}", + surface_swapchain.size(), + window.size + ); + surface_swapchain + .configure_with( + window.size, + if window.vsync { + vk::PresentModeKHR::MAILBOX + } else { + vk::PresentModeKHR::IMMEDIATE + }, + ) + .unwrap(); + } + } + for window_id in to_remove { + self.surfaces.remove(window_id); + } + } + + fn begin_frame(&mut self) -> Result { + let _span = tracing::trace_span!("BeginFrame").entered(); + + if self.frames.is_empty() { + self.frames.reserve_exact(Frame::NUM_FRAMES_IN_FLIGHT); + for _ in 0..Frame::NUM_FRAMES_IN_FLIGHT { + self.frames.push(unsafe { Frame::create(&self.device)? }); + } + } + + let frame = &mut self.frames[self.current_frame]; + unsafe { + self.device + .wait_for_fences(&[frame.finished_fence], true, !0)?; + } + + // cleanup old frame + unsafe { + frame.reset(&self.device)?; + } + + Ok(SubmissionGroup::new()) + } + + fn render_frame( + &mut self, + submission_group: &mut SubmissionGroup, + src_graph: &RenderGraph, + phases: &DrawPhases, + ) -> Result<()> { + let _span = tracing::trace_span!("RunGraph").entered(); + let frame = &mut self.frames[self.current_frame]; + + self.graph + .execute(src_graph, submission_group, &mut frame.command_pool, phases)?; + + Ok(()) + } + + // TODO: remove this! + fn clear_unacquired_swapchain_images( + &mut self, + submission_group: &mut SubmissionGroup, + ) -> Result<()> { + let count = self.get_num_unacquired_swapchains(); + if count == 0 { + return Ok(()); + } + + // TODO: try to clear with empty render-pass + let _span = tracing::trace_span!("ClearImages").entered(); + let frame = &mut self.frames[self.current_frame]; + let mut encoder = frame.command_pool.encoder()?; + encoder.begin_debug_label("ClearImages"); + + let mut images = Vec::with_capacity(count); + for (_window_id, surface_swapchain) in &mut self.surfaces { + if !surface_swapchain.is_acquired() { + let sem = encoder.request_semaphore()?; + submission_group.wait(sem, PipelineStageFlags::TRANSFER); + if let Some(img) = surface_swapchain.acquire_next_image(0, sem)? { + images.push((img.image, surface_swapchain.clear_value())); + } + } + } + + let subrange = vk::ImageSubresourceRange::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .layer_count(vk::REMAINING_ARRAY_LAYERS) + .level_count(vk::REMAINING_MIP_LEVELS) + .build(); + + let barriers = images + .iter() + .map(|(image, _)| { + vk::ImageMemoryBarrier::builder() + .src_access_mask(vk::AccessFlags::empty()) + .dst_access_mask(vk::AccessFlags::TRANSFER_WRITE) + .old_layout(vk::ImageLayout::UNDEFINED) + .new_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL) + .subresource_range(subrange) + .image(*image) + .build() + }) + .collect::>(); + unsafe { + encoder.pipeline_barrier( + vk::PipelineStageFlags::TOP_OF_PIPE, + vk::PipelineStageFlags::TRANSFER, + &[], + &[], + &barriers, + ); + } + + for (image, clear_color) in &images { + unsafe { + encoder.clear_color_image( + *image, + vk::ImageLayout::TRANSFER_DST_OPTIMAL, + clear_color, + &[subrange], + ) + } + } + + let barriers = images + .iter() + .map(|(image, _)| { + vk::ImageMemoryBarrier::builder() + .src_access_mask(vk::AccessFlags::TRANSFER_WRITE) + .dst_access_mask(vk::AccessFlags::empty()) + .old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL) + .new_layout(vk::ImageLayout::PRESENT_SRC_KHR) + .subresource_range(subrange) + .image(*image) + .build() + }) + .collect::>(); + unsafe { + encoder.pipeline_barrier( + vk::PipelineStageFlags::TRANSFER, + vk::PipelineStageFlags::BOTTOM_OF_PIPE, + &[], + &[], + &barriers, + ); + } + + encoder.submit(submission_group)?; + Ok(()) + } + + fn end_frame(&mut self, mut submission_group: SubmissionGroup) -> Result<()> { + let _span = tracing::trace_span!("EndFrame").entered(); + + self.clear_unacquired_swapchain_images(&mut submission_group)?; + + let acquired_swapchains = self.get_num_acquired_swapchains(); + let frame = &self.frames[self.current_frame]; + + unsafe { + self.device + .reset_fences(&[self.frames[self.current_frame].finished_fence])?; + } + + submission_group.flush_queue(); + if acquired_swapchains == 0 { + submission_group.submit(&self.device, frame.finished_fence)?; + } else { + submission_group + .signal(frame.finished_semaphore) + .submit(&self.device, frame.finished_fence)?; + + self.present_acquired_swapchain_images(&[frame.finished_semaphore])?; + } + + let next_frame = self.current_frame; + self.current_frame = next_frame + 1; + if self.current_frame >= self.frames.len() { + self.current_frame = 0; + } + Ok(()) + } + + fn run_render_system( + mut renderer: ResMut<'_, Self>, + mut windows: ResMut<'_, Windows>, + src_graph: Res<'_, RenderGraph>, + draw_phases: Res<'_, DrawPhases>, + ) { + renderer.reconfigure_swapchains(&mut windows); + // TODO: maybe graph needs to consider updated swapchain format & dimensions? + + renderer.graph.update(&src_graph); + + let mut submission_group = renderer.begin_frame().unwrap(); + renderer + .render_frame(&mut submission_group, &src_graph, &draw_phases) + .unwrap(); + renderer.end_frame(submission_group).unwrap(); + } +} + +impl ModuleWithOutput for AshRenderer { + type Output<'l> = &'l mut Self; + + fn install_modules(&self, res: &mut Resources) { + res.install(RenderModule); + } + + fn install_resources(self, res: &mut Resources) -> &mut Self { + let resource_id = res.insert(self); + res.get_mut_id(resource_id).unwrap() + } + + fn install_systems(schedule: &mut Schedule) { + schedule + .add_system(Self::run_render_system) + .into_phase(RenderSystemPhase::Render); + } +} + +pub struct AshRendererBuilder { + flags: AshRendererFlags, + window: Option, +} + +impl AshRendererBuilder { + #[inline] + pub const fn new() -> Self { + Self { + flags: AshRendererFlags::DEBUG, + window: None, + } + } + + #[inline] + pub const fn with_flags(mut self, flags: AshRendererFlags) -> Self { + self.flags = flags; + self + } + + /// # Unsafe + /// Raw Window Handle must be a valid object to create a surface + /// upon and must remain valid for the lifetime of the surface. + #[inline] + pub unsafe fn with_window(mut self, window_id: WindowId) -> Self { + self.window = Some(window_id); + self + } + + pub fn install(self, res: &mut Resources) -> Result<&mut AshRenderer> { + let renderer = if let Some(window_id) = self.window { + let windows = res.borrow_res::().unwrap(); + // TODO: make not dependent on descriptor. + // add size-method to RawWindow + let descriptor = &windows[window_id]; + let raw_window_handles = res.borrow_res::().unwrap(); + let handle = raw_window_handles + .get(window_id) + .and_then(|h| h.upgrade()) + .ok_or(Error::WindowNotAvailable)?; + AshRenderer::for_window(self.flags, window_id, descriptor, handle.as_ref())? + } else { + AshRenderer::new(self.flags)? + }; + Ok(res.install(renderer)) + } +} diff --git a/crates/render-ash/src/resources.rs b/crates/render-ash/src/resources.rs new file mode 100644 index 0000000..21d295f --- /dev/null +++ b/crates/render-ash/src/resources.rs @@ -0,0 +1,409 @@ +use ash::vk; +use pulz_render::{ + backend::GpuResource, + buffer::Buffer, + pipeline::{ + BindGroupLayout, ComputePipeline, GraphicsPipeline, PipelineLayout, RayTracingPipeline, + }, + shader::ShaderModule, + texture::Texture, +}; +use slotmap::SlotMap; + +use crate::{ + convert::{CreateInfoConverter, VkInto}, + device::AshDevice, + shader::compie_into_spv, + Result, +}; + +pub trait AshGpuResource: GpuResource { + type Raw; + + unsafe fn create( + device: &AshDevice, + res: &AshResources, + descriptor: &Self::Descriptor<'_>, + ) -> Result; + unsafe fn create_many( + device: &AshDevice, + res: &AshResources, + descriptors: &[Self::Descriptor<'_>], + ) -> Result> { + descriptors + .iter() + .map(|d| Self::create(device, res, d)) + .collect() + } + unsafe fn destroy(device: &AshDevice, raw: Self::Raw); +} + +impl AshGpuResource for Buffer { + type Raw = vk::Buffer; + + unsafe fn create( + device: &AshDevice, + _res: &AshResources, + descr: &Self::Descriptor<'_>, + ) -> Result { + let create_info: vk::BufferCreateInfo = descr.vk_into(); + let raw = device.create_buffer(&create_info, None)?; + Ok(raw) + } + + unsafe fn destroy(device: &AshDevice, raw: Self::Raw) { + if raw != vk::Buffer::null() { + device.destroy_buffer(raw, None); + } + } +} + +impl AshGpuResource for Texture { + type Raw = (vk::Image, vk::ImageView); + + unsafe fn create( + device: &AshDevice, + _res: &AshResources, + descr: &Self::Descriptor<'_>, + ) -> Result { + let img_create_info: vk::ImageCreateInfo = descr.vk_into(); + let img = device.create(&img_create_info)?; + let view_create_info: vk::ImageViewCreateInfo = descr.vk_into(); + let view = device.create(&view_create_info)?; + Ok((img.take(), view.take())) + } + + unsafe fn destroy(device: &AshDevice, (img, view): Self::Raw) { + if view != vk::ImageView::null() { + device.destroy(view); + } + if img != vk::Image::null() { + device.destroy(img); + } + } +} + +impl AshGpuResource for ShaderModule { + type Raw = vk::ShaderModule; + unsafe fn create( + device: &AshDevice, + _res: &AshResources, + descr: &Self::Descriptor<'_>, + ) -> Result { + let code = compie_into_spv(&descr.source)?; + let create_info = vk::ShaderModuleCreateInfo::builder().code(&code).build(); + let raw = device.create(&create_info)?; + if let Some(label) = descr.label { + device.object_name(raw.raw(), label); + } + Ok(raw.take()) + } + unsafe fn destroy(device: &AshDevice, raw: Self::Raw) { + if raw != vk::ShaderModule::null() { + device.destroy(raw); + } + } +} +impl AshGpuResource for BindGroupLayout { + type Raw = vk::DescriptorSetLayout; + unsafe fn create( + device: &AshDevice, + _res: &AshResources, + descr: &Self::Descriptor<'_>, + ) -> Result { + let mut conv = CreateInfoConverter::new(); + let create_info = conv.bind_group_layout(descr); + let raw = device.create(create_info)?; + if let Some(label) = descr.label { + device.object_name(raw.raw(), label); + } + Ok(raw.take()) + } + unsafe fn destroy(device: &AshDevice, raw: Self::Raw) { + if raw != vk::DescriptorSetLayout::null() { + device.destroy(raw); + } + } +} +impl AshGpuResource for PipelineLayout { + type Raw = vk::PipelineLayout; + + unsafe fn create( + device: &AshDevice, + res: &AshResources, + descr: &Self::Descriptor<'_>, + ) -> Result { + let mut conv = CreateInfoConverter::new(); + let create_info = conv.pipeline_layout(res, descr); + let raw = device.create(create_info)?; + if let Some(label) = descr.label { + device.object_name(raw.raw(), label); + } + Ok(raw.take()) + } + unsafe fn destroy(device: &AshDevice, raw: Self::Raw) { + if raw != vk::PipelineLayout::null() { + device.destroy(raw); + } + } +} +impl AshGpuResource for GraphicsPipeline { + type Raw = vk::Pipeline; + + unsafe fn create_many( + device: &AshDevice, + res: &AshResources, + descrs: &[Self::Descriptor<'_>], + ) -> Result> { + let mut conv = CreateInfoConverter::new(); + let create_infos = conv.graphics_pipeline_descriptor(res, descrs); + match device.create_graphics_pipelines(vk::PipelineCache::null(), create_infos, None) { + Ok(raw) => { + let raw = device.hold(raw); + if let Ok(debug_utils) = device.instance().ext_debug_utils() { + for (i, d) in descrs.iter().enumerate() { + if let Some(label) = d.label { + debug_utils.object_name(device.handle(), raw[i], label); + } + } + } + Ok(raw.take()) + } + Err((pipelines, e)) => { + device.destroy(pipelines); + Err(e.into()) + } + } + } + + unsafe fn create( + device: &AshDevice, + res: &AshResources, + descr: &Self::Descriptor<'_>, + ) -> Result { + let raw = Self::create_many(device, res, std::slice::from_ref(descr))?; + Ok(raw[0]) + } + + unsafe fn destroy(device: &AshDevice, raw: Self::Raw) { + if raw != vk::Pipeline::null() { + device.destroy_pipeline(raw, None); + } + } +} +impl AshGpuResource for ComputePipeline { + type Raw = vk::Pipeline; + + unsafe fn create_many( + device: &AshDevice, + res: &AshResources, + descrs: &[Self::Descriptor<'_>], + ) -> Result> { + let mut conv = CreateInfoConverter::new(); + let create_infos = conv.compute_pipeline_descriptor(res, descrs); + match device.create_compute_pipelines(vk::PipelineCache::null(), create_infos, None) { + Ok(raw) => { + let raw = device.hold(raw); + if let Ok(debug_utils) = device.instance().ext_debug_utils() { + for (i, d) in descrs.iter().enumerate() { + if let Some(label) = d.label { + debug_utils.object_name(device.handle(), raw[i], label); + } + } + } + Ok(raw.take()) + } + Err((pipelines, e)) => { + device.destroy(pipelines); + Err(e.into()) + } + } + } + + unsafe fn create( + device: &AshDevice, + res: &AshResources, + descr: &Self::Descriptor<'_>, + ) -> Result { + let raw = Self::create_many(device, res, std::slice::from_ref(descr))?; + Ok(raw[0]) + } + + unsafe fn destroy(device: &AshDevice, raw: Self::Raw) { + if raw != vk::Pipeline::null() { + device.destroy_pipeline(raw, None); + } + } +} +impl AshGpuResource for RayTracingPipeline { + type Raw = vk::Pipeline; + + unsafe fn create_many( + device: &AshDevice, + res: &AshResources, + descrs: &[Self::Descriptor<'_>], + ) -> Result> { + let ext = device.ext_raytracing_pipeline()?; + let mut conv = CreateInfoConverter::new(); + let create_infos = conv.ray_tracing_pipeline_descriptor(res, descrs); + let raw = ext.create_ray_tracing_pipelines( + vk::DeferredOperationKHR::null(), + vk::PipelineCache::null(), + create_infos, + None, + )?; + let raw = device.hold(raw); + if let Ok(debug_utils) = device.instance().ext_debug_utils() { + for (i, d) in descrs.iter().enumerate() { + if let Some(label) = d.label { + debug_utils.object_name(device.handle(), raw[i], label); + } + } + } + Ok(raw.take()) + } + + unsafe fn create( + device: &AshDevice, + res: &AshResources, + descr: &Self::Descriptor<'_>, + ) -> Result { + let raw = Self::create_many(device, res, std::slice::from_ref(descr))?; + Ok(raw[0]) + } + + unsafe fn destroy(device: &AshDevice, raw: Self::Raw) { + if raw != vk::Pipeline::null() { + device.destroy_pipeline(raw, None); + } + } +} + +macro_rules! define_resources { + ( + $v:vis struct $name:ident { + $( + $vfield:vis $namefield:ident<$keytype:ty, $ashtype:ty> + ),* + $(,)? + } + ) => { + $v struct $name { + $( + $vfield $namefield: ::slotmap::basic::SlotMap<$keytype, $ashtype> + ),* + } + + impl $name { + pub fn new() -> Self { + Self { + $( + $namefield: ::slotmap::basic::SlotMap::with_key(), + )* + } + } + + } + + $( + impl AsRef<::slotmap::basic::SlotMap<$keytype,$ashtype>> for $name { + fn as_ref(&self) -> &::slotmap::basic::SlotMap<$keytype,$ashtype> { + &self.$namefield + } + } + + impl AsMut<::slotmap::basic::SlotMap<$keytype,$ashtype>> for $name { + fn as_mut(&mut self) -> &mut ::slotmap::basic::SlotMap<$keytype,$ashtype> { + &mut self.$namefield + } + } + )* + + impl Drop for AshResources { + fn drop(&mut self) { + fn check_empty(map: &SlotMap) { + if !map.is_empty() { + panic!( + "gpu resources for {} dropped without calling `destroy`", + std::any::type_name::() + ); + } + } + + $( + check_empty(&self.$namefield); + )* + } + } + }; +} + +define_resources! { + pub struct AshResources { + pub buffers, + pub textures, + pub shader_modules, + pub bind_group_layouts, + pub pipeline_layouts, + pub graphics_pipelines, + pub compute_pipelines, + pub ray_tracing_pipelines, + } +} + +impl AshResources { + pub fn create(&mut self, device: &AshDevice, descriptor: &R::Descriptor<'_>) -> Result + where + R: AshGpuResource, + Self: AsMut>, + { + let raw = unsafe { R::create(device, self, descriptor)? }; + let key = self.as_mut().insert(raw); + Ok(key) + } + + pub fn create_many( + &mut self, + device: &AshDevice, + descriptors: &[R::Descriptor<'_>], + ) -> Result> + where + R: AshGpuResource, + Self: AsMut>, + { + let raw = unsafe { R::create_many(device, self, descriptors)? }; + let keys = raw.into_iter().map(|r| self.as_mut().insert(r)).collect(); + Ok(keys) + } + + pub fn destroy(&mut self, device: &AshDevice, key: R) -> bool + where + R: AshGpuResource, + Self: AsMut>, + { + if let Some(raw) = self.as_mut().remove(key) { + unsafe { R::destroy(device, raw) }; + true + } else { + false + } + } + + pub fn destroy_all(&mut self, device: &AshDevice) { + fn destroy_all_in_map(map: &mut SlotMap, device: &AshDevice) { + for (_key, raw) in map.drain() { + unsafe { R::destroy(device, raw) }; + } + } + + // Reverse order! + destroy_all_in_map(&mut self.ray_tracing_pipelines, device); + destroy_all_in_map(&mut self.compute_pipelines, device); + destroy_all_in_map(&mut self.graphics_pipelines, device); + destroy_all_in_map(&mut self.pipeline_layouts, device); + destroy_all_in_map(&mut self.bind_group_layouts, device); + destroy_all_in_map(&mut self.shader_modules, device); + destroy_all_in_map(&mut self.textures, device); + destroy_all_in_map(&mut self.buffers, device); + } +} diff --git a/crates/render-ash/src/shader.rs b/crates/render-ash/src/shader.rs new file mode 100644 index 0000000..f82c696 --- /dev/null +++ b/crates/render-ash/src/shader.rs @@ -0,0 +1,15 @@ +use std::borrow::Cow; + +use pulz_render::shader::ShaderSource; + +use crate::Result; + +pub fn compie_into_spv<'a>(source: &'a ShaderSource<'a>) -> Result> { + match source { + ShaderSource::SpirV(data) => Ok(Cow::Borrowed(data)), + ShaderSource::Glsl(_data) => todo!("implement compile GLSL"), + ShaderSource::Wgsl(_data) => todo!("implement compile WGSL"), + + _ => panic!("unsupported shader source"), + } +} diff --git a/crates/render-ash/src/swapchain.rs b/crates/render-ash/src/swapchain.rs new file mode 100644 index 0000000..86f8a71 --- /dev/null +++ b/crates/render-ash/src/swapchain.rs @@ -0,0 +1,809 @@ +use std::sync::Arc; + +use ash::{extensions::khr, vk}; +use pulz_render::{math::uvec2, texture::Texture}; +use pulz_window::{RawWindow, Size2, WindowId}; +use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; +use slotmap::Key; + +use crate::{ + device::AshDevice, drop_guard::Destroy, instance::AshInstance, AshRenderer, Error, Result, +}; + +pub struct Surface { + instance: Arc, + surface_raw: vk::SurfaceKHR, +} + +impl Drop for Surface { + fn drop(&mut self) { + if self.surface_raw != vk::SurfaceKHR::null() { + let ext_surface = self.instance.ext_surface().unwrap(); + unsafe { + ext_surface.destroy_surface(self.surface_raw, None); + } + } + } +} + +impl Destroy for vk::SurfaceKHR { + type Context = AshInstance; + #[inline] + unsafe fn destroy(self, instance: &AshInstance) { + let ext_surface = instance.ext_surface().unwrap(); + ext_surface.destroy_surface(self, None); + } +} + +macro_rules! check_and_get_extension { + ($self:ident => $ext:ty) => {{ + if !$self.has_instance_extension(<$ext>::name()) { + return Err(Error::ExtensionNotSupported(<$ext>::name())); + } + <$ext>::new($self.entry(), $self) + }}; +} + +impl AshInstance { + #[cfg(all( + unix, + not(target_os = "android"), + not(target_os = "macos"), + not(target_os = "ios") + ))] + unsafe fn create_surface_xlib( + &self, + dpy: *mut vk::Display, + window: vk::Window, + ) -> Result { + let functions = check_and_get_extension!(self => khr::XlibSurface); + let surface = functions.create_xlib_surface( + &vk::XlibSurfaceCreateInfoKHR::builder() + .dpy(dpy) + .window(window), + None, + )?; + Ok(surface) + } + + #[cfg(all( + unix, + not(target_os = "android"), + not(target_os = "macos"), + not(target_os = "ios") + ))] + unsafe fn create_surface_xcb( + &self, + connection: *mut vk::xcb_connection_t, + window: vk::xcb_window_t, + ) -> Result { + let functions = check_and_get_extension!(self => khr::XcbSurface); + let surface = functions.create_xcb_surface( + &vk::XcbSurfaceCreateInfoKHR::builder() + .connection(connection) + .window(window), + None, + )?; + Ok(surface) + } + + #[cfg(all( + unix, + not(target_os = "android"), + not(target_os = "macos"), + not(target_os = "ios") + ))] + unsafe fn create_surface_wayland( + &self, + display: *mut vk::wl_display, + surface: *mut vk::wl_surface, + ) -> Result { + let functions = check_and_get_extension!(self => khr::WaylandSurface); + let surface = functions.create_wayland_surface( + &vk::WaylandSurfaceCreateInfoKHR::builder() + .display(display) + .surface(surface), + None, + )?; + Ok(surface) + } + + #[cfg(target_os = "android")] + unsafe fn create_surface_android( + &self, + window: *mut vk::ANativeWindow, + ) -> Result { + let functions = check_and_get_extension!(self => khr::AndroidSurface); + let surface = functions.create_android_surface( + &vk::AndroidSurfaceCreateInfoKHR::builder() + .window(window) + .build(), + None, + )?; + Ok(surface) + } + + #[cfg(target_os = "windows")] + unsafe fn create_surface_win32( + &self, + hinstance: vk::HINSTANCE, + hwnd: vk::HWND, + ) -> Result { + let functions = check_and_get_extension!(self => khr::Win32Surface); + let surface = functions.create_win32_surface( + &vk::Win32SurfaceCreateInfoKHR::builder() + .hinstance(hinstance) + .hwnd(hwnd) + .build(), + None, + )?; + Ok(surface) + } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + unsafe fn create_surface_metal( + &self, + layer: *const vk::CAMetalLayer, + ) -> Result { + use ash::extensions::ext; + let functions = check_and_get_extension!(self => ext::MetalSurface); + let surface = functions.create_metal_surface( + &vk::MetalSurfaceCreateInfoEXT::builder() + .layer(layer) + .build(), + None, + )?; + Ok(surface) + } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + unsafe fn create_surface_apple(&self, view: *mut c_void) -> Result { + use ash::extensions::ext; + use core_graphics_types::{base::CGFloat, geometry::CGRect}; + use objc::{ + class, msg_send, + runtime::{Object, BOOL, YES}, + sel, sel_impl, + }; + + // early check extension + if !self.has_instance_extension(ext::MetalSurface::name()) { + return Err(Error::ExtensionNotSupported(ext::MetalSurface::name())); + } + + let layer = unsafe { + let view = view as *mut Object; + let existing: *mut Object = msg_send![view, layer]; + let layer_class = class!(CAMetalLayer); + + if !existing.is_null() && msg_send![existing, isKindOfClass: layer_class] == YES { + existing + } else { + let layer: *mut Object = msg_send![layer_class, new]; + let _: () = msg_send![view, setLayer: layer]; + let bounds: CGRect = msg_send![view, bounds]; + let () = msg_send![layer, setBounds: bounds]; + + let window: *mut Object = msg_send![view, window]; + if !window.is_null() { + let scale_factor: CGFloat = msg_send![window, backingScaleFactor]; + let () = msg_send![layer, setContentsScale: scale_factor]; + } + layer + } + }; + + self.create_surface_metal(layer as *mut _) + } + + /// SAFETY: display and window handles must be valid for the complete lifetime of surface + unsafe fn create_surface_raw( + &self, + display_handle: RawDisplayHandle, + window_handle: RawWindowHandle, + ) -> Result { + // check for surface-extension + self.ext_surface()?; + + match (display_handle, window_handle) { + #[cfg(all( + unix, + not(target_os = "android"), + not(target_os = "macos"), + not(target_os = "ios") + ))] + (RawDisplayHandle::Xlib(d), RawWindowHandle::Xlib(w)) => { + self.create_surface_xlib(d.display as *mut _, w.window) + } + #[cfg(all( + unix, + not(target_os = "android"), + not(target_os = "macos"), + not(target_os = "ios") + ))] + (RawDisplayHandle::Xcb(d), RawWindowHandle::Xcb(w)) => { + self.create_surface_xcb(d.connection, w.window) + } + #[cfg(all( + unix, + not(target_os = "android"), + not(target_os = "macos"), + not(target_os = "ios") + ))] + (RawDisplayHandle::Wayland(d), RawWindowHandle::Wayland(w)) => { + self.create_surface_wayland(d.display, w.surface) + } + #[cfg(target_os = "android")] + (RawDisplayHandle::Android(_), RawWindowHandle::Android(w)) => { + self.create_surface_android(w.a_native_window)? + } + #[cfg(target_os = "windows")] + (RawDisplayHandle::Windows(_), RawWindowHandle::Windows(w)) => { + self.create_surface_win32(w.hinstance, w.hwnd)? + } + #[cfg(target_os = "macos")] + (RawDisplayHandle::AppKit(_), RawWindowHandle::AppKit(w)) => { + self.create_surface_apple(w.ns_view)? + } + #[cfg(target_os = "ios")] + (RawDisplayHandle::UiKit(_), RawWindowHandle::UiKit(w)) => { + self.create_surface_apple(w.ui_view)? + } + + _ => Err(Error::UnsupportedWindowSystem), + } + } + + /// SAFETY: display and window handles must be valid for the complete lifetime of surface + pub(crate) fn new_surface(self: &Arc, window: &dyn RawWindow) -> Result { + let surface_raw = unsafe { + self.create_surface_raw(window.raw_display_handle(), window.raw_window_handle())? + }; + Ok(Surface { + instance: self.clone(), + surface_raw, + }) + } +} + +impl Surface { + pub fn get_physical_device_surface_support( + &self, + physical_device: vk::PhysicalDevice, + queue_family_index: u32, + ) -> bool { + let Ok(ext_surface) = self.instance.ext_surface() else { + return false; + }; + + unsafe { + ext_surface + .get_physical_device_surface_support( + physical_device, + queue_family_index, + self.surface_raw, + ) + .unwrap_or(false) + } + } + + pub fn query_swapchain_support( + &self, + physical_device: vk::PhysicalDevice, + ) -> Option { + let ext_surface = self.instance.ext_surface().ok()?; + query_swapchain_support(ext_surface, self.surface_raw, physical_device) + } +} + +fn query_swapchain_support( + ext_surface: &khr::Surface, + surface_raw: vk::SurfaceKHR, + physical_device: vk::PhysicalDevice, +) -> Option { + unsafe { + let capabilities = ext_surface + .get_physical_device_surface_capabilities(physical_device, surface_raw) + .ok()?; + let formats = ext_surface + .get_physical_device_surface_formats(physical_device, surface_raw) + .ok()?; + let present_modes = ext_surface + .get_physical_device_surface_present_modes(physical_device, surface_raw) + .ok()?; + if formats.is_empty() || present_modes.is_empty() { + None + } else { + Some(SwapchainSupportDetail { + capabilities, + formats, + present_modes, + }) + } + } +} + +pub struct SwapchainSupportDetail { + capabilities: vk::SurfaceCapabilitiesKHR, + formats: Vec, + present_modes: Vec, +} + +impl SwapchainSupportDetail { + pub fn preferred_format(&self) -> vk::SurfaceFormatKHR { + for available_format in &self.formats { + if available_format.format == vk::Format::B8G8R8A8_SRGB + && available_format.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR + { + return *available_format; + } + } + + // return the first format from the list + self.formats.first().cloned().unwrap() + } + pub fn preferred_present_mode(&self, suggested: vk::PresentModeKHR) -> vk::PresentModeKHR { + if self.present_modes.contains(&suggested) { + return suggested; + } + if suggested == vk::PresentModeKHR::FIFO || suggested == vk::PresentModeKHR::FIFO_RELAXED { + // find any FIFO Mode + for &present_mode in self.present_modes.iter() { + if present_mode == vk::PresentModeKHR::FIFO + || present_mode == vk::PresentModeKHR::FIFO_RELAXED + { + return present_mode; + } + } + } + if suggested != vk::PresentModeKHR::IMMEDIATE { + // find any VSync Mode (not immediate) + for &present_mode in self.present_modes.iter() { + if present_mode != vk::PresentModeKHR::IMMEDIATE { + return present_mode; + } + } + } + self.present_modes.first().copied().unwrap() + } +} + +pub struct SurfaceSwapchain { + device: Arc, + surface_raw: vk::SurfaceKHR, + swapchain_raw: vk::SwapchainKHR, + size: Size2, + image_count: u32, + surface_format: vk::SurfaceFormatKHR, + present_mode: vk::PresentModeKHR, + image_usage: vk::ImageUsageFlags, + images: Vec, + //image_views: Vec, + texture_id: Texture, + acquired_image: u32, + retired_swapchains: Vec, +} + +impl AshDevice { + pub fn new_swapchain( + self: &Arc, + mut surface: Surface, + suggested_size: Size2, + suggested_image_count: u32, + suggested_present_mode: vk::PresentModeKHR, + ) -> Result { + assert_eq!(self.instance().handle(), surface.instance.handle()); + let mut swapchain = SurfaceSwapchain { + device: self.clone(), + surface_raw: std::mem::take(&mut surface.surface_raw), + swapchain_raw: vk::SwapchainKHR::null(), + size: suggested_size, + image_count: suggested_image_count, + surface_format: Default::default(), + present_mode: suggested_present_mode, + // TODO: custom usage + image_usage: vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::TRANSFER_DST, + images: Vec::new(), + //image_views: Vec::new(), + texture_id: Texture::null(), + acquired_image: !0, + retired_swapchains: Vec::new(), + }; + swapchain.configure()?; + Ok(swapchain) + } +} + +impl SurfaceSwapchain { + #[inline] + pub fn size(&self) -> Size2 { + self.size + } + + #[inline] + pub fn format(&self) -> vk::Format { + self.surface_format.format + } + + #[inline] + pub fn present_mode(&self) -> vk::PresentModeKHR { + self.present_mode + } + + #[inline] + pub fn image_usage(&self) -> vk::ImageUsageFlags { + self.image_usage + } + + #[inline] + pub fn image_count(&self) -> usize { + self.images.len() + } + + pub fn clear_value(&self) -> vk::ClearColorValue { + match self.surface_format.format { + vk::Format::R8_SINT + | vk::Format::R8G8_SINT + | vk::Format::R8G8B8_SINT + | vk::Format::B8G8R8_SINT + | vk::Format::R8G8B8A8_SINT + | vk::Format::B8G8R8A8_SINT + | vk::Format::A8B8G8R8_SINT_PACK32 + | vk::Format::A2R10G10B10_SINT_PACK32 + | vk::Format::A2B10G10R10_SINT_PACK32 + | vk::Format::R16_SINT + | vk::Format::R16G16_SINT + | vk::Format::R16G16B16_SINT + | vk::Format::R16G16B16A16_SINT + | vk::Format::R32_SINT + | vk::Format::R32G32_SINT + | vk::Format::R32G32B32_SINT + | vk::Format::R32G32B32A32_SINT + | vk::Format::R64_SINT + | vk::Format::R64G64_SINT + | vk::Format::R64G64B64_SINT + | vk::Format::R64G64B64A64_SINT => vk::ClearColorValue { + int32: [i32::MIN, i32::MIN, i32::MIN, i32::MAX], + }, + + vk::Format::R8_UINT + | vk::Format::R8G8_UINT + | vk::Format::R8G8B8_UINT + | vk::Format::B8G8R8_UINT + | vk::Format::R8G8B8A8_UINT + | vk::Format::B8G8R8A8_UINT + | vk::Format::A8B8G8R8_UINT_PACK32 + | vk::Format::A2R10G10B10_UINT_PACK32 + | vk::Format::A2B10G10R10_UINT_PACK32 + | vk::Format::R16_UINT + | vk::Format::R16G16_UINT + | vk::Format::R16G16B16_UINT + | vk::Format::R16G16B16A16_UINT + | vk::Format::R32_UINT + | vk::Format::R32G32_UINT + | vk::Format::R32G32B32_UINT + | vk::Format::R32G32B32A32_UINT + | vk::Format::R64_UINT + | vk::Format::R64G64_UINT + | vk::Format::R64G64B64_UINT + | vk::Format::R64G64B64A64_UINT => vk::ClearColorValue { + uint32: [0, 0, 0, u32::MAX], + }, + + _ => vk::ClearColorValue { + float32: [0.0, 0.0, 0.0, 1.0], + }, + } + } + + #[inline] + pub const fn is_acquired(&self) -> bool { + self.acquired_image != !0 + } + + pub fn configure_with( + &mut self, + suggested_size: Size2, + suggested_present_mode: vk::PresentModeKHR, + ) -> Result<()> { + self.size = suggested_size; + self.present_mode = suggested_present_mode; + self.configure() + } + + pub fn configure(&mut self) -> Result<()> { + // TODO: also reconfigure on resize, and when presenting results in `Outdated/Lost` + // TODO: pass swapchain format to graph + + let ext_swapchain = self.device.ext_swapchain()?; + let ext_surface = self.device.instance().ext_surface()?; + + let swapchain_support_info = + query_swapchain_support(ext_surface, self.surface_raw, self.device.physical_device()) + .ok_or(Error::NoSwapchainSupport)?; + + if !swapchain_support_info + .capabilities + .supported_usage_flags + .contains(self.image_usage) + { + return Err(Error::SwapchainUsageNotSupported { + requested: self.image_usage, + supported: swapchain_support_info.capabilities.supported_usage_flags, + }); + } + + self.surface_format = swapchain_support_info.preferred_format(); + + self.present_mode = swapchain_support_info.preferred_present_mode(self.present_mode); + + let vk::Extent2D { width, height } = swapchain_support_info.capabilities.current_extent; + if width != u32::MAX { + self.size = uvec2(width, height) + } else { + // clamp size + if self.size.x < swapchain_support_info.capabilities.min_image_extent.width { + self.size.x = swapchain_support_info.capabilities.min_image_extent.width; + } else if self.size.x > swapchain_support_info.capabilities.max_image_extent.width { + self.size.x = swapchain_support_info.capabilities.max_image_extent.width; + } + if self.size.y < swapchain_support_info.capabilities.min_image_extent.height { + self.size.y = swapchain_support_info.capabilities.min_image_extent.height; + } else if self.size.y > swapchain_support_info.capabilities.max_image_extent.height { + self.size.y = swapchain_support_info.capabilities.max_image_extent.height; + } + } + + if self.image_count < swapchain_support_info.capabilities.min_image_count { + self.image_count = swapchain_support_info.capabilities.min_image_count; + } + if swapchain_support_info.capabilities.max_image_count > 0 + && self.image_count > swapchain_support_info.capabilities.max_image_count + { + self.image_count = swapchain_support_info.capabilities.max_image_count; + } + + let shared_queue_family_indices = [ + self.device.queues().graphics_family, + self.device.queues().present_family, + ]; + let old_swapchain = self.swapchain_raw; + if old_swapchain != vk::SwapchainKHR::null() { + // swapchain is retired, even if `create_swapchain` fails + self.swapchain_raw = vk::SwapchainKHR::null(); + self.retired_swapchains.push(old_swapchain); + } + self.swapchain_raw = unsafe { + ext_swapchain.create_swapchain( + &vk::SwapchainCreateInfoKHR::builder() + .surface(self.surface_raw) + .min_image_count(self.image_count) + .image_format(self.surface_format.format) + .image_color_space(self.surface_format.color_space) + .image_extent(vk::Extent2D { + width: self.size.x, + height: self.size.y, + }) + .image_array_layers(1) + .image_usage(self.image_usage) + .image_sharing_mode(vk::SharingMode::EXCLUSIVE) + .queue_family_indices( + if shared_queue_family_indices[0] != shared_queue_family_indices[1] { + &shared_queue_family_indices + } else { + &[] + }, + ) + .pre_transform(swapchain_support_info.capabilities.current_transform) + .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) + .present_mode(self.present_mode) + .clipped(true) + .old_swapchain(old_swapchain) + .image_array_layers(1) + .build(), + None, + )? + }; + + self.images = unsafe { ext_swapchain.get_swapchain_images(self.swapchain_raw)? }; + + Ok(()) + } + + pub(crate) fn acquire_next_image( + &mut self, + timeout: u64, + signal_semaphore: vk::Semaphore, + ) -> Result> { + let device = self.device.clone(); + let ext_swapchain = device.ext_swapchain()?; + + if self.is_acquired() { + return Err(Error::SwapchainImageAlreadyAcquired); + } + + // TODO: better sync mechanism + + let result = unsafe { + match ext_swapchain.acquire_next_image( + self.swapchain_raw, + timeout, + signal_semaphore, + vk::Fence::null(), + ) { + Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => { + // re-configure and re-acquire + self.configure()?; + ext_swapchain.acquire_next_image( + self.swapchain_raw, + timeout, + signal_semaphore, + vk::Fence::null(), + ) + } + + // TODO: handle Surface-lost + // destroy old surface & swapchain, + // re-create surface and swapchain, + // and acquire again + other => other, + } + }; + + let (index, suboptimal) = match result { + Ok(r) => r, + Err(vk::Result::NOT_READY) | Err(vk::Result::TIMEOUT) => { + return Ok(None); + } + Err(vk::Result::ERROR_SURFACE_LOST_KHR) => return Err(Error::SurfaceLost), + Err(e) => return Err(e.into()), + }; + self.acquired_image = index; + Ok(Some(AcquiredSwapchainImage { + index, + image: self.images[index as usize], + //image_view: self.image_views[index as usize], + suboptimal, + })) + } +} + +impl Drop for SurfaceSwapchain { + fn drop(&mut self) { + // for image_view in self.image_views.drain(..) { + // unsafe { + // self.device.destroy_image_view(image_view, None); + // } + // } + // don't destroy images obtained from get_swapchain_images. They are destroyed together with the swapchain object. + self.images.clear(); + if self.swapchain_raw != vk::SwapchainKHR::null() { + let ext_swapchain = self.device.ext_swapchain().unwrap(); + unsafe { + ext_swapchain.destroy_swapchain(self.swapchain_raw, None); + } + } + + if self.surface_raw != vk::SurfaceKHR::null() { + let ext_surface = self.device.instance().ext_surface().unwrap(); + unsafe { + ext_surface.destroy_surface(self.surface_raw, None); + } + } + } +} + +impl AshRenderer { + pub(crate) fn acquire_swapchain_image( + &mut self, + window_id: WindowId, + timeout: u64, + signal_semaphore: vk::Semaphore, + ) -> Result> { + let _ = tracing::trace_span!("AquireImage").entered(); + let surface_swapchain = self.surfaces.get_mut(window_id).expect("swapchain"); + // TODO: create surface & swapchain on demand? + Ok(surface_swapchain + .acquire_next_image(timeout, signal_semaphore)? + .map(|_swapchain_image| { + //TODO: associate texture + todo!() + })) + } + + pub(crate) fn get_num_acquired_swapchains(&self) -> usize { + self.surfaces + .iter() + .filter(|(_, s)| s.is_acquired()) + .count() + } + + pub(crate) fn get_num_unacquired_swapchains(&self) -> usize { + self.surfaces + .iter() + .filter(|(_, s)| !s.is_acquired()) + .count() + } + + pub(crate) fn present_acquired_swapchain_images( + &mut self, + wait_semaphores: &[vk::Semaphore], + ) -> Result<()> { + let _ = tracing::trace_span!("Present").entered(); + let ext_swapchain = self.device.ext_swapchain()?; + let mut acquired_surface_swapchains: Vec<_> = self + .surfaces + .iter_mut() + .map(|(_, s)| s) + .filter(|s| s.is_acquired()) + .collect(); + let swapchains: Vec<_> = acquired_surface_swapchains + .iter() + .map(|s| s.swapchain_raw) + .collect(); + let image_indices: Vec<_> = acquired_surface_swapchains + .iter() + .map(|s| s.acquired_image) + .collect(); + let mut results = Vec::new(); + results.resize(acquired_surface_swapchains.len(), vk::Result::SUCCESS); + let result = unsafe { + ext_swapchain.queue_present( + self.device.queues().present, + &vk::PresentInfoKHR::builder() + .wait_semaphores(wait_semaphores) + .swapchains(&swapchains) + .image_indices(&image_indices) + .results(&mut results) + .build(), + ) + }; + + // reset image index + for surface_swapchain in &mut acquired_surface_swapchains { + surface_swapchain.acquired_image = !0; + } + + match result { + Ok(_) + | Err(vk::Result::SUBOPTIMAL_KHR) + | Err(vk::Result::ERROR_OUT_OF_DATE_KHR) + | Err(vk::Result::ERROR_SURFACE_LOST_KHR) => (), + Err(e) => { + self.retire_swapchains(); + return Err(e.into()); + } + } + + for (result, surface_swapchain) in results + .into_iter() + .zip(acquired_surface_swapchains.into_iter()) + { + if result == vk::Result::SUBOPTIMAL_KHR || result == vk::Result::ERROR_OUT_OF_DATE_KHR { + surface_swapchain.configure()?; + } else if result == vk::Result::ERROR_SURFACE_LOST_KHR { + // TODO: re-create surface and re-configure swapchain + } + } + + self.retire_swapchains(); + + Ok(()) + } + + fn retire_swapchains(&mut self) { + let frame = &mut self.frames[self.current_frame]; + for (_, surface_swapchain) in &mut self.surfaces { + frame + .retired_swapchains + .append(&mut surface_swapchain.retired_swapchains) + } + } +} + +pub struct AcquiredSwapchainImage { + index: u32, + pub image: vk::Image, + //pub image_view: vk::ImageView, + pub suboptimal: bool, +} diff --git a/crates/render-ash/src/utils.rs b/crates/render-ash/src/utils.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/crates/render-ash/src/utils.rs @@ -0,0 +1 @@ + From 27ea491f755edfe996260e95a22e0515312efdb9 Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Mon, 9 Jan 2023 23:22:55 +0100 Subject: [PATCH 06/36] fixes for android --- crates/assets-loader/Cargo.toml | 1 - crates/assets-loader/src/lib.rs | 16 +++++- crates/assets-loader/src/path.rs | 20 ++++++++ crates/assets-loader/src/platform/android.rs | 54 ++++++++++++++------ crates/assets-loader/src/platform/mod.rs | 13 ----- crates/render-ash/src/swapchain.rs | 4 +- 6 files changed, 74 insertions(+), 34 deletions(-) diff --git a/crates/assets-loader/Cargo.toml b/crates/assets-loader/Cargo.toml index e43bf53..ae3ddc8 100644 --- a/crates/assets-loader/Cargo.toml +++ b/crates/assets-loader/Cargo.toml @@ -22,4 +22,3 @@ web-sys = { workspace = true, features = [ 'Request', 'Response', "Window", ]} [target.'cfg(target_os = "android")'.dependencies] ndk = { workspace = true } -ndk-glue = { workspace = true } diff --git a/crates/assets-loader/src/lib.rs b/crates/assets-loader/src/lib.rs index cb5c585..a4826ec 100644 --- a/crates/assets-loader/src/lib.rs +++ b/crates/assets-loader/src/lib.rs @@ -109,9 +109,23 @@ pub struct AssetServer { } impl AssetServer { + #[cfg(not(target_os = "android"))] pub fn new() -> Self { - Self::with(platform::default_platform_io()) + #[cfg(not(any(target_os = "android", target_arch = "wasm32")))] + let io = platform::fs::FileSystemAssetLoaderIo::new(); + + #[cfg(target_arch = "wasm32")] + let io = platform::wasm::WasmFetchAssetLoaderIo::new(); + + Self::with(io) } + + #[cfg(target_os = "android")] + pub fn with_asset_manager(manager: ::ndk::asset::AssetManager) -> Self { + let io = platform::android::AndroidAssetLoaderIo::with_asset_manager(manager); + Self::with(io) + } + #[inline] pub fn with(io: impl AssetOpen + 'static) -> Self { Self { io: Box::new(io) } diff --git a/crates/assets-loader/src/path.rs b/crates/assets-loader/src/path.rs index 340096d..19e1c4d 100644 --- a/crates/assets-loader/src/path.rs +++ b/crates/assets-loader/src/path.rs @@ -25,10 +25,12 @@ impl AssetPathBuf { Self(String::with_capacity(capacity)) } + #[inline] pub fn as_path(&self) -> &AssetPath { self } + #[inline] pub fn push>(&mut self, path: P) { self._push(path.as_ref()) } @@ -85,6 +87,7 @@ impl AssetPathBuf { true } + #[inline] pub fn set_file_name>(&mut self, file_name: S) { self._set_file_name(file_name.as_ref()) } @@ -97,6 +100,7 @@ impl AssetPathBuf { self.push(file_name); } + #[inline] pub fn set_fragment>(&mut self, fragment: S) { self._set_fragment(fragment.as_ref()) } @@ -192,10 +196,12 @@ impl Default for AssetPathBuf { } impl AssetPath { + #[inline] fn is_seperator(c: char) -> bool { c == '/' || c == '\\' } + #[inline] pub fn new + ?Sized>(s: &S) -> &Self { let s: *const str = s.as_ref(); unsafe { &*(s as *const Self) } @@ -206,6 +212,16 @@ impl AssetPath { &self.0 } + #[inline] + pub fn len(&self) -> usize { + self.0.len() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + pub fn to_path_buf(&self) -> AssetPathBuf { AssetPathBuf::from(self.0.to_string()) } @@ -289,21 +305,25 @@ impl AssetPath { } } + #[inline] pub fn file_stem(&self) -> Option<&str> { let (_, stem, _, _) = self.split_parent_file_extension_fragment(); stem } + #[inline] pub fn extension(&self) -> Option<&str> { let (_, _, ext, _) = self.split_parent_file_extension_fragment(); ext } + #[inline] pub fn fragment(&self) -> Option<&str> { let (_path, frag) = self.split_fragment(); frag } + #[inline] pub fn components(&self) -> Components<'_> { Components { path: &self.0, diff --git a/crates/assets-loader/src/platform/android.rs b/crates/assets-loader/src/platform/android.rs index df8aefa..2d2a7ef 100644 --- a/crates/assets-loader/src/platform/android.rs +++ b/crates/assets-loader/src/platform/android.rs @@ -1,12 +1,27 @@ -use ndk::asset::Asset as AndroidAsset; +use std::{borrow::Cow, ffi::CString, io::Read}; + +use ndk::asset::{Asset as AndroidAsset, AssetManager as AndroidAssetManager}; + +use crate::{ + path::{AssetPath, AssetPathBuf}, + platform::{AssetOpen, BoxedFuture}, +}; pub struct AndroidAssetLoaderIo { + manager: AndroidAssetManager, base: Cow<'static, str>, } +fn clone_mgr(mgr: &AndroidAssetManager) -> AndroidAssetManager { + // SAFETY: pointer is a valid asset manager! + unsafe { AndroidAssetManager::from_ptr(mgr.ptr()) } +} + impl AndroidAssetLoaderIo { - pub const fn new() -> Self { + #[inline] + pub const fn with_asset_manager(manager: AndroidAssetManager) -> Self { Self { + manager, base: Cow::Borrowed(""), } } @@ -24,34 +39,39 @@ impl AndroidAssetLoaderIo { } fn resolve_path(&self, asset_path: &AssetPath) -> String { + let resolved_path = self.resolve_path(asset_path); + // make absolute & normalize - let norm_asset_path = AssetPathBuf::with_capacity(asset_path.as_str().len() + 1); + let mut norm_asset_path = AssetPathBuf::with_capacity(asset_path.len() + 1); norm_asset_path.push("/"); norm_asset_path.push(asset_path); let base = self.base.trim_end_matches('/'); - let result = String::with_capacity(base.len() + asset_path.len()); + let mut result = String::with_capacity(base.len() + asset_path.len()); result.push_str(base); - result.push_str(&norm_asset_path); + result.push_str(&norm_asset_path.as_str()); result } - fn open(&self, resolved_path: String) -> std::io::Result> { - let resolved_path = CString::new(resolved_path.into_bytes()).ok()?; - - let asset = ndk_glue::native_activity() - .asset_manager() - .open(&full_path) - .ok_or(std::io::ErrorKind::NotFound)?; - let mut buf = Vec::with_capacity(len as usize); - file.read_to_end(&mut buf)?; - Ok(buf) + async fn open(&self, asset_path: &AssetPath) -> std::io::Result> { + let resolved_path = self.resolve_path(asset_path); + let resolved_path = CString::new(resolved_path.into_bytes())?; + let manager = clone_mgr(&self.manager); + blocking::unblock(move || { + let mut asset = manager + .open(&resolved_path) + .ok_or(std::io::ErrorKind::NotFound)?; + let mut buf = Vec::with_capacity(asset.get_length()); + asset.read_to_end(&mut buf)?; + Ok(buf) + }) + .await } } impl AssetOpen for AndroidAssetLoaderIo { fn load(&self, asset_path: &AssetPath) -> BoxedFuture<'_, std::io::Result>> { - let resolved_path = self.resolve_path(asset_path); - Box::pin(blocking::unblock(move || self.open(resolved_path))) + let asset_path = asset_path.to_owned(); + Box::pin(async move { self.open(&asset_path).await }) } } diff --git a/crates/assets-loader/src/platform/mod.rs b/crates/assets-loader/src/platform/mod.rs index 1484bcf..4578cb1 100644 --- a/crates/assets-loader/src/platform/mod.rs +++ b/crates/assets-loader/src/platform/mod.rs @@ -16,16 +16,3 @@ type BoxedFuture<'l, O> = Pin + Send + Sync + 'l>>; pub trait AssetOpen { fn load(&self, asset_path: &AssetPath) -> BoxedFuture<'_, std::io::Result>>; } - -pub fn default_platform_io() -> impl AssetOpen { - #[cfg(not(any(target_os = "android", target_arch = "wasm32")))] - let io = fs::FileSystemAssetLoaderIo::new(); - - #[cfg(target_os = "android")] - let io = android::AndroidAssetLoaderIo::new(); - - #[cfg(target_arch = "wasm32")] - let io = wasm::WasmFetchAssetLoaderIo::new(); - - io -} diff --git a/crates/render-ash/src/swapchain.rs b/crates/render-ash/src/swapchain.rs index 86f8a71..72b0109 100644 --- a/crates/render-ash/src/swapchain.rs +++ b/crates/render-ash/src/swapchain.rs @@ -234,8 +234,8 @@ impl AshInstance { self.create_surface_wayland(d.display, w.surface) } #[cfg(target_os = "android")] - (RawDisplayHandle::Android(_), RawWindowHandle::Android(w)) => { - self.create_surface_android(w.a_native_window)? + (RawDisplayHandle::Android(_), RawWindowHandle::AndroidNdk(w)) => { + self.create_surface_android(w.a_native_window) } #[cfg(target_os = "windows")] (RawDisplayHandle::Windows(_), RawWindowHandle::Windows(w)) => { From 76a5eb6e84b2a59506ca083d4af617eb59b24ede Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Mon, 9 Jan 2023 23:27:30 +0100 Subject: [PATCH 07/36] fmt --- crates/render-ash/src/graph.rs | 2 +- crates/render-wgpu/examples/render-wgpu-demo.rs | 2 +- crates/render-wgpu/src/convert.rs | 9 ++++++++- crates/render-wgpu/src/resources.rs | 14 +++++++++----- crates/render-wgpu/src/surface.rs | 2 +- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/crates/render-ash/src/graph.rs b/crates/render-ash/src/graph.rs index da2ffa8..d7d32b8 100644 --- a/crates/render-ash/src/graph.rs +++ b/crates/render-ash/src/graph.rs @@ -17,8 +17,8 @@ use crate::{ into_texture_usage_read_access, into_texture_usage_write_access, VkInto, }, device::AshDevice, - encoder::{AshCommandPool, SubmissionGroup}, drop_guard::Guard, + encoder::{AshCommandPool, SubmissionGroup}, Result, }; diff --git a/crates/render-wgpu/examples/render-wgpu-demo.rs b/crates/render-wgpu/examples/render-wgpu-demo.rs index e1f1c14..2021ae0 100644 --- a/crates/render-wgpu/examples/render-wgpu-demo.rs +++ b/crates/render-wgpu/examples/render-wgpu-demo.rs @@ -2,8 +2,8 @@ use std::rc::Rc; use pulz_ecs::prelude::*; use pulz_render::camera::{Camera, RenderTarget}; -use pulz_render_wgpu::WgpuRendererBuilder; use pulz_render_pipeline_core::core_3d::CoreShadingModule; +use pulz_render_wgpu::WgpuRendererBuilder; use pulz_window::{WindowDescriptor, WindowId}; use pulz_window_winit::{ winit::{event_loop::EventLoop, window::Window}, diff --git a/crates/render-wgpu/src/convert.rs b/crates/render-wgpu/src/convert.rs index 7fb577d..6d1ae4d 100644 --- a/crates/render-wgpu/src/convert.rs +++ b/crates/render-wgpu/src/convert.rs @@ -4,7 +4,14 @@ use pulz_render::{ buffer::{BufferDescriptor, BufferUsage}, color::Srgba, math::USize3, - pipeline::{Face, FrontFace, IndexFormat, PrimitiveTopology, VertexFormat, PipelineLayout, BindGroupLayoutEntry, VertexAttribute, BindGroupLayoutDescriptor, GraphicsPipelineDescriptor, ComputePipelineDescriptor, PipelineLayoutDescriptor, VertexState, FragmentState, ColorTargetState, BlendState, BlendComponent, PrimitiveState, DepthStencilState, StencilFaceState, ColorWrite, BlendOperation, BlendFactor, CompareFunction, StencilOperation}, + pipeline::{ + BindGroupLayoutDescriptor, BindGroupLayoutEntry, BlendComponent, BlendFactor, + BlendOperation, BlendState, ColorTargetState, ColorWrite, CompareFunction, + ComputePipelineDescriptor, DepthStencilState, Face, FragmentState, FrontFace, + GraphicsPipelineDescriptor, IndexFormat, PipelineLayout, PipelineLayoutDescriptor, + PrimitiveState, PrimitiveTopology, StencilFaceState, StencilOperation, VertexAttribute, + VertexFormat, VertexState, + }, shader::{ShaderModule, ShaderModuleDescriptor, ShaderSource}, texture::{ ImageDataLayout, Texture, TextureDescriptor, TextureDimensions, TextureFormat, TextureUsage, diff --git a/crates/render-wgpu/src/resources.rs b/crates/render-wgpu/src/resources.rs index 2f8e85b..0fc7d0e 100644 --- a/crates/render-wgpu/src/resources.rs +++ b/crates/render-wgpu/src/resources.rs @@ -1,10 +1,14 @@ -use crate::convert as c; -use crate::Result; -use pulz_render::pipeline::{BindGroupLayout, ComputePipeline, GraphicsPipeline, PipelineLayout}; -use pulz_render::shader::ShaderModule; -use pulz_render::{backend::GpuResource, buffer::Buffer, texture::Texture}; +use pulz_render::{ + backend::GpuResource, + buffer::Buffer, + pipeline::{BindGroupLayout, ComputePipeline, GraphicsPipeline, PipelineLayout}, + shader::ShaderModule, + texture::Texture, +}; use slotmap::SlotMap; +use crate::{convert as c, Result}; + pub trait WgpuResource: GpuResource { type Wgpu; diff --git a/crates/render-wgpu/src/surface.rs b/crates/render-wgpu/src/surface.rs index fa28b10..7fd979e 100644 --- a/crates/render-wgpu/src/surface.rs +++ b/crates/render-wgpu/src/surface.rs @@ -55,7 +55,7 @@ impl Surface { changed } - pub fn configure(&mut self, adapter: &wgpu::Adapter, device:&wgpu::Device) { + pub fn configure(&mut self, adapter: &wgpu::Adapter, device: &wgpu::Device) { // TODO: also reconfigure on resize, and when presenting results in `Outdated/Lost` self.format = self .surface From 0279da5e4f6860bca5484f00797c1f10d1103ae4 Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sat, 28 Jan 2023 12:31:32 +0100 Subject: [PATCH 08/36] adjust to simplified systems --- crates/assets/src/lib.rs | 9 +-- .../render-pipeline-core/src/core_3d/mod.rs | 2 +- .../src/deferred_3d/mod.rs | 2 +- crates/render-wgpu/src/lib.rs | 20 +++--- crates/render/src/draw.rs | 38 +++------- crates/render/src/graph/builder.rs | 71 ++++++++----------- crates/render/src/lib.rs | 4 +- 7 files changed, 57 insertions(+), 89 deletions(-) diff --git a/crates/assets/src/lib.rs b/crates/assets/src/lib.rs index 71682d3..c72a1ab 100644 --- a/crates/assets/src/lib.rs +++ b/crates/assets/src/lib.rs @@ -191,13 +191,6 @@ impl Assets { events_writer.send_batch(self.events.drain(..)) } - pub fn update_system( - mut assets: ResMut<'_, Self>, - events_writer: EventWriter<'_, AssetEvent>, - ) { - assets.update(events_writer) - } - pub fn install_into(res: &mut Resources) where T: Send + Sync + 'static, @@ -212,7 +205,7 @@ impl Assets { CoreSystemPhase::Update.as_label(), ]); schedule - .add_system(Self::update_system) + .add_system(Self::update) .into_phase(AssetSystemPhase::UpdateAssets); } } diff --git a/crates/render-pipeline-core/src/core_3d/mod.rs b/crates/render-pipeline-core/src/core_3d/mod.rs index fbec086..831d9bf 100644 --- a/crates/render-pipeline-core/src/core_3d/mod.rs +++ b/crates/render-pipeline-core/src/core_3d/mod.rs @@ -16,7 +16,7 @@ pub struct CoreShadingModule; impl CoreShadingModule { fn build_graph_system( - mut builder: ResMut<'_, RenderGraphBuilder>, + builder: &mut RenderGraphBuilder, cams_qry: Query<'_, (&Camera, &RenderTarget, Entity)>, ) { for (camera, render_target, entity) in cams_qry { diff --git a/crates/render-pipeline-core/src/deferred_3d/mod.rs b/crates/render-pipeline-core/src/deferred_3d/mod.rs index 241f186..383e22d 100644 --- a/crates/render-pipeline-core/src/deferred_3d/mod.rs +++ b/crates/render-pipeline-core/src/deferred_3d/mod.rs @@ -18,7 +18,7 @@ pub struct DeferredShadingModule; impl DeferredShadingModule { fn build_graph_system( - mut builder: ResMut<'_, RenderGraphBuilder>, + builder: &mut RenderGraphBuilder, cams_qry: Query<'_, (&Camera, &RenderTarget, Entity)>, ) { for (_camera, render_target, entity) in cams_qry { diff --git a/crates/render-wgpu/src/lib.rs b/crates/render-wgpu/src/lib.rs index f51bbda..7017cc3 100644 --- a/crates/render-wgpu/src/lib.rs +++ b/crates/render-wgpu/src/lib.rs @@ -215,16 +215,16 @@ impl WgpuRenderer { self.queue.submit(cmds); } - fn run_render_system( - mut renderer: ResMut<'_, Self>, - windows: Res<'_, Windows>, - src_graph: Res<'_, RenderGraph>, + fn run( + &mut self, + windows: &Windows, + src_graph: &RenderGraph, ) { - renderer.reconfigure_surfaces(&windows); - renderer.graph.update(&src_graph); - renderer.aquire_swapchain_images(); - renderer.run_graph(&src_graph); - renderer.present_swapchain_images(); + self.reconfigure_surfaces(&windows); + self.graph.update(&src_graph); + self.aquire_swapchain_images(); + self.run_graph(&src_graph); + self.present_swapchain_images(); } } @@ -242,7 +242,7 @@ impl ModuleWithOutput for WgpuRenderer { fn install_systems(schedule: &mut Schedule) { schedule - .add_system(Self::run_render_system) + .add_system(Self::run) .into_phase(RenderSystemPhase::Render); } } diff --git a/crates/render/src/draw.rs b/crates/render/src/draw.rs index 961af1c..9cb5db8 100644 --- a/crates/render/src/draw.rs +++ b/crates/render/src/draw.rs @@ -2,11 +2,7 @@ use std::{hash::Hash, marker::PhantomData, ops::Deref}; use atomic_refcell::AtomicRefCell; use dynsequence::{dyn_sequence, DynSequence}; -use pulz_ecs::{ - prelude::*, - resource::ResourceAccess, - system::param::{SystemParam, SystemParamState}, -}; +use pulz_ecs::{prelude::*, resource::ResState, system::param::SystemParam}; use crate::{backend::CommandEncoder, RenderSystemPhase}; @@ -66,7 +62,8 @@ pub trait PhaseItem: Send + Sync + Sized + 'static { E: Deref; } -struct DrawQueue(crossbeam_queue::SegQueue<(I::TargetKey, PhaseData)>); +#[doc(hidden)] +pub struct DrawQueue(crossbeam_queue::SegQueue<(I::TargetKey, PhaseData)>); struct KeyType(PhantomData); impl typemap::Key for KeyType { @@ -200,7 +197,7 @@ impl DrawPhases { } pub struct Draw<'l, I: PhaseItem> { - destination: Res<'l, DrawQueue>, + destination: &'l DrawQueue, } impl Draw<'_, I> { @@ -227,10 +224,7 @@ impl Default for DrawQueue { } } -fn collect_and_sort_draws_system( - mut queue: ResMut<'_, DrawQueue>, - phases: Res<'_, DrawPhases>, -) { +fn collect_and_sort_draws_system(queue: &mut DrawQueue, phases: &DrawPhases) { let mut phase_map = phases.0.get::>().unwrap().borrow_mut(); // clear sequences @@ -280,23 +274,13 @@ impl Drop for DrawTarget<'_, I> { } } -unsafe impl SystemParam for Draw<'_, I> { - type State = DrawState; -} -pub struct DrawState(ResourceId>); +impl SystemParam for Draw<'_, I> { + type State = ResState>; + type Fetch<'r> = Res<'r, DrawQueue>; + type Item<'a> = Draw<'a, I>; -unsafe impl SystemParamState for DrawState { - type Item<'r> = Draw<'r, I>; - fn init(resources: &mut Resources) -> Self { - Self(resources.init::>()) - } - fn update_access(&self, _resources: &Resources, access: &mut ResourceAccess) { - access.add_shared_checked(self.0); - } - fn fetch<'r>(&'r mut self, resources: &'r Resources) -> Self::Item<'r> { - Draw { - destination: resources.borrow_res_id(self.0).unwrap(), - } + fn get<'a>(fetch: &'a mut Self::Fetch<'_>) -> Self::Item<'a> { + Draw { destination: fetch } } } diff --git a/crates/render/src/graph/builder.rs b/crates/render/src/graph/builder.rs index f14c734..972ad7f 100644 --- a/crates/render/src/graph/builder.rs +++ b/crates/render/src/graph/builder.rs @@ -64,34 +64,43 @@ impl RenderGraphBuilder { hasher.finish() } - fn reset(&mut self) { + pub(crate) fn reset(&mut self) { + debug_assert!(!self.is_reset); self.is_reset = true; self.textures.reset(); self.buffers.reset(); self.passes.clear(); - self.passes_run.clear(); + } +} + +impl RenderGraph { + fn reset(&mut self) { + self.init = false; + self.was_updated = true; + self.textures.reset(); + self.buffers.reset(); + self.passes.clear(); + self.passes_exec.clear(); self.groups.clear(); + self.topo_order.clear(); } - pub(crate) fn build_graph_system( - mut builder: ResMut<'_, Self>, - mut graph: ResMut<'_, RenderGraph>, - ) { + pub(crate) fn build_from_builder(&mut self, builder: &mut RenderGraphBuilder) { debug_assert!(builder.is_reset); builder.is_reset = false; let builder_hash = builder.hash(); - if graph.init - && builder_hash == graph.hash - && builder.textures.len() == graph.textures.len() - && builder.buffers.len() == graph.buffers.len() - && builder.passes.len() == graph.passes.len() - && builder.passes_run.len() == graph.passes_exec.len() - && builder.groups.len() == graph.groups.len() + if self.init + && builder_hash == self.hash + && builder.textures.len() == self.textures.len() + && builder.buffers.len() == self.buffers.len() + && builder.passes.len() == self.passes.len() + && builder.passes_run.len() == self.passes_run.len() + && builder.groups.len() == self.groups.len() { // graph not changed: swap data from builder (rest stays the same) - graph.was_updated = false; - swap_graph_data(&mut builder, &mut graph); + self.was_updated = false; + swap_graph_data(builder, self); trace!("RenderGraph not changed"); return; } @@ -101,40 +110,22 @@ impl RenderGraphBuilder { builder.passes.len() ); - graph.reset(); - swap_graph_data(&mut builder, &mut graph); - graph.hash = builder_hash; + self.reset(); + swap_graph_data(builder, self); + self.hash = builder_hash; // TODO: detect unused nodes / dead-stripping - let mut m = graph.build_group_dependency_matrix(); + let mut m = self.build_group_dependency_matrix(); m.remove_self_references(); - graph.topo_order = m.into_topological_order(); + self.topo_order = m.into_topological_order(); - debug!("Topological order: {:?}", graph.topo_order); + debug!("Topological order: {:?}", self.topo_order); // TODO: resource aliasing (e.g. share Image resource when ) - graph.init = true; - } - - pub(crate) fn reset_graph_builder(mut builder: ResMut<'_, Self>) { - debug_assert!(!builder.is_reset); - builder.reset() - } -} - -impl RenderGraph { - fn reset(&mut self) { - self.init = false; - self.was_updated = true; - self.textures.reset(); - self.buffers.reset(); - self.passes.clear(); - self.passes_exec.clear(); - self.groups.clear(); - self.topo_order.clear(); + self.init = true; } fn build_pass_dependency_matrix(&self) -> DependencyMatrix { diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index b4abd2b..3058569 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -91,10 +91,10 @@ impl Module for RenderModule { // SORTING after update UPDATE schedule.add_phase_dependency(CoreSystemPhase::Update, RenderSystemPhase::Sorting); schedule - .add_system(RenderGraphBuilder::reset_graph_builder) + .add_system(RenderGraphBuilder::reset) .before(RenderSystemPhase::BuildGraph); schedule - .add_system(RenderGraphBuilder::build_graph_system) + .add_system(RenderGraph::build_from_builder) .after(RenderSystemPhase::BuildGraph) .before(RenderSystemPhase::UpdateGraph); } From 9060b1efb202758d7502f9704df6dbc930b2fee4 Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sat, 28 Jan 2023 15:41:46 +0100 Subject: [PATCH 09/36] refactored graph --- .../render-pipeline-core/src/core_3d/mod.rs | 3 +- .../src/deferred_3d/mod.rs | 3 +- crates/render/src/buffer.rs | 17 +- crates/render/src/camera.rs | 6 +- crates/render/src/graph/access.rs | 297 +++--------------- crates/render/src/graph/builder.rs | 91 +++--- crates/render/src/graph/mod.rs | 134 ++++---- crates/render/src/graph/pass/builder.rs | 273 +++++++++------- crates/render/src/graph/pass/mod.rs | 21 +- crates/render/src/graph/pass/run.rs | 3 + crates/render/src/graph/resources.rs | 294 ++++++++++------- crates/render/src/lib.rs | 4 +- crates/render/src/pipeline/binding.rs | 13 +- .../render/src/pipeline/compute_pipeline.rs | 15 + crates/render/src/pipeline/graphics_pass.rs | 200 ++++++++++++ .../{descriptor.rs => graphics_pipeline.rs} | 194 ++++++------ crates/render/src/pipeline/mod.rs | 17 +- crates/render/src/pipeline/pipeline_layout.rs | 5 +- .../src/pipeline/ray_tracing_pipeline.rs | 53 ++++ crates/render/src/pipeline/specialization.rs | 55 ++++ crates/render/src/shader/mod.rs | 3 + crates/render/src/texture/descriptor.rs | 42 ++- crates/transform/src/lib.rs | 1 + 23 files changed, 990 insertions(+), 754 deletions(-) create mode 100644 crates/render/src/pipeline/compute_pipeline.rs create mode 100644 crates/render/src/pipeline/graphics_pass.rs rename crates/render/src/pipeline/{descriptor.rs => graphics_pipeline.rs} (68%) create mode 100644 crates/render/src/pipeline/ray_tracing_pipeline.rs create mode 100644 crates/render/src/pipeline/specialization.rs diff --git a/crates/render-pipeline-core/src/core_3d/mod.rs b/crates/render-pipeline-core/src/core_3d/mod.rs index 831d9bf..8469d78 100644 --- a/crates/render-pipeline-core/src/core_3d/mod.rs +++ b/crates/render-pipeline-core/src/core_3d/mod.rs @@ -3,10 +3,11 @@ use pulz_render::{ camera::{Camera, RenderTarget}, graph::{ pass::{builder::PassBuilder, run::PassExec, Graphics, Pass}, - resources::{Texture, WriteSlot}, + resources::WriteSlot, RenderGraphBuilder, }, math::Mat4, + texture::Texture, RenderSystemPhase, }; diff --git a/crates/render-pipeline-core/src/deferred_3d/mod.rs b/crates/render-pipeline-core/src/deferred_3d/mod.rs index 383e22d..e524d92 100644 --- a/crates/render-pipeline-core/src/deferred_3d/mod.rs +++ b/crates/render-pipeline-core/src/deferred_3d/mod.rs @@ -7,9 +7,10 @@ use pulz_render::{ run::PassExec, Graphics, Pass, PassGroup, }, - resources::{Slot, Texture, WriteSlot}, + resources::{Slot, WriteSlot}, RenderGraphBuilder, }, + texture::Texture, RenderSystemPhase, }; diff --git a/crates/render/src/buffer.rs b/crates/render/src/buffer.rs index 852cd23..7553957 100644 --- a/crates/render/src/buffer.rs +++ b/crates/render/src/buffer.rs @@ -25,15 +25,14 @@ impl Default for BufferDescriptor { bitflags! { pub struct BufferUsage: u32 { - const MAP_READ = 1; - const MAP_WRITE = 2; - const TRANSFER_SRC = 4; - const TRANSFER_DST = 8; - const INDEX = 16; - const VERTEX = 32; - const UNIFORM = 64; - const STORAGE = 128; - const INDIRECT = 256; + const TRANSFER_SRC = 1; + const TRANSFER_DST = 2; + const HOST = 4; // used in combination with TRANSFER_SRC / TRANSFER_DST + const INDEX = 8; + const VERTEX = 17; + const UNIFORM = 32; + const STORAGE = 64; + const INDIRECT = 128; const NONE = 0; } diff --git a/crates/render/src/camera.rs b/crates/render/src/camera.rs index 0b36818..33d7221 100644 --- a/crates/render/src/camera.rs +++ b/crates/render/src/camera.rs @@ -1,6 +1,6 @@ use pulz_assets::{Assets, Handle}; use pulz_ecs::prelude::*; -use pulz_transform::math::{vec2, Mat4, Size2, USize2}; +use pulz_transform::math::{size2, Mat4, Size2, USize2}; use pulz_window::{Window, WindowId, Windows}; use crate::texture::Image; @@ -141,9 +141,9 @@ impl AsProjectionMatrix for OrthographicProjection { OrthographicScalingMode::WindowSize => size, OrthographicScalingMode::AutoFit(min) => { if size.x * min.y > min.x * size.y { - vec2(size.x * min.y / size.y, min.y) + size2(size.x * min.y / size.y, min.y) } else { - vec2(min.x, size.y * min.x / size.x) + size2(min.x, size.y * min.x / size.x) } } }; diff --git a/crates/render/src/graph/access.rs b/crates/render/src/graph/access.rs index d339230..d2bc12c 100644 --- a/crates/render/src/graph/access.rs +++ b/crates/render/src/graph/access.rs @@ -1,15 +1,17 @@ use std::{ fmt::Debug, hash::Hash, - ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, RangeBounds, Sub}, + ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, Sub}, }; use bitflags::bitflags; -use super::resources::{Texture, TextureUsage}; -use crate::buffer::{Buffer, BufferUsage}; +use crate::{ + buffer::{Buffer, BufferUsage}, + texture::{Texture, TextureDimensions, TextureFormat, TextureUsage}, +}; -pub trait ResourceAccess { +pub trait ResourceAccess: Copy + Eq + Hash { // Bitflags! type Usage: Copy + Clone @@ -24,14 +26,30 @@ pub trait ResourceAccess { + Not + Sub + Hash; + + type Meta; + + fn check_usage_is_pass_compatible(combined_usage: Self::Usage); } impl ResourceAccess for Texture { type Usage = TextureUsage; + type Meta = (TextureFormat, TextureDimensions, u8); + + fn check_usage_is_pass_compatible(combined_usage: Self::Usage) { + if combined_usage.is_non_attachment() { + panic!("Can't use texture as non-attachment resource multiple times in the same pass"); + } + } } impl ResourceAccess for Buffer { type Usage = BufferUsage; + type Meta = usize; + + fn check_usage_is_pass_compatible(_combined_usage: Self::Usage) { + panic!("Can't use buffer multiple times in the same pass"); + } } bitflags! { @@ -63,270 +81,31 @@ bitflags! { } bitflags! { - pub struct Access: u32 { - // const INDIRECT_COMMAND_READ = 0x00000001; - const INDEX_READ = 0x00000002; - const VERTEX_ATTRIBUTE_READ = 0x00000004; - const UNIFORM_READ = 0x00000008; - const INPUT_ATTACHMENT_READ = 0x00000010; - const SHADER_READ = 0x00000020; - const SHADER_WRITE = 0x00000040; - const COLOR_ATTACHMENT_READ = 0x00000080; - const COLOR_ATTACHMENT_WRITE = 0x00000100; - const DEPTH_STENCIL_ATTACHMENT_READ = 0x00000200; - const DEPTH_STENCIL_ATTACHMENT_WRITE = 0x00000400; - const TRANSFER_READ = 0x00000800; - const TRANSFER_WRITE = 0x00001000; - const HOST_READ = 0x00002000; - const HOST_WRITE = 0x00004000; - // const MEMORY_READ = 0x00008000; - // const MEMORY_WRITE = 0x00010000; - - const ACCELERATION_STRUCTURE_READ = 0x00200000; - const ACCELERATION_STRUCTURE_WRITE = 0x00400000; + pub struct ShaderStage: u32 { + // SUBSET of Stage + const VERTEX = 0x00000008; + const TESSELLATION_CONTROL = 0x00000010; + const TESSELLATION_EVALUATION = 0x00000020; + const GEOMETRY = 0x00000040; + const FRAGMENT = 0x00000080; + const COMPUTE = 0x00000800; + const RAY_TRACING = 0x00200000; const NONE = 0; } } -bitflags! { - pub struct BufferReadAccess: u32 { - // const IndirectCommand = 0x01; - const INDEX = 0x0002; - const VERTEX_ATTRIBUTE = 0x0004; - const TRANSFER = 0x0800; - const HOST = 0x2000; - - const VERTEX_SHADER_UNIFORM = 0x00010000; - const VERTEX_SHADER_STORAGE = 0x00020000; - const TESS_CTRL_SHADER_UNIFORM = 0x00040000; - const TESS_CTRL_SHADER_STORAGE = 0x00080000; - const TESS_EVAL_SHADER_UNIFORM = 0x00100000; - const TESS_EVAL_SHADER_STORAGE = 0x00200000; - const GEOMETRY_SHADER_UNIFORM = 0x00400000; - const GEOMETRY_SHADER_STORAGE = 0x00800000; - const FRAGMENT_SHADER_UNIFORM = 0x01000000; - const FRAGMENT_SHADER_STORAGE = 0x02000000; - const COMPUTE_SHADER_UNIFORM = 0x04000000; - const COMPUTE_SHADER_STORAGE = 0x08000000; - } -} - -bitflags! { - pub struct BufferWriteAccess: u32 { - const TRANSFER = 0x1000; - const HOST = 0x4000; - - const VERTEX_SHADER_STORAGE = 0x00020000; - const TESS_CTRL_SHADER_STORAGE = 0x00080000; - const TESS_EVAL_SHADER_STORAGE = 0x00200000; - const GEOMETRY_SHADER_STORAGE = 0x00800000; - const FRAGMENT_SHADER_STORAGE = 0x02000000; - const COMPUTE_SHADER_STORAGE = 0x08000000; - } -} - -bitflags! { - pub struct TextureReadAccess: u32 { - const INPUT_ATTACHMENT = 0x0010; - const COLOR_ATTACHMENT = 0x0080; - const DEPTH_STENCIL_ATTACHMENT = 0x0200; - const TRANSFER = 0x0800; - const HOST = 0x2000; - - const VERTEX_SHADER = 0x00020000; - const TESS_CTRL_SHADER = 0x00080000; - const TESS_EVAL_SHADER = 0x00200000; - const GEOMETRY_SHADER = 0x00800000; - const FRAGMENT_SHADER = 0x02000000; - const COMPUTE_SHADER = 0x08000000; - } -} - -bitflags! { - pub struct TextureWriteAccess: u32 { - const COLOR_ATTACHMENT = 0x0100; - const DEPTH_STENCIL_ATTACHMENT = 0x0400; - const TRANSFER = 0x1000; - const HOST = 0x4000; - - const VERTEX_SHADER = 0x00020000; - const TESS_CTRL_SHADER = 0x00080000; - const TESS_EVAL_SHADER = 0x00200000; - const GEOMETRY_SHADER = 0x00800000; - const FRAGMENT_SHADER = 0x02000000; - const COMPUTE_SHADER = 0x08000000; - } -} - -trait AccessStage: Copy { - fn stage(self) -> Stage; -} - -impl AccessStage for BufferReadAccess { - #[inline] - fn stage(self) -> Stage { - let mut stage = Stage::empty(); - //if self.contains(Self::IndirectCommand) { stage |= Stage::DRAW_INDIRECT; } - if self.contains(Self::INDEX) { - stage |= Stage::VERTEX_INPUT; - } - if self.contains(Self::VERTEX_ATTRIBUTE) { - stage |= Stage::VERTEX_INPUT; - } - if self.contains(Self::TRANSFER) { - stage |= Stage::TRANSFER; - } - if self.contains(Self::HOST) { - stage |= Stage::HOST; - } - if self.contains(Self::VERTEX_SHADER_UNIFORM) { - stage |= Stage::VERTEX_SHADER; - } - if self.contains(Self::VERTEX_SHADER_STORAGE) { - stage |= Stage::VERTEX_SHADER; - } - if self.contains(Self::TESS_CTRL_SHADER_UNIFORM) { - stage |= Stage::TESSELLATION_CONTROL_SHADER; - } - if self.contains(Self::TESS_CTRL_SHADER_STORAGE) { - stage |= Stage::TESSELLATION_CONTROL_SHADER; - } - if self.contains(Self::TESS_EVAL_SHADER_UNIFORM) { - stage |= Stage::TESSELLATION_EVALUATION_SHADER; - } - if self.contains(Self::TESS_EVAL_SHADER_STORAGE) { - stage |= Stage::TESSELLATION_EVALUATION_SHADER; - } - if self.contains(Self::GEOMETRY_SHADER_UNIFORM) { - stage |= Stage::GEOMETRY_SHADER; - } - if self.contains(Self::GEOMETRY_SHADER_STORAGE) { - stage |= Stage::GEOMETRY_SHADER; - } - if self.contains(Self::FRAGMENT_SHADER_UNIFORM) { - stage |= Stage::FRAGMENT_SHADER; - } - if self.contains(Self::FRAGMENT_SHADER_STORAGE) { - stage |= Stage::FRAGMENT_SHADER; - } - if self.contains(Self::COMPUTE_SHADER_UNIFORM) { - stage |= Stage::COMPUTE_SHADER; - } - if self.contains(Self::COMPUTE_SHADER_STORAGE) { - stage |= Stage::COMPUTE_SHADER; - } - stage - } -} - -impl AccessStage for BufferWriteAccess { - #[inline] - fn stage(self) -> Stage { - let mut stage = Stage::empty(); - if self.contains(Self::TRANSFER) { - stage |= Stage::TRANSFER; - } - if self.contains(Self::HOST) { - stage |= Stage::HOST; - } - if self.contains(Self::VERTEX_SHADER_STORAGE) { - stage |= Stage::VERTEX_SHADER; - } - if self.contains(Self::TESS_CTRL_SHADER_STORAGE) { - stage |= Stage::TESSELLATION_CONTROL_SHADER; - } - if self.contains(Self::TESS_EVAL_SHADER_STORAGE) { - stage |= Stage::TESSELLATION_EVALUATION_SHADER; - } - if self.contains(Self::GEOMETRY_SHADER_STORAGE) { - stage |= Stage::GEOMETRY_SHADER; - } - if self.contains(Self::FRAGMENT_SHADER_STORAGE) { - stage |= Stage::FRAGMENT_SHADER; - } - if self.contains(Self::COMPUTE_SHADER_STORAGE) { - stage |= Stage::COMPUTE_SHADER; - } - stage - } -} - -impl AccessStage for TextureReadAccess { +impl ShaderStage { #[inline] - fn stage(self) -> Stage { - let mut stage = Stage::empty(); - if self.contains(Self::INPUT_ATTACHMENT) { - stage |= Stage::FRAGMENT_SHADER; - } - if self.contains(Self::COLOR_ATTACHMENT) { - stage |= Stage::COLOR_ATTACHMENT_OUTPUT; - } - if self.contains(Self::DEPTH_STENCIL_ATTACHMENT) { - stage |= Stage::FRAGMENT_TESTS; - } - if self.contains(Self::TRANSFER) { - stage |= Stage::TRANSFER; - } - if self.contains(Self::HOST) { - stage |= Stage::HOST; - } - if self.contains(Self::VERTEX_SHADER) { - stage |= Stage::VERTEX_SHADER; - } - if self.contains(Self::TESS_CTRL_SHADER) { - stage |= Stage::TESSELLATION_CONTROL_SHADER; - } - if self.contains(Self::TESS_EVAL_SHADER) { - stage |= Stage::TESSELLATION_EVALUATION_SHADER; - } - if self.contains(Self::GEOMETRY_SHADER) { - stage |= Stage::GEOMETRY_SHADER; - } - if self.contains(Self::FRAGMENT_SHADER) { - stage |= Stage::FRAGMENT_SHADER; - } - if self.contains(Self::COMPUTE_SHADER) { - stage |= Stage::COMPUTE_SHADER; - } - stage + pub fn as_stage(self) -> Stage { + // SAFETY: bits are a subset of Stage + unsafe { Stage::from_bits_unchecked(self.bits) } } } -impl AccessStage for TextureWriteAccess { +impl From for Stage { #[inline] - fn stage(self) -> Stage { - let mut stage = Stage::empty(); - if self.contains(Self::COLOR_ATTACHMENT) { - stage |= Stage::COLOR_ATTACHMENT_OUTPUT; - } - if self.contains(Self::DEPTH_STENCIL_ATTACHMENT) { - stage |= Stage::FRAGMENT_TESTS; - } - if self.contains(Self::TRANSFER) { - stage |= Stage::TRANSFER; - } - if self.contains(Self::HOST) { - stage |= Stage::HOST; - } - if self.contains(Self::VERTEX_SHADER) { - stage |= Stage::VERTEX_SHADER; - } - if self.contains(Self::TESS_CTRL_SHADER) { - stage |= Stage::TESSELLATION_CONTROL_SHADER; - } - if self.contains(Self::TESS_EVAL_SHADER) { - stage |= Stage::TESSELLATION_EVALUATION_SHADER; - } - if self.contains(Self::GEOMETRY_SHADER) { - stage |= Stage::GEOMETRY_SHADER; - } - if self.contains(Self::FRAGMENT_SHADER) { - stage |= Stage::FRAGMENT_SHADER; - } - if self.contains(Self::COMPUTE_SHADER) { - stage |= Stage::COMPUTE_SHADER; - } - stage + fn from(shader_state: ShaderStage) -> Self { + shader_state.as_stage() } } diff --git a/crates/render/src/graph/builder.rs b/crates/render/src/graph/builder.rs index 972ad7f..6100771 100644 --- a/crates/render/src/graph/builder.rs +++ b/crates/render/src/graph/builder.rs @@ -3,64 +3,79 @@ use std::{ hash::{Hash, Hasher}, }; -use pulz_ecs::prelude::*; -use tracing::{debug, trace, Callsite}; +use tracing::{debug, trace}; use super::{ - access::ResourceAccess, - deps::DependencyMatrix, - resources::{Slot, Texture}, - RenderGraph, RenderGraphBuilder, + access::ResourceAccess, deps::DependencyMatrix, resources::Slot, RenderGraph, + RenderGraphBuilder, }; -use crate::buffer::Buffer; +use crate::{buffer::Buffer, texture::Texture}; + +pub trait GetExternalResource { + fn get_external_resource(&self) -> (R, R::Meta); +} + +impl GetExternalResource for F +where + R: ResourceAccess, + F: Fn() -> (R, R::Meta), +{ + fn get_external_resource(&self) -> (R, R::Meta) { + self() + } +} pub trait GraphImport { type Resource: ResourceAccess; + + fn import(&self) -> Box>; } pub trait GraphExport { type Resource: ResourceAccess; + + fn export(&self) -> Box>; } impl RenderGraphBuilder { - pub fn import_texture(&mut self, _import_from: &I) -> Slot + pub fn import_texture(&mut self, import_from: &I) -> Slot where I: GraphImport, { - // TODO: associate resource - self.textures.import() + let f = import_from.import(); + self.textures.import(f) } - pub fn import_buffer(&mut self, _import_from: &I) -> Slot + pub fn import_buffer(&mut self, import_from: &I) -> Slot where I: GraphImport, { - // TODO: associate resource - self.buffers.import() + let f = import_from.import(); + self.buffers.import(f) } - pub fn export_texture(&mut self, slot: Slot, _export_to: &E) + pub fn export_texture(&mut self, slot: Slot, export_to: &E) where E: GraphExport, { - // TODO: associate resource - self.textures.export(slot) + let f = export_to.export(); + self.textures.export(slot, f) } - pub fn export_buffer(&mut self, slot: Slot, _export_to: &E) + pub fn export_buffer(&mut self, slot: Slot, export_to: &E) where E: GraphExport, { - // TODO: associate resource - self.buffers.export(slot) + let f = export_to.export(); + self.buffers.export(slot, f) } fn hash(&self) -> u64 { let mut hasher = DefaultHasher::new(); Hash::hash(&self.textures, &mut hasher); Hash::hash(&self.buffers, &mut hasher); + Hash::hash(&self.subpasses, &mut hasher); Hash::hash(&self.passes, &mut hasher); - Hash::hash(&self.groups, &mut hasher); hasher.finish() } @@ -69,6 +84,8 @@ impl RenderGraphBuilder { self.is_reset = true; self.textures.reset(); self.buffers.reset(); + self.subpasses.clear(); + self.subpasses_run.clear(); self.passes.clear(); } } @@ -79,10 +96,10 @@ impl RenderGraph { self.was_updated = true; self.textures.reset(); self.buffers.reset(); + self.subpasses.clear(); + self.subpasses_exec.clear(); self.passes.clear(); - self.passes_exec.clear(); - self.groups.clear(); - self.topo_order.clear(); + self.passes_topo_order.clear(); } pub(crate) fn build_from_builder(&mut self, builder: &mut RenderGraphBuilder) { @@ -94,9 +111,9 @@ impl RenderGraph { && builder_hash == self.hash && builder.textures.len() == self.textures.len() && builder.buffers.len() == self.buffers.len() + && builder.subpasses.len() == self.subpasses.len() + && builder.subpasses_run.len() == self.subpasses_exec.len() && builder.passes.len() == self.passes.len() - && builder.passes_run.len() == self.passes_run.len() - && builder.groups.len() == self.groups.len() { // graph not changed: swap data from builder (rest stays the same) self.was_updated = false; @@ -107,7 +124,7 @@ impl RenderGraph { debug!( "Updating RenderGraph with {} passes...", - builder.passes.len() + builder.subpasses.len() ); self.reset(); @@ -116,19 +133,19 @@ impl RenderGraph { // TODO: detect unused nodes / dead-stripping - let mut m = self.build_group_dependency_matrix(); + let mut m = self.build_dependency_matrix(); m.remove_self_references(); - self.topo_order = m.into_topological_order(); + self.passes_topo_order = m.into_topological_order(); - debug!("Topological order: {:?}", self.topo_order); + debug!("Topological order: {:?}", self.passes_topo_order); // TODO: resource aliasing (e.g. share Image resource when ) self.init = true; } - fn build_pass_dependency_matrix(&self) -> DependencyMatrix { + fn build_dependency_matrix(&self) -> DependencyMatrix { let mut m = DependencyMatrix::new(self.passes.len()); // TODO: only mark used nodes for p in &self.passes { @@ -137,22 +154,12 @@ impl RenderGraph { } m } - - fn build_group_dependency_matrix(&self) -> DependencyMatrix { - let mut m = DependencyMatrix::new(self.passes.len()); - // TODO: only mark used nodes - for p in &self.passes { - p.textures - .mark_group_dependency_matrix(&mut m, &self.passes, p.group_index); - } - m - } } fn swap_graph_data(builder: &mut RenderGraphBuilder, dest: &mut RenderGraph) { std::mem::swap(&mut builder.textures, &mut dest.textures); std::mem::swap(&mut builder.buffers, &mut dest.buffers); + std::mem::swap(&mut builder.subpasses, &mut dest.subpasses); + std::mem::swap(&mut builder.subpasses_run, &mut dest.subpasses_exec); std::mem::swap(&mut builder.passes, &mut dest.passes); - std::mem::swap(&mut builder.passes_run, &mut dest.passes_exec); - std::mem::swap(&mut builder.groups, &mut dest.groups); } diff --git a/crates/render/src/graph/mod.rs b/crates/render/src/graph/mod.rs index 8b0b761..e36d59e 100644 --- a/crates/render/src/graph/mod.rs +++ b/crates/render/src/graph/mod.rs @@ -1,8 +1,12 @@ use self::{ pass::{run::PassExec, PipelineBindPoint}, - resources::{Buffer, ResourceDeps, ResourceSet, SlotAccess, Texture}, + resources::{ResourceDeps, ResourceSet}, +}; +use crate::{ + buffer::Buffer, + draw::{DrawContext, DrawPhases}, + texture::Texture, }; -use crate::draw::{DrawContext, DrawPhases}; pub mod access; #[macro_use] @@ -11,26 +15,31 @@ pub mod builder; pub mod deps; pub mod pass; +pub type ResourceIndex = u16; +pub type PassIndex = u16; +type SubPassIndex = (u16, u16); + +const PASS_UNDEFINED: PassIndex = !0; +const SUBPASS_UNDEFINED: SubPassIndex = (!0, !0); + #[derive(Hash)] -pub struct PassDescription { - index: usize, - group_index: usize, +pub struct SubPassDescription { + pass_index: PassIndex, name: &'static str, - bind_point: PipelineBindPoint, - textures: ResourceDeps, - buffers: ResourceDeps, - color_attachments: Vec, - depth_stencil_attachments: Option, - input_attachments: Vec, + color_attachments: Vec, + depth_stencil_attachment: Option, + input_attachments: Vec, } #[derive(Hash)] -pub struct PassGroupDescription { - index: usize, +pub struct PassDescription { + index: PassIndex, name: &'static str, bind_point: PipelineBindPoint, - begin_passes: usize, - end_passes: usize, // exclusive! + textures: ResourceDeps, + buffers: ResourceDeps, + begin_subpasses: usize, + end_subpasses: usize, // exclusive! } pub struct RenderGraph { @@ -39,19 +48,19 @@ pub struct RenderGraph { was_updated: bool, textures: ResourceSet, buffers: ResourceSet, + subpasses: Vec, + subpasses_exec: Vec>, passes: Vec, - passes_exec: Vec>, - groups: Vec, - topo_order: Vec>, + passes_topo_order: Vec>, } pub struct RenderGraphBuilder { is_reset: bool, textures: ResourceSet, buffers: ResourceSet, + subpasses: Vec, + subpasses_run: Vec>, passes: Vec, - passes_run: Vec>, - groups: Vec, } impl RenderGraph { @@ -63,10 +72,10 @@ impl RenderGraph { was_updated: false, textures: ResourceSet::new(), buffers: ResourceSet::new(), + subpasses: Vec::new(), + subpasses_exec: Vec::new(), passes: Vec::new(), - passes_exec: Vec::new(), - groups: Vec::new(), - topo_order: Vec::new(), + passes_topo_order: Vec::new(), } } @@ -82,45 +91,35 @@ impl RenderGraph { #[inline] pub fn get_num_topological_groups(&self) -> usize { - self.topo_order.len() + self.passes_topo_order.len() } pub fn get_topological_group( &self, group: usize, - ) -> impl Iterator + '_ { - self.topo_order + ) -> impl Iterator + '_ { + self.passes_topo_order .get(group) .into_iter() .flatten() - .map(|g| &self.groups[*g]) + .map(|g| &self.passes[*g]) } - #[inline] - pub fn get_num_passes(&self) -> usize { - self.passes.len() - } - - pub fn get_pass(&self, pass: usize) -> Option<&PassDescription> { - self.passes.get(pass) - } - - #[inline] - pub fn get_num_pass_groups(&self) -> usize { - self.groups.len() + pub fn get_subpass(&self, sub_pass_index: usize) -> Option<&SubPassDescription> { + self.subpasses.get(sub_pass_index) } - pub fn get_pass_group(&self, pass: usize) -> Option<&PassGroupDescription> { - self.groups.get(pass) + pub fn get_pass(&self, pass_index: PassIndex) -> Option<&PassDescription> { + self.passes.get(pass_index as usize) } - pub fn execute_pass( + pub fn execute_sub_pass( &self, - pass: usize, + sub_pass_index: usize, draw_context: DrawContext<'_>, draw_phases: &DrawPhases, ) { - self.passes_exec[pass].execute(draw_context, draw_phases) + self.subpasses_exec[sub_pass_index].execute(draw_context, draw_phases) } } @@ -131,9 +130,9 @@ impl RenderGraphBuilder { is_reset: false, textures: ResourceSet::new(), buffers: ResourceSet::new(), + subpasses: Vec::new(), + subpasses_run: Vec::new(), passes: Vec::new(), - passes_run: Vec::new(), - groups: Vec::new(), } } } @@ -152,15 +151,10 @@ impl Default for RenderGraph { } } -impl PassDescription { - #[inline] - pub const fn index(&self) -> usize { - self.index - } - +impl SubPassDescription { #[inline] - pub const fn group_index(&self) -> usize { - self.group_index + pub const fn pass_index(&self) -> PassIndex { + self.pass_index } #[inline] @@ -169,24 +163,22 @@ impl PassDescription { } #[inline] - pub const fn bind_point(&self) -> PipelineBindPoint { - self.bind_point + pub fn color_attachments(&self) -> &[ResourceIndex] { + &self.color_attachments } - #[inline] - pub const fn textures(&self) -> &ResourceDeps { - &self.textures + pub fn input_attachments(&self) -> &[ResourceIndex] { + &self.input_attachments } - #[inline] - pub const fn buffers(&self) -> &ResourceDeps { - &self.buffers + pub fn depth_stencil_attachment(&self) -> Option { + self.depth_stencil_attachment } } -impl PassGroupDescription { +impl PassDescription { #[inline] - pub const fn group_index(&self) -> usize { + pub const fn index(&self) -> PassIndex { self.index } #[inline] @@ -200,7 +192,17 @@ impl PassGroupDescription { } #[inline] - pub fn range(&self) -> std::ops::Range { - self.begin_passes..self.end_passes + pub const fn textures(&self) -> &ResourceDeps { + &self.textures + } + + #[inline] + pub const fn buffers(&self) -> &ResourceDeps { + &self.buffers + } + + #[inline] + pub fn sub_pass_range(&self) -> std::ops::Range { + self.begin_subpasses..self.end_subpasses } } diff --git a/crates/render/src/graph/pass/builder.rs b/crates/render/src/graph/pass/builder.rs index f746590..65bb858 100644 --- a/crates/render/src/graph/pass/builder.rs +++ b/crates/render/src/graph/pass/builder.rs @@ -1,181 +1,208 @@ use std::marker::PhantomData; -use super::{Graphics, Pass, PassDescription, PassGroup, PipelineType}; -use crate::graph::{ - access::Stage, - resources::{Buffer, BufferUsage, Slot, SlotAccess, Texture, TextureUsage, WriteSlot}, - PassGroupDescription, RenderGraphBuilder, +use super::{Graphics, Pass, PassGroup, PipelineType, SubPassDescription}; +use crate::{ + buffer::{Buffer, BufferUsage}, + graph::{ + access::{ShaderStage, Stage}, + resources::{ResourceDeps, Slot, SlotAccess, WriteSlot}, + PassDescription, PassIndex, RenderGraphBuilder, SubPassIndex, + }, + texture::{Texture, TextureUsage}, }; impl RenderGraphBuilder { - pub fn add_pass>(&mut self, pass_group: P) -> P::Output { + pub fn add_pass(&mut self, pass: P) -> P::Output + where + Q: PipelineType, + P: PassGroup, + { debug_assert!(self.is_reset); - let begin_passes = self.passes.len(); - let group_index = self.groups.len(); - self.groups.push(PassGroupDescription { - index: group_index, - name: pass_group.type_name(), + let begin_subpasses = self.subpasses.len(); + let index = self.passes.len() as PassIndex; + let mut descr = PassDescription { + index, + name: pass.type_name(), bind_point: Q::BIND_POINT, - begin_passes, - end_passes: begin_passes, - }); - - let output = pass_group.build(PassGroupBuilder { - base: self, - group_index, - _pipeline_type: PhantomData, - }); + textures: ResourceDeps::new(), + buffers: ResourceDeps::new(), + begin_subpasses, + end_subpasses: begin_subpasses, + }; + + let output = pass.build(PassGroupBuilder( + PassBuilderIntern { + graph: self, + pass: &mut descr, + current_subpass: 0, + }, + PhantomData, + )); // update end marker - let end_passes = self.passes.len(); - if begin_passes == end_passes { - // group was empty, remove it! - self.groups.pop(); - } else { - self.groups[group_index].end_passes = end_passes; + let end_subpasses = self.subpasses.len(); + // only add pass, if not empty + if begin_subpasses < end_subpasses { + descr.end_subpasses = end_subpasses; + self.passes.push(descr); } output } } -pub struct PassGroupBuilder<'a, Q> { - base: &'a mut RenderGraphBuilder, - group_index: usize, - _pipeline_type: PhantomData, +struct PassBuilderIntern<'a> { + graph: &'a mut RenderGraphBuilder, + pass: &'a mut PassDescription, + current_subpass: u16, } -impl PassGroupBuilder<'_, Q> { + +impl PassBuilderIntern<'_> { #[inline] - pub fn creates_texture(&mut self) -> WriteSlot { - self.base.textures.create() + fn current_subpass(&self) -> SubPassIndex { + (self.pass.index, self.current_subpass) } - #[inline] - pub fn creates_buffer(&mut self) -> WriteSlot { - self.base.buffers.create() + fn writes_texture_intern( + &mut self, + slot: WriteSlot, + stages: Stage, + usage: TextureUsage, + ) -> WriteSlot { + let current_subpass = self.current_subpass(); + assert_ne!( + slot.last_written_by, current_subpass, + "trying to write to a texture multiple times in the same sub-pass" + ); + self.pass.textures.access(&slot, true, stages, usage); + self.graph.textures.writes(slot, current_subpass) + } + + fn reads_texture_intern(&mut self, slot: Slot, stages: Stage, usage: TextureUsage) { + assert_ne!( + slot.last_written_by, + self.current_subpass(), + "trying to read and write a texture in the same sub-pass" + ); + self.pass.textures.access(&slot, false, stages, usage); + self.graph.textures.reads(slot); + } + + fn writes_buffer_intern( + &mut self, + slot: WriteSlot, + stages: Stage, + usage: BufferUsage, + ) -> WriteSlot { + let current_subpass = self.current_subpass(); + assert_ne!( + slot.last_written_by, current_subpass, + "trying to write to a buffer multiple times in the same sub-pass" + ); + self.pass.buffers.access(&slot, true, stages, usage); + self.graph.buffers.writes(slot, current_subpass) + } + + fn reads_buffer_intern(&mut self, slot: Slot, stages: Stage, usage: BufferUsage) { + assert_ne!( + slot.last_written_by, + self.current_subpass(), + "trying to read and write a buffer in the same sub-pass" + ); + self.pass.buffers.access(&slot, false, stages, usage); + self.graph.buffers.reads(slot); } } +pub struct PassGroupBuilder<'a, Q>(PassBuilderIntern<'a>, PhantomData); + impl PassGroupBuilder<'_, Q> { #[inline] - pub(super) fn push_pass>(&mut self, pass: P) -> P::Output { - let index = self.base.passes.len(); - let mut descr = - PassDescription::new(index, self.group_index, pass.type_name(), Q::BIND_POINT); - let (output, run) = pass.build(PassBuilder { - base: self.base, - pass: &mut descr, + pub(super) fn push_sub_pass>(&mut self, sub_pass: P) -> P::Output { + let mut descr = SubPassDescription::new(self.0.pass.index, sub_pass.type_name()); + let (output, run) = sub_pass.build(PassBuilder { + base: PassBuilderIntern { + graph: self.0.graph, + pass: self.0.pass, + current_subpass: self.0.current_subpass, + }, + subpass: &mut descr, _pipeline_type: PhantomData, }); - self.base.passes.push(descr); - self.base.passes_run.push(run.erased()); + self.0.current_subpass += 1; + self.0.graph.subpasses.push(descr); + self.0.graph.subpasses_run.push(run.erased()); output } } impl PassGroupBuilder<'_, Graphics> { #[inline] - pub fn sub_pass>(&mut self, pass: P) -> P::Output { - self.push_pass(pass) + pub fn sub_pass>(&mut self, sub_pass: P) -> P::Output { + self.push_sub_pass(sub_pass) } } pub struct PassBuilder<'a, Q> { - base: &'a mut RenderGraphBuilder, - pass: &'a mut PassDescription, + base: PassBuilderIntern<'a>, + subpass: &'a mut SubPassDescription, _pipeline_type: PhantomData, } impl PassBuilder<'_, Q> { #[inline] - pub fn creates_texture(&mut self, usage: TextureUsage, stages: Stage) -> WriteSlot { - let slot = self.base.textures.create(); - self.writes_texture(slot, stages, usage) + pub fn reads_texture(&mut self, texture: Slot, stages: ShaderStage) { + self.base + .reads_texture_intern(texture, stages.as_stage(), TextureUsage::SAMPLED) } #[inline] - pub fn writes_or_creates_texture( - &mut self, - slot: Option>, - stages: Stage, - usage: TextureUsage, - ) -> WriteSlot { - let slot = slot.unwrap_or_else(|| self.base.textures.create()); - self.writes_texture(slot, stages, usage) + pub fn reads_storage_texture(&mut self, texture: Slot, stages: ShaderStage) { + self.base + .reads_texture_intern(texture, stages.as_stage(), TextureUsage::STORAGE) } - pub fn writes_texture( + #[inline] + pub fn writes_storage_texture( &mut self, - slot: WriteSlot, - stages: Stage, - usage: TextureUsage, + texture: WriteSlot, + stages: ShaderStage, ) -> WriteSlot { - self.pass.textures.access(&slot, true, stages, usage); - let last_written_by_pass = slot.last_written_by_pass as usize; - if last_written_by_pass != self.pass.index { - return self.base.textures.writes(slot, self.pass.index); - } - slot + self.base + .writes_texture_intern(texture, stages.as_stage(), TextureUsage::STORAGE) } #[inline] - pub fn reads_texture(&mut self, slot: Slot, stages: Stage, usage: TextureUsage) { - self.pass.textures.access(&slot, false, stages, usage); - let last_written_by_pass = slot.last_written_by_pass as usize; - if last_written_by_pass != self.pass.index { - self.base.textures.reads(slot); - } + pub fn reads_uniform_buffer(&mut self, buffer: Slot, stages: ShaderStage) { + self.base + .reads_buffer_intern(buffer, stages.as_stage(), BufferUsage::UNIFORM) } #[inline] - pub fn creates_buffer(&mut self, usage: BufferUsage, stages: Stage) -> WriteSlot { - let slot = self.base.buffers.create(); - self.writes_buffer(slot, stages, usage) + pub fn reads_storage_buffer(&mut self, buffer: Slot, stages: ShaderStage) { + self.base + .reads_buffer_intern(buffer, stages.as_stage(), BufferUsage::STORAGE) } #[inline] - pub fn writes_or_creates_buffer( - &mut self, - slot: Option>, - stages: Stage, - usage: BufferUsage, - ) -> WriteSlot { - let slot = slot.unwrap_or_else(|| self.base.buffers.create()); - self.writes_buffer(slot, stages, usage) - } - - pub fn writes_buffer( + pub fn writes_storage_buffer( &mut self, - slot: WriteSlot, - stages: Stage, - usage: BufferUsage, + buffer: WriteSlot, + stages: ShaderStage, ) -> WriteSlot { - self.pass.buffers.access(&slot, true, stages, usage); - let last_written_by_pass = slot.last_written_by_pass as usize; - if last_written_by_pass != self.pass.index { - return self.base.buffers.writes(slot, self.pass.index); - } - slot - } - - pub fn reads_buffer(&mut self, slot: Slot, stages: Stage, usage: BufferUsage) { - self.pass.buffers.access(&slot, false, stages, usage); - let last_written_by_pass = slot.last_written_by_pass as usize; - if last_written_by_pass != self.pass.index { - self.base.buffers.reads(slot); - } + self.base + .writes_buffer_intern(buffer, stages.as_stage(), BufferUsage::STORAGE) } } impl PassBuilder<'_, Graphics> { #[inline] pub fn creates_color_attachment(&mut self) -> WriteSlot { - let slot = self.base.textures.create(); + let slot = self.base.graph.textures.create(); self.color_attachment(slot) } - pub fn color_attachment(&mut self, texture: WriteSlot) -> WriteSlot { - self.pass.color_attachments.push(texture.index()); - self.writes_texture( + self.subpass.color_attachments.push(texture.index()); + self.base.writes_texture_intern( texture, Stage::COLOR_ATTACHMENT_OUTPUT, TextureUsage::COLOR_ATTACHMENT, @@ -184,13 +211,15 @@ impl PassBuilder<'_, Graphics> { #[inline] pub fn creates_depth_stencil_attachment(&mut self) -> WriteSlot { - let slot = self.base.textures.create(); + let slot = self.base.graph.textures.create(); self.depth_stencil_attachment(slot) } pub fn depth_stencil_attachment(&mut self, texture: WriteSlot) -> WriteSlot { - self.pass.depth_stencil_attachments.replace(texture.index()); + self.subpass + .depth_stencil_attachment + .replace(texture.index()); // TODO: support early & late fragment tests - self.writes_texture( + self.base.writes_texture_intern( texture, Stage::FRAGMENT_TESTS, TextureUsage::DEPTH_STENCIL_ATTACHMENT, @@ -198,11 +227,23 @@ impl PassBuilder<'_, Graphics> { } pub fn input_attachment(&mut self, texture: Slot) { - self.pass.input_attachments.push(texture.index()); - self.reads_texture( + self.subpass.input_attachments.push(texture.index()); + self.base.reads_texture_intern( texture, Stage::FRAGMENT_SHADER, TextureUsage::INPUT_ATTACHMENT, ) } + + #[inline] + pub fn reads_vertex_buffer(&mut self, buffer: Slot) { + self.base + .reads_buffer_intern(buffer, Stage::VERTEX_INPUT, BufferUsage::VERTEX) + } + + #[inline] + pub fn reads_index_buffer(&mut self, buffer: Slot) { + self.base + .reads_buffer_intern(buffer, Stage::VERTEX_INPUT, BufferUsage::INDEX) + } } diff --git a/crates/render/src/graph/pass/mod.rs b/crates/render/src/graph/pass/mod.rs index 3906c84..5103f33 100644 --- a/crates/render/src/graph/pass/mod.rs +++ b/crates/render/src/graph/pass/mod.rs @@ -2,27 +2,18 @@ use self::{ builder::{PassBuilder, PassGroupBuilder}, run::PassExec, }; -use super::{resources::ResourceDeps, PassDescription}; +use super::{PassIndex, SubPassDescription}; pub mod builder; pub mod run; -impl PassDescription { - const fn new( - index: usize, - group_index: usize, - name: &'static str, - bind_point: PipelineBindPoint, - ) -> Self { +impl SubPassDescription { + const fn new(pass_index: PassIndex, name: &'static str) -> Self { Self { - index, - group_index, + pass_index, name, - bind_point, - textures: ResourceDeps::new(), - buffers: ResourceDeps::new(), color_attachments: Vec::new(), - depth_stencil_attachments: None, + depth_stencil_attachment: None, input_attachments: Vec::new(), } } @@ -78,7 +69,7 @@ impl> PassGroup for P { type Output = P::Output; #[inline] fn build(self, mut builder: PassGroupBuilder<'_, Q>) -> Self::Output { - builder.push_pass(self) + builder.push_sub_pass(self) } fn type_name(&self) -> &'static str { diff --git a/crates/render/src/graph/pass/run.rs b/crates/render/src/graph/pass/run.rs index 3f3917f..7bd49ac 100644 --- a/crates/render/src/graph/pass/run.rs +++ b/crates/render/src/graph/pass/run.rs @@ -14,6 +14,8 @@ pub trait PassRun: Send + Sync + 'static { } trait PassRunAny: Send + Sync + 'static { + #[inline] + fn import(&self) {} fn run(&self, draw_context: DrawContext<'_>, draw_phases: &DrawPhases); } @@ -40,6 +42,7 @@ where PassRun::::run(&self.0, ctx); } } + pub struct PassExec { run: Box, _phantom: PhantomData, diff --git a/crates/render/src/graph/resources.rs b/crates/render/src/graph/resources.rs index 69b67a4..09eeceb 100644 --- a/crates/render/src/graph/resources.rs +++ b/crates/render/src/graph/resources.rs @@ -9,35 +9,52 @@ use pulz_window::WindowId; use super::{ access::{ResourceAccess, Stage}, - builder::{GraphExport, GraphImport}, + builder::{GetExternalResource, GraphExport, GraphImport}, deps::DependencyMatrix, - PassDescription, + PassIndex, ResourceIndex, SubPassIndex, PASS_UNDEFINED, SUBPASS_UNDEFINED, }; -pub use crate::{ - buffer::{Buffer, BufferUsage}, - texture::{Texture, TextureUsage}, +use crate::{ + camera::RenderTarget, + texture::{Image, Texture}, }; -use crate::{camera::RenderTarget, texture::Image}; #[derive(Copy, Clone)] pub struct Slot { - pub(crate) index: u16, - pub(crate) last_written_by_pass: u16, + pub(crate) index: ResourceIndex, + pub(crate) last_written_by: SubPassIndex, _phantom: PhantomData R>, } +impl std::fmt::Debug for Slot { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let typename = std::any::type_name::(); + f.debug_tuple(&format!("Slot<{typename}>")) + .field(&self.index) + .finish() + } +} + // Not Copy by intention! pub struct WriteSlot(Slot); +impl std::fmt::Debug for WriteSlot { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let typename = std::any::type_name::(); + f.debug_tuple(&format!("WriteSlot<{typename}>")) + .field(&self.0.index) + .finish() + } +} + pub trait SlotAccess { const WRITE: bool; - fn index(&self) -> u16; + fn index(&self) -> ResourceIndex; } impl SlotAccess for Slot { const WRITE: bool = false; #[inline] - fn index(&self) -> u16 { + fn index(&self) -> ResourceIndex { self.index } } @@ -45,7 +62,7 @@ impl SlotAccess for Slot { impl SlotAccess for WriteSlot { const WRITE: bool = true; #[inline] - fn index(&self) -> u16 { + fn index(&self) -> ResourceIndex { self.0.index } } @@ -59,10 +76,10 @@ impl Deref for WriteSlot { } impl Slot { - const fn new(index: usize, last_written_by_pass: u16) -> Self { + const fn new(index: ResourceIndex, last_written_by: SubPassIndex) -> Self { Self { - index: index as u16, - last_written_by_pass, + index, + last_written_by, _phantom: PhantomData, } } @@ -70,8 +87,8 @@ impl Slot { impl WriteSlot { #[inline] - const fn new(index: usize, last_writing_pass: u16) -> Self { - Self(Slot::new(index, last_writing_pass)) + const fn new(index: ResourceIndex, last_written_by: SubPassIndex) -> Self { + Self(Slot::new(index, last_written_by)) } #[inline] pub const fn read(self) -> Slot { @@ -86,128 +103,129 @@ enum ResourceVariant { Export, } -pub(super) struct ResourceSet { - last_written: Vec, - first_written: Vec, - variant: Vec, - _phantom: PhantomData R>, +struct Resource { + first_written: SubPassIndex, + last_written: SubPassIndex, + variant: ResourceVariant, + extern_res: Option>>, } +#[derive(Hash)] +pub(super) struct ResourceSet(Vec>); + impl ResourceSet { pub fn len(&self) -> usize { - self.last_written.len() + self.0.len() } } -impl Hash for ResourceSet { +impl Hash for Resource { fn hash(&self, state: &mut H) { - Hash::hash_slice(&self.last_written, state); - Hash::hash_slice(&self.first_written, state); - Hash::hash_slice(&self.variant, state); + self.first_written.hash(state); + self.last_written.hash(state); + self.variant.hash(state); + // ignore extern_res! } } -pub struct ResourceDeps { - deps: Vec>, -} +pub struct ResourceDeps(Vec>); impl Hash for ResourceDeps { fn hash(&self, state: &mut H) { - Hash::hash_slice(&self.deps, state); + Hash::hash_slice(&self.0, state); } } #[derive(Hash)] -pub struct ResourceDep { - index: u16, - last_written_by_pass: u16, +pub struct ResourceDep { + index: ResourceIndex, + last_written_by_pass: PassIndex, write_access: bool, stages: Stage, - usage: U, + usage: R::Usage, } impl ResourceSet { #[inline] pub(super) const fn new() -> Self { - Self { - last_written: Vec::new(), - first_written: Vec::new(), - variant: Vec::new(), - _phantom: PhantomData, - } + Self(Vec::new()) } pub(super) fn reset(&mut self) { - self.last_written.clear(); - self.first_written.clear(); - self.variant.clear(); + self.0.clear(); } pub(super) fn create(&mut self) -> WriteSlot { - let index = self.last_written.len(); - self.last_written.push(!0); - self.first_written.push(!0); - self.variant.push(ResourceVariant::Transient); - WriteSlot::new(index, !0) + let index = self.0.len() as ResourceIndex; + self.0.push(Resource { + first_written: SUBPASS_UNDEFINED, + last_written: SUBPASS_UNDEFINED, + variant: ResourceVariant::Transient, + extern_res: None, + }); + WriteSlot::new(index, SUBPASS_UNDEFINED) } - pub(super) fn writes(&mut self, slot: WriteSlot, new_pass: usize) -> WriteSlot { - let new_pass = new_pass as u16; - let index = slot.0.index as usize; - let last_written_by_pass = self.last_written[index]; + pub(super) fn writes(&mut self, slot: WriteSlot, new_pass: SubPassIndex) -> WriteSlot { + let r = &mut self.0[slot.0.index as usize]; + let last_written_by_pass = r.last_written; assert_eq!( - last_written_by_pass, slot.0.last_written_by_pass, + last_written_by_pass, slot.0.last_written_by, "resource also written by an other pass (slot out of sync)" ); if new_pass != last_written_by_pass { - self.last_written[index] = new_pass; - if self.first_written[index] == !0 { - self.first_written[index] = new_pass + r.last_written = new_pass; + if r.first_written.0 == PASS_UNDEFINED { + r.first_written = new_pass } } - WriteSlot::new(index, new_pass) + WriteSlot::new(slot.0.index, new_pass) } pub(super) fn reads(&mut self, slot: Slot) { assert_ne!( - slot.last_written_by_pass, !0, + slot.last_written_by.0, PASS_UNDEFINED, "resource was not yet written!" ); - let index = slot.index as usize; - let last_written_by_pass = self.last_written[index]; - // TODO: allow usage of older slots for reading + let r = &self.0[slot.index as usize]; + let last_written_by_pass = r.last_written; + // TODO: allow usage of older slots for reading (Write>Read>Write) assert_eq!( - last_written_by_pass, slot.last_written_by_pass, - "resource also written by an other pass (slot out of sync), TODO!" + last_written_by_pass, slot.last_written_by, + "resource also written by an other pass (slot out of sync)" ); } - pub(super) fn import(&mut self) -> Slot { - let s = self.create(); - let index = s.index as usize; - self.variant[index] = ResourceVariant::Import; - s.read() + pub(super) fn import(&mut self, f: Box>) -> Slot { + let slot = self.create(); + let r = &mut self.0[slot.index as usize]; + r.variant = ResourceVariant::Import; + r.extern_res = Some(f); + slot.read() } - pub(super) fn export(&mut self, slot: Slot) { - let index = slot.index as usize; - assert_eq!(ResourceVariant::Transient, self.variant[index]); - self.variant[index] = ResourceVariant::Export; + pub(super) fn export(&mut self, slot: Slot, f: Box>) { + let r = &mut self.0[slot.index as usize]; + assert_eq!( + ResourceVariant::Transient, + r.variant, + "resource can be exported only once" + ); + // TODO: allow multiple exports by copying resource? + r.variant = ResourceVariant::Export; + r.extern_res = Some(f); } } impl ResourceDeps { #[inline] - pub fn deps(&self) -> &[ResourceDep] { - &self.deps + pub fn deps(&self) -> &[ResourceDep] { + &self.0 } - pub fn find_by_resource_index(&self, resource_index: usize) -> Option<&ResourceDep> { - if let Ok(i) = self - .deps - .binary_search_by_key(&resource_index, |d| d.index as usize) - { - Some(&self.deps[i]) + pub fn find_by_resource_index(&self, resource_index: ResourceIndex) -> Option<&ResourceDep> { + if let Ok(i) = self.0.binary_search_by_key(&resource_index, |d| d.index) { + Some(&self.0[i]) } else { None } @@ -215,28 +233,14 @@ impl ResourceDeps { #[inline] pub(super) const fn new() -> Self { - Self { deps: Vec::new() } - } - - pub(super) fn mark_pass_dependency_matrix(&self, m: &mut DependencyMatrix, to_pass: usize) { - for dep in &self.deps { - let pass_index = dep.src_pass(); - if pass_index != !0 { - m.insert(pass_index, to_pass); - } - } + Self(Vec::new()) } - pub(super) fn mark_group_dependency_matrix( - &self, - m: &mut DependencyMatrix, - passes: &[PassDescription], - to_group: usize, - ) { - for dep in &self.deps { + pub(super) fn mark_pass_dependency_matrix(&self, m: &mut DependencyMatrix, to_pass: PassIndex) { + for dep in &self.0 { let pass_index = dep.src_pass(); if pass_index != !0 { - m.insert(passes[pass_index].group_index, to_group); + m.insert(pass_index as usize, to_pass as usize); } } } @@ -247,44 +251,53 @@ impl ResourceDeps { write_access: bool, stages: Stage, usage: R::Usage, - ) { - match self.deps.binary_search_by_key(&slot.index, |e| e.index) { + ) -> bool { + match self.0.binary_search_by_key(&slot.index, |e| e.index) { Ok(i) => { - let entry = &mut self.deps[i]; - assert_eq!(entry.last_written_by_pass, slot.last_written_by_pass); + let entry = &mut self.0[i]; + assert_eq!(entry.last_written_by_pass, slot.last_written_by.0); entry.write_access |= write_access; entry.stages |= stages; entry.usage |= usage; + if entry.write_access { + R::check_usage_is_pass_compatible(entry.usage); + } + false } Err(i) => { - self.deps.insert( + self.0.insert( i, ResourceDep { index: slot.index, - last_written_by_pass: slot.last_written_by_pass, + last_written_by_pass: slot.last_written_by.0, write_access, stages, usage, }, ); + true } } } } -impl ResourceDep { +impl Deref for ResourceDeps { + type Target = [ResourceDep]; #[inline] - pub fn resource_index(&self) -> usize { - self.index as usize + fn deref(&self) -> &Self::Target { + &self.0 } +} +impl ResourceDep { #[inline] - pub fn src_pass(&self) -> usize { - if self.last_written_by_pass == !0 { - !0 - } else { - self.last_written_by_pass as usize - } + pub fn resource_index(&self) -> ResourceIndex { + self.index + } + + #[inline] + pub fn src_pass(&self) -> PassIndex { + self.last_written_by_pass } #[inline] @@ -293,28 +306,79 @@ impl ResourceDep { } #[inline] - pub fn usage(&self) -> U { + pub fn usage(&self) -> R::Usage { self.usage } #[inline] - pub fn write_access(&self) -> bool { + pub fn is_read(&self) -> bool { + self.last_written_by_pass != !0 + } + + #[inline] + pub fn is_write(&self) -> bool { self.write_access } } +pub struct ResourceAssignments(Vec>); + +impl ResourceAssignments { + #[inline] + pub const fn new() -> Self { + Self(Vec::new()) + } + + #[inline] + pub fn get(&self, resource_index: ResourceIndex) -> Option<(R, &R::Meta)> { + if let Some(Some((r, m))) = self.0.get(resource_index as usize) { + Some((*r, m)) + } else { + None + } + } + + pub(super) fn assign_external_resources(&mut self, res: &ResourceSet) { + assert_eq!(res.0.len(), self.0.len()); + for (i, r) in res.0.iter().enumerate() { + if let Some(ext_res) = &r.extern_res { + self.0[i] = Some(ext_res.get_external_resource()); + } + } + } +} + impl GraphImport for Handle { type Resource = Texture; + + fn import(&self) -> Box> { + todo!("import image handle") + } } impl GraphExport for Handle { type Resource = Texture; + + fn export(&self) -> Box> { + todo!("export image handle") + } } impl GraphExport for WindowId { type Resource = Texture; + + fn export(&self) -> Box> { + todo!("export swapchain image") + } } impl GraphExport for RenderTarget { type Resource = Texture; + + fn export(&self) -> Box> { + match self { + Self::Image(i) => i.export(), + Self::Window(w) => w.export(), + } + } } diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index 3058569..864fdf5 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -70,8 +70,8 @@ impl Module for RenderModule { fn install_once(&self, res: &mut Resources) { Assets::::install_into(res); - res.init::(); - res.init::(); + res.init_unsend::(); + res.init_unsend::(); // TODO: //res.init::(); //render_graph::graph::RenderGraph::install_into(res, schedule); diff --git a/crates/render/src/pipeline/binding.rs b/crates/render/src/pipeline/binding.rs index 76b2771..3143c42 100644 --- a/crates/render/src/pipeline/binding.rs +++ b/crates/render/src/pipeline/binding.rs @@ -1,9 +1,16 @@ +use std::borrow::Cow; + +pub use pulz_render_macros::AsBindingLayout; + +crate::backend::define_gpu_resource!(BindGroupLayout, BindGroupLayoutDescriptor<'l>); + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct BindGroupLayoutDescriptor<'a> { pub label: Option<&'a str>, - pub entries: &'a [BindGroupLayoutEntry], + pub entries: Cow<'a, [BindGroupLayoutEntry]>, } -#[derive(Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct BindGroupLayoutEntry { pub binding: u32, // pub visibility: ShaderStages, @@ -12,8 +19,6 @@ pub struct BindGroupLayoutEntry { pub count: u32, } -pub use pulz_render_macros::AsBindingLayout; - pub trait AsBindingLayout { // TODO (also macro) } diff --git a/crates/render/src/pipeline/compute_pipeline.rs b/crates/render/src/pipeline/compute_pipeline.rs new file mode 100644 index 0000000..214b3b8 --- /dev/null +++ b/crates/render/src/pipeline/compute_pipeline.rs @@ -0,0 +1,15 @@ +use crate::{ + pipeline::{PipelineLayout, SpecializationInfo}, + shader::ShaderModule, +}; + +crate::backend::define_gpu_resource!(ComputePipeline, ComputePipelineDescriptor<'l>); + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ComputePipelineDescriptor<'a> { + pub label: Option<&'a str>, + pub layout: Option, + pub module: ShaderModule, + pub entry_point: &'a str, + pub specialization: SpecializationInfo<'a>, +} diff --git a/crates/render/src/pipeline/graphics_pass.rs b/crates/render/src/pipeline/graphics_pass.rs new file mode 100644 index 0000000..3862955 --- /dev/null +++ b/crates/render/src/pipeline/graphics_pass.rs @@ -0,0 +1,200 @@ +use pulz_transform::math::USize2; + +use crate::{ + graph::{ + pass::PipelineBindPoint, resources::ResourceAssignments, PassDescription, RenderGraph, + ResourceIndex, + }, + texture::{Texture, TextureFormat, TextureUsage}, +}; + +crate::backend::define_gpu_resource!(GraphicsPass, GraphicsPassDescriptor); + +#[derive( + Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, +)] +pub enum LoadOp { + #[default] + Load, + Clear, + DontCare, +} + +#[derive( + Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, +)] +pub enum StoreOp { + #[default] + Store, + DontCare, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)] + +pub struct LoadStoreOps { + pub load_op: LoadOp, + pub store_op: StoreOp, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)] +pub struct AttachmentDescriptor { + pub format: TextureFormat, + pub usage: TextureUsage, + //pub initial_layout: TextureLayout, + pub samples: u8, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] +pub struct SubpassDescriptor { + input_attachments: Vec, + color_attachments: Vec, + depth_stencil_attachment: Option, + //resolve_attachments: Vec, +} + +impl SubpassDescriptor { + #[inline] + pub fn input_attachments(&self) -> &[u16] { + &self.input_attachments + } + #[inline] + pub fn color_attachments(&self) -> &[u16] { + &self.color_attachments + } + #[inline] + pub fn depth_stencil_attachment(&self) -> Option { + self.depth_stencil_attachment + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] +pub struct GraphicsPassDescriptor { + attachments: Vec, + load_store_ops: Vec, + subpasses: Vec, +} + +impl GraphicsPassDescriptor { + #[inline] + pub fn attachments(&self) -> &[AttachmentDescriptor] { + &self.attachments + } + #[inline] + pub fn load_store_ops(&self) -> &[LoadStoreOps] { + &self.load_store_ops + } + #[inline] + pub fn subpasses(&self) -> &[SubpassDescriptor] { + &self.subpasses + } +} + +pub struct GraphicsPassDescriptorWithTextures { + pub graphics_pass: GraphicsPassDescriptor, + pub resource_indices: Vec, + pub textures: Vec, + pub size: USize2, +} + +impl GraphicsPassDescriptorWithTextures { + pub fn from_graph( + graph: &RenderGraph, + pass: &PassDescription, + assignments: &ResourceAssignments, + ) -> Option { + if pass.bind_point() != PipelineBindPoint::Graphics { + return None; + } + let mut attachment_indices = Vec::with_capacity(pass.textures().len()); + for (i, tex) in pass.textures().deps().iter().enumerate() { + if tex.usage().is_attachment() { + attachment_indices.push(i as ResourceIndex); + } + } + + let mut attachments = Vec::with_capacity(attachment_indices.len()); + let mut load_store_ops = Vec::with_capacity(attachment_indices.len()); + let mut textures = Vec::with_capacity(attachment_indices.len()); + let mut size = USize2::ZERO; + for i in attachment_indices.iter().copied() { + let a = &pass.textures()[i as usize]; + let resource_index = a.resource_index(); + let (tex, &(format, dim, samples)) = assignments + .get(resource_index) + .expect("unassigned resource"); + let dim = dim.subimage_extents(); + if size == USize2::ZERO { + size = dim; + } else if size != dim { + // TODO: error handling + panic!("all framebuffer textures need to have the same dimensions"); + } + + textures.push(tex); + + attachments.push(AttachmentDescriptor { + format, + samples, + usage: a.usage(), + }); + load_store_ops.push(LoadStoreOps { + load_op: if a.is_read() { + LoadOp::Load + } else { + // TODO: provide a way to use DONT_CARE or CLEAR + LoadOp::Clear + }, + // TODO: is resource used in later pass? then STORE, else DONT_CARE + store_op: StoreOp::Store, + }); + } + + // map attachment_indices into their actual resource indices + for i in &mut attachment_indices { + // pass.textures() is sorted by resource-index! + *i = pass.textures()[*i as usize].resource_index(); + } + let map_attachment_index = |resource_index: &u16| { + attachment_indices + .binary_search(resource_index) + .expect("unvalid resource index") as u16 + }; + + let mut subpasses = Vec::with_capacity(pass.sub_pass_range().len()); + for sp in pass.sub_pass_range() { + let sp = graph.get_subpass(sp).unwrap(); + let input_attachments = sp + .input_attachments() + .iter() + .map(map_attachment_index) + .collect(); + let color_attachments = sp + .color_attachments() + .iter() + .map(map_attachment_index) + .collect(); + let depth_stencil_attachment = sp + .depth_stencil_attachment() + .as_ref() + .map(map_attachment_index); + subpasses.push(SubpassDescriptor { + input_attachments, + color_attachments, + depth_stencil_attachment, + }) + } + + let graphics_pass = GraphicsPassDescriptor { + attachments, + load_store_ops, + subpasses, + }; + + Some(Self { + graphics_pass, + resource_indices: attachment_indices, + textures, + size, + }) + } +} diff --git a/crates/render/src/pipeline/descriptor.rs b/crates/render/src/pipeline/graphics_pipeline.rs similarity index 68% rename from crates/render/src/pipeline/descriptor.rs rename to crates/render/src/pipeline/graphics_pipeline.rs index 185f136..b753cba 100644 --- a/crates/render/src/pipeline/descriptor.rs +++ b/crates/render/src/pipeline/graphics_pipeline.rs @@ -1,18 +1,19 @@ +use std::{ + borrow::Cow, + hash::{Hash, Hasher}, +}; + use bitflags::bitflags; -use super::PipelineLayout; -use crate::{shader::ShaderModule, texture::TextureFormat}; +use crate::{ + pipeline::{GraphicsPass, PipelineLayout, SpecializationInfo}, + shader::ShaderModule, + texture::TextureFormat, +}; -#[derive(Debug, Clone, PartialEq)] -pub struct ComputePipelineDescriptor<'a> { - pub label: Option<&'a str>, - pub layout: Option, - pub module: ShaderModule, - pub entry_point: &'a str, - pub specialization: SpecializationInfo<'a>, -} +crate::backend::define_gpu_resource!(GraphicsPipeline, GraphicsPipelineDescriptor<'l>); -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct GraphicsPipelineDescriptor<'a> { pub label: Option<&'a str>, pub layout: Option, @@ -22,55 +23,29 @@ pub struct GraphicsPipelineDescriptor<'a> { pub fragment: Option>, pub samples: u32, pub specialization: SpecializationInfo<'a>, + pub graphics_pass: GraphicsPass, } -#[derive(Debug, Clone, PartialEq)] -pub struct RayTracingPipelineDescriptor<'a> { - pub label: Option<&'a str>, - pub layout: Option, - pub modules: Vec>, - pub groups: Vec, - pub max_recursion_depth: u32, - pub specialization: SpecializationInfo<'a>, -} - -pub type SpecializationInfo<'a> = Vec>; - -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct SpecializationMapEntry<'a> { - pub constant_id: u32, - pub name: &'a str, - pub value: PipelineConstantValue, -} - -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum PipelineConstantValue { - Bool(bool), - Float(f32), - Sint(i32), - Uint(u32), -} - -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct VertexState<'a> { pub module: ShaderModule, pub entry_point: &'a str, - pub buffers: &'a [VertexBufferLayout<'a>], + pub buffers: Cow<'a, [VertexBufferLayout<'a>]>, } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct VertexBufferLayout<'a> { pub array_stride: usize, - pub attributes: &'a [VertexAttribute], + pub attributes: Cow<'a, [VertexAttribute]>, } -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct VertexAttribute { pub format: VertexFormat, pub offset: usize, } -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone)] pub struct PrimitiveState { pub topology: PrimitiveTopology, pub polygon_mode: PolygonMode, @@ -95,7 +70,29 @@ impl Default for PrimitiveState { } } -#[derive(Debug, Copy, Clone, PartialEq)] +impl Eq for PrimitiveState {} + +impl PartialEq for PrimitiveState { + fn eq(&self, other: &Self) -> bool { + self.topology.eq(&other.topology) + && self.polygon_mode.eq(&other.polygon_mode) + && self.front_face.eq(&other.front_face) + && self.cull_mode.eq(&other.cull_mode) + && self.line_width.eq(&other.line_width) + } +} + +impl Hash for PrimitiveState { + fn hash(&self, state: &mut H) { + self.topology.hash(state); + self.polygon_mode.hash(state); + self.front_face.hash(state); + self.cull_mode.hash(state); + state.write_u32(self.line_width.to_bits()); + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct DepthStencilState { pub format: TextureFormat, pub depth: DepthState, @@ -104,7 +101,7 @@ pub struct DepthStencilState { impl DepthStencilState { pub const DEFAULT: Self = Self { - format: TextureFormat::DEFAULT, + format: TextureFormat::Depth24PlusStencil8, depth: DepthState::DEFAULT, stencil: StencilState::DEFAULT, }; @@ -124,7 +121,7 @@ impl Default for DepthStencilState { } } -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone)] pub struct DepthState { pub write_enabled: bool, pub compare: CompareFunction, @@ -145,13 +142,35 @@ impl DepthState { }; } +impl Eq for DepthState {} + +impl PartialEq for DepthState { + fn eq(&self, other: &Self) -> bool { + self.write_enabled.eq(&other.write_enabled) + && self.compare.eq(&other.compare) + && self.bias.eq(&other.bias) + && self.bias_slope_scale.eq(&other.bias_slope_scale) + && self.bias_clamp.eq(&other.bias_clamp) + } +} + +impl Hash for DepthState { + fn hash(&self, state: &mut H) { + self.write_enabled.hash(state); + self.compare.hash(state); + state.write_i32(self.bias); + state.write_u32(self.bias_slope_scale.to_bits()); + state.write_u32(self.bias_clamp.to_bits()); + } +} + impl Default for DepthState { fn default() -> Self { Self::DEFAULT } } -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct StencilState { pub front: StencilFaceState, pub back: StencilFaceState, @@ -185,7 +204,7 @@ impl Default for StencilState { } } -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct StencilFaceState { pub compare: CompareFunction, pub fail_op: StencilOperation, @@ -208,14 +227,14 @@ impl Default for StencilFaceState { } } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct FragmentState<'a> { pub module: ShaderModule, pub entry_point: &'a str, - pub targets: &'a [ColorTargetState], + pub targets: Cow<'a, [ColorTargetState]>, } -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct ColorTargetState { pub format: TextureFormat, pub blend: Option, @@ -246,13 +265,13 @@ impl From for ColorTargetState { } } -#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)] pub struct BlendState { pub color: BlendComponent, pub alpha: BlendComponent, } -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct BlendComponent { pub operation: BlendOperation, pub src_factor: BlendFactor, @@ -273,42 +292,9 @@ impl Default for BlendComponent { } } -#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)] -pub struct RayTracingShaderGroup { - pub group_type: RayTracingGroupType, - pub general_shader: u32, - pub closest_hit_shader: u32, - pub any_hit_shader: u32, - pub intersection_shader: u32, -} - -#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)] -pub struct RayTracingShaderModule<'a> { - pub stage: RayTracingStage, - pub module: ShaderModule, - pub entry_point: &'a str, -} - -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)] -pub enum RayTracingStage { - #[default] - Raygen, - AnyHit, - ClosestHit, - Miss, - Intersection, - Callable, -} - -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)] -pub enum RayTracingGroupType { - #[default] - General, - TrianglesHitGroup, - ProceduralHitGroup, -} - -#[derive(Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +#[derive( + Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, +)] pub enum PolygonMode { #[default] Fill, @@ -316,7 +302,9 @@ pub enum PolygonMode { Point, } -#[derive(Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +#[derive( + Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, +)] pub enum PrimitiveTopology { PointList, LineList, @@ -326,14 +314,18 @@ pub enum PrimitiveTopology { TriangleStrip, } -#[derive(Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +#[derive( + Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, +)] pub enum VertexStepMode { #[default] Vertex, Instance, } -#[derive(Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +#[derive( + Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, +)] #[non_exhaustive] pub enum VertexFormat { Uint8x2, @@ -374,14 +366,18 @@ pub enum VertexFormat { Sint32x4, } -#[derive(Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +#[derive( + Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, +)] pub enum IndexFormat { Uint16, #[default] Uint32, } -#[derive(Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +#[derive( + Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, +)] pub enum FrontFace { #[default] CounterClockwise, @@ -394,7 +390,9 @@ pub enum Face { Back, } -#[derive(Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +#[derive( + Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, +)] pub enum CompareFunction { Never, Less, @@ -407,7 +405,9 @@ pub enum CompareFunction { Always, } -#[derive(Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +#[derive( + Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, +)] pub enum StencilOperation { #[default] Keep, diff --git a/crates/render/src/pipeline/mod.rs b/crates/render/src/pipeline/mod.rs index 371028d..7d2d528 100644 --- a/crates/render/src/pipeline/mod.rs +++ b/crates/render/src/pipeline/mod.rs @@ -1,11 +1,12 @@ mod binding; -mod descriptor; +mod compute_pipeline; +mod graphics_pass; +mod graphics_pipeline; mod pipeline_layout; +mod ray_tracing_pipeline; +mod specialization; -pub use self::{binding::*, descriptor::*, pipeline_layout::*}; - -crate::backend::define_gpu_resource!(BindGroupLayout, BindGroupLayoutDescriptor<'l>); -crate::backend::define_gpu_resource!(PipelineLayout, PipelineLayoutDescriptor<'l>); -crate::backend::define_gpu_resource!(ComputePipeline, ComputePipelineDescriptor<'l>); -crate::backend::define_gpu_resource!(GraphicsPipeline, GraphicsPipelineDescriptor<'l>); -crate::backend::define_gpu_resource!(RayTracingPipeline, RayTracingPipelineDescriptor<'l>); +pub use self::{ + binding::*, compute_pipeline::*, graphics_pass::*, graphics_pipeline::*, pipeline_layout::*, + ray_tracing_pipeline::*, specialization::*, +}; diff --git a/crates/render/src/pipeline/pipeline_layout.rs b/crates/render/src/pipeline/pipeline_layout.rs index bed25bd..0662c94 100644 --- a/crates/render/src/pipeline/pipeline_layout.rs +++ b/crates/render/src/pipeline/pipeline_layout.rs @@ -1,6 +1,9 @@ use super::BindGroupLayout; +crate::backend::define_gpu_resource!(PipelineLayout, PipelineLayoutDescriptor<'l>); + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct PipelineLayoutDescriptor<'a> { pub label: Option<&'a str>, - pub bind_group_layouts: &'a [BindGroupLayout], + pub bind_group_layouts: std::borrow::Cow<'a, [BindGroupLayout]>, } diff --git a/crates/render/src/pipeline/ray_tracing_pipeline.rs b/crates/render/src/pipeline/ray_tracing_pipeline.rs new file mode 100644 index 0000000..0570d70 --- /dev/null +++ b/crates/render/src/pipeline/ray_tracing_pipeline.rs @@ -0,0 +1,53 @@ +use std::borrow::Cow; + +use crate::{ + pipeline::{PipelineLayout, SpecializationInfo}, + shader::ShaderModule, +}; + +crate::backend::define_gpu_resource!(RayTracingPipeline, RayTracingPipelineDescriptor<'l>); + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct RayTracingPipelineDescriptor<'a> { + pub label: Option<&'a str>, + pub layout: Option, + pub modules: Cow<'a, [RayTracingShaderModule<'a>]>, + pub groups: Cow<'a, [RayTracingShaderGroup]>, + pub max_recursion_depth: u32, + pub specialization: SpecializationInfo<'a>, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct RayTracingShaderGroup { + pub group_type: RayTracingGroupType, + pub general_shader: u32, + pub closest_hit_shader: u32, + pub any_hit_shader: u32, + pub intersection_shader: u32, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct RayTracingShaderModule<'a> { + pub stage: RayTracingStage, + pub module: ShaderModule, + pub entry_point: &'a str, +} + +#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash)] +pub enum RayTracingStage { + #[default] + Raygen, + AnyHit, + ClosestHit, + Miss, + Intersection, + Callable, +} + +#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash)] +pub enum RayTracingGroupType { + #[default] + General, + TrianglesHitGroup, + ProceduralHitGroup, +} diff --git a/crates/render/src/pipeline/specialization.rs b/crates/render/src/pipeline/specialization.rs new file mode 100644 index 0000000..59f2623 --- /dev/null +++ b/crates/render/src/pipeline/specialization.rs @@ -0,0 +1,55 @@ +use std::hash::{Hash, Hasher}; + +pub type SpecializationInfo<'a> = Vec>; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct SpecializationMapEntry<'a> { + pub constant_id: u32, + pub name: &'a str, + pub value: PipelineConstantValue, +} + +#[derive(Debug, Copy, Clone)] +pub enum PipelineConstantValue { + Bool(bool), + Float(f32), + Sint(i32), + Uint(u32), +} + +impl Eq for PipelineConstantValue {} + +impl PartialEq for PipelineConstantValue { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Bool(v1), Self::Bool(v2)) => v1.eq(v2), + (Self::Float(v1), Self::Float(v2)) => v1.eq(v2), + (Self::Sint(v1), Self::Sint(v2)) => v1.eq(v2), + (Self::Uint(v1), Self::Uint(v2)) => v1.eq(v2), + _ => false, + } + } +} + +impl Hash for PipelineConstantValue { + fn hash(&self, state: &mut H) { + match self { + Self::Bool(v) => { + state.write_u8(2); + v.hash(state); + } + Self::Float(v) => { + state.write_u8(3); + state.write_u32(v.to_bits()); + } + Self::Sint(v) => { + state.write_u8(5); + state.write_i32(*v); + } + Self::Uint(v) => { + state.write_u8(7); + state.write_u32(*v); + } + } + } +} diff --git a/crates/render/src/shader/mod.rs b/crates/render/src/shader/mod.rs index fc47918..d3d1af5 100644 --- a/crates/render/src/shader/mod.rs +++ b/crates/render/src/shader/mod.rs @@ -4,14 +4,17 @@ mod preprocessor; pub use ::encase::*; pub use ::pulz_render_macros::ShaderType; +use serde::{Deserialize, Serialize}; crate::backend::define_gpu_resource!(ShaderModule, ShaderModuleDescriptor<'l>); +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct ShaderModuleDescriptor<'a> { pub label: Option<&'a str>, pub source: ShaderSource<'a>, } +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[non_exhaustive] pub enum ShaderSource<'a> { Wgsl(Cow<'a, str>), diff --git a/crates/render/src/texture/descriptor.rs b/crates/render/src/texture/descriptor.rs index 79e1da8..b913805 100644 --- a/crates/render/src/texture/descriptor.rs +++ b/crates/render/src/texture/descriptor.rs @@ -1,12 +1,13 @@ use bitflags::bitflags; +use pulz_transform::math::{usize2, usize3}; -use crate::math::{uvec2, uvec3, USize2, USize3}; +use crate::math::{USize2, USize3}; #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct TextureDescriptor { pub dimensions: TextureDimensions, pub mip_level_count: u32, - pub sample_count: u32, + pub sample_count: u8, pub format: TextureFormat, pub aspects: TextureAspects, pub usage: TextureUsage, @@ -58,11 +59,11 @@ impl TextureDimensions { #[inline] pub fn extents(&self) -> USize3 { match *self { - Self::D1(len) => uvec3(len, 1, 1), - Self::D2(size) => uvec3(size.x, size.y, 1), - Self::D2Array { size, array_len } => uvec3(size.x, size.y, array_len), - Self::Cube(size) => uvec3(size.x, size.y, 6), - Self::CubeArray { size, array_len } => uvec3(size.x, size.y, array_len * 6), + Self::D1(len) => usize3(len, 1, 1), + Self::D2(size) => usize3(size.x, size.y, 1), + Self::D2Array { size, array_len } => usize3(size.x, size.y, array_len), + Self::Cube(size) => usize3(size.x, size.y, 6), + Self::CubeArray { size, array_len } => usize3(size.x, size.y, array_len * 6), Self::D3(size) => size, } } @@ -70,12 +71,12 @@ impl TextureDimensions { #[inline] pub fn subimage_extents(&self) -> USize2 { match *self { - Self::D1(len) => uvec2(len, 1), + Self::D1(len) => usize2(len, 1), Self::D2(size) => size, Self::D2Array { size, .. } => size, Self::Cube(size) => size, Self::CubeArray { size, .. } => size, - Self::D3(size) => uvec2(size.x, size.y), + Self::D3(size) => usize2(size.x, size.y), } } } @@ -220,9 +221,7 @@ impl TextureFormat { } }) } -} -impl TextureFormat { pub fn aspects(self) -> TextureAspects { match self { Self::Stencil8 => TextureAspects::STENCIL, @@ -262,8 +261,8 @@ bitflags! { pub struct TextureUsage: u32 { const TRANSFER_SRC = 1; const TRANSFER_DST = 2; - const TEXTURE_BINDING = 4; - const STORAGE_BINDING = 8; + const SAMPLED = 4; + const STORAGE = 8; const COLOR_ATTACHMENT = 16; const DEPTH_STENCIL_ATTACHMENT = 32; const INPUT_ATTACHMENT = 64; @@ -272,11 +271,24 @@ bitflags! { const BY_REGION = 128; const NONE = 0; - const ALL_READ = Self::TRANSFER_SRC.bits | Self::TEXTURE_BINDING.bits | Self::INPUT_ATTACHMENT.bits; - const ALL_WRITE = Self::TRANSFER_DST.bits | Self::STORAGE_BINDING.bits | Self::COLOR_ATTACHMENT.bits | Self::DEPTH_STENCIL_ATTACHMENT.bits; + const ALL_READ = Self::TRANSFER_SRC.bits | Self::SAMPLED.bits | Self::INPUT_ATTACHMENT.bits; + const ALL_WRITE = Self::TRANSFER_DST.bits | Self::STORAGE.bits | Self::COLOR_ATTACHMENT.bits | Self::DEPTH_STENCIL_ATTACHMENT.bits; + const ALL_ATTACHMENTS = Self::COLOR_ATTACHMENT.bits | Self::DEPTH_STENCIL_ATTACHMENT.bits | Self::INPUT_ATTACHMENT.bits; } } +impl TextureUsage { + #[inline] + pub const fn is_attachment(self) -> bool { + self.intersects(Self::ALL_ATTACHMENTS) + } + + #[inline] + pub const fn is_non_attachment(self) -> bool { + self.intersects(Self::ALL_ATTACHMENTS.complement()) + } +} + #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)] pub struct ImageDataLayout { pub offset: usize, diff --git a/crates/transform/src/lib.rs b/crates/transform/src/lib.rs index 7e7d67b..fd6f211 100644 --- a/crates/transform/src/lib.rs +++ b/crates/transform/src/lib.rs @@ -33,5 +33,6 @@ pub mod math { pub type Size3 = Vec3; pub type USize2 = UVec2; pub type USize3 = UVec3; + pub use glam::{uvec2 as usize2, uvec3 as usize3, vec2 as size2, vec3 as size3}; } pub mod components; From 0508008502cccb6037c7e3cafb441a3f42c987f8 Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sat, 28 Jan 2023 16:09:28 +0100 Subject: [PATCH 10/36] pipeline serialization & cache --- crates/render-ash/Cargo.toml | 4 + crates/render-ash/src/attachments.rs | 90 ---- crates/render-ash/src/convert.rs | 367 +++++++++++----- crates/render-ash/src/graph.rs | 249 +++-------- crates/render-ash/src/lib.rs | 7 +- crates/render-ash/src/resources.rs | 409 ------------------ crates/render-ash/src/resources/mod.rs | 206 +++++++++ crates/render-ash/src/resources/replay.rs | 206 +++++++++ .../render-ash/src/resources/resource_impl.rs | 321 ++++++++++++++ crates/render-ash/src/resources/traits.rs | 102 +++++ crates/render-wgpu/src/convert.rs | 52 +-- crates/render-wgpu/src/lib.rs | 3 +- crates/render-wgpu/src/resources.rs | 31 +- crates/render/Cargo.toml | 1 + crates/render/src/buffer.rs | 4 +- crates/render/src/lib.rs | 2 + crates/render/src/pipeline/binding.rs | 5 +- .../render/src/pipeline/compute_pipeline.rs | 6 +- crates/render/src/pipeline/graphics_pass.rs | 13 +- .../render/src/pipeline/graphics_pipeline.rs | 54 ++- crates/render/src/pipeline/pipeline_layout.rs | 5 +- .../src/pipeline/ray_tracing_pipeline.rs | 14 +- crates/render/src/pipeline/specialization.rs | 6 +- crates/render/src/texture/descriptor.rs | 11 +- crates/render/src/utils.rs | 248 +++++++++++ 25 files changed, 1503 insertions(+), 913 deletions(-) delete mode 100644 crates/render-ash/src/attachments.rs delete mode 100644 crates/render-ash/src/resources.rs create mode 100644 crates/render-ash/src/resources/mod.rs create mode 100644 crates/render-ash/src/resources/replay.rs create mode 100644 crates/render-ash/src/resources/resource_impl.rs create mode 100644 crates/render-ash/src/resources/traits.rs create mode 100644 crates/render/src/utils.rs diff --git a/crates/render-ash/Cargo.toml b/crates/render-ash/Cargo.toml index 4bbf083..980ef4a 100644 --- a/crates/render-ash/Cargo.toml +++ b/crates/render-ash/Cargo.toml @@ -11,13 +11,17 @@ readme = "README.md" pulz-ecs = { path = "../ecs" } pulz-window = { path = "../window" } pulz-render = { path = "../render" } +pulz-bitset = { path = "../bitset" } thiserror = { workspace = true } tracing = { workspace = true } bitflags = { workspace = true } slotmap = { workspace = true } +serde = { workspace = true } +fnv = { workspace = true } raw-window-handle = { workspace = true } ash = "0.37" +scratchbuffer = "0.1.0-alpha.1" gpu-alloc = "0.5" gpu-alloc-ash = "0.5" gpu-descriptor = "0.2" diff --git a/crates/render-ash/src/attachments.rs b/crates/render-ash/src/attachments.rs deleted file mode 100644 index 166dd38..0000000 --- a/crates/render-ash/src/attachments.rs +++ /dev/null @@ -1,90 +0,0 @@ -use ash::vk; -use bitflags::bitflags; - -bitflags!( - pub struct AttachmentOps: u8 { - const LOAD = 1 << 0; - const STORE = 1 << 1; - } -); - -impl AttachmentOps { - #[inline] - pub fn load_op(self) -> vk::AttachmentLoadOp { - if self.contains(Self::LOAD) { - vk::AttachmentLoadOp::LOAD - } else { - vk::AttachmentLoadOp::CLEAR - } - } - #[inline] - pub fn store_op(self) -> vk::AttachmentStoreOp { - if self.contains(Self::STORE) { - vk::AttachmentStoreOp::STORE - } else { - vk::AttachmentStoreOp::DONT_CARE - } - } -} - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ColorAttachmentKey { - pub format: vk::Format, - pub layout: vk::ImageLayout, - pub samples: u8, - pub ops: AttachmentOps, - pub resolve_ops: AttachmentOps, -} - -impl ColorAttachmentKey { - pub fn attachment_base_desc(&self) -> vk::AttachmentDescription { - vk::AttachmentDescription::builder() - .format(self.format) - .samples(vk::SampleCountFlags::from_raw(self.samples as u32)) - .load_op(self.ops.load_op()) - .store_op(self.ops.store_op()) - .initial_layout(self.layout) - .final_layout(self.layout) - .build() - } - pub fn attachment_resolve_desc(&self) -> Option { - if self.resolve_ops.is_empty() { - None - } else { - Some( - vk::AttachmentDescription::builder() - .format(self.format) - .samples(vk::SampleCountFlags::TYPE_1) - .load_op(self.resolve_ops.load_op()) - .store_op(self.resolve_ops.store_op()) - .initial_layout(self.layout) - .final_layout(self.layout) - .build(), - ) - } - } -} - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DepthStencilAttachmentKey { - pub format: vk::Format, - pub layout: vk::ImageLayout, - pub samples: u8, - pub depth_ops: AttachmentOps, - pub stencil_ops: AttachmentOps, -} - -impl DepthStencilAttachmentKey { - pub fn attachment_desc(&self) -> vk::AttachmentDescription { - vk::AttachmentDescription::builder() - .format(self.format) - .samples(vk::SampleCountFlags::from_raw(self.samples as u32)) - .load_op(self.depth_ops.load_op()) - .store_op(self.depth_ops.store_op()) - .stencil_load_op(self.stencil_ops.load_op()) - .stencil_store_op(self.stencil_ops.store_op()) - .initial_layout(self.layout) - .final_layout(self.layout) - .build() - } -} diff --git a/crates/render-ash/src/convert.rs b/crates/render-ash/src/convert.rs index 427d2b7..5c5949f 100644 --- a/crates/render-ash/src/convert.rs +++ b/crates/render-ash/src/convert.rs @@ -1,18 +1,19 @@ -use std::marker::PhantomData; - use ash::vk; +use pulz_bitset::BitSet; use pulz_render::{ buffer::{BufferDescriptor, BufferUsage}, graph::{access::Stage, pass::PipelineBindPoint}, math::{USize2, USize3}, pipeline::{ BindGroupLayoutDescriptor, BlendFactor, BlendOperation, CompareFunction, - ComputePipelineDescriptor, DepthStencilState, Face, FrontFace, GraphicsPipelineDescriptor, - IndexFormat, PipelineLayoutDescriptor, PrimitiveState, PrimitiveTopology, - RayTracingPipelineDescriptor, StencilFaceState, StencilOperation, VertexFormat, + ComputePipelineDescriptor, DepthStencilState, Face, FrontFace, GraphicsPassDescriptor, + GraphicsPipelineDescriptor, IndexFormat, LoadOp, PipelineLayoutDescriptor, PrimitiveState, + PrimitiveTopology, RayTracingPipelineDescriptor, StencilFaceState, StencilOperation, + StoreOp, VertexFormat, }, texture::{TextureAspects, TextureDescriptor, TextureDimensions, TextureFormat, TextureUsage}, }; +use scratchbuffer::ScratchBuffer; use crate::resources::AshResources; @@ -101,7 +102,7 @@ impl VkFrom for vk::ImageCreateInfo { .extent(val.dimensions.vk_into()) .array_layers(get_array_layers(&val.dimensions)) .mip_levels(val.mip_level_count) - .samples(vk::SampleCountFlags::from_raw(val.sample_count)) + .samples(vk::SampleCountFlags::from_raw(val.sample_count as u32)) .usage(val.usage.vk_into()) .tiling(vk::ImageTiling::OPTIMAL) .sharing_mode(vk::SharingMode::EXCLUSIVE) @@ -450,10 +451,10 @@ impl VkFrom for vk::ImageUsageFlags { if val.contains(TextureUsage::TRANSFER_DST) { result |= Self::TRANSFER_DST; } - if val.contains(TextureUsage::TEXTURE_BINDING) { + if val.contains(TextureUsage::SAMPLED) { result |= Self::SAMPLED; } - if val.contains(TextureUsage::STORAGE_BINDING) { + if val.contains(TextureUsage::STORAGE) { result |= Self::STORAGE; } if val.contains(TextureUsage::COLOR_ATTACHMENT) { @@ -474,9 +475,7 @@ pub fn into_texture_usage_read_access(usage: TextureUsage) -> vk::AccessFlags { if usage.contains(TextureUsage::TRANSFER_SRC) | usage.contains(TextureUsage::TRANSFER_DST) { result |= vk::AccessFlags::TRANSFER_READ; } - if usage.contains(TextureUsage::TEXTURE_BINDING) - || usage.contains(TextureUsage::STORAGE_BINDING) - { + if usage.contains(TextureUsage::SAMPLED) || usage.contains(TextureUsage::STORAGE) { result |= vk::AccessFlags::SHADER_READ; } if usage.contains(TextureUsage::COLOR_ATTACHMENT) { @@ -496,9 +495,7 @@ pub fn into_texture_usage_write_access(usage: TextureUsage) -> vk::AccessFlags { if usage.contains(TextureUsage::TRANSFER_SRC) | usage.contains(TextureUsage::TRANSFER_DST) { result |= vk::AccessFlags::TRANSFER_WRITE; } - if usage.contains(TextureUsage::TEXTURE_BINDING) - || usage.contains(TextureUsage::STORAGE_BINDING) - { + if usage.contains(TextureUsage::STORAGE) { result |= vk::AccessFlags::SHADER_WRITE; } if usage.contains(TextureUsage::COLOR_ATTACHMENT) { @@ -512,7 +509,7 @@ pub fn into_texture_usage_write_access(usage: TextureUsage) -> vk::AccessFlags { pub fn into_buffer_usage_read_access(usage: BufferUsage) -> vk::AccessFlags { let mut result = vk::AccessFlags::empty(); - if usage.contains(BufferUsage::MAP_READ) | usage.contains(BufferUsage::MAP_WRITE) { + if usage.contains(BufferUsage::HOST) { result |= vk::AccessFlags::HOST_READ; } if usage.contains(BufferUsage::TRANSFER_SRC) | usage.contains(BufferUsage::TRANSFER_DST) { @@ -538,7 +535,7 @@ pub fn into_buffer_usage_read_access(usage: BufferUsage) -> vk::AccessFlags { pub fn into_buffer_usage_write_access(usage: BufferUsage) -> vk::AccessFlags { let mut result = vk::AccessFlags::empty(); - if usage.contains(BufferUsage::MAP_READ) | usage.contains(BufferUsage::MAP_WRITE) { + if usage.contains(BufferUsage::HOST) { result |= vk::AccessFlags::HOST_WRITE; } if usage.contains(BufferUsage::TRANSFER_SRC) | usage.contains(BufferUsage::TRANSFER_DST) { @@ -779,131 +776,261 @@ impl VkFrom for vk::StencilOp { } } -pub struct CreateInfoBuffer { - buf: *mut u8, - len_bytes: usize, - capacity: usize, - _phantom: PhantomData, -} - -impl CreateInfoBuffer { +impl VkFrom for vk::AttachmentLoadOp { #[inline] - pub const fn new() -> Self { - Self { - buf: std::ptr::null_mut(), - len_bytes: 0, - capacity: 0, - _phantom: PhantomData, + fn from(val: &LoadOp) -> Self { + match val { + LoadOp::Load => Self::LOAD, + LoadOp::Clear => Self::CLEAR, + LoadOp::DontCare => Self::DONT_CARE, } } - - fn reset_as(&mut self) -> &mut CreateInfoBuffer { - assert!(std::mem::align_of::() <= std::mem::align_of::()); - assert_ne!(0, std::mem::size_of::()); - self.len_bytes = 0; - unsafe { std::mem::transmute(self) } - } } -impl CreateInfoBuffer { +impl VkFrom for vk::AttachmentStoreOp { #[inline] - pub fn len(&self) -> usize { - self.len_bytes / std::mem::size_of::() + fn from(val: &StoreOp) -> Self { + match val { + StoreOp::Store => Self::STORE, + StoreOp::DontCare => Self::DONT_CARE, + } } +} - #[inline] - pub fn is_empty(&self) -> bool { - self.len_bytes == 0 - } +pub struct CreateInfoConverter6( + ScratchBuffer, + ScratchBuffer, + ScratchBuffer, + ScratchBuffer, + ScratchBuffer, + ScratchBuffer, +); + +pub struct CreateInfoConverter2(ScratchBuffer, ScratchBuffer); + +fn get_or_create_subpass_dep( + buf: &mut ScratchBuffer, + src: u32, + dst: u32, + src_access_mask: vk::AccessFlags, +) -> &mut vk::SubpassDependency { + buf.binary_search_insert_by_key_with( + &(src, dst), + |d| (d.src_subpass, d.dst_subpass), + || { + vk::SubpassDependency::builder() + .src_subpass(src) + .dst_subpass(dst) + .src_stage_mask( + if src_access_mask.contains(vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE) { + vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS + | vk::PipelineStageFlags::LATE_FRAGMENT_TESTS + } else { + vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT + }, + ) + .src_access_mask(src_access_mask) + // use BY-REGION by default + .dependency_flags(vk::DependencyFlags::BY_REGION) + .build() + }, + ) +} - pub fn reserve(&mut self, _num_items: usize) { - let value_len = std::mem::size_of::(); - let new_len = self.len_bytes + value_len * 2; - if new_len > self.capacity { - let new_capacity = new_len.next_power_of_two(); - unsafe { - if self.buf.is_null() { - let layout = std::alloc::Layout::from_size_align( - new_capacity, - std::mem::align_of::(), - ) - .unwrap(); - self.buf = std::alloc::alloc(layout); - } else { - let layout = std::alloc::Layout::from_size_align( - self.capacity, - std::mem::align_of::(), - ) - .unwrap(); - self.buf = std::alloc::realloc(self.buf, layout, new_capacity) +impl CreateInfoConverter6 { + #[inline] + pub const fn new() -> Self { + Self( + ScratchBuffer::new(), + ScratchBuffer::new(), + ScratchBuffer::new(), + ScratchBuffer::new(), + ScratchBuffer::new(), + ScratchBuffer::new(), + ) + } + + pub fn graphics_pass(&mut self, desc: &GraphicsPassDescriptor) -> &vk::RenderPassCreateInfo { + // collect attachments + let attachments = self.0.clear_and_use_as::(); + let num_attachments = desc.attachments().len(); + attachments.reserve(num_attachments); + let mut attachment_dep_data = Vec::with_capacity(num_attachments); + for (i, a) in desc.attachments().iter().enumerate() { + let load_store_ops = desc.load_store_ops().get(i).copied().unwrap_or_default(); + attachments.push( + vk::AttachmentDescription::builder() + .format(a.format.vk_into()) + .samples(vk::SampleCountFlags::from_raw(a.samples as u32)) + .load_op(load_store_ops.load_op.vk_into()) + .store_op(load_store_ops.store_op.vk_into()) + .stencil_load_op(load_store_ops.load_op.vk_into()) + .stencil_store_op(load_store_ops.store_op.vk_into()) + // TODO: initial_layout if load-op == LOAD + .initial_layout(vk::ImageLayout::UNDEFINED) + .final_layout(vk::ImageLayout::GENERAL) + .build(), + ); + attachment_dep_data.push((vk::SUBPASS_EXTERNAL, vk::AccessFlags::NONE)); + } + + // calculate subpass deps + let sp_deps = self.1.clear_and_use_as::(); + let num_subpasses = desc.subpasses().len(); + let mut attachment_usage = BitSet::with_capacity_for(num_attachments * num_subpasses); + for (i, sp) in desc.subpasses().iter().enumerate() { + // TODO: handle Write>Read>Write! + let dst = i as u32; + for &a in sp.input_attachments() { + let a = a as usize; + attachments[a].final_layout = vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL; + let (src, src_access) = attachment_dep_data[a]; + if src != vk::SUBPASS_EXTERNAL { + let dep = get_or_create_subpass_dep(sp_deps, src, dst, src_access); + dep.dst_stage_mask |= vk::PipelineStageFlags::FRAGMENT_SHADER; + dep.dst_access_mask |= vk::AccessFlags::INPUT_ATTACHMENT_READ; } + attachment_usage.insert(i * num_attachments + a); + } + for &a in sp.color_attachments() { + let a = a as usize; + attachments[a].final_layout = vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL; + let (src, src_access) = attachment_dep_data[a]; + if src != vk::SUBPASS_EXTERNAL { + let dep = get_or_create_subpass_dep(sp_deps, src, dst, src_access); + dep.dst_stage_mask |= vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT; + dep.dst_access_mask |= vk::AccessFlags::COLOR_ATTACHMENT_READ; + } + attachment_dep_data[a] = (dst, vk::AccessFlags::COLOR_ATTACHMENT_WRITE); + attachment_usage.insert(i * num_attachments + a); + } + if let Some(a) = sp.depth_stencil_attachment() { + let a = a as usize; + attachments[a].final_layout = vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + let (src, src_access) = attachment_dep_data[a]; + if src != vk::SUBPASS_EXTERNAL { + let dep = get_or_create_subpass_dep(sp_deps, src, dst, src_access); + dep.dst_stage_mask |= vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS + | vk::PipelineStageFlags::LATE_FRAGMENT_TESTS; + dep.dst_access_mask |= vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ; + } + attachment_dep_data[a] = (dst, vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE); + attachment_usage.insert(i * num_attachments + a); } - self.capacity = new_capacity; } - } + drop(attachment_dep_data); - pub fn push(&mut self, value: T) -> &mut T { - self.reserve(1); - let value_len = std::mem::size_of::(); - unsafe { - let ptr = self.buf.add(self.len_bytes) as *mut T; - std::ptr::write(ptr, value); - self.len_bytes += value_len; - &mut *ptr + // collect references + let a_refs = self.2.clear_and_use_as::(); + for s in desc.subpasses() { + for &a in s.input_attachments() { + a_refs.push(vk::AttachmentReference { + attachment: a as u32, // if a==!0 => vk::ATTACHMENT_UNUSED + layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, + }); + } + for &a in s.color_attachments() { + a_refs.push(vk::AttachmentReference { + attachment: a as u32, // if a==!0 => vk::ATTACHMENT_UNUSED + layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, + }); + } + if let Some(a) = s.depth_stencil_attachment() { + a_refs.push(vk::AttachmentReference { + attachment: a as u32, + layout: vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + }); + } } - } - pub fn as_slice(&self) -> &[T] { - let len = self.len(); - if len == 0 { - return &[]; - } - unsafe { - let ptr = self.buf as *const T; - std::slice::from_raw_parts(ptr, len) - } - } - pub fn as_slice_mut(&mut self) -> &mut [T] { - let len = self.len(); - if len == 0 { - return &mut []; - } - unsafe { - let ptr = self.buf as *mut T; - std::slice::from_raw_parts_mut(ptr, len) + // preserve attachment + let mut a_preserve_tmp: Vec> = Vec::new(); + a_preserve_tmp.resize_with(desc.subpasses().len(), Vec::new); + loop { + let mut changed = false; + for dep in sp_deps.iter() { + if dep.src_subpass == vk::SUBPASS_EXTERNAL + || dep.dst_subpass == vk::SUBPASS_EXTERNAL + { + continue; + } + let src = dep.src_subpass as usize; + let dst = dep.dst_subpass as usize; + // There is a subpass dependency from S1 (`src`) to S (`dst`). + let a_start = src * num_subpasses; + let a_preserve_tmp_offset = a_preserve_tmp[dst].len(); + for a in attachment_usage.iter_range(a_start..a_start + num_attachments) { + // There is a subpass S1 that uses or preserves the attachment (`a`) + if !attachment_usage.contains(dst * num_attachments + a) { + // The attachment is not used or preserved in subpass S. + a_preserve_tmp[dst].push(a as u32); + changed = true; + } + } + for &a in &a_preserve_tmp[dst][a_preserve_tmp_offset..] { + // mark as used (perserved) + attachment_usage.insert(dst * num_attachments + a as usize); + } + } + if !changed { + break; + } } - } -} - -impl Drop for CreateInfoBuffer { - fn drop(&mut self) { - if !self.buf.is_null() { - let layout = - std::alloc::Layout::from_size_align(self.capacity, std::mem::align_of::()) - .unwrap(); - unsafe { - std::alloc::dealloc(self.buf, layout); + let a_preserves = self.3.clear_and_use_as::(); + for a in a_preserve_tmp.iter().flatten().copied() { + a_preserves.push(a); + } + + let a_refs = a_refs.as_slice(); + let a_preserves = a_preserves.as_slice(); + let subpasses = self.4.clear_and_use_as::(); + let mut a_refs_offset = 0; + let mut a_preserves_offset = 0; + for (i, s) in desc.subpasses().iter().enumerate() { + let end_input_offset = a_refs_offset + s.input_attachments().len(); + let end_color_offset = end_input_offset + s.color_attachments().len(); + let end_preserves_offset = a_preserves_offset + a_preserve_tmp[i].len(); + let mut b = vk::SubpassDescription::builder() + .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS) + .preserve_attachments(&a_preserves[a_preserves_offset..end_preserves_offset]) + .input_attachments(&a_refs[a_refs_offset..end_input_offset]) + .color_attachments(&a_refs[end_input_offset..end_color_offset]); + // TODO: resolve + a_refs_offset = end_color_offset; + a_preserves_offset = end_preserves_offset; + if s.depth_stencil_attachment().is_some() { + a_refs_offset += 1; + b = b.depth_stencil_attachment(&a_refs[end_color_offset]); } - self.buf = std::ptr::null_mut(); + subpasses.push(b.build()); } + // TODO: subpass dependencies + + let buf = self.5.clear_and_use_as::(); + buf.reserve(1); + buf.push( + vk::RenderPassCreateInfo::builder() + .attachments(attachments.as_slice()) + .subpasses(subpasses.as_slice()) + .build(), + ); + &buf.as_slice()[0] } } -pub struct CreateInfoConverter(CreateInfoBuffer, CreateInfoBuffer); - -impl CreateInfoConverter { +impl CreateInfoConverter2 { #[inline] pub const fn new() -> Self { - Self(CreateInfoBuffer::new(), CreateInfoBuffer::new()) + Self(ScratchBuffer::new(), ScratchBuffer::new()) } pub fn bind_group_layout( &mut self, desc: &BindGroupLayoutDescriptor<'_>, ) -> &vk::DescriptorSetLayoutCreateInfo { - let buf0 = self.0.reset_as::(); + let buf0 = self.0.clear_and_use_as::(); buf0.reserve(desc.entries.len()); - for e in desc.entries { + for e in desc.entries.as_ref() { buf0.push( vk::DescriptorSetLayoutBinding::builder() .binding(e.binding) @@ -914,7 +1041,9 @@ impl CreateInfoConverter { todo!(); } - let buf = self.1.reset_as::(); + let buf = self + .1 + .clear_and_use_as::(); buf.reserve(1); buf.push( vk::DescriptorSetLayoutCreateInfo::builder() @@ -929,13 +1058,13 @@ impl CreateInfoConverter { res: &AshResources, desc: &PipelineLayoutDescriptor<'_>, ) -> &vk::PipelineLayoutCreateInfo { - let buf0 = self.0.reset_as::(); + let buf0 = self.0.clear_and_use_as::(); buf0.reserve(desc.bind_group_layouts.len()); - for bgl in desc.bind_group_layouts { + for bgl in desc.bind_group_layouts.as_ref() { buf0.push(res.bind_group_layouts[*bgl]); } - let buf = self.1.reset_as::(); + let buf = self.1.clear_and_use_as::(); buf.reserve(1); buf.push( vk::PipelineLayoutCreateInfo::builder() @@ -950,7 +1079,7 @@ impl CreateInfoConverter { res: &AshResources, descs: &[GraphicsPipelineDescriptor<'_>], ) -> &[vk::GraphicsPipelineCreateInfo] { - let buf = self.0.reset_as::(); + let buf = self.0.clear_and_use_as::(); buf.reserve(descs.len()); for desc in descs { let layout = desc @@ -972,7 +1101,7 @@ impl CreateInfoConverter { res: &AshResources, descs: &[ComputePipelineDescriptor<'_>], ) -> &[vk::ComputePipelineCreateInfo] { - let buf = self.0.reset_as::(); + let buf = self.0.clear_and_use_as::(); buf.reserve(descs.len()); for desc in descs { let layout = desc @@ -994,7 +1123,9 @@ impl CreateInfoConverter { res: &AshResources, descs: &[RayTracingPipelineDescriptor<'_>], ) -> &[vk::RayTracingPipelineCreateInfoKHR] { - let buf = self.0.reset_as::(); + let buf = self + .0 + .clear_and_use_as::(); buf.reserve(descs.len()); for desc in descs { let layout = desc diff --git a/crates/render-ash/src/graph.rs b/crates/render-ash/src/graph.rs index d7d32b8..ab37b42 100644 --- a/crates/render-ash/src/graph.rs +++ b/crates/render-ash/src/graph.rs @@ -1,24 +1,17 @@ use std::sync::Arc; -use ash::vk::{self, SubpassDependency}; +use ash::vk::{self}; use pulz_render::{ - buffer::BufferUsage, draw::DrawPhases, - graph::{ - pass::PipelineBindPoint, resources::ResourceDep, PassDescription, PassGroupDescription, - RenderGraph, - }, - texture::TextureUsage, + graph::{pass::PipelineBindPoint, resources::ResourceAssignments, PassIndex, RenderGraph}, + pipeline::{GraphicsPass, GraphicsPassDescriptorWithTextures}, }; use crate::{ - convert::{ - into_buffer_usage_read_access, into_buffer_usage_write_access, - into_texture_usage_read_access, into_texture_usage_write_access, VkInto, - }, device::AshDevice, drop_guard::Guard, encoder::{AshCommandPool, SubmissionGroup}, + resources::AshResources, Result, }; @@ -31,9 +24,9 @@ pub struct AshRenderGraph { #[derive(Default)] pub struct TopoGroup { - render_passes: Vec<(usize, vk::RenderPass, vk::Framebuffer)>, // group-index - compute_passes: Vec, // pass-index - ray_tracing_passes: Vec, // pass-index + render_passes: Vec<(PassIndex, vk::RenderPass, vk::Framebuffer)>, // pass-index + compute_passes: Vec, // sub-pass-index + ray_tracing_passes: Vec, // sub-pass-index } pub struct Barrier { @@ -48,7 +41,7 @@ unsafe impl Sync for Barrier {} impl AshRenderGraph { #[inline] - pub fn create(device: &Arc) -> Self { + pub fn new(device: &Arc) -> Self { Self { device: device.clone(), hash: 0, @@ -72,179 +65,38 @@ impl AshRenderGraph { self.barriers.clear(); } - pub fn update(&mut self, src_graph: &RenderGraph) { + pub fn update(&mut self, src_graph: &RenderGraph, res: &mut AshResources) -> Result { + // TODO: update render-pass, if resource-formats changed + // TODO: update framebuffer if render-pass or dimensions changed if src_graph.was_updated() || self.hash != src_graph.hash() { - self.force_update(src_graph); - } - } - - fn create_render_pass<'d>( - device: &'d AshDevice, - src_graph: &RenderGraph, - group: &PassGroupDescription, - ) -> Result> { - let range = group.range(); - let attachments = Vec::new(); - let mut subpasses = Vec::with_capacity(range.len()); - let mut dependencies = Vec::new(); - - fn map_pass_index_to_subpass_index(group: &PassGroupDescription, pass_index: usize) -> u32 { - let range = group.range(); - if range.contains(&pass_index) { - (pass_index - range.start) as u32 - } else { - vk::SUBPASS_EXTERNAL - } - } - - fn get_subpass_dep<'l>( - deps: &'l mut Vec, - group: &PassGroupDescription, - src_pass: usize, - dst_pass: usize, - ) -> &'l mut SubpassDependency { - let src = map_pass_index_to_subpass_index(group, src_pass); - let dst = map_pass_index_to_subpass_index(group, dst_pass); - match deps.binary_search_by_key(&(src, dst), |d| (d.src_subpass, d.dst_subpass)) { - Ok(i) => &mut deps[i], - Err(i) => { - deps.insert( - i, - SubpassDependency::builder() - .src_subpass(src) - .dst_subpass(dst) - // use BY-REGION by default - .dependency_flags(vk::DependencyFlags::BY_REGION) - .build(), - ); - &mut deps[i] - } - } - } - - fn get_texture_access(dep: &ResourceDep, dst: bool) -> vk::AccessFlags { - let reads = dep.src_pass() != !0; // resource was written in a different pass - let writes = dep.write_access(); - let usage = dep.usage(); - let mut result = vk::AccessFlags::empty(); - if reads && (dst || !writes) { - result |= into_texture_usage_read_access(usage); - } - if writes { - result |= into_texture_usage_write_access(usage); - } - result - } - - fn get_buffer_access(dep: &ResourceDep, _dst: bool) -> vk::AccessFlags { - let reads = dep.src_pass() != !0; // resource was written in a different pass - let writes = dep.write_access(); - let usage = dep.usage(); - let mut result = vk::AccessFlags::empty(); - if reads { - result |= into_buffer_usage_read_access(usage); - } - if writes { - result |= into_buffer_usage_write_access(usage); - } - result - } - - fn add_subpass_deps( - src_graph: &RenderGraph, - deps: &mut Vec, - group: &PassGroupDescription, - pass: &PassDescription, - ) { - let dst_pass = pass.index(); - for tex_dep in pass.textures().deps() { - if tex_dep.src_pass() != !0 { - let usage = tex_dep.usage(); - let dst_dep = get_subpass_dep(deps, group, tex_dep.src_pass(), dst_pass); - if !usage.contains(TextureUsage::BY_REGION) { - // remove by-region - dst_dep.dependency_flags &= !vk::DependencyFlags::BY_REGION; - } - dst_dep.dst_stage_mask |= tex_dep.stages().vk_into(); - dst_dep.dst_access_mask |= get_texture_access(tex_dep, true); - - let tex_src = src_graph - .get_pass(tex_dep.src_pass()) - .unwrap() - .textures() - .find_by_resource_index(tex_dep.resource_index()) - .unwrap(); - dst_dep.src_stage_mask |= tex_src.stages().vk_into(); - dst_dep.src_access_mask |= get_texture_access(tex_src, false); - } - } - for buf_dep in pass.buffers().deps() { - if buf_dep.src_pass() != !0 { - let dst_dep = get_subpass_dep(deps, group, buf_dep.src_pass(), dst_pass); - dst_dep.dst_stage_mask |= buf_dep.stages().vk_into(); - dst_dep.dst_access_mask |= get_buffer_access(buf_dep, true); - - let buf_src = src_graph - .get_pass(buf_dep.src_pass()) - .unwrap() - .buffers() - .find_by_resource_index(buf_dep.resource_index()) - .unwrap(); - dst_dep.src_stage_mask |= buf_src.stages().vk_into(); - dst_dep.src_access_mask |= get_buffer_access(buf_src, false); - } - } - } - - for pass_index in range { - let pass = src_graph.get_pass(pass_index).unwrap(); - subpasses.push( - vk::SubpassDescription::builder() - .pipeline_bind_point(pass.bind_point().vk_into()) - // TODO: attachments - .build(), - ); - add_subpass_deps(src_graph, &mut dependencies, group, pass); - } - - let create_info = vk::RenderPassCreateInfo::builder() - .attachments(&attachments) - .subpasses(&subpasses) - .dependencies(&dependencies); - - unsafe { - let pass = device.create(&create_info.build())?; - if let Ok(debug_utils) = device.instance().ext_debug_utils() { - debug_utils.object_name(device.handle(), pass.raw(), group.name()); - } - Ok(pass) + self.force_update(src_graph, res)?; + Ok(true) + } else { + Ok(false) } } fn create_framebuffer<'d>( - device: &'d AshDevice, - _src_graph: &RenderGraph, - group: &PassGroupDescription, + res: &'d mut AshResources, + descr: &GraphicsPassDescriptorWithTextures, render_pass: vk::RenderPass, ) -> Result> { + // TODO: caching? + let image_views: Vec<_> = descr.textures.iter().map(|&t| res[t].1).collect(); let create_info = vk::FramebufferCreateInfo::builder() .render_pass(render_pass) // TODO - // .attachments() - .width(800) - .height(600) + .attachments(&image_views) + .width(descr.size.x) + .height(descr.size.y) .layers(1); - unsafe { - let fb = device.create(&create_info.build())?; - if let Ok(debug_utils) = device.instance().ext_debug_utils() { - debug_utils.object_name(device.handle(), fb.raw(), group.name()); - } + let fb = res.device().create(&create_info.build())?; Ok(fb) } } - pub fn force_update(&mut self, src: &RenderGraph) -> Result<()> { + pub fn force_update(&mut self, src: &RenderGraph, res: &mut AshResources) -> Result<()> { self.reset(); self.hash = src.hash(); @@ -252,25 +104,36 @@ impl AshRenderGraph { self.topo .resize_with(num_topological_groups, Default::default); + let texture_assignments = ResourceAssignments::new(); for topo_index in 0..num_topological_groups { - let topo = &mut self.topo[topo_index]; - for group in src.get_topological_group(topo_index) { - match group.bind_point() { + let topo_group = &mut self.topo[topo_index]; + for pass in src.get_topological_group(topo_index) { + match pass.bind_point() { PipelineBindPoint::Graphics => { - let pass = Self::create_render_pass(&self.device, src, group)?; - let fb = Self::create_framebuffer(&self.device, src, group, pass.raw())?; - topo.render_passes - .push((group.group_index(), pass.take(), fb.take())); + // TODO: no unwrap / error handling + let pass_descr = GraphicsPassDescriptorWithTextures::from_graph( + src, + pass, + &texture_assignments, + ) + .unwrap(); + let graphics_pass = + res.create::(&pass_descr.graphics_pass)?; + let render_pass = res[graphics_pass]; + let framebuf = Self::create_framebuffer(res, &pass_descr, render_pass)?; + topo_group + .render_passes + .push((pass.index(), render_pass, framebuf.take())); } PipelineBindPoint::Compute => { - let range = group.range(); + let range = pass.sub_pass_range(); assert_eq!(range.start + 1, range.end); - topo.compute_passes.push(range.start); + topo_group.compute_passes.push(range.start); } PipelineBindPoint::RayTracing => { - let range = group.range(); + let range = pass.sub_pass_range(); assert_eq!(range.start + 1, range.end); - topo.ray_tracing_passes.push(range.start); + topo_group.ray_tracing_passes.push(range.start); } } } @@ -289,11 +152,11 @@ impl AshRenderGraph { let mut encoder = command_pool.encoder()?; for (topo_index, topo) in self.topo.iter().enumerate() { // render-passes - for &(group_index, render_pass, fb) in &topo.render_passes { - let group = src_graph.get_pass_group(group_index).unwrap(); - let multi_pass = group.range().len() > 1; - if multi_pass { - encoder.begin_debug_label(group.name()); + for &(pass_index, render_pass, fb) in &topo.render_passes { + let pass = src_graph.get_pass(pass_index).unwrap(); + let has_multiple_subpass = pass.sub_pass_range().len() > 1; + if has_multiple_subpass { + encoder.begin_debug_label(pass.name()); } unsafe { // TODO: caching of render-pass & framebuffer @@ -306,20 +169,20 @@ impl AshRenderGraph { vk::SubpassContents::INLINE, ); let mut first = true; - for pass_index in group.range() { + for subpass_index in pass.sub_pass_range() { if first { first = false; } else { encoder.next_subpass(vk::SubpassContents::INLINE); } - let pass = src_graph.get_pass(pass_index).unwrap(); - encoder.begin_debug_label(pass.name()); - src_graph.execute_pass(pass.index(), &mut encoder, draw_phases); + let subpass = src_graph.get_subpass(subpass_index).unwrap(); + encoder.begin_debug_label(subpass.name()); + src_graph.execute_sub_pass(subpass_index, &mut encoder, draw_phases); encoder.end_debug_label(); } encoder.end_render_pass(); } - if multi_pass { + if has_multiple_subpass { encoder.end_debug_label(); } } diff --git a/crates/render-ash/src/lib.rs b/crates/render-ash/src/lib.rs index 2826045..f802514 100644 --- a/crates/render-ash/src/lib.rs +++ b/crates/render-ash/src/lib.rs @@ -39,7 +39,6 @@ use resources::AshResources; use thiserror::Error; use tracing::info; -mod attachments; mod convert; mod debug_utils; mod device; @@ -94,6 +93,12 @@ pub enum Error { #[error("Allocation Error")] AllocationError(#[from] gpu_alloc::AllocationError), + #[error("Serialization Error")] + SerializationError(Box), + + #[error("Deserialization Error")] + DeserializationError(Box), + #[error("unknown renderer error")] Unknown, } diff --git a/crates/render-ash/src/resources.rs b/crates/render-ash/src/resources.rs deleted file mode 100644 index 21d295f..0000000 --- a/crates/render-ash/src/resources.rs +++ /dev/null @@ -1,409 +0,0 @@ -use ash::vk; -use pulz_render::{ - backend::GpuResource, - buffer::Buffer, - pipeline::{ - BindGroupLayout, ComputePipeline, GraphicsPipeline, PipelineLayout, RayTracingPipeline, - }, - shader::ShaderModule, - texture::Texture, -}; -use slotmap::SlotMap; - -use crate::{ - convert::{CreateInfoConverter, VkInto}, - device::AshDevice, - shader::compie_into_spv, - Result, -}; - -pub trait AshGpuResource: GpuResource { - type Raw; - - unsafe fn create( - device: &AshDevice, - res: &AshResources, - descriptor: &Self::Descriptor<'_>, - ) -> Result; - unsafe fn create_many( - device: &AshDevice, - res: &AshResources, - descriptors: &[Self::Descriptor<'_>], - ) -> Result> { - descriptors - .iter() - .map(|d| Self::create(device, res, d)) - .collect() - } - unsafe fn destroy(device: &AshDevice, raw: Self::Raw); -} - -impl AshGpuResource for Buffer { - type Raw = vk::Buffer; - - unsafe fn create( - device: &AshDevice, - _res: &AshResources, - descr: &Self::Descriptor<'_>, - ) -> Result { - let create_info: vk::BufferCreateInfo = descr.vk_into(); - let raw = device.create_buffer(&create_info, None)?; - Ok(raw) - } - - unsafe fn destroy(device: &AshDevice, raw: Self::Raw) { - if raw != vk::Buffer::null() { - device.destroy_buffer(raw, None); - } - } -} - -impl AshGpuResource for Texture { - type Raw = (vk::Image, vk::ImageView); - - unsafe fn create( - device: &AshDevice, - _res: &AshResources, - descr: &Self::Descriptor<'_>, - ) -> Result { - let img_create_info: vk::ImageCreateInfo = descr.vk_into(); - let img = device.create(&img_create_info)?; - let view_create_info: vk::ImageViewCreateInfo = descr.vk_into(); - let view = device.create(&view_create_info)?; - Ok((img.take(), view.take())) - } - - unsafe fn destroy(device: &AshDevice, (img, view): Self::Raw) { - if view != vk::ImageView::null() { - device.destroy(view); - } - if img != vk::Image::null() { - device.destroy(img); - } - } -} - -impl AshGpuResource for ShaderModule { - type Raw = vk::ShaderModule; - unsafe fn create( - device: &AshDevice, - _res: &AshResources, - descr: &Self::Descriptor<'_>, - ) -> Result { - let code = compie_into_spv(&descr.source)?; - let create_info = vk::ShaderModuleCreateInfo::builder().code(&code).build(); - let raw = device.create(&create_info)?; - if let Some(label) = descr.label { - device.object_name(raw.raw(), label); - } - Ok(raw.take()) - } - unsafe fn destroy(device: &AshDevice, raw: Self::Raw) { - if raw != vk::ShaderModule::null() { - device.destroy(raw); - } - } -} -impl AshGpuResource for BindGroupLayout { - type Raw = vk::DescriptorSetLayout; - unsafe fn create( - device: &AshDevice, - _res: &AshResources, - descr: &Self::Descriptor<'_>, - ) -> Result { - let mut conv = CreateInfoConverter::new(); - let create_info = conv.bind_group_layout(descr); - let raw = device.create(create_info)?; - if let Some(label) = descr.label { - device.object_name(raw.raw(), label); - } - Ok(raw.take()) - } - unsafe fn destroy(device: &AshDevice, raw: Self::Raw) { - if raw != vk::DescriptorSetLayout::null() { - device.destroy(raw); - } - } -} -impl AshGpuResource for PipelineLayout { - type Raw = vk::PipelineLayout; - - unsafe fn create( - device: &AshDevice, - res: &AshResources, - descr: &Self::Descriptor<'_>, - ) -> Result { - let mut conv = CreateInfoConverter::new(); - let create_info = conv.pipeline_layout(res, descr); - let raw = device.create(create_info)?; - if let Some(label) = descr.label { - device.object_name(raw.raw(), label); - } - Ok(raw.take()) - } - unsafe fn destroy(device: &AshDevice, raw: Self::Raw) { - if raw != vk::PipelineLayout::null() { - device.destroy(raw); - } - } -} -impl AshGpuResource for GraphicsPipeline { - type Raw = vk::Pipeline; - - unsafe fn create_many( - device: &AshDevice, - res: &AshResources, - descrs: &[Self::Descriptor<'_>], - ) -> Result> { - let mut conv = CreateInfoConverter::new(); - let create_infos = conv.graphics_pipeline_descriptor(res, descrs); - match device.create_graphics_pipelines(vk::PipelineCache::null(), create_infos, None) { - Ok(raw) => { - let raw = device.hold(raw); - if let Ok(debug_utils) = device.instance().ext_debug_utils() { - for (i, d) in descrs.iter().enumerate() { - if let Some(label) = d.label { - debug_utils.object_name(device.handle(), raw[i], label); - } - } - } - Ok(raw.take()) - } - Err((pipelines, e)) => { - device.destroy(pipelines); - Err(e.into()) - } - } - } - - unsafe fn create( - device: &AshDevice, - res: &AshResources, - descr: &Self::Descriptor<'_>, - ) -> Result { - let raw = Self::create_many(device, res, std::slice::from_ref(descr))?; - Ok(raw[0]) - } - - unsafe fn destroy(device: &AshDevice, raw: Self::Raw) { - if raw != vk::Pipeline::null() { - device.destroy_pipeline(raw, None); - } - } -} -impl AshGpuResource for ComputePipeline { - type Raw = vk::Pipeline; - - unsafe fn create_many( - device: &AshDevice, - res: &AshResources, - descrs: &[Self::Descriptor<'_>], - ) -> Result> { - let mut conv = CreateInfoConverter::new(); - let create_infos = conv.compute_pipeline_descriptor(res, descrs); - match device.create_compute_pipelines(vk::PipelineCache::null(), create_infos, None) { - Ok(raw) => { - let raw = device.hold(raw); - if let Ok(debug_utils) = device.instance().ext_debug_utils() { - for (i, d) in descrs.iter().enumerate() { - if let Some(label) = d.label { - debug_utils.object_name(device.handle(), raw[i], label); - } - } - } - Ok(raw.take()) - } - Err((pipelines, e)) => { - device.destroy(pipelines); - Err(e.into()) - } - } - } - - unsafe fn create( - device: &AshDevice, - res: &AshResources, - descr: &Self::Descriptor<'_>, - ) -> Result { - let raw = Self::create_many(device, res, std::slice::from_ref(descr))?; - Ok(raw[0]) - } - - unsafe fn destroy(device: &AshDevice, raw: Self::Raw) { - if raw != vk::Pipeline::null() { - device.destroy_pipeline(raw, None); - } - } -} -impl AshGpuResource for RayTracingPipeline { - type Raw = vk::Pipeline; - - unsafe fn create_many( - device: &AshDevice, - res: &AshResources, - descrs: &[Self::Descriptor<'_>], - ) -> Result> { - let ext = device.ext_raytracing_pipeline()?; - let mut conv = CreateInfoConverter::new(); - let create_infos = conv.ray_tracing_pipeline_descriptor(res, descrs); - let raw = ext.create_ray_tracing_pipelines( - vk::DeferredOperationKHR::null(), - vk::PipelineCache::null(), - create_infos, - None, - )?; - let raw = device.hold(raw); - if let Ok(debug_utils) = device.instance().ext_debug_utils() { - for (i, d) in descrs.iter().enumerate() { - if let Some(label) = d.label { - debug_utils.object_name(device.handle(), raw[i], label); - } - } - } - Ok(raw.take()) - } - - unsafe fn create( - device: &AshDevice, - res: &AshResources, - descr: &Self::Descriptor<'_>, - ) -> Result { - let raw = Self::create_many(device, res, std::slice::from_ref(descr))?; - Ok(raw[0]) - } - - unsafe fn destroy(device: &AshDevice, raw: Self::Raw) { - if raw != vk::Pipeline::null() { - device.destroy_pipeline(raw, None); - } - } -} - -macro_rules! define_resources { - ( - $v:vis struct $name:ident { - $( - $vfield:vis $namefield:ident<$keytype:ty, $ashtype:ty> - ),* - $(,)? - } - ) => { - $v struct $name { - $( - $vfield $namefield: ::slotmap::basic::SlotMap<$keytype, $ashtype> - ),* - } - - impl $name { - pub fn new() -> Self { - Self { - $( - $namefield: ::slotmap::basic::SlotMap::with_key(), - )* - } - } - - } - - $( - impl AsRef<::slotmap::basic::SlotMap<$keytype,$ashtype>> for $name { - fn as_ref(&self) -> &::slotmap::basic::SlotMap<$keytype,$ashtype> { - &self.$namefield - } - } - - impl AsMut<::slotmap::basic::SlotMap<$keytype,$ashtype>> for $name { - fn as_mut(&mut self) -> &mut ::slotmap::basic::SlotMap<$keytype,$ashtype> { - &mut self.$namefield - } - } - )* - - impl Drop for AshResources { - fn drop(&mut self) { - fn check_empty(map: &SlotMap) { - if !map.is_empty() { - panic!( - "gpu resources for {} dropped without calling `destroy`", - std::any::type_name::() - ); - } - } - - $( - check_empty(&self.$namefield); - )* - } - } - }; -} - -define_resources! { - pub struct AshResources { - pub buffers, - pub textures, - pub shader_modules, - pub bind_group_layouts, - pub pipeline_layouts, - pub graphics_pipelines, - pub compute_pipelines, - pub ray_tracing_pipelines, - } -} - -impl AshResources { - pub fn create(&mut self, device: &AshDevice, descriptor: &R::Descriptor<'_>) -> Result - where - R: AshGpuResource, - Self: AsMut>, - { - let raw = unsafe { R::create(device, self, descriptor)? }; - let key = self.as_mut().insert(raw); - Ok(key) - } - - pub fn create_many( - &mut self, - device: &AshDevice, - descriptors: &[R::Descriptor<'_>], - ) -> Result> - where - R: AshGpuResource, - Self: AsMut>, - { - let raw = unsafe { R::create_many(device, self, descriptors)? }; - let keys = raw.into_iter().map(|r| self.as_mut().insert(r)).collect(); - Ok(keys) - } - - pub fn destroy(&mut self, device: &AshDevice, key: R) -> bool - where - R: AshGpuResource, - Self: AsMut>, - { - if let Some(raw) = self.as_mut().remove(key) { - unsafe { R::destroy(device, raw) }; - true - } else { - false - } - } - - pub fn destroy_all(&mut self, device: &AshDevice) { - fn destroy_all_in_map(map: &mut SlotMap, device: &AshDevice) { - for (_key, raw) in map.drain() { - unsafe { R::destroy(device, raw) }; - } - } - - // Reverse order! - destroy_all_in_map(&mut self.ray_tracing_pipelines, device); - destroy_all_in_map(&mut self.compute_pipelines, device); - destroy_all_in_map(&mut self.graphics_pipelines, device); - destroy_all_in_map(&mut self.pipeline_layouts, device); - destroy_all_in_map(&mut self.bind_group_layouts, device); - destroy_all_in_map(&mut self.shader_modules, device); - destroy_all_in_map(&mut self.textures, device); - destroy_all_in_map(&mut self.buffers, device); - } -} diff --git a/crates/render-ash/src/resources/mod.rs b/crates/render-ash/src/resources/mod.rs new file mode 100644 index 0000000..b99976e --- /dev/null +++ b/crates/render-ash/src/resources/mod.rs @@ -0,0 +1,206 @@ +use std::{collections::HashMap, hash::Hasher, ops::Index, sync::Arc}; + +use ash::vk; +use pulz_render::{ + buffer::Buffer, + pipeline::{ + BindGroupLayout, ComputePipeline, GraphicsPass, GraphicsPipeline, PipelineLayout, + RayTracingPipeline, + }, + shader::ShaderModule, + texture::Texture, +}; +use slotmap::SlotMap; + +use crate::{device::AshDevice, Result}; + +mod replay; +mod resource_impl; +mod traits; + +use self::{ + replay::RecordResource, + traits::{AshGpuResourceCreate, AshGpuResourceRemove}, +}; + +#[derive(Default)] +struct PreHashedHasherHasher(u64); +type PreHashedHasherBuildHasher = std::hash::BuildHasherDefault; + +impl Hasher for PreHashedHasherHasher { + #[inline] + fn finish(&self) -> u64 { + self.0 + } + + #[inline] + fn write(&mut self, bytes: &[u8]) { + let mut hash = self.0; + for byte in bytes.iter() { + hash <<= 8; + hash |= *byte as u64; + } + self.0 = hash; + } + + #[inline] + fn write_u64(&mut self, i: u64) { + self.0 = i; + } + + #[inline] + fn write_i64(&mut self, i: i64) { + self.0 = i as u64; + } +} + +type PreHashedU64Map = HashMap; + +pub struct AshResources { + device: Arc, + record: Option>, + pipeline_cache: vk::PipelineCache, + graphics_passes_cache: PreHashedU64Map, + shader_modules_cache: PreHashedU64Map, + bind_group_layouts_cache: PreHashedU64Map, + pipeline_layouts_cache: PreHashedU64Map, + graphics_pipelines_cache: PreHashedU64Map, + compute_pipelines_cache: PreHashedU64Map, + ray_tracing_pipelines_cache: PreHashedU64Map, + pub graphics_passes: SlotMap, + pub shader_modules: SlotMap, + pub bind_group_layouts: SlotMap, + pub pipeline_layouts: SlotMap, + pub graphics_pipelines: SlotMap, + pub compute_pipelines: SlotMap, + pub ray_tracing_pipelines: SlotMap, + pub buffers: SlotMap, + pub textures: SlotMap, +} + +impl AshResources { + pub fn new(device: &Arc) -> Self { + Self { + device: device.clone(), + record: None, + graphics_passes_cache: HashMap::default(), + shader_modules_cache: HashMap::default(), + bind_group_layouts_cache: HashMap::default(), + pipeline_layouts_cache: HashMap::default(), + graphics_pipelines_cache: HashMap::default(), + compute_pipelines_cache: HashMap::default(), + ray_tracing_pipelines_cache: HashMap::default(), + pipeline_cache: vk::PipelineCache::null(), + graphics_passes: SlotMap::with_key(), + shader_modules: SlotMap::with_key(), + bind_group_layouts: SlotMap::with_key(), + pipeline_layouts: SlotMap::with_key(), + graphics_pipelines: SlotMap::with_key(), + compute_pipelines: SlotMap::with_key(), + ray_tracing_pipelines: SlotMap::with_key(), + buffers: SlotMap::with_key(), + textures: SlotMap::with_key(), + } + } + + #[inline] + pub fn device(&self) -> &AshDevice { + &self.device + } + + pub fn with_pipeline_cache(mut self, initial_data: &[u8]) -> Result { + self.set_pipeline_cache(initial_data)?; + Ok(self) + } + + pub fn set_pipeline_cache(&mut self, initial_data: &[u8]) -> Result<()> { + unsafe { + if self.pipeline_cache != vk::PipelineCache::null() { + self.device + .destroy_pipeline_cache(self.pipeline_cache, None); + self.pipeline_cache = vk::PipelineCache::null(); + } + self.pipeline_cache = self.device.create_pipeline_cache( + &vk::PipelineCacheCreateInfo::builder() + .initial_data(initial_data) + .build(), + None, + )?; + } + Ok(()) + } + + pub fn get_pipeline_cache_data(&self) -> Result> { + if self.pipeline_cache == vk::PipelineCache::null() { + return Ok(Vec::new()); + } + unsafe { + let data = self.device.get_pipeline_cache_data(self.pipeline_cache)?; + Ok(data) + } + } + + #[inline] + pub fn create(&mut self, descriptor: &R::Descriptor<'_>) -> Result + where + R: AshGpuResourceCreate, + { + R::create(self, descriptor) + } + + #[inline] + pub fn get_raw(&self, key: R) -> Option + where + R: AshGpuResourceCreate, + { + R::get_raw(self, key).copied() + } + + #[inline] + pub fn clear(&mut self) + where + R: AshGpuResourceCreate, + { + R::clear(self) + } + + #[inline] + pub fn remove(&mut self, key: R) -> bool + where + R: AshGpuResourceRemove, + { + R::remove(self, key) + } + + pub fn clear_all(&mut self) { + self.clear::(); + self.clear::(); + self.clear::(); + self.clear::(); + self.clear::(); + self.clear::(); + self.clear::(); + self.clear::(); + } +} + +impl Index for AshResources { + type Output = R::Raw; + #[inline] + fn index(&self, index: R) -> &Self::Output { + R::get_raw(self, index).expect("invalid resource") + } +} + +impl Drop for AshResources { + #[inline] + fn drop(&mut self) { + self.clear_all(); + if self.pipeline_cache != vk::PipelineCache::null() { + unsafe { + self.device + .destroy_pipeline_cache(self.pipeline_cache, None); + } + } + } +} diff --git a/crates/render-ash/src/resources/replay.rs b/crates/render-ash/src/resources/replay.rs new file mode 100644 index 0000000..689dd35 --- /dev/null +++ b/crates/render-ash/src/resources/replay.rs @@ -0,0 +1,206 @@ +use std::borrow::Cow; + +use pulz_render::{ + backend::GpuResource, + pipeline::{ + BindGroupLayout, BindGroupLayoutDescriptor, ComputePipeline, ComputePipelineDescriptor, + GraphicsPass, GraphicsPassDescriptor, GraphicsPipeline, GraphicsPipelineDescriptor, + PipelineLayout, PipelineLayoutDescriptor, RayTracingPipeline, RayTracingPipelineDescriptor, + }, + shader::{ShaderModule, ShaderModuleDescriptor}, + utils::serde_slots::SlotDeserializationMapper, +}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use slotmap::Key; + +use super::AshResources; +use crate::{Error, Result}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +enum ReplayResourceDescr<'a, 'b> { + GraphicsPass(Cow<'a, GraphicsPassDescriptor>), + #[serde(borrow)] + ShaderModule(Cow<'a, ShaderModuleDescriptor<'b>>), + BindGroupLayout(Cow<'a, BindGroupLayoutDescriptor<'b>>), + PipelineLayout(Cow<'a, PipelineLayoutDescriptor<'b>>), + GraphicsPipeline(Cow<'a, GraphicsPipelineDescriptor<'b>>), + ComputePipeline(Cow<'a, ComputePipelineDescriptor<'b>>), + RayTracingPipeline(Cow<'a, RayTracingPipelineDescriptor<'b>>), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub(super) struct ReplayResourceDescrEntry<'a, 'b>( + u64, + #[serde(borrow)] ReplayResourceDescr<'a, 'b>, +); + +pub(super) trait AsResourceRecord: GpuResource { + fn as_record<'a, 'b: 'a>( + &self, + descr: &'a Self::Descriptor<'b>, + ) -> ReplayResourceDescrEntry<'a, 'b>; +} + +macro_rules! define_replay_resources { + ($($r:ident),*) => { + + impl ReplayResourceDescr<'_, '_> { + fn create( + &self, + res: &mut AshResources, + id: u64, + mapper: &mut SlotDeserializationMapper, + ) -> Result<()> { + match self { + $( + Self::$r(d) => { + let key = res.create::<$r>(d)?; + mapper.define(id, key); + }, + )* + } + Ok(()) + } + } + + $( + impl AsResourceRecord for $r { + fn as_record<'a, 'b: 'a>(&self, descr: &'a Self::Descriptor<'b>) -> ReplayResourceDescrEntry<'a, 'b> { + ReplayResourceDescrEntry(self.data().as_ffi(), ReplayResourceDescr::$r(Cow::Borrowed(descr))) + } + } + )* + }; +} + +define_replay_resources!( + GraphicsPass, + ShaderModule, + BindGroupLayout, + PipelineLayout, + GraphicsPipeline, + ComputePipeline, + RayTracingPipeline +); + +pub(super) trait RecordResource: 'static { + fn record(&mut self, record: ReplayResourceDescrEntry<'_, '_>) -> Result<()>; + + fn end(&mut self) -> Result<()>; +} + +pub struct NoopRecorder; + +impl RecordResource for NoopRecorder { + fn record(&mut self, _record: ReplayResourceDescrEntry<'_, '_>) -> Result<()> { + Ok(()) + } + fn end(&mut self) -> Result<()> { + Ok(()) + } +} + +struct Recorder(Option); + +impl RecordResource for Recorder +where + S: 'static, +{ + fn record(&mut self, record: ReplayResourceDescrEntry<'_, '_>) -> Result<()> { + use serde::ser::SerializeSeq; + let Some(seq) = &mut self.0 else { + return Ok(()); + }; + if let Err(e) = seq.serialize_element(&record) { + Err(Error::SerializationError(Box::new(e))) + } else { + Ok(()) + } + } + fn end(&mut self) -> Result<()> { + use serde::ser::SerializeSeq; + let Some(seq) = self.0.take() else { + return Ok(()); + }; + + if let Err(e) = seq.end() { + Err(Error::SerializationError(Box::new(e))) + } else { + Ok(()) + } + } +} + +impl Drop for Recorder { + fn drop(&mut self) { + use serde::ser::SerializeSeq; + let Some(seq) = self.0.take() else { + return; + }; + seq.end() + .expect("recording not ended, and produced an error"); + } +} + +impl AshResources { + #[inline] + pub fn replay<'de, D>(&mut self, deserializer: D) -> Result<()> + where + D: Deserializer<'de>, + D::Error: 'static, + { + struct VisitResources<'l>(&'l mut AshResources, &'l mut Option); + impl<'de> serde::de::Visitor<'de> for VisitResources<'_> { + type Value = (); + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("a sequence") + } + + fn visit_seq(self, mut seq: A) -> std::result::Result + where + A: serde::de::SeqAccess<'de>, + { + SlotDeserializationMapper::with(|mapper| { + while let Some(ReplayResourceDescrEntry(id, elem)) = seq.next_element()? { + if let Err(error) = elem.create(self.0, id, mapper) { + *self.1 = Some(error); + return Err(serde::de::Error::custom("Unable to create resource")); + } + } + Ok(()) + }) + } + } + let mut error = None; + let result = deserializer.deserialize_seq(VisitResources(self, &mut error)); + if let Some(error) = error { + return Err(error); + } + if let Err(error) = result { + return Err(Error::DeserializationError(Box::new(error))); + } + Ok(()) + } + + pub fn start_recording( + &mut self, + serializer: S, + ) -> Result<(), S::Error> { + assert!( + self.record.is_none(), + "there is already an active recording session" + ); + let seq = serializer.serialize_seq(None)?; + self.record.replace(Box::new(Recorder::(Some(seq)))); + Ok(()) + } + + pub fn end_recording(&mut self) -> Result { + if let Some(mut record) = self.record.take() { + record.end()?; + Ok(true) + } else { + Ok(false) + } + } +} diff --git a/crates/render-ash/src/resources/resource_impl.rs b/crates/render-ash/src/resources/resource_impl.rs new file mode 100644 index 0000000..db46dcc --- /dev/null +++ b/crates/render-ash/src/resources/resource_impl.rs @@ -0,0 +1,321 @@ +use ash::vk; +use pulz_render::{ + buffer::Buffer, + pipeline::{ + BindGroupLayout, ComputePipeline, GraphicsPass, GraphicsPipeline, PipelineLayout, + RayTracingPipeline, + }, + shader::ShaderModule, + texture::Texture, +}; +use slotmap::SlotMap; + +use super::{ + traits::{AshGpuResource, AshGpuResourceCached, AshGpuResourceCreate, AshGpuResourceRemove}, + AshResources, PreHashedU64Map, +}; +use crate::{ + convert::{CreateInfoConverter2, CreateInfoConverter6, VkInto}, + device::AshDevice, + shader::compie_into_spv, + Result, +}; + +impl AshGpuResource for Buffer { + type Raw = vk::Buffer; + + fn slotmap(res: &AshResources) -> &SlotMap { + &res.buffers + } + + fn slotmap_mut(res: &mut AshResources) -> &mut SlotMap { + &mut res.buffers + } + + unsafe fn create_raw(res: &AshResources, descr: &Self::Descriptor<'_>) -> Result { + let create_info: vk::BufferCreateInfo = descr.vk_into(); + let raw = res.device.create_buffer(&create_info, None)?; + Ok(raw) + } + + unsafe fn destroy_raw(device: &AshDevice, raw: Self::Raw) { + if raw != vk::Buffer::null() { + device.destroy_buffer(raw, None); + } + } +} +impl AshGpuResourceCreate for Buffer {} +impl AshGpuResourceRemove for Buffer {} + +impl AshGpuResource for Texture { + type Raw = (vk::Image, vk::ImageView); + + fn slotmap(res: &AshResources) -> &SlotMap { + &res.textures + } + + fn slotmap_mut(res: &mut AshResources) -> &mut SlotMap { + &mut res.textures + } + + unsafe fn create_raw(res: &AshResources, descr: &Self::Descriptor<'_>) -> Result { + let img_create_info: vk::ImageCreateInfo = descr.vk_into(); + let img = res.device.create(&img_create_info)?; + let view_create_info: vk::ImageViewCreateInfo = descr.vk_into(); + let view = res.device.create(&view_create_info)?; + Ok((img.take(), view.take())) + } + + unsafe fn destroy_raw(device: &AshDevice, (img, view): Self::Raw) { + if view != vk::ImageView::null() { + device.destroy(view); + } + if img != vk::Image::null() { + device.destroy(img); + } + } +} +impl AshGpuResourceCreate for Texture {} +impl AshGpuResourceRemove for Texture {} + +impl AshGpuResource for GraphicsPass { + type Raw = vk::RenderPass; + fn slotmap(res: &AshResources) -> &SlotMap { + &res.graphics_passes + } + fn slotmap_mut(res: &mut AshResources) -> &mut SlotMap { + &mut res.graphics_passes + } + + unsafe fn create_raw(res: &AshResources, descr: &Self::Descriptor<'_>) -> Result { + let mut conv = CreateInfoConverter6::new(); + let create_info = conv.graphics_pass(descr); + let raw = res.device.create(create_info)?; + Ok(raw.take()) + } + unsafe fn destroy_raw(device: &AshDevice, raw: Self::Raw) { + if raw != vk::RenderPass::null() { + device.destroy(raw); + } + } +} +impl AshGpuResourceCached for GraphicsPass { + #[inline] + fn get_hashs_mut(res: &mut AshResources) -> &mut PreHashedU64Map { + &mut res.graphics_passes_cache + } +} +impl AshGpuResource for ShaderModule { + type Raw = vk::ShaderModule; + fn slotmap(res: &AshResources) -> &SlotMap { + &res.shader_modules + } + fn slotmap_mut(res: &mut AshResources) -> &mut SlotMap { + &mut res.shader_modules + } + + unsafe fn create_raw(res: &AshResources, descr: &Self::Descriptor<'_>) -> Result { + let code = compie_into_spv(&descr.source)?; + let create_info = vk::ShaderModuleCreateInfo::builder().code(&code).build(); + let raw = res.device.create(&create_info)?; + if let Some(label) = descr.label { + res.device.object_name(raw.raw(), label); + } + Ok(raw.take()) + } + unsafe fn destroy_raw(device: &AshDevice, raw: Self::Raw) { + if raw != vk::ShaderModule::null() { + device.destroy(raw); + } + } +} +impl AshGpuResourceCached for ShaderModule { + #[inline] + fn get_hashs_mut(res: &mut AshResources) -> &mut PreHashedU64Map { + &mut res.shader_modules_cache + } +} +impl AshGpuResource for BindGroupLayout { + type Raw = vk::DescriptorSetLayout; + fn slotmap(res: &AshResources) -> &SlotMap { + &res.bind_group_layouts + } + fn slotmap_mut(res: &mut AshResources) -> &mut SlotMap { + &mut res.bind_group_layouts + } + + unsafe fn create_raw(res: &AshResources, descr: &Self::Descriptor<'_>) -> Result { + let mut conv = CreateInfoConverter2::new(); + let create_info = conv.bind_group_layout(descr); + let raw = res.device.create(create_info)?; + if let Some(label) = descr.label { + res.device.object_name(raw.raw(), label); + } + Ok(raw.take()) + } + unsafe fn destroy_raw(device: &AshDevice, raw: Self::Raw) { + if raw != vk::DescriptorSetLayout::null() { + device.destroy(raw); + } + } +} +impl AshGpuResourceCached for BindGroupLayout { + #[inline] + fn get_hashs_mut(res: &mut AshResources) -> &mut PreHashedU64Map { + &mut res.bind_group_layouts_cache + } +} +impl AshGpuResource for PipelineLayout { + type Raw = vk::PipelineLayout; + fn slotmap(res: &AshResources) -> &SlotMap { + &res.pipeline_layouts + } + fn slotmap_mut(res: &mut AshResources) -> &mut SlotMap { + &mut res.pipeline_layouts + } + + unsafe fn create_raw(res: &AshResources, descr: &Self::Descriptor<'_>) -> Result { + let mut conv = CreateInfoConverter2::new(); + let create_info = conv.pipeline_layout(res, descr); + let raw = res.device.create(create_info)?; + if let Some(label) = descr.label { + res.device.object_name(raw.raw(), label); + } + Ok(raw.take()) + } + unsafe fn destroy_raw(device: &AshDevice, raw: Self::Raw) { + if raw != vk::PipelineLayout::null() { + device.destroy(raw); + } + } +} +impl AshGpuResourceCached for PipelineLayout { + #[inline] + fn get_hashs_mut(res: &mut AshResources) -> &mut PreHashedU64Map { + &mut res.pipeline_layouts_cache + } +} +impl AshGpuResource for GraphicsPipeline { + type Raw = vk::Pipeline; + fn slotmap(res: &AshResources) -> &SlotMap { + &res.graphics_pipelines + } + fn slotmap_mut(res: &mut AshResources) -> &mut SlotMap { + &mut res.graphics_pipelines + } + + unsafe fn create_raw(res: &AshResources, descr: &Self::Descriptor<'_>) -> Result { + let mut conv = CreateInfoConverter2::new(); + let create_infos = conv.graphics_pipeline_descriptor(res, std::slice::from_ref(descr)); + match res + .device + .create_graphics_pipelines(res.pipeline_cache, create_infos, None) + { + Ok(raw) => { + let raw = res.device.hold(raw[0]); + if let Some(label) = descr.label { + res.device.object_name(raw.raw(), label); + } + Ok(raw.take()) + } + Err((pipelines, e)) => { + res.device.destroy(pipelines); + Err(e.into()) + } + } + } + + unsafe fn destroy_raw(device: &AshDevice, raw: Self::Raw) { + if raw != vk::Pipeline::null() { + device.destroy_pipeline(raw, None); + } + } +} +impl AshGpuResourceCached for GraphicsPipeline { + #[inline] + fn get_hashs_mut(res: &mut AshResources) -> &mut PreHashedU64Map { + &mut res.graphics_pipelines_cache + } +} +impl AshGpuResource for ComputePipeline { + type Raw = vk::Pipeline; + fn slotmap(res: &AshResources) -> &SlotMap { + &res.compute_pipelines + } + fn slotmap_mut(res: &mut AshResources) -> &mut SlotMap { + &mut res.compute_pipelines + } + + unsafe fn create_raw(res: &AshResources, descr: &Self::Descriptor<'_>) -> Result { + let mut conv = CreateInfoConverter2::new(); + let create_infos = conv.compute_pipeline_descriptor(res, std::slice::from_ref(descr)); + match res + .device + .create_compute_pipelines(res.pipeline_cache, create_infos, None) + { + Ok(raw) => { + let raw = res.device.hold(raw[0]); + if let Some(label) = descr.label { + res.device.object_name(raw.raw(), label); + } + Ok(raw.take()) + } + Err((pipelines, e)) => { + res.device.destroy(pipelines); + Err(e.into()) + } + } + } + + unsafe fn destroy_raw(device: &AshDevice, raw: Self::Raw) { + if raw != vk::Pipeline::null() { + device.destroy_pipeline(raw, None); + } + } +} +impl AshGpuResourceCached for ComputePipeline { + #[inline] + fn get_hashs_mut(res: &mut AshResources) -> &mut PreHashedU64Map { + &mut res.compute_pipelines_cache + } +} +impl AshGpuResource for RayTracingPipeline { + type Raw = vk::Pipeline; + + fn slotmap(res: &AshResources) -> &SlotMap { + &res.ray_tracing_pipelines + } + + fn slotmap_mut(res: &mut AshResources) -> &mut SlotMap { + &mut res.ray_tracing_pipelines + } + + unsafe fn create_raw(res: &AshResources, descr: &Self::Descriptor<'_>) -> Result { + let ext = res.device.ext_raytracing_pipeline()?; + let mut conv = CreateInfoConverter2::new(); + let create_infos = conv.ray_tracing_pipeline_descriptor(res, std::slice::from_ref(descr)); + let raw = ext.create_ray_tracing_pipelines( + vk::DeferredOperationKHR::null(), + res.pipeline_cache, + create_infos, + None, + )?; + let raw = res.device.hold(raw[0]); + if let Some(label) = descr.label { + res.device.object_name(raw.raw(), label); + } + Ok(raw.take()) + } + + unsafe fn destroy_raw(device: &AshDevice, raw: Self::Raw) { + if raw != vk::Pipeline::null() { + device.destroy_pipeline(raw, None); + } + } +} +impl AshGpuResourceCached for RayTracingPipeline { + #[inline] + fn get_hashs_mut(res: &mut AshResources) -> &mut PreHashedU64Map { + &mut res.ray_tracing_pipelines_cache + } +} diff --git a/crates/render-ash/src/resources/traits.rs b/crates/render-ash/src/resources/traits.rs new file mode 100644 index 0000000..dc5de68 --- /dev/null +++ b/crates/render-ash/src/resources/traits.rs @@ -0,0 +1,102 @@ +use std::hash::{Hash, Hasher}; + +use pulz_render::backend::GpuResource; +use slotmap::SlotMap; + +use super::{replay::AsResourceRecord, AshResources, PreHashedU64Map}; +use crate::{device::AshDevice, Result}; + +pub trait AshGpuResource: GpuResource + 'static { + type Raw: Copy; + + fn slotmap(res: &AshResources) -> &SlotMap; + fn slotmap_mut(res: &mut AshResources) -> &mut SlotMap; + + unsafe fn create_raw( + res: &AshResources, + descriptor: &Self::Descriptor<'_>, + ) -> Result; + unsafe fn destroy_raw(device: &AshDevice, raw: Self::Raw); +} + +fn hash_one(value: &T) -> u64 { + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + value.hash(&mut hasher); + hasher.finish() +} + +pub(super) trait AshGpuResourceCached: AshGpuResource +where + for<'l> Self::Descriptor<'l>: Hash, +{ + fn hash_descriptor(descr: &Self::Descriptor<'_>) -> u64 { + hash_one(descr) + } + + fn get_hashs_mut(res: &mut AshResources) -> &mut PreHashedU64Map; +} + +pub trait AshGpuResourceCreate: AshGpuResource { + #[inline] + fn get_raw(res: &AshResources, key: Self) -> Option<&Self::Raw> { + Self::slotmap(res).get(key) + } + #[inline] + fn create(res: &mut AshResources, descr: &Self::Descriptor<'_>) -> Result { + unsafe { + let raw = Self::create_raw(res, descr)?; + let key = Self::slotmap_mut(res).insert(raw); + Ok(key) + } + } + fn clear(res: &mut AshResources) { + let device = res.device.clone(); + for (_key, raw) in Self::slotmap_mut(res).drain() { + unsafe { + Self::destroy_raw(&device, raw); + } + } + } +} + +pub trait AshGpuResourceRemove: AshGpuResource { + fn remove(res: &mut AshResources, key: Self) -> bool { + if let Some(raw) = Self::slotmap_mut(res).remove(key) { + unsafe { Self::destroy_raw(&res.device, raw) } + true + } else { + false + } + } +} + +impl AshGpuResourceCreate for R +where + R: AshGpuResourceCached + AsResourceRecord, + for<'l> R::Descriptor<'l>: Hash, +{ + fn create(res: &mut AshResources, descr: &Self::Descriptor<'_>) -> Result { + let hash = Self::hash_descriptor(descr); + if let Some(key) = Self::get_hashs_mut(res).get(&hash) { + return Ok(*key); + } + let key = unsafe { + let raw = Self::create_raw(res, descr)?; + Self::slotmap_mut(res).insert(raw) + }; + Self::get_hashs_mut(res).insert(hash, key); + if let Some(record) = &mut res.record { + record.record(key.as_record(descr))?; + } + Ok(key) + } + fn clear(res: &mut AshResources) { + Self::get_hashs_mut(res).clear(); + let device = res.device.clone(); + for (_key, raw) in Self::slotmap_mut(res).drain() { + unsafe { + Self::destroy_raw(&device, raw); + } + } + } +} diff --git a/crates/render-wgpu/src/convert.rs b/crates/render-wgpu/src/convert.rs index 6d1ae4d..de45d0e 100644 --- a/crates/render-wgpu/src/convert.rs +++ b/crates/render-wgpu/src/convert.rs @@ -79,7 +79,7 @@ pub fn convert_texture_descriptor(val: &TextureDescriptor) -> Result wgpu::TextureUsages { if val.contains(TextureUsage::TRANSFER_DST) { result |= wgpu::TextureUsages::COPY_DST; } - if val.contains(TextureUsage::TEXTURE_BINDING) { + if val.contains(TextureUsage::SAMPLED) { result |= wgpu::TextureUsages::TEXTURE_BINDING; } - if val.contains(TextureUsage::STORAGE_BINDING) { + if val.contains(TextureUsage::STORAGE) { result |= wgpu::TextureUsages::STORAGE_BINDING; } if val.contains(TextureUsage::COLOR_ATTACHMENT) @@ -307,7 +307,7 @@ pub fn convert_bind_group_layout_descriptor<'l>( } pub fn convert_pipeline_layout_descriptor<'l>( - _res: &'l WgpuResources, + _res: &WgpuResources, desc: &PipelineLayoutDescriptor<'l>, layouts_tmp: &'l mut Vec<&'l wgpu::BindGroupLayout>, ) -> wgpu::PipelineLayoutDescriptor<'l> { @@ -318,8 +318,8 @@ pub fn convert_pipeline_layout_descriptor<'l>( } } -pub fn convert_compute_pipeline_descriptor<'l>( - res: &'l WgpuResources, +pub fn convert_compute_pipeline_descriptor<'l, 'r: 'l>( + res: &'r WgpuResources, desc: &ComputePipelineDescriptor<'l>, ) -> Result> { let layout = if let Some(layout) = desc.layout { @@ -337,7 +337,7 @@ pub fn convert_compute_pipeline_descriptor<'l>( .get(desc.module) .ok_or(ConversionError::ShaderModuleNotAvailable(desc.module))?; - Ok(wgpu::ComputePipelineDescriptor { + Ok(wgpu::ComputePipelineDescriptor::<'l> { label: desc.label, layout, module, @@ -345,9 +345,9 @@ pub fn convert_compute_pipeline_descriptor<'l>( }) } -pub fn convert_graphics_pipeline_descriptor<'l>( - res: &'l WgpuResources, - desc: &GraphicsPipelineDescriptor<'l>, +pub fn convert_graphics_pipeline_descriptor<'l, 'r: 'l>( + res: &'r WgpuResources, + desc: &'r GraphicsPipelineDescriptor<'_>, buffers_tmp: &'l mut Vec>, attribs_tmp: &'l mut Vec, targets_tmp: &'l mut Vec>, @@ -376,7 +376,7 @@ pub fn convert_graphics_pipeline_descriptor<'l>( None }; - Ok(wgpu::RenderPipelineDescriptor { + Ok(wgpu::RenderPipelineDescriptor::<'l> { label: desc.label, layout, vertex, @@ -392,9 +392,9 @@ pub fn convert_graphics_pipeline_descriptor<'l>( }) } -fn convert_vertex_state<'l>( - res: &'l WgpuResources, - state: &VertexState<'l>, +fn convert_vertex_state<'l, 'r: 'l>( + res: &'r WgpuResources, + state: &'r VertexState<'_>, buffers_tmp: &'l mut Vec>, attributes_tmp: &'l mut Vec, ) -> Result> { @@ -407,7 +407,7 @@ fn convert_vertex_state<'l>( for (i, attr) in state .buffers .iter() - .flat_map(|l| l.attributes) + .flat_map(|l| l.attributes.as_ref()) .copied() .enumerate() { @@ -416,7 +416,7 @@ fn convert_vertex_state<'l>( buffers_tmp.reserve_exact(state.buffers.len()); let mut offset = 0; - for layout in state.buffers { + for layout in state.buffers.as_ref() { let next_offset = offset + layout.attributes.len(); buffers_tmp.push(wgpu::VertexBufferLayout { array_stride: layout.array_stride as u64, @@ -426,15 +426,15 @@ fn convert_vertex_state<'l>( offset = next_offset; } - Ok(wgpu::VertexState { + Ok(wgpu::VertexState::<'l> { module, entry_point: state.entry_point, buffers: buffers_tmp, }) } -fn convert_fragment_state<'l>( - res: &'l WgpuResources, +fn convert_fragment_state<'l, 'r: 'l>( + res: &'r WgpuResources, state: &FragmentState<'l>, targets_tmp: &'l mut Vec>, ) -> Result> { @@ -444,7 +444,7 @@ fn convert_fragment_state<'l>( .ok_or(ConversionError::ShaderModuleNotAvailable(state.module))?; targets_tmp.reserve_exact(state.targets.len()); - for target in state.targets { + for target in state.targets.as_ref() { targets_tmp.push(convert_color_target_state(&target)?); } @@ -668,8 +668,8 @@ fn convert_color(color: Srgba) -> wgpu::Color { } } -// pub fn convert_render_pass<'l>( -// res: &'l RenderBackendResources, +// pub fn convert_render_pass<'l, 'r: 'l>( +// res: &'r RenderBackendResources, // desc: &GraphicsPassDescriptor<'l>, // tmp_color: &'l mut Vec>, // ) -> Result> { @@ -690,8 +690,8 @@ fn convert_color(color: Srgba) -> wgpu::Color { // }) // } -// pub fn convert_color_attachment<'l>( -// res: &'l RenderBackendResources, +// pub fn convert_color_attachment<'r>( +// res: &'r RenderBackendResources, // desc: &ColorAttachment, // ) -> Result> { // let view = res @@ -716,8 +716,8 @@ fn convert_color(color: Srgba) -> wgpu::Color { // }) // } -// pub fn convert_depth_stencil_attachment<'l>( -// res: &'l RenderBackendResources, +// pub fn convert_depth_stencil_attachment<'r>( +// res: &'r RenderBackendResources, // desc: &DepthStencilAttachment, // ) -> Result> { // let view = res diff --git a/crates/render-wgpu/src/lib.rs b/crates/render-wgpu/src/lib.rs index 7017cc3..bb0f677 100644 --- a/crates/render-wgpu/src/lib.rs +++ b/crates/render-wgpu/src/lib.rs @@ -30,8 +30,7 @@ use std::rc::Rc; use convert::ConversionError; use graph::WgpuRenderGraph; use pulz_ecs::prelude::*; -use pulz_render::{graph::RenderGraph, RenderModule, RenderSystemPhase}; -use pulz_window::{RawWindow, RawWindowHandles, Window, WindowId, Windows, WindowsMirror}; +use pulz_render::{draw::DrawPhases, graph::RenderGraph, RenderModule, RenderSystemPhase}; use resources::WgpuResources; use surface::Surface; use thiserror::Error; diff --git a/crates/render-wgpu/src/resources.rs b/crates/render-wgpu/src/resources.rs index 0fc7d0e..1d4f75a 100644 --- a/crates/render-wgpu/src/resources.rs +++ b/crates/render-wgpu/src/resources.rs @@ -9,25 +9,14 @@ use slotmap::SlotMap; use crate::{convert as c, Result}; -pub trait WgpuResource: GpuResource { - type Wgpu; +pub trait WgpuResource: GpuResource + 'static { + type Wgpu: 'static; fn create( device: &wgpu::Device, res: &WgpuResources, descriptor: &Self::Descriptor<'_>, ) -> Result; - - fn create_many( - device: &wgpu::Device, - res: &WgpuResources, - descriptors: &[Self::Descriptor<'_>], - ) -> Result> { - descriptors - .iter() - .map(|d| Self::create(device, res, d)) - .collect() - } } impl WgpuResource for Buffer { @@ -196,22 +185,8 @@ impl WgpuResources { R: WgpuResource, Self: AsMut>, { - let raw = unsafe { R::create(device, self, descriptor)? }; + let raw = R::create(device, self, descriptor)?; let key = self.as_mut().insert(raw); Ok(key) } - - pub fn create_many( - &mut self, - device: &wgpu::Device, - descriptors: &[R::Descriptor<'_>], - ) -> Result> - where - R: WgpuResource, - Self: AsMut>, - { - let raw = unsafe { R::create_many(device, self, descriptors)? }; - let keys = raw.into_iter().map(|r| self.as_mut().insert(r)).collect(); - Ok(keys) - } } diff --git a/crates/render/Cargo.toml b/crates/render/Cargo.toml index 23ef0a3..7b18122 100644 --- a/crates/render/Cargo.toml +++ b/crates/render/Cargo.toml @@ -19,6 +19,7 @@ pulz-render-macros = { path = "macros" } pulz-functional-utils = { version = "0.1.0-alpha", path = "../functional-utils" } atomic_refcell = { workspace = true } +serde = { worspace = true } ambassador = { workspace = true } typemap = { workspace = true } dynsequence = { workspace = true } diff --git a/crates/render/src/buffer.rs b/crates/render/src/buffer.rs index 7553957..5c47106 100644 --- a/crates/render/src/buffer.rs +++ b/crates/render/src/buffer.rs @@ -1,8 +1,9 @@ use bitflags::bitflags; +use serde::{Deserialize, Serialize}; crate::backend::define_gpu_resource!(Buffer, BufferDescriptor); -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct BufferDescriptor { pub size: usize, pub usage: BufferUsage, @@ -24,6 +25,7 @@ impl Default for BufferDescriptor { } bitflags! { + #[derive(Default, Serialize, Deserialize)] pub struct BufferUsage: u32 { const TRANSFER_SRC = 1; const TRANSFER_DST = 2; diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index 864fdf5..5aa761c 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -45,6 +45,8 @@ pub mod shader; pub mod texture; pub mod view; +pub mod utils; + pub use pulz_window as window; pub mod color { diff --git a/crates/render/src/pipeline/binding.rs b/crates/render/src/pipeline/binding.rs index 3143c42..fe29397 100644 --- a/crates/render/src/pipeline/binding.rs +++ b/crates/render/src/pipeline/binding.rs @@ -1,16 +1,17 @@ use std::borrow::Cow; pub use pulz_render_macros::AsBindingLayout; +use serde::{Deserialize, Serialize}; crate::backend::define_gpu_resource!(BindGroupLayout, BindGroupLayoutDescriptor<'l>); -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct BindGroupLayoutDescriptor<'a> { pub label: Option<&'a str>, pub entries: Cow<'a, [BindGroupLayoutEntry]>, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct BindGroupLayoutEntry { pub binding: u32, // pub visibility: ShaderStages, diff --git a/crates/render/src/pipeline/compute_pipeline.rs b/crates/render/src/pipeline/compute_pipeline.rs index 214b3b8..5947619 100644 --- a/crates/render/src/pipeline/compute_pipeline.rs +++ b/crates/render/src/pipeline/compute_pipeline.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + use crate::{ pipeline::{PipelineLayout, SpecializationInfo}, shader::ShaderModule, @@ -5,10 +7,12 @@ use crate::{ crate::backend::define_gpu_resource!(ComputePipeline, ComputePipelineDescriptor<'l>); -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct ComputePipelineDescriptor<'a> { pub label: Option<&'a str>, + #[serde(with = "crate::utils::serde_slots::option")] pub layout: Option, + #[serde(with = "crate::utils::serde_slots")] pub module: ShaderModule, pub entry_point: &'a str, pub specialization: SpecializationInfo<'a>, diff --git a/crates/render/src/pipeline/graphics_pass.rs b/crates/render/src/pipeline/graphics_pass.rs index 3862955..7a93c6b 100644 --- a/crates/render/src/pipeline/graphics_pass.rs +++ b/crates/render/src/pipeline/graphics_pass.rs @@ -1,4 +1,5 @@ use pulz_transform::math::USize2; +use serde::{Deserialize, Serialize}; use crate::{ graph::{ @@ -11,7 +12,7 @@ use crate::{ crate::backend::define_gpu_resource!(GraphicsPass, GraphicsPassDescriptor); #[derive( - Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, + Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize, )] pub enum LoadOp { #[default] @@ -21,7 +22,7 @@ pub enum LoadOp { } #[derive( - Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, + Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize, )] pub enum StoreOp { #[default] @@ -29,14 +30,14 @@ pub enum StoreOp { DontCare, } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] pub struct LoadStoreOps { pub load_op: LoadOp, pub store_op: StoreOp, } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] pub struct AttachmentDescriptor { pub format: TextureFormat, pub usage: TextureUsage, @@ -44,7 +45,7 @@ pub struct AttachmentDescriptor { pub samples: u8, } -#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] pub struct SubpassDescriptor { input_attachments: Vec, color_attachments: Vec, @@ -67,7 +68,7 @@ impl SubpassDescriptor { } } -#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] pub struct GraphicsPassDescriptor { attachments: Vec, load_store_ops: Vec, diff --git a/crates/render/src/pipeline/graphics_pipeline.rs b/crates/render/src/pipeline/graphics_pipeline.rs index b753cba..b0c8763 100644 --- a/crates/render/src/pipeline/graphics_pipeline.rs +++ b/crates/render/src/pipeline/graphics_pipeline.rs @@ -4,6 +4,7 @@ use std::{ }; use bitflags::bitflags; +use serde::{Deserialize, Serialize}; use crate::{ pipeline::{GraphicsPass, PipelineLayout, SpecializationInfo}, @@ -13,9 +14,10 @@ use crate::{ crate::backend::define_gpu_resource!(GraphicsPipeline, GraphicsPipelineDescriptor<'l>); -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct GraphicsPipelineDescriptor<'a> { pub label: Option<&'a str>, + #[serde(with = "crate::utils::serde_slots::option")] pub layout: Option, pub vertex: VertexState<'a>, pub primitive: PrimitiveState, @@ -23,29 +25,31 @@ pub struct GraphicsPipelineDescriptor<'a> { pub fragment: Option>, pub samples: u32, pub specialization: SpecializationInfo<'a>, + #[serde(with = "crate::utils::serde_slots")] pub graphics_pass: GraphicsPass, } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct VertexState<'a> { + #[serde(with = "crate::utils::serde_slots")] pub module: ShaderModule, pub entry_point: &'a str, pub buffers: Cow<'a, [VertexBufferLayout<'a>]>, } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct VertexBufferLayout<'a> { pub array_stride: usize, pub attributes: Cow<'a, [VertexAttribute]>, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct VertexAttribute { pub format: VertexFormat, pub offset: usize, } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] pub struct PrimitiveState { pub topology: PrimitiveTopology, pub polygon_mode: PolygonMode, @@ -92,7 +96,7 @@ impl Hash for PrimitiveState { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct DepthStencilState { pub format: TextureFormat, pub depth: DepthState, @@ -121,7 +125,7 @@ impl Default for DepthStencilState { } } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] pub struct DepthState { pub write_enabled: bool, pub compare: CompareFunction, @@ -170,7 +174,7 @@ impl Default for DepthState { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct StencilState { pub front: StencilFaceState, pub back: StencilFaceState, @@ -204,7 +208,7 @@ impl Default for StencilState { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct StencilFaceState { pub compare: CompareFunction, pub fail_op: StencilOperation, @@ -227,14 +231,15 @@ impl Default for StencilFaceState { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct FragmentState<'a> { + #[serde(with = "crate::utils::serde_slots")] pub module: ShaderModule, pub entry_point: &'a str, pub targets: Cow<'a, [ColorTargetState]>, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct ColorTargetState { pub format: TextureFormat, pub blend: Option, @@ -265,13 +270,13 @@ impl From for ColorTargetState { } } -#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct BlendState { pub color: BlendComponent, pub alpha: BlendComponent, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct BlendComponent { pub operation: BlendOperation, pub src_factor: BlendFactor, @@ -293,7 +298,7 @@ impl Default for BlendComponent { } #[derive( - Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, + Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, )] pub enum PolygonMode { #[default] @@ -303,7 +308,7 @@ pub enum PolygonMode { } #[derive( - Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, + Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, )] pub enum PrimitiveTopology { PointList, @@ -315,7 +320,7 @@ pub enum PrimitiveTopology { } #[derive( - Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, + Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, )] pub enum VertexStepMode { #[default] @@ -324,7 +329,7 @@ pub enum VertexStepMode { } #[derive( - Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, + Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, )] #[non_exhaustive] pub enum VertexFormat { @@ -367,7 +372,7 @@ pub enum VertexFormat { } #[derive( - Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, + Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, )] pub enum IndexFormat { Uint16, @@ -376,7 +381,7 @@ pub enum IndexFormat { } #[derive( - Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, + Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, )] pub enum FrontFace { #[default] @@ -384,14 +389,14 @@ pub enum FrontFace { Clockwise, } -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] pub enum Face { Front, Back, } #[derive( - Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, + Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, )] pub enum CompareFunction { Never, @@ -406,7 +411,7 @@ pub enum CompareFunction { } #[derive( - Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, + Copy, Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, )] pub enum StencilOperation { #[default] @@ -420,7 +425,7 @@ pub enum StencilOperation { DecrementWrap, } -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] pub enum BlendOperation { Add, Subtract, @@ -429,7 +434,7 @@ pub enum BlendOperation { Max, } -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] pub enum BlendFactor { Zero, One, @@ -447,6 +452,7 @@ pub enum BlendFactor { } bitflags! { + #[derive(Serialize, Deserialize)] pub struct ColorWrite: u32 { const RED = 1; const GREEN = 2; diff --git a/crates/render/src/pipeline/pipeline_layout.rs b/crates/render/src/pipeline/pipeline_layout.rs index 0662c94..06dd036 100644 --- a/crates/render/src/pipeline/pipeline_layout.rs +++ b/crates/render/src/pipeline/pipeline_layout.rs @@ -1,9 +1,12 @@ +use serde::{Deserialize, Serialize}; + use super::BindGroupLayout; crate::backend::define_gpu_resource!(PipelineLayout, PipelineLayoutDescriptor<'l>); -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct PipelineLayoutDescriptor<'a> { pub label: Option<&'a str>, + #[serde(with = "crate::utils::serde_slots::cow_vec")] pub bind_group_layouts: std::borrow::Cow<'a, [BindGroupLayout]>, } diff --git a/crates/render/src/pipeline/ray_tracing_pipeline.rs b/crates/render/src/pipeline/ray_tracing_pipeline.rs index 0570d70..4ceca6a 100644 --- a/crates/render/src/pipeline/ray_tracing_pipeline.rs +++ b/crates/render/src/pipeline/ray_tracing_pipeline.rs @@ -1,5 +1,7 @@ use std::borrow::Cow; +use serde::{Deserialize, Serialize}; + use crate::{ pipeline::{PipelineLayout, SpecializationInfo}, shader::ShaderModule, @@ -7,9 +9,10 @@ use crate::{ crate::backend::define_gpu_resource!(RayTracingPipeline, RayTracingPipelineDescriptor<'l>); -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct RayTracingPipelineDescriptor<'a> { pub label: Option<&'a str>, + #[serde(with = "crate::utils::serde_slots::option")] pub layout: Option, pub modules: Cow<'a, [RayTracingShaderModule<'a>]>, pub groups: Cow<'a, [RayTracingShaderGroup]>, @@ -17,7 +20,7 @@ pub struct RayTracingPipelineDescriptor<'a> { pub specialization: SpecializationInfo<'a>, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct RayTracingShaderGroup { pub group_type: RayTracingGroupType, pub general_shader: u32, @@ -26,14 +29,15 @@ pub struct RayTracingShaderGroup { pub intersection_shader: u32, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct RayTracingShaderModule<'a> { pub stage: RayTracingStage, + #[serde(with = "crate::utils::serde_slots")] pub module: ShaderModule, pub entry_point: &'a str, } -#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash)] +#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)] pub enum RayTracingStage { #[default] Raygen, @@ -44,7 +48,7 @@ pub enum RayTracingStage { Callable, } -#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash)] +#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)] pub enum RayTracingGroupType { #[default] General, diff --git a/crates/render/src/pipeline/specialization.rs b/crates/render/src/pipeline/specialization.rs index 59f2623..b621a9a 100644 --- a/crates/render/src/pipeline/specialization.rs +++ b/crates/render/src/pipeline/specialization.rs @@ -1,15 +1,17 @@ use std::hash::{Hash, Hasher}; +use serde::{Deserialize, Serialize}; + pub type SpecializationInfo<'a> = Vec>; -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct SpecializationMapEntry<'a> { pub constant_id: u32, pub name: &'a str, pub value: PipelineConstantValue, } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] pub enum PipelineConstantValue { Bool(bool), Float(f32), diff --git a/crates/render/src/texture/descriptor.rs b/crates/render/src/texture/descriptor.rs index b913805..26a46cb 100644 --- a/crates/render/src/texture/descriptor.rs +++ b/crates/render/src/texture/descriptor.rs @@ -1,9 +1,10 @@ use bitflags::bitflags; use pulz_transform::math::{usize2, usize3}; +use serde::{Deserialize, Serialize}; use crate::math::{USize2, USize3}; -#[derive(Debug, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] pub struct TextureDescriptor { pub dimensions: TextureDimensions, pub mip_level_count: u32, @@ -45,7 +46,7 @@ impl Default for TextureDescriptor { } } -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] pub enum TextureDimensions { D1(u32), D2(USize2), @@ -81,7 +82,7 @@ impl TextureDimensions { } } -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] #[non_exhaustive] pub enum TextureFormat { // 8-bit formats @@ -241,6 +242,7 @@ impl Default for TextureFormat { } bitflags! { + #[derive(Serialize, Deserialize)] pub struct TextureAspects: u32 { const COLOR = 1; const DEPTH = 2; @@ -258,6 +260,7 @@ impl Default for TextureAspects { } bitflags! { + #[derive(Default, Serialize, Deserialize)] pub struct TextureUsage: u32 { const TRANSFER_SRC = 1; const TRANSFER_DST = 2; @@ -289,7 +292,7 @@ impl TextureUsage { } } -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default, Serialize, Deserialize)] pub struct ImageDataLayout { pub offset: usize, pub bytes_per_row: u32, diff --git a/crates/render/src/utils.rs b/crates/render/src/utils.rs new file mode 100644 index 0000000..1522311 --- /dev/null +++ b/crates/render/src/utils.rs @@ -0,0 +1,248 @@ +pub mod serde_slots { + use std::{cell::RefCell, marker::PhantomData}; + + use fnv::FnvHashMap; + + thread_local! { static CURENT_MAPPER: RefCell = RefCell::new(typemap::TypeMap::new()) } + + struct SlotTypeKey(PhantomData); + impl typemap::Key for SlotTypeKey { + type Value = FnvHashMap; + } + pub struct SlotDeserializationMapper { + _private: (), + } + + impl SlotDeserializationMapper { + const INSTANCE: Self = Self { _private: () }; + + pub fn define(&mut self, old: u64, new: K) -> Option { + CURENT_MAPPER.with(|m| { + m.borrow_mut() + .entry::>() + .or_insert_with(Default::default) + .insert(old, new) + }) + } + pub fn resolve(&self, old: u64) -> Option { + CURENT_MAPPER.with(|m| m.borrow().get::>()?.get(&old).copied()) + } + + pub fn with(f: F) -> R + where + F: FnOnce(&mut Self) -> R, + { + let is_empty = CURENT_MAPPER.with(|m| m.borrow().is_empty()); + assert!(is_empty, "nested calls are not allowed"); + let mut tmp = Self::INSTANCE; + let r = f(&mut tmp); + CURENT_MAPPER + .with(|m| std::mem::replace(&mut *m.borrow_mut(), typemap::TypeMap::new())); + r + } + } + + struct SlotVisitor(PhantomData); + + impl<'de, K: slotmap::Key + 'static> serde::de::Visitor<'de> for SlotVisitor { + type Value = K; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("an integer between 0 and 2^64") + } + + fn visit_i64(self, value: i64) -> Result + where + E: serde::de::Error, + { + self.visit_u64(value as u64) + } + + fn visit_u64(self, old_value: u64) -> Result + where + E: serde::de::Error, + { + if let Some(new_value) = SlotDeserializationMapper::INSTANCE.resolve(old_value) { + Ok(new_value) + } else { + Err(E::custom(format!( + "The reference {} for {} was not defined", + old_value, + std::any::type_name::() + ))) + } + } + } + + struct OptionVisitor(PhantomData); + impl<'de, T> serde::de::Visitor<'de> for OptionVisitor + where + T: slotmap::Key + 'static, + { + type Value = Option; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("option") + } + + #[inline] + fn visit_unit(self) -> Result + where + E: serde::de::Error, + { + Ok(None) + } + + #[inline] + fn visit_none(self) -> Result + where + E: serde::de::Error, + { + Ok(None) + } + + #[inline] + fn visit_some(self, deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + deserialize(deserializer).map(Some) + } + } + + struct VecVisitor(PhantomData); + + impl<'de, T> serde::de::Visitor<'de> for VecVisitor + where + T: slotmap::Key + 'static, + { + type Value = Vec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("a sequence") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut values = Vec::with_capacity(seq.size_hint().unwrap_or(0)); + + while let Some(SerdeSlotKey(value)) = seq.next_element()? { + values.push(value); + } + + Ok(values) + } + } + + pub struct SerdeSlotKey(pub K); + + impl serde::Serialize for SerdeSlotKey { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serialize(&self.0, serializer) + } + } + + impl<'de, K: slotmap::Key + 'static> serde::Deserialize<'de> for SerdeSlotKey { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserialize(deserializer).map(SerdeSlotKey) + } + } + + #[inline] + pub fn serialize(value: &K, s: S) -> Result + where + S: serde::ser::Serializer, + K: slotmap::Key + 'static, + { + s.serialize_u64(value.data().as_ffi()) + } + + #[inline] + pub fn deserialize<'de, D, K>(d: D) -> Result + where + D: serde::Deserializer<'de>, + K: slotmap::Key + 'static, + { + d.deserialize_u64(SlotVisitor(PhantomData::)) + } + + pub mod option { + use std::marker::PhantomData; + + #[inline] + pub fn serialize(value: &Option, s: S) -> Result + where + S: serde::ser::Serializer, + K: slotmap::Key + 'static, + { + if let Some(value) = value { + s.serialize_some(&super::SerdeSlotKey(*value)) + } else { + s.serialize_none() + } + } + + #[inline] + pub fn deserialize<'de, D, K>(d: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + K: slotmap::Key + 'static, + { + d.deserialize_u64(super::OptionVisitor(PhantomData::)) + } + } + + pub mod slice { + use serde::ser::SerializeSeq; + + #[inline] + pub fn serialize(value: &[K], s: S) -> Result + where + S: serde::ser::Serializer, + K: slotmap::Key + 'static, + { + let mut seq = s.serialize_seq(Some(value.len()))?; + for item in value { + seq.serialize_element(&super::SerdeSlotKey(*item))?; + } + seq.end() + } + } + + pub mod vec { + use std::marker::PhantomData; + + pub use super::slice::serialize; + + #[inline] + pub fn deserialize<'de, D, K>(d: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + K: slotmap::Key + 'static, + { + d.deserialize_seq(super::VecVisitor(PhantomData::)) + } + } + + pub mod cow_vec { + pub use super::slice::serialize; + + #[inline] + pub fn deserialize<'de, D, K>(d: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + K: slotmap::Key + 'static, + { + let vec = super::vec::deserialize(d)?; + Ok(std::borrow::Cow::Owned(vec)) + } + } +} From d0ac6b4235193440ab33b56bc11a6706a56338c1 Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sat, 28 Jan 2023 16:11:46 +0100 Subject: [PATCH 11/36] refactored renderers & window-system to create surface after Resumed-Event --- Cargo.toml | 2 +- crates/render-ash/examples/render-ash-demo.rs | 10 +- crates/render-ash/src/device.rs | 221 +++++++++-------- crates/render-ash/src/lib.rs | 224 +++++++++-------- crates/render-ash/src/swapchain.rs | 127 +++++----- crates/render-wgpu/Cargo.toml | 3 + .../render-wgpu/examples/render-wgpu-demo.rs | 12 +- crates/render-wgpu/src/lib.rs | 223 +++++++++++------ crates/render-wgpu/src/surface.rs | 9 +- crates/window-winit/Cargo.toml | 2 + crates/window-winit/src/lib.rs | 225 +++++++++--------- crates/window/src/lib.rs | 1 + crates/window/src/listener.rs | 63 +++++ crates/window/src/window.rs | 46 ++-- 14 files changed, 664 insertions(+), 504 deletions(-) create mode 100644 crates/window/src/listener.rs diff --git a/Cargo.toml b/Cargo.toml index 8e5599b..2c80f00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ tracing = "0.1" bitflags = "1.3" fnv = "1.0" radsort = "0.1" -bytemuck = "1.12" +bytemuck = "1.13" downcast-rs = "1.2" dynsequence = { version = "0.1.0-alpha.3" } ambassador = "0.3" diff --git a/crates/render-ash/examples/render-ash-demo.rs b/crates/render-ash/examples/render-ash-demo.rs index 021e9ba..f04e95e 100644 --- a/crates/render-ash/examples/render-ash-demo.rs +++ b/crates/render-ash/examples/render-ash-demo.rs @@ -2,7 +2,7 @@ use std::rc::Rc; use pulz_ecs::prelude::*; use pulz_render::camera::{Camera, RenderTarget}; -use pulz_render_ash::AshRendererBuilder; +use pulz_render_ash::AshRenderer; use pulz_render_pipeline_core::core_3d::CoreShadingModule; use pulz_window::{WindowDescriptor, WindowId}; use pulz_window_winit::{ @@ -15,6 +15,7 @@ fn init() -> (Resources, EventLoop<()>, Rc, WinitWindowSystem) { info!("Initializing..."); let mut resources = Resources::new(); resources.install(CoreShadingModule); + resources.install(AshRenderer::new().unwrap()); let event_loop = EventLoop::new(); let (window_system, window_id, window) = @@ -22,13 +23,6 @@ fn init() -> (Resources, EventLoop<()>, Rc, WinitWindowSystem) { .unwrap() .install(&mut resources); - unsafe { - AshRendererBuilder::new() - .with_window(window_id) - .install(&mut resources) - .unwrap(); - }; - // let mut schedule = resources.remove::().unwrap(); // schedule.init(&mut resources); // schedule.debug_dump_if_env(None).unwrap(); diff --git a/crates/render-ash/src/device.rs b/crates/render-ash/src/device.rs index f63197c..4181ca7 100644 --- a/crates/render-ash/src/device.rs +++ b/crates/render-ash/src/device.rs @@ -6,7 +6,6 @@ use tracing::{debug, info, warn}; use crate::{ instance::{AshInstance, VK_API_VERSION}, - swapchain::Surface, Error, ErrorNoExtension, Result, }; @@ -35,17 +34,12 @@ impl Deref for AshDevice { } impl AshInstance { - pub(crate) fn new_device(self: &Arc) -> Result> { - let (physical_device, indices, device_extensions) = pick_physical_device(self, None)?; - AshDevice::new(self, physical_device, indices, device_extensions) - } - - pub(crate) fn new_device_for_surface( + pub(crate) fn new_device( self: &Arc, - surface: &Surface, + surface_opt: vk::SurfaceKHR, ) -> Result> { let (physical_device, indices, device_extensions) = - pick_physical_device(self, Some(surface))?; + self.pick_physical_device(surface_opt)?; AshDevice::new(self, physical_device, indices, device_extensions) } } @@ -60,8 +54,7 @@ impl AshDevice { let gpu_alloc_props = unsafe { gpu_alloc_ash::device_properties(instance, VK_API_VERSION, physical_device)? }; - let (device_raw, queues) = create_logical_device( - instance, + let (device_raw, queues) = instance.create_logical_device( physical_device, indices, device_extensions.iter().copied(), @@ -216,123 +209,123 @@ fn get_device_extensions( Ok(extensions) } -fn pick_physical_device( - instance: &ash::Instance, - for_surface: Option<&Surface>, -) -> Result<(vk::PhysicalDevice, QueueFamilyIndices, Vec<&'static CStr>)> { - let physical_devices = unsafe { instance.enumerate_physical_devices()? }; +impl AshInstance { + fn pick_physical_device( + &self, + for_surface_opt: vk::SurfaceKHR, + ) -> Result<(vk::PhysicalDevice, QueueFamilyIndices, Vec<&'static CStr>)> { + let physical_devices = unsafe { self.enumerate_physical_devices()? }; - info!( - "{} devices (GPU) found with vulkan support.", - physical_devices.len() - ); + info!( + "{} devices (GPU) found with vulkan support.", + physical_devices.len() + ); - let mut result = None; - for (i, &physical_device) in physical_devices.iter().enumerate() { - log_device_infos(instance, physical_device, i); + let mut result = None; + for (i, &physical_device) in physical_devices.iter().enumerate() { + self.log_device_infos(physical_device, i); - if let Some((indices, extensions)) = - check_physical_device_suitable(instance, physical_device, for_surface) - { - if result.is_none() { - result = Some((i, physical_device, indices, extensions)) + if let Some((indices, extensions)) = + self.check_physical_device_suitable(physical_device, for_surface_opt) + { + if result.is_none() { + result = Some((i, physical_device, indices, extensions)) + } } } - } - match result { - Some((i, physical_device, indices, extensions)) => { - info!("Selected device: #{}", i); - Ok((physical_device, indices, extensions)) - } - None => { - warn!("Unable to find a suitable GPU!"); - Err(Error::NoAdapter) + match result { + Some((i, physical_device, indices, extensions)) => { + info!("Selected device: #{}", i); + Ok((physical_device, indices, extensions)) + } + None => { + warn!("Unable to find a suitable GPU!"); + Err(Error::NoAdapter) + } } } -} -fn log_device_infos( - instance: &ash::Instance, - physical_device: vk::PhysicalDevice, - device_index: usize, -) { - let device_properties = unsafe { instance.get_physical_device_properties(physical_device) }; - let _device_features = unsafe { instance.get_physical_device_features(physical_device) }; - let device_queue_families = - unsafe { instance.get_physical_device_queue_family_properties(physical_device) }; - - let device_name = unsafe { CStr::from_ptr(device_properties.device_name.as_ptr()) }; - - info!( - "Device #{}\tName: {:?}, id: {:?}, type: {:?}", - device_index, device_name, device_properties.device_id, device_properties.device_type - ); - - info!("\tQueue Families: {}", device_queue_families.len()); - for (i, queue_family) in device_queue_families.iter().enumerate() { + fn log_device_infos(&self, physical_device: vk::PhysicalDevice, device_index: usize) { + let device_properties = unsafe { self.get_physical_device_properties(physical_device) }; + let _device_features = unsafe { self.get_physical_device_features(physical_device) }; + let device_queue_families = + unsafe { self.get_physical_device_queue_family_properties(physical_device) }; + + let device_name = unsafe { CStr::from_ptr(device_properties.device_name.as_ptr()) }; + info!( - "\t #{}:{:4} x {:?}", - i, queue_family.queue_count, queue_family.queue_flags + "Device #{}\tName: {:?}, id: {:?}, type: {:?}", + device_index, device_name, device_properties.device_id, device_properties.device_type ); + + info!("\tQueue Families: {}", device_queue_families.len()); + for (i, queue_family) in device_queue_families.iter().enumerate() { + info!( + "\t #{}:{:4} x {:?}", + i, queue_family.queue_count, queue_family.queue_flags + ); + } } -} -fn check_physical_device_suitable( - instance: &ash::Instance, - physical_device: vk::PhysicalDevice, - for_surface: Option<&Surface>, -) -> Option<(QueueFamilyIndices, Vec<&'static CStr>)> { - let indices = QueueFamilyIndices::from_physical_device(instance, physical_device, for_surface)?; - let extensions = get_device_extensions(instance, physical_device).ok()?; - - if let Some(surface) = for_surface { - if !extensions.contains(&khr::Swapchain::name()) - || surface.query_swapchain_support(physical_device).is_none() + fn check_physical_device_suitable( + &self, + physical_device: vk::PhysicalDevice, + for_surface_opt: vk::SurfaceKHR, + ) -> Option<(QueueFamilyIndices, Vec<&'static CStr>)> { + let indices = + QueueFamilyIndices::from_physical_device(self, physical_device, for_surface_opt)?; + let extensions = get_device_extensions(self, physical_device).ok()?; + + if for_surface_opt != vk::SurfaceKHR::null() + && (!extensions.contains(&khr::Swapchain::name()) + || self + .query_swapchain_support(for_surface_opt, physical_device) + .is_none()) { return None; } + + Some((indices, extensions)) } - Some((indices, extensions)) -} + #[inline] + fn create_logical_device<'a>( + &self, + physical_device: vk::PhysicalDevice, + indices: QueueFamilyIndices, + extensions: impl IntoIterator, + ) -> Result<(ash::Device, Queues)> { + let extensions_ptr: Vec<_> = extensions.into_iter().map(CStr::as_ptr).collect(); + self._create_logical_device(physical_device, indices, &extensions_ptr) + } -#[inline] -fn create_logical_device<'a>( - instance: &ash::Instance, - physical_device: vk::PhysicalDevice, - indices: QueueFamilyIndices, - extensions: impl IntoIterator, -) -> Result<(ash::Device, Queues)> { - let extensions_ptr: Vec<_> = extensions.into_iter().map(CStr::as_ptr).collect(); - _create_logical_device(instance, physical_device, indices, &extensions_ptr) -} + fn _create_logical_device( + &self, + physical_device: vk::PhysicalDevice, + indices: QueueFamilyIndices, + extensions_ptr: &[*const c_char], + ) -> Result<(ash::Device, Queues)> { + let device = unsafe { + self.create_device( + physical_device, + &vk::DeviceCreateInfo::builder() + .queue_create_infos(&[vk::DeviceQueueCreateInfo::builder() + .queue_family_index(indices.graphics_family) + .queue_priorities(&[1.0_f32]) + .build()]) + .enabled_extension_names(extensions_ptr), + // .enabled_features(&vk::PhysicalDeviceFeatures { + // ..Default::default() // default just enable no feature. + // }) + None, + )? + }; -fn _create_logical_device( - instance: &ash::Instance, - physical_device: vk::PhysicalDevice, - indices: QueueFamilyIndices, - extensions_ptr: &[*const c_char], -) -> Result<(ash::Device, Queues)> { - let device = unsafe { - instance.create_device( - physical_device, - &vk::DeviceCreateInfo::builder() - .queue_create_infos(&[vk::DeviceQueueCreateInfo::builder() - .queue_family_index(indices.graphics_family) - .queue_priorities(&[1.0_f32]) - .build()]) - .enabled_extension_names(extensions_ptr), - // .enabled_features(&vk::PhysicalDeviceFeatures { - // ..Default::default() // default just enable no feature. - // }) - None, - )? - }; - - let queues = Queues::from_device(&device, indices); - - Ok((device, queues)) + let queues = Queues::from_device(&device, indices); + + Ok((device, queues)) + } } pub struct QueueFamilyIndices { @@ -343,9 +336,9 @@ pub struct QueueFamilyIndices { impl QueueFamilyIndices { fn from_physical_device( - instance: &ash::Instance, + instance: &AshInstance, physical_device: vk::PhysicalDevice, - for_surface: Option<&Surface>, + for_surface_opt: vk::SurfaceKHR, ) -> Option { let queue_families = unsafe { instance.get_physical_device_queue_family_properties(physical_device) }; @@ -377,7 +370,7 @@ impl QueueFamilyIndices { && queue_family.queue_flags.contains(vk::QueueFlags::GRAPHICS) { indices.graphics = Some(i); - if for_surface.is_none() { + if for_surface_opt == vk::SurfaceKHR::null() { indices.present = Some(i); } } @@ -389,10 +382,8 @@ impl QueueFamilyIndices { } if indices.present.is_none() - && for_surface.is_some() - && for_surface - .unwrap() - .get_physical_device_surface_support(physical_device, i) + && for_surface_opt != vk::SurfaceKHR::null() + && instance.get_physical_device_surface_support(physical_device, i, for_surface_opt) { indices.present = Some(i); } diff --git a/crates/render-ash/src/lib.rs b/crates/render-ash/src/lib.rs index f802514..44109b0 100644 --- a/crates/render-ash/src/lib.rs +++ b/crates/render-ash/src/lib.rs @@ -51,7 +51,8 @@ mod shader; mod swapchain; use pulz_window::{ - RawWindow, RawWindowHandles, WindowDescriptor, WindowId, Windows, WindowsMirror, + listener::{WindowSystemListener, WindowSystemListeners}, + RawWindow, Window, WindowId, Windows, WindowsMirror, }; #[derive(Error, Debug)] @@ -122,7 +123,7 @@ impl From<&vk::Result> for Error { pub type Result = std::result::Result; -pub struct AshRenderer { +struct AshRendererFull { device: Arc, res: AshResources, frames: Vec, @@ -131,13 +132,13 @@ pub struct AshRenderer { graph: AshRenderGraph, } -impl Drop for AshRenderer { +impl Drop for AshRendererFull { fn drop(&mut self) { unsafe { self.device.device_wait_idle().unwrap(); } self.frames.clear(); - self.res.destroy_all(&self.device); + self.res.clear_all(); } } @@ -210,45 +211,13 @@ impl Drop for Frame { } } -impl AshRenderer { - pub fn new(flags: AshRendererFlags) -> Result { - let instance = AshInstance::new(flags)?; - let device = instance.new_device()?; - Ok(Self::from_device(device)) - } - - pub fn for_window( - flags: AshRendererFlags, - window_id: WindowId, - window_descriptor: &WindowDescriptor, - // TODO: link lifetimes of HasRawWindowHandle and Surface! - raw_window: &dyn RawWindow, - ) -> Result { - let instance = AshInstance::new(flags)?; - let surface = instance.new_surface(raw_window)?; - let device = instance.new_device_for_surface(&surface)?; - let mut renderer = Self::from_device(device); - - let surface_swapchain = renderer.device.new_swapchain( - surface, - window_descriptor.size, - //TODO: ergonomics - if window_descriptor.vsync { 3 } else { 2 }, - if window_descriptor.vsync { - vk::PresentModeKHR::MAILBOX - } else { - vk::PresentModeKHR::IMMEDIATE - }, - )?; - renderer.surfaces.insert(window_id, surface_swapchain); - Ok(renderer) - } - +impl AshRendererFull { fn from_device(device: Arc) -> Self { - let graph = AshRenderGraph::create(&device); + let res = AshResources::new(&device); + let graph = AshRenderGraph::new(&device); Self { device, - res: AshResources::new(), + res, frames: Vec::with_capacity(Frame::NUM_FRAMES_IN_FLIGHT), current_frame: 0, surfaces: WindowsMirror::new(), @@ -453,88 +422,141 @@ impl AshRenderer { Ok(()) } - fn run_render_system( - mut renderer: ResMut<'_, Self>, - mut windows: ResMut<'_, Windows>, - src_graph: Res<'_, RenderGraph>, - draw_phases: Res<'_, DrawPhases>, - ) { - renderer.reconfigure_swapchains(&mut windows); + fn run(&mut self, windows: &mut Windows, src_graph: &RenderGraph, draw_phases: &DrawPhases) { + self.reconfigure_swapchains(windows); // TODO: maybe graph needs to consider updated swapchain format & dimensions? - renderer.graph.update(&src_graph); + self.graph.update(src_graph, &mut self.res).unwrap(); - let mut submission_group = renderer.begin_frame().unwrap(); - renderer - .render_frame(&mut submission_group, &src_graph, &draw_phases) + let mut submission_group = self.begin_frame().unwrap(); + self.render_frame(&mut submission_group, src_graph, draw_phases) .unwrap(); - renderer.end_frame(submission_group).unwrap(); + self.end_frame(submission_group).unwrap(); } } -impl ModuleWithOutput for AshRenderer { - type Output<'l> = &'l mut Self; +#[allow(clippy::large_enum_variant)] +enum AshRendererInner { + Early { + instance: Arc, + flags: AshRendererFlags, + }, + Full(AshRendererFull), +} - fn install_modules(&self, res: &mut Resources) { - res.install(RenderModule); +pub struct AshRenderer(AshRendererInner); + +impl AshRenderer { + #[inline] + pub fn new() -> Result { + Self::with_flags(AshRendererFlags::DEBUG) } - fn install_resources(self, res: &mut Resources) -> &mut Self { - let resource_id = res.insert(self); - res.get_mut_id(resource_id).unwrap() + #[inline] + pub fn with_flags(flags: AshRendererFlags) -> Result { + let instance = AshInstance::new(flags)?; + Ok(Self(AshRendererInner::Early { instance, flags })) } - fn install_systems(schedule: &mut Schedule) { - schedule - .add_system(Self::run_render_system) - .into_phase(RenderSystemPhase::Render); + fn init(&mut self) -> Result<&mut AshRendererFull> { + if let AshRendererInner::Early { instance, .. } = &self.0 { + let device = instance.new_device(vk::SurfaceKHR::null())?; + let renderer = AshRendererFull::from_device(device); + self.0 = AshRendererInner::Full(renderer); + } + let AshRendererInner::Full(renderer) = &mut self.0 else { + unreachable!() + }; + Ok(renderer) } -} -pub struct AshRendererBuilder { - flags: AshRendererFlags, - window: Option, -} + fn init_window( + &mut self, + window_id: WindowId, + window_descriptor: &Window, + window_raw: &dyn RawWindow, + ) -> Result<&mut AshRendererFull> { + if let AshRendererInner::Full(renderer) = &mut self.0 { + renderer.surfaces.remove(window_id); // replaces old surface + let surface = renderer.device.instance().new_surface(window_raw)?; + let swapchain = renderer.device.new_swapchain(surface, window_descriptor)?; + renderer.surfaces.insert(window_id, swapchain); + } else { + let AshRendererInner::Early { instance, .. } = &self.0 else { + unreachable!() + }; + let surface = instance.new_surface(&window_raw)?; + let device = instance.new_device(surface.raw())?; + let swapchain = device.new_swapchain(surface, window_descriptor)?; + let mut renderer = AshRendererFull::from_device(device); + renderer.surfaces.insert(window_id, swapchain); + self.0 = AshRendererInner::Full(renderer); + } + let AshRendererInner::Full(renderer) = &mut self.0 else { + unreachable!() + }; + Ok(renderer) + } -impl AshRendererBuilder { - #[inline] - pub const fn new() -> Self { - Self { - flags: AshRendererFlags::DEBUG, - window: None, + fn run(&mut self, windows: &mut Windows, src_graph: &RenderGraph, draw_phases: &DrawPhases) { + if let AshRendererInner::Full(renderer) = &mut self.0 { + renderer.run(windows, src_graph, draw_phases); + } else { + panic!("renderer uninitialized"); } } +} - #[inline] - pub const fn with_flags(mut self, flags: AshRendererFlags) -> Self { - self.flags = flags; - self +struct AshRendererInitWindowSystemListener(ResourceId); + +impl WindowSystemListener for AshRendererInitWindowSystemListener { + fn on_created( + &self, + res: &Resources, + window_id: WindowId, + window_desc: &Window, + window_raw: &dyn RawWindow, + ) { + let Some(mut renderer) = res.borrow_res_mut_id(self.0) else { return }; + renderer + .init_window(window_id, window_desc, window_raw) + .unwrap(); + } + fn on_resumed(&self, res: &Resources) { + let Some(mut renderer) = res.borrow_res_mut_id(self.0) else { return }; + renderer.init().unwrap(); } + fn on_closed(&self, res: &Resources, window_id: WindowId) { + let Some(mut renderer) = res.borrow_res_mut_id(self.0) else { return }; + let AshRendererInner::Full(renderer) = &mut renderer.0 else { return }; + renderer.surfaces.remove(window_id); + } + fn on_suspended(&self, res: &Resources) { + let Some(mut renderer) = res.borrow_res_mut_id(self.0) else { return }; + let AshRendererInner::Full(renderer) = &mut renderer.0 else { return }; + renderer.surfaces.clear(); + } +} - /// # Unsafe - /// Raw Window Handle must be a valid object to create a surface - /// upon and must remain valid for the lifetime of the surface. - #[inline] - pub unsafe fn with_window(mut self, window_id: WindowId) -> Self { - self.window = Some(window_id); - self +impl ModuleWithOutput for AshRenderer { + type Output<'l> = &'l mut Self; + + fn install_modules(&self, res: &mut Resources) { + res.install(RenderModule); } - pub fn install(self, res: &mut Resources) -> Result<&mut AshRenderer> { - let renderer = if let Some(window_id) = self.window { - let windows = res.borrow_res::().unwrap(); - // TODO: make not dependent on descriptor. - // add size-method to RawWindow - let descriptor = &windows[window_id]; - let raw_window_handles = res.borrow_res::().unwrap(); - let handle = raw_window_handles - .get(window_id) - .and_then(|h| h.upgrade()) - .ok_or(Error::WindowNotAvailable)?; - AshRenderer::for_window(self.flags, window_id, descriptor, handle.as_ref())? - } else { - AshRenderer::new(self.flags)? - }; - Ok(res.install(renderer)) + fn install_resources(self, res: &mut Resources) -> &mut Self { + let listeners_id = res.init_unsend::(); + let resource_id = res.insert_unsend(self); + res.get_mut_id(listeners_id) + .unwrap() + .insert(AshRendererInitWindowSystemListener(resource_id)); + res.get_mut_id(resource_id).unwrap() + } + + fn install_systems(schedule: &mut Schedule) { + schedule + .add_system(Self::run) + .into_phase(RenderSystemPhase::Render); } } diff --git a/crates/render-ash/src/swapchain.rs b/crates/render-ash/src/swapchain.rs index 72b0109..7610b9e 100644 --- a/crates/render-ash/src/swapchain.rs +++ b/crates/render-ash/src/swapchain.rs @@ -2,12 +2,15 @@ use std::sync::Arc; use ash::{extensions::khr, vk}; use pulz_render::{math::uvec2, texture::Texture}; -use pulz_window::{RawWindow, Size2, WindowId}; +use pulz_window::{RawWindow, Size2, Window, WindowId}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; use slotmap::Key; use crate::{ - device::AshDevice, drop_guard::Destroy, instance::AshInstance, AshRenderer, Error, Result, + device::AshDevice, + drop_guard::{Destroy, Guard}, + instance::AshInstance, + AshRendererFull, Error, Result, }; pub struct Surface { @@ -255,70 +258,59 @@ impl AshInstance { } /// SAFETY: display and window handles must be valid for the complete lifetime of surface - pub(crate) fn new_surface(self: &Arc, window: &dyn RawWindow) -> Result { + pub(crate) fn new_surface(&self, window: &dyn RawWindow) -> Result> { let surface_raw = unsafe { self.create_surface_raw(window.raw_display_handle(), window.raw_window_handle())? }; - Ok(Surface { - instance: self.clone(), - surface_raw, - }) + Ok(Guard::new(self, surface_raw)) } } -impl Surface { +impl AshInstance { pub fn get_physical_device_surface_support( &self, physical_device: vk::PhysicalDevice, queue_family_index: u32, + surface: vk::SurfaceKHR, ) -> bool { - let Ok(ext_surface) = self.instance.ext_surface() else { + let Ok(ext_surface) = self.ext_surface() else { return false; }; unsafe { ext_surface - .get_physical_device_surface_support( - physical_device, - queue_family_index, - self.surface_raw, - ) + .get_physical_device_surface_support(physical_device, queue_family_index, surface) .unwrap_or(false) } } pub fn query_swapchain_support( &self, + surface: vk::SurfaceKHR, physical_device: vk::PhysicalDevice, ) -> Option { - let ext_surface = self.instance.ext_surface().ok()?; - query_swapchain_support(ext_surface, self.surface_raw, physical_device) - } -} - -fn query_swapchain_support( - ext_surface: &khr::Surface, - surface_raw: vk::SurfaceKHR, - physical_device: vk::PhysicalDevice, -) -> Option { - unsafe { - let capabilities = ext_surface - .get_physical_device_surface_capabilities(physical_device, surface_raw) - .ok()?; - let formats = ext_surface - .get_physical_device_surface_formats(physical_device, surface_raw) - .ok()?; - let present_modes = ext_surface - .get_physical_device_surface_present_modes(physical_device, surface_raw) - .ok()?; - if formats.is_empty() || present_modes.is_empty() { - None - } else { - Some(SwapchainSupportDetail { - capabilities, - formats, - present_modes, - }) + let Ok(ext_surface) = self.ext_surface() else { + return None; + }; + unsafe { + let capabilities = ext_surface + .get_physical_device_surface_capabilities(physical_device, surface) + .ok()?; + let formats = ext_surface + .get_physical_device_surface_formats(physical_device, surface) + .ok()?; + let present_modes = ext_surface + .get_physical_device_surface_present_modes(physical_device, surface) + .ok()?; + if formats.is_empty() || present_modes.is_empty() { + None + } else { + Some(SwapchainSupportDetail { + capabilities, + formats, + present_modes, + }) + } } } } @@ -385,22 +377,24 @@ pub struct SurfaceSwapchain { } impl AshDevice { - pub fn new_swapchain( + pub(crate) fn new_swapchain( self: &Arc, - mut surface: Surface, - suggested_size: Size2, - suggested_image_count: u32, - suggested_present_mode: vk::PresentModeKHR, + surface: Guard<'_, vk::SurfaceKHR>, + window_descriptor: &Window, ) -> Result { - assert_eq!(self.instance().handle(), surface.instance.handle()); + let (image_count, present_mode) = if window_descriptor.vsync { + (3, vk::PresentModeKHR::MAILBOX) + } else { + (2, vk::PresentModeKHR::IMMEDIATE) + }; let mut swapchain = SurfaceSwapchain { device: self.clone(), - surface_raw: std::mem::take(&mut surface.surface_raw), + surface_raw: surface.take(), swapchain_raw: vk::SwapchainKHR::null(), - size: suggested_size, - image_count: suggested_image_count, + size: window_descriptor.size, + image_count, surface_format: Default::default(), - present_mode: suggested_present_mode, + present_mode, // TODO: custom usage image_usage: vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::TRANSFER_DST, images: Vec::new(), @@ -516,11 +510,12 @@ impl SurfaceSwapchain { // TODO: pass swapchain format to graph let ext_swapchain = self.device.ext_swapchain()?; - let ext_surface = self.device.instance().ext_surface()?; - let swapchain_support_info = - query_swapchain_support(ext_surface, self.surface_raw, self.device.physical_device()) - .ok_or(Error::NoSwapchainSupport)?; + let swapchain_support_info = self + .device + .instance() + .query_swapchain_support(self.surface_raw, self.device.physical_device()) + .ok_or(Error::NoSwapchainSupport)?; if !swapchain_support_info .capabilities @@ -670,6 +665,24 @@ impl SurfaceSwapchain { impl Drop for SurfaceSwapchain { fn drop(&mut self) { + if self.swapchain_raw != vk::SwapchainKHR::null() + || self.surface_raw != vk::SurfaceKHR::null() + || !self.retired_swapchains.is_empty() + { + unsafe { + self.device.device_wait_idle(); + } + } + + if !self.retired_swapchains.is_empty() { + let ext_swapchain = self.device.ext_swapchain().unwrap(); + for r in self.retired_swapchains.drain(..) { + unsafe { + ext_swapchain.destroy_swapchain(r, None); + } + } + } + // for image_view in self.image_views.drain(..) { // unsafe { // self.device.destroy_image_view(image_view, None); @@ -693,7 +706,7 @@ impl Drop for SurfaceSwapchain { } } -impl AshRenderer { +impl AshRendererFull { pub(crate) fn acquire_swapchain_image( &mut self, window_id: WindowId, diff --git a/crates/render-wgpu/Cargo.toml b/crates/render-wgpu/Cargo.toml index c858b7e..384d180 100644 --- a/crates/render-wgpu/Cargo.toml +++ b/crates/render-wgpu/Cargo.toml @@ -18,6 +18,9 @@ slotmap = { workspace = true } wgpu = "0.14" raw-window-handle = { workspace = true } +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +pollster = "0.2" + [dev-dependencies] anyhow = { workspace = true } naga = "0.10" diff --git a/crates/render-wgpu/examples/render-wgpu-demo.rs b/crates/render-wgpu/examples/render-wgpu-demo.rs index 2021ae0..5cfc4a2 100644 --- a/crates/render-wgpu/examples/render-wgpu-demo.rs +++ b/crates/render-wgpu/examples/render-wgpu-demo.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use pulz_ecs::prelude::*; use pulz_render::camera::{Camera, RenderTarget}; use pulz_render_pipeline_core::core_3d::CoreShadingModule; -use pulz_render_wgpu::WgpuRendererBuilder; +use pulz_render_wgpu::WgpuRenderer; use pulz_window::{WindowDescriptor, WindowId}; use pulz_window_winit::{ winit::{event_loop::EventLoop, window::Window}, @@ -15,6 +15,7 @@ async fn init() -> (Resources, EventLoop<()>, Rc, WinitWindowSystem) { info!("Initializing..."); let mut resources = Resources::new(); resources.install(CoreShadingModule); + resources.install(WgpuRenderer::new().await.unwrap()); let event_loop = EventLoop::new(); let (window_system, window_id, window) = @@ -22,15 +23,6 @@ async fn init() -> (Resources, EventLoop<()>, Rc, WinitWindowSystem) { .unwrap() .install(&mut resources); - // TODO: SAFETY - unsafe { - WgpuRendererBuilder::new() - .with_window(window_id) - .install(&mut resources) - .await - .unwrap() - }; - // let mut schedule = resources.remove::().unwrap(); // schedule.init(&mut resources); // schedule.debug_dump_if_env(None).unwrap(); diff --git a/crates/render-wgpu/src/lib.rs b/crates/render-wgpu/src/lib.rs index bb0f677..0255471 100644 --- a/crates/render-wgpu/src/lib.rs +++ b/crates/render-wgpu/src/lib.rs @@ -31,6 +31,10 @@ use convert::ConversionError; use graph::WgpuRenderGraph; use pulz_ecs::prelude::*; use pulz_render::{draw::DrawPhases, graph::RenderGraph, RenderModule, RenderSystemPhase}; +use pulz_window::{ + listener::{WindowSystemListener, WindowSystemListeners}, + RawWindow, Window, WindowId, Windows, WindowsMirror, +}; use resources::WgpuResources; use surface::Surface; use thiserror::Error; @@ -69,7 +73,7 @@ impl From for Error { pub type Result = std::result::Result; -pub struct WgpuRenderer { +struct WgpuRendererFull { instance: wgpu::Instance, adapter: wgpu::Adapter, device: wgpu::Device, @@ -104,36 +108,8 @@ async fn initialize_adapter_from_env_or_default( } } -impl WgpuRenderer { - pub async fn new() -> Result { - let backends = backend_bits_from_env_or_default(); - let instance = wgpu::Instance::new(backends); - let adapter = initialize_adapter_from_env_or_default(&instance, backends, None) - .await - .ok_or(Error::NoAdapter)?; - Self::for_adapter(instance, adapter).await - } - - /// # Unsafe - /// Raw Window Handle must be a valid object to create a surface - /// upon and must remain valid for the lifetime of the surface. - pub async fn for_window( - window_id: WindowId, - window: &Window, - window_handle: Rc, - ) -> Result { - let backends = backend_bits_from_env_or_default(); - let instance = wgpu::Instance::new(backends); - let surface = Surface::create(&instance, window, window_handle); - let adapter = initialize_adapter_from_env_or_default(&instance, backends, Some(&surface)) - .await - .ok_or(Error::NoAdapter)?; - let mut renderer = Self::for_adapter(instance, adapter).await?; - renderer.surfaces.insert(window_id, surface); - Ok(renderer) - } - - pub async fn for_adapter(instance: wgpu::Instance, adapter: wgpu::Adapter) -> Result { +impl WgpuRendererFull { + async fn for_adapter(instance: wgpu::Instance, adapter: wgpu::Adapter) -> Result { let trace_dir = std::env::var("WGPU_TRACE"); let (device, queue) = adapter .request_device( @@ -214,11 +190,7 @@ impl WgpuRenderer { self.queue.submit(cmds); } - fn run( - &mut self, - windows: &Windows, - src_graph: &RenderGraph, - ) { + fn run(&mut self, windows: &Windows, src_graph: &RenderGraph, _draw_phases: &DrawPhases) { self.reconfigure_surfaces(&windows); self.graph.update(&src_graph); self.aquire_swapchain_images(); @@ -227,6 +199,143 @@ impl WgpuRenderer { } } +#[allow(clippy::large_enum_variant)] +enum WgpuRendererInner { + #[cfg(not(target_arch = "wasm32"))] + Early { + instance: wgpu::Instance, + }, + #[cfg(not(target_arch = "wasm32"))] + Tmp, + Full(WgpuRendererFull), +} + +pub struct WgpuRenderer(WgpuRendererInner); + +impl WgpuRenderer { + pub async fn new() -> Result { + let backends = backend_bits_from_env_or_default(); + let instance = wgpu::Instance::new(backends); + if let Some(adapter) = wgpu::util::initialize_adapter_from_env(&instance, backends) { + let renderer = WgpuRendererFull::for_adapter(instance, adapter).await?; + return Ok(Self(WgpuRendererInner::Full(renderer))); + } + #[cfg(target_arch = "wasm32")] + { + let adapter = Self::default_adapter(&instance, None).await?; + let renderer = Self::for_adapter(instance, adapter).await?; + return Ok(Self(WgpuRendererInner::Full(renderer))); + } + #[cfg(not(target_arch = "wasm32"))] + Ok(Self(WgpuRendererInner::Early { instance })) + } + + async fn default_adapter( + instance: &wgpu::Instance, + compatible_surface: Option<&wgpu::Surface>, + ) -> Result { + let power_preference = wgpu::util::power_preference_from_env().unwrap_or_default(); + instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference, + force_fallback_adapter: false, + compatible_surface, + }) + .await + .ok_or(Error::NoAdapter) + } + + fn init_window( + &mut self, + window_id: WindowId, + window_descriptor: &Window, + window_raw: &dyn RawWindow, + ) -> Result<&mut WgpuRendererFull> { + if let WgpuRendererInner::Full(renderer) = &mut self.0 { + renderer.surfaces.remove(window_id); // replaces old surface + let surface = Surface::create(&renderer.instance, window_descriptor, window_raw); + renderer.surfaces.insert(window_id, surface); + } else { + #[cfg(not(target_arch = "wasm32"))] + { + let WgpuRendererInner::Early { instance } = std::mem::replace(&mut self.0, WgpuRendererInner::Tmp) else { + panic!("unexpected state"); + }; + let surface = Surface::create(&instance, window_descriptor, window_raw); + let mut renderer = pollster::block_on(async { + let adapter = Self::default_adapter(&instance, Some(&surface)).await?; + WgpuRendererFull::for_adapter(instance, adapter).await + })?; + renderer.surfaces.insert(window_id, surface); + self.0 = WgpuRendererInner::Full(renderer); + } + } + let WgpuRendererInner::Full(renderer) = &mut self.0 else { + unreachable!() + }; + Ok(renderer) + } + + fn init(&mut self) -> Result<&mut WgpuRendererFull> { + #[cfg(not(target_arch = "wasm32"))] + if !matches!(self.0, WgpuRendererInner::Full { .. }) { + let WgpuRendererInner::Early { instance } = std::mem::replace(&mut self.0, WgpuRendererInner::Tmp) else { + panic!("unexpected state"); + }; + let renderer = pollster::block_on(async { + let adapter = Self::default_adapter(&instance, None).await?; + WgpuRendererFull::for_adapter(instance, adapter).await + })?; + self.0 = WgpuRendererInner::Full(renderer); + } + let WgpuRendererInner::Full(renderer) = &mut self.0 else { + unreachable!() + }; + Ok(renderer) + } + + fn run(&mut self, windows: &mut Windows, src_graph: &RenderGraph, draw_phases: &DrawPhases) { + if let WgpuRendererInner::Full(renderer) = &mut self.0 { + renderer.run(windows, src_graph, draw_phases); + } else { + panic!("renderer uninitialized"); + } + } +} + +struct WgpuRendererInitWindowSystemListener(ResourceId); + +impl WindowSystemListener for WgpuRendererInitWindowSystemListener { + fn on_created( + &self, + res: &Resources, + window_id: WindowId, + window_desc: &Window, + window_raw: &dyn RawWindow, + ) { + let Some(mut renderer) = res.borrow_res_mut_id(self.0) else { return }; + renderer + .init_window(window_id, window_desc, window_raw) + .unwrap(); + } + + #[cfg(not(target_arch = "wasm32"))] + fn on_resumed(&self, res: &Resources) { + let Some(mut renderer) = res.borrow_res_mut_id(self.0) else { return }; + renderer.init().unwrap(); + } + fn on_closed(&self, res: &Resources, window_id: WindowId) { + let Some(mut renderer) = res.borrow_res_mut_id(self.0) else { return }; + let WgpuRendererInner::Full(renderer) = &mut renderer.0 else { return }; + renderer.surfaces.remove(window_id); + } + fn on_suspended(&self, res: &Resources) { + let Some(mut renderer) = res.borrow_res_mut_id(self.0) else { return }; + let WgpuRendererInner::Full(renderer) = &mut renderer.0 else { return }; + renderer.surfaces.clear(); + } +} + impl ModuleWithOutput for WgpuRenderer { type Output<'l> = &'l mut Self; @@ -235,7 +344,11 @@ impl ModuleWithOutput for WgpuRenderer { } fn install_resources(self, res: &mut Resources) -> &mut Self { + let listeners_id = res.init_unsend::(); let resource_id = res.insert_unsend(self); + res.get_mut_id(listeners_id) + .unwrap() + .insert(WgpuRendererInitWindowSystemListener(resource_id)); res.get_mut_id(resource_id).unwrap() } @@ -245,41 +358,3 @@ impl ModuleWithOutput for WgpuRenderer { .into_phase(RenderSystemPhase::Render); } } - -pub struct WgpuRendererBuilder { - window: Option, -} - -impl WgpuRendererBuilder { - #[inline] - pub const fn new() -> Self { - Self { window: None } - } - - /// # Unsafe - /// Raw Window Handle must be a valid object to create a surface - /// upon and must remain valid for the lifetime of the surface. - #[inline] - pub unsafe fn with_window(mut self, window_id: WindowId) -> Self { - self.window = Some(window_id); - self - } - - pub async fn install(self, res: &mut Resources) -> Result<&mut WgpuRenderer> { - let renderer = if let Some(window_id) = self.window { - let windows = res.borrow_res::().unwrap(); - // TODO: make not dependent on descriptor. - // add size-method to RawWindow - let descriptor = &windows[window_id]; - let raw_window_handles = res.borrow_res::().unwrap(); - let handle = raw_window_handles - .get(window_id) - .and_then(|h| h.upgrade()) - .ok_or(Error::WindowNotAvailable)?; - WgpuRenderer::for_window(window_id, descriptor, handle).await? - } else { - WgpuRenderer::new().await? - }; - Ok(res.install(renderer)) - } -} diff --git a/crates/render-wgpu/src/surface.rs b/crates/render-wgpu/src/surface.rs index 7fd979e..d9d0bde 100644 --- a/crates/render-wgpu/src/surface.rs +++ b/crates/render-wgpu/src/surface.rs @@ -1,14 +1,10 @@ -use std::{ - ops::{Deref, DerefMut}, - rc::Rc, -}; +use std::ops::{Deref, DerefMut}; use pulz_window::{RawWindow, Size2, Window}; use tracing::info; pub struct Surface { surface: wgpu::Surface, - window_handle: Rc, // holds reference to window to ensure sufface is still valid until destruction size: Size2, vsync: bool, format: wgpu::TextureFormat, @@ -18,12 +14,11 @@ impl Surface { pub fn create( instance: &wgpu::Instance, window: &Window, - window_handle: Rc, + window_handle: &dyn RawWindow, ) -> Self { let surface = unsafe { instance.create_surface(&window_handle) }; Self { surface, - window_handle, size: window.size, vsync: window.vsync, format: wgpu::TextureFormat::Rgba8UnormSrgb, diff --git a/crates/window-winit/Cargo.toml b/crates/window-winit/Cargo.toml index e5d4ffc..60b26b3 100644 --- a/crates/window-winit/Cargo.toml +++ b/crates/window-winit/Cargo.toml @@ -11,6 +11,8 @@ readme = "README.md" default = ["x11", "wayland"] x11 = ["winit/x11"] wayland = ["winit/wayland", "winit/wayland-dlopen", "winit/wayland-csd-adwaita"] +android-native-activity = ["winit/android-native-activity"] +android-game-activity = ["winit/android-game-activity"] [dependencies] pulz-ecs = { path = "../ecs" } diff --git a/crates/window-winit/src/lib.rs b/crates/window-winit/src/lib.rs index e22c0ac..ebf9873 100644 --- a/crates/window-winit/src/lib.rs +++ b/crates/window-winit/src/lib.rs @@ -30,7 +30,7 @@ use std::{ops::Deref, rc::Rc}; use fnv::FnvHashMap; use pulz_ecs::prelude::*; use pulz_window::{ - RawWindow, RawWindowHandles, Size2, WindowDescriptor, WindowId, Windows, WindowsMirror, + listener::WindowSystemListeners, Size2, WindowDescriptor, WindowId, Windows, WindowsMirror, }; use tracing::{debug, info, warn}; pub use winit; @@ -49,48 +49,9 @@ pub struct WinitWindows { } impl WinitWindows { - fn builder_for_descriptor(descriptor: &WindowDescriptor) -> winit::window::WindowBuilder { - let mut builder = - winit::window::WindowBuilder::new().with_title(descriptor.title.to_owned()); - - #[cfg(target_os = "windows")] - { - use winit::platform::windows::WindowBuilderExtWindows; - builder = builder.with_drag_and_drop(false); - } - - if descriptor.size != Size2::ZERO { - builder = - builder.with_inner_size(PhysicalSize::new(descriptor.size.x, descriptor.size.y)); - } - - builder - } - - fn create( - &mut self, - window_id: WindowId, - window: &mut WindowDescriptor, - event_loop: &EventLoopWindowTarget, - ) -> Result, OsError> { - let builder = Self::builder_for_descriptor(window); - let winit_window = builder.build(event_loop)?; - Self::update_window_descriptor(window, &winit_window); - debug!( - "created window {:?} with {:?}, {:?}", - window_id, - winit_window.id(), - winit_window.inner_size(), - ); - - Ok(self.insert(window_id, winit_window)) - } - - fn insert(&mut self, window_id: WindowId, winit_window: WinitWindow) -> Rc { - let winit_window = Rc::new(winit_window); + fn insert(&mut self, window_id: WindowId, winit_window: Rc) { self.windows.insert(window_id, winit_window.clone()); self.window_id_map.insert(winit_window.id(), window_id); - winit_window } #[inline] @@ -123,7 +84,6 @@ impl WinitWindows { }; self.window_id_map.remove(&window.id()); window.set_visible(false); - drop(window); true } } @@ -136,22 +96,38 @@ impl std::ops::Index for WinitWindows { } } +fn builder_for_descriptor(descriptor: &WindowDescriptor) -> winit::window::WindowBuilder { + let mut builder = winit::window::WindowBuilder::new().with_title(descriptor.title.to_owned()); + + #[cfg(target_os = "windows")] + { + use winit::platform::windows::WindowBuilderExtWindows; + builder = builder.with_drag_and_drop(false); + } + + if descriptor.size != Size2::ZERO { + builder = builder.with_inner_size(PhysicalSize::new(descriptor.size.x, descriptor.size.y)); + } + + builder +} + pub struct WinitWindowSystem { windows_id: ResourceId, + listeners_id: ResourceId, winit_windows_id: ResourceId, - raw_window_handles_id: ResourceId, active: bool, } pub struct WinitWindowSystemMut<'l> { windows: ResMut<'l, Windows>, + listeners: ResMut<'l, WindowSystemListeners>, winit_windows: ResMut<'l, WinitWindows>, - raw_window_handles: ResMut<'l, RawWindowHandles>, } pub struct WinitWindowModule { descriptor: WindowDescriptor, - window: WinitWindow, + window: Rc, } impl WinitWindowModule { @@ -159,13 +135,14 @@ impl WinitWindowModule { mut descriptor: WindowDescriptor, event_loop: &EventLoopWindowTarget, ) -> Result { - let builder = WinitWindows::builder_for_descriptor(&descriptor); - let window = builder.build(event_loop)?; + let builder = builder_for_descriptor(&descriptor); + let window = Rc::new(builder.build(event_loop)?); WinitWindows::update_window_descriptor(&mut descriptor, &window); Ok(Self { descriptor, window }) } - pub fn from_window(window: WinitWindow) -> Self { + pub fn from_window(window: impl Into>) -> Self { + let window: Rc = window.into(); let mut descriptor = WindowDescriptor::default(); WinitWindows::update_window_descriptor(&mut descriptor, &window); Self { descriptor, window } @@ -177,8 +154,8 @@ impl ModuleWithOutput for WinitWindowModule { fn install_resources(self, resources: &mut Resources) -> Self::Output<'_> { let sys = WinitWindowSystem::install(resources); let mut sys_mut = sys.as_mut(resources); - let (window_id, window) = - sys_mut.add_winit_window_with_descriptor(self.descriptor, self.window); + let Self { descriptor, window } = self; + let window_id = sys_mut.add_winit_window_with_descriptor(descriptor, window.clone()); (sys, window_id, window) } } @@ -186,69 +163,47 @@ impl ModuleWithOutput for WinitWindowModule { impl WinitWindowSystemMut<'_> { pub fn add_window( &mut self, - descriptor: WindowDescriptor, + mut descriptor: WindowDescriptor, event_loop: &EventLoopWindowTarget, ) -> Result<(WindowId, Rc), OsError> { - let window_id = self.windows.create(descriptor); - let winit_window = - self.winit_windows - .create(window_id, &mut self.windows[window_id], event_loop)?; - let raw_window: Rc = winit_window.clone(); - self.raw_window_handles - .insert(window_id, Rc::downgrade(&raw_window)); - Ok((window_id, winit_window)) + let builder = builder_for_descriptor(&descriptor); + let window = Rc::new(builder.build(event_loop)?); + WinitWindows::update_window_descriptor(&mut descriptor, &window); + let window_id = self.add_winit_window_with_descriptor(descriptor, window.clone()); + Ok((window_id, window)) } - pub fn add_winit_window(&mut self, window: WinitWindow) -> WindowId { + pub fn add_winit_window(&mut self, window: Rc) -> WindowId { let mut descriptor = WindowDescriptor::default(); WinitWindows::update_window_descriptor(&mut descriptor, &window); - self.add_winit_window_with_descriptor(descriptor, window).0 + self.add_winit_window_with_descriptor(descriptor, window) } fn add_winit_window_with_descriptor( &mut self, descriptor: WindowDescriptor, - window: WinitWindow, - ) -> (WindowId, Rc) { + window: Rc, + ) -> WindowId { let window_id = self.windows.create(descriptor); - let window = self.winit_windows.insert(window_id, window); - let raw_window: Rc = window.clone(); - self.raw_window_handles - .insert(window_id, Rc::downgrade(&raw_window)); debug!( "Added window {:?} with {:?}, {:?}", window_id, window.id(), window.inner_size() ); - (window_id, window) + self.winit_windows.insert(window_id, window); + window_id } - pub fn update_windows( + fn handle_window_event( &mut self, - event_loop: &EventLoopWindowTarget, - ) -> Result<(), OsError> { - for (window_id, window) in self.windows.iter_mut() { - if self.winit_windows.windows.get(window_id).is_none() { - // create missing window - let winit_window: Rc = - self.winit_windows.create(window_id, window, event_loop)?; - self.raw_window_handles - .insert(window_id, Rc::downgrade(&winit_window)); - } - - // handle commands - // TODO - } - Ok(()) - } - - fn handle_window_event(&mut self, window_id: WinitWindowId, event: WindowEvent<'_>) { - if let Some(&window_id) = self.winit_windows.window_id_map.get(&window_id) { + res: &Resources, + window_id: WinitWindowId, + event: WindowEvent<'_>, + ) { + if let Some(window_id) = self.winit_windows.window_id_map.get(&window_id).copied() { if matches!(event, WindowEvent::Destroyed) { - self.windows.close(window_id); - self.winit_windows.close(window_id); - self.raw_window_handles.remove(window_id); + self.close(res, window_id); } else if let Some(window) = self.windows.get_mut(window_id) { match event { WindowEvent::CloseRequested => { @@ -272,7 +227,29 @@ impl WinitWindowSystemMut<'_> { } } - fn handle_close(&mut self) -> bool { + fn handle_created( + &mut self, + res: &Resources, + event_loop: &EventLoopWindowTarget, + ) -> Result<(), OsError> { + while let Some((window_id, window_descr)) = self.windows.pop_next_created_window() { + if let Some(winit_window) = self.winit_windows.get(window_id) { + self.listeners + .call_on_created(res, window_id, window_descr, winit_window); + } else { + let builder = builder_for_descriptor(window_descr); + let winit_window = Rc::new(builder.build(event_loop)?); + WinitWindows::update_window_descriptor(window_descr, &winit_window); + self.winit_windows.insert(window_id, winit_window.clone()); + self.listeners + .call_on_created(res, window_id, window_descr, &winit_window); + }; + } + Ok(()) + } + + // close all windows where the close_requested flag is not cleared + fn handle_close(&mut self, res: &Resources) -> bool { let mut to_close = Vec::new(); for (window_id, _) in self.winit_windows.windows.iter() { match self.windows.get(window_id) { @@ -284,36 +261,49 @@ impl WinitWindowSystemMut<'_> { if !to_close.is_empty() { debug!("Closing {} windows", to_close.len()); for window_id in to_close { - self.windows.close(window_id); - self.raw_window_handles.remove(window_id); - self.winit_windows.close(window_id); + self.close(res, window_id); } } self.winit_windows.windows.is_empty() // all windows closed } + + fn close(&mut self, res: &Resources, window_id: WindowId) -> bool { + if self.winit_windows.get(window_id).is_some() { + self.listeners.call_on_closed(res, window_id); + self.windows.close(window_id); + self.winit_windows.close(window_id); + true + } else { + false + } + } } impl WinitWindowSystem { fn install(res: &mut Resources) -> Self { let windows_id = res.init::(); + let listeners_id = res.init_unsend::(); let winit_windows_id = res.init_unsend::(); - let raw_window_handles_id = res.init_unsend::(); Self { windows_id, + listeners_id, winit_windows_id, - raw_window_handles_id, active: false, } } - fn as_mut<'l>(&self, res: &'l mut Resources) -> WinitWindowSystemMut<'l> { + fn as_mut<'l>(&self, res: &'l Resources) -> WinitWindowSystemMut<'l> { WinitWindowSystemMut { windows: res.borrow_res_mut_id(self.windows_id).unwrap(), + listeners: res.borrow_res_mut_id(self.listeners_id).unwrap(), winit_windows: res.borrow_res_mut_id(self.winit_windows_id).unwrap(), - raw_window_handles: res.borrow_res_mut_id(self.raw_window_handles_id).unwrap(), } } + fn listeners_mut<'l>(&self, res: &'l Resources) -> ResMut<'l, WindowSystemListeners> { + res.borrow_res_mut_id(self.listeners_id).unwrap() + } + pub fn handle_event( &mut self, resources: &mut Resources, @@ -327,38 +317,43 @@ impl WinitWindowSystem { match event { Event::NewEvents(StartCause::Init) => { info!("event loop started..."); - self.as_mut(resources).update_windows(event_loop).unwrap(); + } + Event::Resumed => { + info!("resumed"); self.active = true; + let mut s = self.as_mut(resources); + s.handle_created(resources, event_loop).unwrap(); + s.listeners.call_on_resumed(resources); + *control_flow = winit::event_loop::ControlFlow::Poll; } Event::WindowEvent { window_id, event } => { - self.as_mut(resources).handle_window_event(window_id, event); + self.as_mut(resources) + .handle_window_event(resources, window_id, event); } Event::Suspended => { info!("suspended"); self.active = false; - // TODO: ON ANDROID: all surfaces need to be destroyed, and re-created on RESUME + self.listeners_mut(resources).call_on_suspended(resources); *control_flow = winit::event_loop::ControlFlow::Wait; } - Event::Resumed => { - info!("resumed"); - self.active = true; - // TODO: ON ANDROID: surface-creation needs to be delayed until this Event - // TODO: clever way how to link that to the render-system, and delay creation of device - *control_flow = winit::event_loop::ControlFlow::Poll; - } Event::MainEventsCleared => { - self.as_mut(resources).update_windows(event_loop).unwrap(); if self.active { + self.as_mut(resources) + .handle_created(resources, event_loop) + .unwrap(); schedule.run(resources); - } - if self.as_mut(resources).handle_close() { - // all windows closed - *control_flow = winit::event_loop::ControlFlow::Exit; + if self.as_mut(resources).handle_close(resources) { + // all windows closed + *control_flow = winit::event_loop::ControlFlow::Exit; + } } } Event::LoopDestroyed => { info!("event loop ended"); - self.active = false; + if self.active { + self.active = false; + self.listeners_mut(resources).call_on_suspended(resources); + } } _ => {} } diff --git a/crates/window/src/lib.rs b/crates/window/src/lib.rs index 79eb1c8..d9bc576 100644 --- a/crates/window/src/lib.rs +++ b/crates/window/src/lib.rs @@ -26,6 +26,7 @@ #![doc = include_str!("../README.md")] pub mod event; +pub mod listener; mod window; pub type Point2 = glam::IVec2; diff --git a/crates/window/src/listener.rs b/crates/window/src/listener.rs new file mode 100644 index 0000000..8ab4332 --- /dev/null +++ b/crates/window/src/listener.rs @@ -0,0 +1,63 @@ +use pulz_ecs::resource::Resources; +use slotmap::SlotMap; + +use crate::{RawWindow, Window, WindowId}; + +pub trait WindowSystemListener: 'static { + fn on_created( + &self, + _res: &Resources, + _window_id: WindowId, + _window_desc: &Window, + _window_raw: &dyn RawWindow, + ) { + } + fn on_resumed(&self, _res: &Resources) {} + fn on_closed(&self, _res: &Resources, _window_id: WindowId) {} + fn on_suspended(&self, _res: &Resources) {} +} + +slotmap::new_key_type! { + pub struct WindowSystemListenerId; +} + +#[derive(Default)] +pub struct WindowSystemListeners(SlotMap>); + +impl WindowSystemListeners { + #[inline] + pub fn insert(&mut self, l: impl WindowSystemListener) -> WindowSystemListenerId { + self.0.insert(Box::new(l)) + } + + #[inline] + pub fn remove(&mut self, id: WindowSystemListenerId) -> bool { + self.0.remove(id).is_some() + } + pub fn call_on_created( + &self, + res: &Resources, + window_id: WindowId, + window_descr: &Window, + window_raw: &dyn RawWindow, + ) { + for (_, l) in self.0.iter() { + l.on_created(res, window_id, window_descr, window_raw); + } + } + pub fn call_on_resumed(&self, res: &Resources) { + for (_, l) in self.0.iter() { + l.on_resumed(res); + } + } + pub fn call_on_closed(&self, res: &Resources, window_id: WindowId) { + for (_, l) in self.0.iter() { + l.on_closed(res, window_id); + } + } + pub fn call_on_suspended(&self, res: &Resources) { + for (_, l) in self.0.iter() { + l.on_suspended(res); + } + } +} diff --git a/crates/window/src/window.rs b/crates/window/src/window.rs index 21b307f..112bcfa 100644 --- a/crates/window/src/window.rs +++ b/crates/window/src/window.rs @@ -2,7 +2,6 @@ use std::{ borrow::Cow, collections::VecDeque, ops::{Deref, DerefMut}, - rc::Weak, }; use pulz_ecs::Component; @@ -34,7 +33,10 @@ pub struct Window { command_queue: VecDeque, } -pub struct Windows(SlotMap); +pub struct Windows { + windows: SlotMap, + created: VecDeque, +} impl WindowDescriptor { pub const DEFAULT_TITLE: &'static str = @@ -72,7 +74,10 @@ impl DerefMut for Window { impl Windows { pub fn new() -> Self { - Self(SlotMap::with_key()) + Self { + windows: SlotMap::with_key(), + created: VecDeque::new(), + } } #[inline] @@ -83,43 +88,54 @@ impl Windows { command_queue: VecDeque::new(), }; - //self.new_windows.push_back(id); - self.0.insert(window) + let id = self.windows.insert(window); + self.created.push_back(id); + id } #[inline] pub fn len(&self) -> usize { - self.0.len() + self.windows.len() } #[inline] pub fn is_empty(&self) -> bool { - self.0.is_empty() + self.windows.is_empty() } #[inline] pub fn get(&self, id: WindowId) -> Option<&Window> { - self.0.get(id) + self.windows.get(id) } #[inline] pub fn get_mut(&mut self, id: WindowId) -> Option<&mut Window> { - self.0.get_mut(id) + self.windows.get_mut(id) } #[inline] pub fn close(&mut self, id: WindowId) -> bool { - self.0.remove(id).is_some() + self.windows.remove(id).is_some() + } + + pub fn pop_next_created_window(&mut self) -> Option<(WindowId, &mut Window)> { + let id = loop { + let id = self.created.pop_front()?; + if self.windows.contains_key(id) { + break id; + } + }; + Some((id, &mut self.windows[id])) } #[inline] pub fn iter(&self) -> Iter<'_> { - self.0.iter() + self.windows.iter() } #[inline] pub fn iter_mut(&mut self) -> IterMut<'_> { - self.0.iter_mut() + self.windows.iter_mut() } } @@ -134,14 +150,14 @@ impl std::ops::Index for Windows { type Output = Window; #[inline] fn index(&self, id: WindowId) -> &Self::Output { - &self.0[id] + &self.windows[id] } } impl std::ops::IndexMut for Windows { #[inline] fn index_mut(&mut self, id: WindowId) -> &mut Self::Output { - &mut self.0[id] + &mut self.windows[id] } } @@ -155,5 +171,3 @@ pub enum WindowCommand { pub trait RawWindow: HasRawWindowHandle + HasRawDisplayHandle {} impl RawWindow for W where W: HasRawWindowHandle + HasRawDisplayHandle {} - -pub type RawWindowHandles = WindowsMirror>; From ae81f73b9edc9522b201f97cf2a59da8fc07ea48 Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sat, 28 Jan 2023 16:12:30 +0100 Subject: [PATCH 12/36] changed logging of vulkan debug-utils --- crates/render-ash/src/debug_utils.rs | 57 +++++++++++++--------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/crates/render-ash/src/debug_utils.rs b/crates/render-ash/src/debug_utils.rs index 9ee7aee..de135fb 100644 --- a/crates/render-ash/src/debug_utils.rs +++ b/crates/render-ash/src/debug_utils.rs @@ -35,52 +35,47 @@ unsafe extern "system" fn debug_callback( match message_severity { DebugUtilsMessageSeverityFlagsEXT::VERBOSE => { debug!( - ?message_type, - ?message_severity, ?message_id_name, - ?message_id_number, - "Vulkan Message: {:?}", - message + "Vk[{:?},#{}]: {}", + message_type, + message_id_number, + message.to_string_lossy() ) } DebugUtilsMessageSeverityFlagsEXT::INFO => { info!( - ?message_type, - ?message_severity, ?message_id_name, - ?message_id_number, - "Vulkan Message: {:?}", - message + "Vk[{:?},#{}]: {}", + message_type, + message_id_number, + message.to_string_lossy() ) } DebugUtilsMessageSeverityFlagsEXT::WARNING => { warn!( - ?message_type, - ?message_severity, ?message_id_name, - ?message_id_number, - "Vulkan Message: {:?}", - message + "Vk[{:?},#{}]: {}", + message_type, + message_id_number, + message.to_string_lossy() ) } DebugUtilsMessageSeverityFlagsEXT::ERROR => { error!( - ?message_type, - ?message_severity, ?message_id_name, - ?message_id_number, - "Vulkan Message: {:?}", - message + "Vk[{:?},#{}]: {}", + message_type, + message_id_number, + message.to_string_lossy() ) } _ => { warn!( - ?message_type, - ?message_severity, ?message_id_name, - ?message_id_number, - "Vulkan Message: {:?}", - message + "Vk[{:?},#{}]: {}", + message_type, + message_id_number, + message.to_string_lossy() ) } }; @@ -105,7 +100,7 @@ impl CStrBuf { } #[inline] - fn to_cstr<'a>(&'a mut self, s: &'a str) -> &'a CStr { + fn get_cstr<'a>(&'a mut self, s: &'a str) -> &'a CStr { if s.ends_with('\0') { // SAFETY: string always ends with 0-byte. // Don't care, if there are 0-bytes before end. @@ -217,7 +212,7 @@ impl DebugUtils { device, object_type, object_handle, - cstr_buf.to_cstr(object_name), + cstr_buf.get_cstr(object_name), ) } @@ -243,7 +238,7 @@ impl DebugUtils { #[inline] pub unsafe fn cmd_insert_debug_label(&self, command_buffer: vk::CommandBuffer, label: &str) { let mut cstr_buf = CStrBuf::new(); - self.cmd_insert_debug_label_cstr(command_buffer, cstr_buf.to_cstr(label)) + self.cmd_insert_debug_label_cstr(command_buffer, cstr_buf.get_cstr(label)) } pub unsafe fn cmd_insert_debug_label_cstr( &self, @@ -259,7 +254,7 @@ impl DebugUtils { #[inline] pub unsafe fn cmd_begin_debug_label(&self, command_buffer: vk::CommandBuffer, label: &str) { let mut cstr_buf = CStrBuf::new(); - self.cmd_begin_debug_label_cstr(command_buffer, cstr_buf.to_cstr(label)) + self.cmd_begin_debug_label_cstr(command_buffer, cstr_buf.get_cstr(label)) } pub unsafe fn cmd_begin_debug_label_cstr( &self, @@ -280,7 +275,7 @@ impl DebugUtils { #[inline] pub unsafe fn queue_insert_debug_label(&self, queue: vk::Queue, label: &str) { let mut cstr_buf = CStrBuf::new(); - self.queue_insert_debug_label_cstr(queue, cstr_buf.to_cstr(label)) + self.queue_insert_debug_label_cstr(queue, cstr_buf.get_cstr(label)) } pub unsafe fn queue_insert_debug_label_cstr(&self, queue: vk::Queue, label: &CStr) { @@ -293,7 +288,7 @@ impl DebugUtils { #[inline] pub unsafe fn queue_begin_debug_label(&self, queue: vk::Queue, label: &str) { let mut cstr_buf = CStrBuf::new(); - self.queue_begin_debug_label_cstr(queue, cstr_buf.to_cstr(label)) + self.queue_begin_debug_label_cstr(queue, cstr_buf.get_cstr(label)) } pub unsafe fn queue_begin_debug_label_cstr(&self, queue: vk::Queue, label: &CStr) { From 298bdf2b5fb835a83ea613787b9856ada9f209e0 Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sat, 28 Jan 2023 16:20:58 +0100 Subject: [PATCH 13/36] android example --- examples/android/.gitignore | 15 ++ examples/android/Cargo.toml | 24 +++ examples/android/app/.gitignore | 2 + examples/android/app/build.gradle | 61 ++++++ examples/android/app/proguard-rules.pro | 21 ++ .../pulzdemo/ExampleInstrumentedTest.kt | 24 +++ .../android/app/src/main/AndroidManifest.xml | 39 ++++ .../app/src/main/assets/android_robot.png | Bin 0 -> 20317 bytes .../java/eu/chommel/pulzdemo/MainActivity.kt | 39 ++++ .../drawable-v24/ic_launcher_foreground.xml | 30 +++ .../res/drawable/ic_launcher_background.xml | 170 ++++++++++++++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../res/mipmap-anydpi-v33/ic_launcher.xml | 6 + .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes .../app/src/main/res/values-night/themes.xml | 19 ++ .../app/src/main/res/values/colors.xml | 10 + .../app/src/main/res/values/strings.xml | 3 + .../app/src/main/res/values/themes.xml | 19 ++ .../app/src/main/res/xml/backup_rules.xml | 13 ++ .../main/res/xml/data_extraction_rules.xml | 19 ++ .../eu/chommel/pulzdemo/ExampleUnitTest.kt | 17 ++ examples/android/build.gradle | 11 ++ examples/android/gradle.properties | 23 +++ .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + examples/android/gradlew | 185 ++++++++++++++++++ examples/android/gradlew.bat | 89 +++++++++ examples/android/settings.gradle | 16 ++ examples/android/src/lib.rs | 51 +++++ 39 files changed, 922 insertions(+) create mode 100644 examples/android/.gitignore create mode 100644 examples/android/Cargo.toml create mode 100644 examples/android/app/.gitignore create mode 100644 examples/android/app/build.gradle create mode 100644 examples/android/app/proguard-rules.pro create mode 100644 examples/android/app/src/androidTest/java/eu/chommel/pulzdemo/ExampleInstrumentedTest.kt create mode 100644 examples/android/app/src/main/AndroidManifest.xml create mode 100644 examples/android/app/src/main/assets/android_robot.png create mode 100644 examples/android/app/src/main/java/eu/chommel/pulzdemo/MainActivity.kt create mode 100644 examples/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 examples/android/app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 examples/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 examples/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 examples/android/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml create mode 100644 examples/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 examples/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 examples/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 examples/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 examples/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 examples/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 examples/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 examples/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 examples/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 examples/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 examples/android/app/src/main/res/values-night/themes.xml create mode 100644 examples/android/app/src/main/res/values/colors.xml create mode 100644 examples/android/app/src/main/res/values/strings.xml create mode 100644 examples/android/app/src/main/res/values/themes.xml create mode 100644 examples/android/app/src/main/res/xml/backup_rules.xml create mode 100644 examples/android/app/src/main/res/xml/data_extraction_rules.xml create mode 100644 examples/android/app/src/test/java/eu/chommel/pulzdemo/ExampleUnitTest.kt create mode 100644 examples/android/build.gradle create mode 100644 examples/android/gradle.properties create mode 100644 examples/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 examples/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 examples/android/gradlew create mode 100644 examples/android/gradlew.bat create mode 100644 examples/android/settings.gradle create mode 100644 examples/android/src/lib.rs diff --git a/examples/android/.gitignore b/examples/android/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/examples/android/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/examples/android/Cargo.toml b/examples/android/Cargo.toml new file mode 100644 index 0000000..81b64d9 --- /dev/null +++ b/examples/android/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "example-android" +publish = false +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true + +[lib] +name="pulzdemo_android" +crate_type=["cdylib"] + +[dependencies] +pulz-ecs = { path = "../../crates/ecs" } +pulz-render = { path = "../../crates/render" } +pulz-render-ash = { path = "../../crates/render-ash" } +pulz-render-pipeline-core = { path = "../../crates/render-pipeline-core" } +pulz-window = { path = "../../crates/window" } +pulz-window-winit = { path = "../../crates/window-winit", features = ["android-game-activity"]} + +log = "0.4" +android_logger = "0.12" +tracing = { workspace = true, features = ["log"] } diff --git a/examples/android/app/.gitignore b/examples/android/app/.gitignore new file mode 100644 index 0000000..1410476 --- /dev/null +++ b/examples/android/app/.gitignore @@ -0,0 +1,2 @@ +/build +/src/main/jniLibs/ diff --git a/examples/android/app/build.gradle b/examples/android/app/build.gradle new file mode 100644 index 0000000..faf04cb --- /dev/null +++ b/examples/android/app/build.gradle @@ -0,0 +1,61 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' + id 'org.mozilla.rust-android-gradle.rust-android' +} + +android { + namespace 'eu.chommel.pulzdemo' + compileSdk 33 + + defaultConfig { + applicationId "eu.chommel.pulzdemo" + minSdk 28 + targetSdk 33 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + buildFeatures { + prefab true + } +} + +cargo { + module = "../" + targetDirectory = "../../../target" + libname = "pulzdemo_android" + targets = ["arm", "arm64", "x86", "x86_64"] +} + +tasks.whenTaskAdded { task -> + if ((task.name == 'javaPreCompileDebug' || task.name == 'javaPreCompileRelease')) { + task.dependsOn 'cargoBuild' + } +} + +dependencies { + + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.6.0' + implementation 'com.google.android.material:material:1.7.0' + implementation 'androidx.games:games-activity:1.1.0' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' +} diff --git a/examples/android/app/proguard-rules.pro b/examples/android/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/examples/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/examples/android/app/src/androidTest/java/eu/chommel/pulzdemo/ExampleInstrumentedTest.kt b/examples/android/app/src/androidTest/java/eu/chommel/pulzdemo/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..c5e27f7 --- /dev/null +++ b/examples/android/app/src/androidTest/java/eu/chommel/pulzdemo/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package eu.chommel.pulzdemo + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("eu.chommel.pulzdemo", appContext.packageName) + } +} \ No newline at end of file diff --git a/examples/android/app/src/main/AndroidManifest.xml b/examples/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2dc983b --- /dev/null +++ b/examples/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + diff --git a/examples/android/app/src/main/assets/android_robot.png b/examples/android/app/src/main/assets/android_robot.png new file mode 100644 index 0000000000000000000000000000000000000000..61385f2fbdc1e80d055804deb8926cbed0a20b7d GIT binary patch literal 20317 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

C%4 zc_9eOXvPbC4kzU8YowIA8cW~Uv|eB&yYwAObSwL2vY~UYI7NXPvf3b+c^?wcs~_t{ ze_m66-0)^{JdOMKPwjpQ@Sna!*?$wTZ~su*tNv7o!gXT!GRgivP}ec?5>l1!7<(rT zds|8x(qGc673zrvYIz;J23FG{9nHMnAuP}NpAED^laz3mAN1sy+NXK)!6v1FxQ;lh zOBLA>$~P3r4b*NcqR;y6pwyhZ3_PiDb|%n1gGjl3ZU}ujInlP{eks-#oA6>rh&g+!f`hv#_%JrgYPu z(U^&XLW^QX7F9Z*SRPpQl{B%x)_AMp^}_v~?j7 zapvHMKxSf*Mtyx8I}-<*UGn3)oHd(nn=)BZ`d$lDBwq_GL($_TPaS{UeevT(AJ`p0 z9%+hQb6z)U9qjbuXjg|dExCLjpS8$VKQ55VsIC%@{N5t{NsW)=hNGI`J=x97_kbz@ E0Of=7!TQj4N+cqN`nQhxvX7dAV-`K|Ub$-q+H-5I?Tx0g9jWxd@A|?POE8`3b8fO$T))xP* z(X?&brZw({`)WU&rdAs1iTa0x6F@PIxJ&&L|dpySV!ID|iUhjCcKz(@mE z!x@~W#3H<)4Ae(4eQJRk`Iz3<1)6^m)0b_4_TRZ+cz#eD3f8V;2r-1fE!F}W zEi0MEkTTx}8i1{`l_6vo0(Vuh0HD$I4SjZ=?^?k82R51bC)2D_{y8mi_?X^=U?2|F{Vr7s!k(AZC$O#ZMyavHhlQ7 zUR~QXuH~#o#>(b$u4?s~HLF*3IcF7023AlwAYudn0FV~|odGH^05AYPEfR)8p`i{n zwg3zPVp{+wOsxKc>)(pMupKF!Y2HoUqQ3|Yu|8lwR=?5zZuhG6J?H`bSNk_wPoM{u zSL{c@pY7+c2kck>`^q1^^gR0QB7Y?KUD{vz-uVX~;V-rW)PDcI)$_UjgVV?S?=oLR zf4}zz{#*R_{LkiJ#0RdQLNC^2Vp%JPEUvG9ra2BVZ92(p9h7Ka@!yf9(lj#}>+|u* z;^_?KWdzkM`6gqPo9;;r6&JEa)}R3X{(CWv?NvgLeOTq$cZXqf7|sPImi-7cS8DCN zGf;DVt3Am`>hH3{4-WzH43Ftx)SofNe^-#|0HdCo<+8Qs!}TZP{HH8~z5n`ExcHuT zDL1m&|DVpIy=xsLO>8k92HcmfSKhflQ0H~9=^-{#!I1g(;+44xw~=* zxvNz35vfsQE)@)Zsp*6_GjYD};Squ83<_?^SbALb{a`j<0Gn%6JY!zhp=Fg}Ga2|8 z52e1WU%^L1}15Ex0fF$e@eCT(()_P zvV?CA%#Sy08_U6VPt4EtmVQraWJX` zh=N|WQ>LgrvF~R&qOfB$!%D3cGv?;Xh_z$z7k&s4N)$WYf*k=|*jCEkO19{h_(%W4 zPuOqbCw`SeAX*R}UUsbVsgtuG?xs(#Ikx9`JZoQFz0n*7ZG@Fv@kZk`gzO$HoA9kN z8U5{-yY zvV{`&WKU2$mZeoBmiJrEdzUZAv1sRxpePdg1)F*X^Y)zp^Y*R;;z~vOv-z&)&G)JQ{m!C9cmziu1^nHA z`#`0c>@PnQ9CJKgC5NjJD8HM3|KC(g5nnCq$n0Gsu_DXk36@ql%npEye|?%RmG)

FJ$wK}0tWNB{uH;AM~i literal 0 HcmV?d00001 diff --git a/examples/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/examples/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..948a3070fe34c611c42c0d3ad3013a0dce358be0 GIT binary patch literal 1900 zcmV-y2b1_xNk&Fw2LJ$9MM6+kP&il$0000G0001A003VA06|PpNH75a00DqwTbm-~ zullQTcXxO9ki!OCRx^i?oR|n!<8G0=kI^!JSjFi-LL*`V;ET0H2IXfU0*i>o6o6Gy zRq6Ap5(_{XLdXcL-MzlN`ugSdZY_`jXhcENAu)N_0?GhF))9R;E`!bo9p?g?SRgw_ zEXHhFG$0{qYOqhdX<(wE4N@es3VIo$%il%6xP9gjiBri+2pI6aY4 zJbgh-Ud|V%3O!IcHKQx1FQH(_*TK;1>FQWbt^$K1zNn^cczkBs=QHCYZ8b&l!UV{K z{L0$KCf_&KR^}&2Fe|L&?1I7~pBENnCtCuH3sjcx6$c zwqkNkru);ie``q+_QI;IYLD9OV0ZxkuyBz|5<$1BH|vtey$> z5oto4=l-R-Aaq`Dk0}o9N0VrkqW_#;!u{!bJLDq%0092{Ghe=F;(kn} z+sQ@1=UlX30+2nWjkL$B^b!H2^QYO@iFc0{(-~yXj2TWz?VG{v`Jg zg}WyYnwGgn>{HFaG7E~pt=)sOO}*yd(UU-D(E&x{xKEl6OcU?pl)K%#U$dn1mDF19 zSw@l8G!GNFB3c3VVK0?uyqN&utT-D5%NM4g-3@Sii9tSXKtwce~uF zS&Jn746EW^wV~8zdQ1XC28~kXu8+Yo9p!<8h&(Q({J*4DBglPdpe4M_mD8AguZFn~ ztiuO~{6Bx?SfO~_ZV(GIboeR9~hAym{{fV|VM=77MxDrbW6`ujX z<3HF(>Zr;#*uCvC*bpoSr~C$h?_%nXps@A)=l_;({Fo#6Y1+Zv`!T5HB+)#^-Ud_; zBwftPN=d8Vx)*O1Mj+0oO=mZ+NVH*ptNDC-&zZ7Hwho6UQ#l-yNvc0Cm+2$$6YUk2D2t#vdZX-u3>-Be1u9gtTBiMB^xwWQ_rgvGpZ6(C@e23c!^K=>ai-Rqu zhqT`ZQof;9Bu!AD(i^PCbYV%yha9zuoKMp`U^z;3!+&d@Hud&_iy!O-$b9ZLcSRh? z)R|826w}TU!J#X6P%@Zh=La$I6zXa#h!B;{qfug}O%z@K{EZECu6zl)7CiNi%xti0 zB{OKfAj83~iJvmpTU|&q1^?^cIMn2RQ?jeSB95l}{DrEPTW{_gmU_pqTc)h@4T>~& zluq3)GM=xa(#^VU5}@FNqpc$?#SbVsX!~RH*5p0p@w z;~v{QMX0^bFT1!cXGM8K9FP+=9~-d~#TK#ZE{4umGT=;dfvWi?rYj;^l_Zxywze`W z^Cr{55U@*BalS}K%Czii_80e0#0#Zkhlij4-~I@}`-JFJ7$5{>LnoJSs??J8kWVl6|8A}RCGAu9^rAsfCE=2}tHwl93t0C?#+jMpvr7O3`2=tr{Hg$=HlnjVG^ewm|Js0J*kfPa6*GhtB>`fN!m#9J(sU!?(OSfzY*zS(FJ<-Vb zfAIg+`U)YaXv#sY(c--|X zEB+TVyZ%Ie4L$gi#Fc++`h6%vzsS$pjz9aLt+ZL(g;n$Dzy5=m=_TV(3H8^C{r0xd zp#a%}ht55dOq?yhwYPrtp-m1xXp;4X;)NhxxUpgP%XTLmO zcjaFva^}dP3$&sfFTIR_jC=2pHh9kpI@2(6V*GQo7Ws)`j)hd+tr@P~gR*2gO@+1? zG<`_tB+LJuF|SZ9tIec;h%}}6WClT`L>HSW?E{Hp1h^+mlbf_$9zA>!ug>NALJsO{ mU%z=YwVD?}XMya)Bp;vlyE5&E_6!fzx9pwrdz474!~g(M6R?N? literal 0 HcmV?d00001 diff --git a/examples/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/examples/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..1b9a6956b3acdc11f40ce2bb3f6efbd845cc243f GIT binary patch literal 3918 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�(p-{x$&=IACIfoAR!za)F-^da-t3#0Dycnp zwO~NVXwXCl;jE<}>%@xz|=8fIJAB?>+E{7)|4l${4ngA3G|=r z2Dyv;VVWSgZx9Wj>qUjleGl3Ei9K4>h!(lPS%8VOG>Xu0%6VDz^O=bjJmuP7>DeUv zrbI}MlHB^^d?{zv6d=@_ZD2lg1&G7UjnVN{1}9WkaM3H~btX0GtSzB+tZ^qRgWo4m z!GmimlG$=wgXCnr6j@m<1gAL46#T~5Bnm=2{^@>|t&`9mkEPddj zAvG~@Tv~TAm2i%VW}R-g(Z0)z-Y|szHr@rk>4MAyG*Ma*7Yh#H7(!-5>DZ@8r;_dx z{prSe<>~099F8vsYd2xff7uAS%7{S)f(|@me3t2$iy&NEc7OUEchp@9A|X;;IA>8!oX+y(BKJ$EzV* znR$z;!L$s7uy@{OT~nG#B!NRraT8(X##Ho!0r_o@gg0CA-9H^;-uE&?$2$nHv_00o z%cbuUc-tCx$Uh&EZ4Nf4Zgqv)Y6>usG3>GeQnxx_Z6+PcbX-+ysbt1hQ`K1LDpOE? zrAhIZhSN9yVIAOa22gn577tbc&i3|3V8NWy&!tw##`}9*x}gtI^h1DzZRA>UuaJG) zaZ7j)dq!O}{?#8Y7~7i6fHh4{`pL?>-18|p!S75Y#^DM>-S3)vuZG+Q7l@ek zQP~#cBpWgg#mApc_sPYjpw8odQuRokmTkzcNl`^CcKB7e&;zViV;{Y{o^Y$%7i0m# z62%#1Lq!RC?}lK>%mp}T!3Xv;L*0v*>USLm``N%>w>@fwC+#T&Tx2bN4w(20JB}oU zuSa6v^kXi0xPs?pbaOHnyiqq6By1EZY9OZ^^QA>{q-Hsd&m`pbQ%8121aWG-F5xf zlZ%;B{;C>X19|`^_?dVyCq>n+41w7|!tUS!{9rHlbhX=SZO5CQ^;!Du_E7*`GiR^Q w)2!4MKjfSAeNo!9>IaV6aUZ*?W>} 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

UmY2RIYtNPVDpE$%vda+HD#3m z&VuXJ{BK&Qe+rBa7eq}Q(bq|tn(RrJAk|ztj2(i{d>nmQnM?;HF2k&9sA6up5tmjl z7lySlzMbifH17-m-Lwa_F&e7nOH?ESi3#ckR3tsM+jsck3`oG!uMS}|eAwVXv>}qxwq?QY%QJ0}r@^;fhuUA9W z*BVl>TGo&N004@xSiwDUXUvp51sVmqO3m)=B55aPwf@0=e}cN+$-BdKxY`YrT_4)0 z_d10#i44Q*rFr8MC>*)v$EJvz``(pb{e&*6k+b zsMz%($|1+8hn8c2?P(l@;Rb&CsZeYoCI3?2!LqjbwPXW3z4G$Qfj=cT5Yb%vY0(AX oeb?AaKtwrnc|$|zzw9vfvn^aJJ!zd)XFXqqy0000001=f@-~a#s literal 0 HcmV?d00001 diff --git a/examples/android/app/src/main/res/values-night/themes.xml b/examples/android/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..833cde0 --- /dev/null +++ b/examples/android/app/src/main/res/values-night/themes.xml @@ -0,0 +1,19 @@ + + + + diff --git a/examples/android/app/src/main/res/values/colors.xml b/examples/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/examples/android/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/examples/android/app/src/main/res/values/strings.xml b/examples/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..c462f23 --- /dev/null +++ b/examples/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Pulz Demo + \ No newline at end of file diff --git a/examples/android/app/src/main/res/values/themes.xml b/examples/android/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..a989b4f --- /dev/null +++ b/examples/android/app/src/main/res/values/themes.xml @@ -0,0 +1,19 @@ + + + + diff --git a/examples/android/app/src/main/res/xml/backup_rules.xml b/examples/android/app/src/main/res/xml/backup_rules.xml new file mode 100644 index 0000000..fa0f996 --- /dev/null +++ b/examples/android/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/examples/android/app/src/main/res/xml/data_extraction_rules.xml b/examples/android/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 0000000..9ee9997 --- /dev/null +++ b/examples/android/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/examples/android/app/src/test/java/eu/chommel/pulzdemo/ExampleUnitTest.kt b/examples/android/app/src/test/java/eu/chommel/pulzdemo/ExampleUnitTest.kt new file mode 100644 index 0000000..5c9b8ca --- /dev/null +++ b/examples/android/app/src/test/java/eu/chommel/pulzdemo/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package eu.chommel.pulzdemo + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/examples/android/build.gradle b/examples/android/build.gradle new file mode 100644 index 0000000..10cd045 --- /dev/null +++ b/examples/android/build.gradle @@ -0,0 +1,11 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + id 'com.android.application' version '7.4.0' apply false + id 'com.android.library' version '7.4.0' apply false + id 'org.jetbrains.kotlin.android' version '1.7.21' apply false + id "org.mozilla.rust-android-gradle.rust-android" version "0.9.3" +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/examples/android/gradle.properties b/examples/android/gradle.properties new file mode 100644 index 0000000..3c5031e --- /dev/null +++ b/examples/android/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/examples/android/gradle/wrapper/gradle-wrapper.jar b/examples/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f GIT binary patch literal 59203 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

K7UpUK$>l@ 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 E2O(~oYXATM literal 0 HcmV?d00001 diff --git a/examples/android/gradle/wrapper/gradle-wrapper.properties b/examples/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..404f439 --- /dev/null +++ b/examples/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Jan 17 19:51:29 CET 2023 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/examples/android/gradlew b/examples/android/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/examples/android/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/examples/android/gradlew.bat b/examples/android/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/examples/android/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/examples/android/settings.gradle b/examples/android/settings.gradle new file mode 100644 index 0000000..8215ca4 --- /dev/null +++ b/examples/android/settings.gradle @@ -0,0 +1,16 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} +rootProject.name = "Pulz Demo" +include ':app' diff --git a/examples/android/src/lib.rs b/examples/android/src/lib.rs new file mode 100644 index 0000000..12d1f2a --- /dev/null +++ b/examples/android/src/lib.rs @@ -0,0 +1,51 @@ +use std::rc::Rc; + +use log::info; +use pulz_ecs::prelude::*; +use pulz_render::camera::{Camera, RenderTarget}; +use pulz_render_ash::AshRenderer; +use pulz_render_pipeline_core::core_3d::CoreShadingModule; +use pulz_window::{WindowDescriptor, WindowId}; +use pulz_window_winit::{winit, WinitWindowModule, WinitWindowSystem}; +use winit::{ + event_loop::{EventLoop, EventLoopBuilder, EventLoopWindowTarget}, + platform::android::activity::AndroidApp, + window::Window, +}; + +fn init(event_loop: &EventLoopWindowTarget<()>) -> (Resources, Rc, WinitWindowSystem) { + info!("Initializing..."); + let mut resources = Resources::new(); + resources.install(CoreShadingModule); + resources.install(AshRenderer::new().unwrap()); + + let (window_system, window_id, window) = + WinitWindowModule::new(WindowDescriptor::default(), event_loop) + .unwrap() + .install(&mut resources); + + setup_demo_scene(&mut resources, window_id); + + (resources, window, window_system) +} + +fn setup_demo_scene(resources: &mut Resources, window: WindowId) { + let mut world = resources.world_mut(); + + world + .spawn() + .insert(Camera::new()) + .insert(RenderTarget::Window(window)); +} + +#[no_mangle] +pub fn android_main(app: AndroidApp) { + use winit::platform::android::EventLoopBuilderExtAndroid; + // #[cfg(debug_assertions)] + // std::env::set_var("RUST_BACKTRACE", "1"); + android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Info)); + + let event_loop = EventLoopBuilder::new().with_android_app(app).build(); + let (resources, _window, window_system) = init(&event_loop); + window_system.run(resources, event_loop); +} From 7ef408af0dfd6a8c2a1c238c0187cbb61688277a Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sun, 5 Feb 2023 14:29:05 +0100 Subject: [PATCH 14/36] update winit --- crates/window-winit/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/window-winit/Cargo.toml b/crates/window-winit/Cargo.toml index 60b26b3..e0a5a32 100644 --- a/crates/window-winit/Cargo.toml +++ b/crates/window-winit/Cargo.toml @@ -21,7 +21,7 @@ pulz-input = { path = "../input" } fnv = { workspace = true } tracing = { workspace = true } -winit = { version = "0.27", default-features = false } +winit = { version = "0.28", default-features = false } [target.'cfg(not(target_os = "unknown"))'.dev-dependencies] tracing-subscriber = { workspace = true } From c9002121460c53d2e83181bb259a8dc4b156e3ac Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sun, 5 Feb 2023 14:44:47 +0100 Subject: [PATCH 15/36] update wgpu --- crates/render-wgpu/Cargo.toml | 6 +++--- crates/render-wgpu/src/convert.rs | 1 + crates/render-wgpu/src/lib.rs | 12 +++++++++--- crates/render-wgpu/src/surface.rs | 19 ++++++++++--------- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/crates/render-wgpu/Cargo.toml b/crates/render-wgpu/Cargo.toml index 384d180..5e48bb6 100644 --- a/crates/render-wgpu/Cargo.toml +++ b/crates/render-wgpu/Cargo.toml @@ -15,7 +15,7 @@ pulz-render = { path = "../render" } thiserror = { workspace = true } tracing = { workspace = true } slotmap = { workspace = true } -wgpu = "0.14" +wgpu = "0.15" raw-window-handle = { workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] @@ -23,7 +23,7 @@ pollster = "0.2" [dev-dependencies] anyhow = { workspace = true } -naga = "0.10" +naga = "0.11" pulz-window-winit = { path = "../window-winit" } pulz-render-pipeline-core = { path = "../render-pipeline-core" } @@ -32,7 +32,7 @@ tracing-subscriber = { workspace = true } async-std = { workspace = true } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wgpu = { version = "0.14" , features = ["webgl"] } +wgpu = { version = "0.15" , features = ["webgl"] } tracing-wasm = { workspace = true } tracing-log = { workspace = true } console_error_panic_hook = { workspace = true } diff --git a/crates/render-wgpu/src/convert.rs b/crates/render-wgpu/src/convert.rs index de45d0e..94c4d0b 100644 --- a/crates/render-wgpu/src/convert.rs +++ b/crates/render-wgpu/src/convert.rs @@ -83,6 +83,7 @@ pub fn convert_texture_descriptor(val: &TextureDescriptor) -> Result Result { let backends = backend_bits_from_env_or_default(); - let instance = wgpu::Instance::new(backends); + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends, + ..Default::default() + }); if let Some(adapter) = wgpu::util::initialize_adapter_from_env(&instance, backends) { let renderer = WgpuRendererFull::for_adapter(instance, adapter).await?; return Ok(Self(WgpuRendererInner::Full(renderer))); @@ -253,7 +259,7 @@ impl WgpuRenderer { ) -> Result<&mut WgpuRendererFull> { if let WgpuRendererInner::Full(renderer) = &mut self.0 { renderer.surfaces.remove(window_id); // replaces old surface - let surface = Surface::create(&renderer.instance, window_descriptor, window_raw); + let surface = Surface::create(&renderer.instance, window_descriptor, window_raw)?; renderer.surfaces.insert(window_id, surface); } else { #[cfg(not(target_arch = "wasm32"))] @@ -261,7 +267,7 @@ impl WgpuRenderer { let WgpuRendererInner::Early { instance } = std::mem::replace(&mut self.0, WgpuRendererInner::Tmp) else { panic!("unexpected state"); }; - let surface = Surface::create(&instance, window_descriptor, window_raw); + let surface = Surface::create(&instance, window_descriptor, window_raw)?; let mut renderer = pollster::block_on(async { let adapter = Self::default_adapter(&instance, Some(&surface)).await?; WgpuRendererFull::for_adapter(instance, adapter).await diff --git a/crates/render-wgpu/src/surface.rs b/crates/render-wgpu/src/surface.rs index d9d0bde..f00b339 100644 --- a/crates/render-wgpu/src/surface.rs +++ b/crates/render-wgpu/src/surface.rs @@ -15,14 +15,14 @@ impl Surface { instance: &wgpu::Instance, window: &Window, window_handle: &dyn RawWindow, - ) -> Self { - let surface = unsafe { instance.create_surface(&window_handle) }; - Self { + ) -> Result { + let surface = unsafe { instance.create_surface(&window_handle)? }; + Ok(Self { surface, size: window.size, vsync: window.vsync, format: wgpu::TextureFormat::Rgba8UnormSrgb, - } + }) } #[inline] @@ -52,16 +52,16 @@ impl Surface { pub fn configure(&mut self, adapter: &wgpu::Adapter, device: &wgpu::Device) { // TODO: also reconfigure on resize, and when presenting results in `Outdated/Lost` - self.format = self - .surface - .get_supported_formats(adapter) + let capabilities = self.surface.get_capabilities(adapter); + self.format = capabilities + .formats .first() .copied() .expect("surface not compatible"); let present_mode = if self.vsync { - wgpu::PresentMode::Fifo + wgpu::PresentMode::AutoVsync } else { - wgpu::PresentMode::Immediate + wgpu::PresentMode::AutoNoVsync }; let surface_config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, @@ -70,6 +70,7 @@ impl Surface { height: self.size.y, present_mode, alpha_mode: wgpu::CompositeAlphaMode::Auto, + view_formats: vec![], }; self.surface.configure(device, &surface_config); } From 0a448aecd5fdc953a07867ee5da5692e1b44f20d Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sat, 1 Apr 2023 17:17:54 +0200 Subject: [PATCH 16/36] update deps --- Cargo.toml | 8 ++-- crates/render-wgpu/Cargo.toml | 2 +- crates/render/Cargo.toml | 2 +- crates/render/src/buffer.rs | 2 +- crates/render/src/graph/access.rs | 7 ++-- .../render/src/pipeline/graphics_pipeline.rs | 2 +- crates/render/src/texture/descriptor.rs | 38 +++++++++---------- examples/android/Cargo.toml | 2 +- examples/android/build.gradle | 4 +- examples/android/src/lib.rs | 4 +- run-wasm/Cargo.toml | 2 +- 11 files changed, 38 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2c80f00..bd13fe1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ edition = "2024" rust-version = "1.86" [workspace.dependencies] -glam = "0.22" +glam = "0.23" serde = "1.0" raw-window-handle = "0.5" slotmap = "1.0" @@ -27,7 +27,7 @@ typemap = "0.3" anyhow = "1.0" thiserror = "1.0" tracing = "0.1" -bitflags = "1.3" +bitflags = "2.0" fnv = "1.0" radsort = "0.1" bytemuck = "1.13" @@ -40,8 +40,8 @@ backtrace = "0.3" atomic_refcell = "0.1" palette = { version = "0.6", default-features = false } image = { version = "0.24", default-features = false } -encase = { version = "0.4", features = ["glam"], default-features = false } -encase_derive_impl = { version = "0.4" } +encase = { version = "0.5", features = ["glam"], default-features = false } +encase_derive_impl = { version = "0.5" } crossbeam-utils = "0.8" darling = "0.20" proc-macro2 = "1.0" diff --git a/crates/render-wgpu/Cargo.toml b/crates/render-wgpu/Cargo.toml index 5e48bb6..41ec3b5 100644 --- a/crates/render-wgpu/Cargo.toml +++ b/crates/render-wgpu/Cargo.toml @@ -19,7 +19,7 @@ wgpu = "0.15" raw-window-handle = { workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -pollster = "0.2" +pollster = "0.3" [dev-dependencies] anyhow = { workspace = true } diff --git a/crates/render/Cargo.toml b/crates/render/Cargo.toml index 7b18122..d4e6ed1 100644 --- a/crates/render/Cargo.toml +++ b/crates/render/Cargo.toml @@ -28,7 +28,7 @@ image = { workspace = true, default-features = false } slotmap = { workspace = true } bytemuck = { workspace = true } fnv = { workspace = true } -bitflags = { workspace = true } +bitflags = { workspace = true, features = ["serde"] } thiserror = { workspace = true } tracing = { workspace = true } downcast-rs = { workspace = true } diff --git a/crates/render/src/buffer.rs b/crates/render/src/buffer.rs index 5c47106..50253d8 100644 --- a/crates/render/src/buffer.rs +++ b/crates/render/src/buffer.rs @@ -25,7 +25,7 @@ impl Default for BufferDescriptor { } bitflags! { - #[derive(Default, Serialize, Deserialize)] + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] pub struct BufferUsage: u32 { const TRANSFER_SRC = 1; const TRANSFER_DST = 2; diff --git a/crates/render/src/graph/access.rs b/crates/render/src/graph/access.rs index d2bc12c..6af482b 100644 --- a/crates/render/src/graph/access.rs +++ b/crates/render/src/graph/access.rs @@ -53,6 +53,7 @@ impl ResourceAccess for Buffer { } bitflags! { + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)] pub struct Stage: u32 { // const TOP_OF_PIPE = 0x00000001; const DRAW_INDIRECT = 0x00000002; @@ -81,6 +82,7 @@ bitflags! { } bitflags! { + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)] pub struct ShaderStage: u32 { // SUBSET of Stage const VERTEX = 0x00000008; @@ -97,9 +99,8 @@ bitflags! { impl ShaderStage { #[inline] - pub fn as_stage(self) -> Stage { - // SAFETY: bits are a subset of Stage - unsafe { Stage::from_bits_unchecked(self.bits) } + pub const fn as_stage(self) -> Stage { + Stage::from_bits_truncate(self.bits()) } } diff --git a/crates/render/src/pipeline/graphics_pipeline.rs b/crates/render/src/pipeline/graphics_pipeline.rs index b0c8763..9bb9e3e 100644 --- a/crates/render/src/pipeline/graphics_pipeline.rs +++ b/crates/render/src/pipeline/graphics_pipeline.rs @@ -452,7 +452,7 @@ pub enum BlendFactor { } bitflags! { - #[derive(Serialize, Deserialize)] + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct ColorWrite: u32 { const RED = 1; const GREEN = 2; diff --git a/crates/render/src/texture/descriptor.rs b/crates/render/src/texture/descriptor.rs index 26a46cb..0d83f27 100644 --- a/crates/render/src/texture/descriptor.rs +++ b/crates/render/src/texture/descriptor.rs @@ -242,7 +242,7 @@ impl Default for TextureFormat { } bitflags! { - #[derive(Serialize, Deserialize)] + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct TextureAspects: u32 { const COLOR = 1; const DEPTH = 2; @@ -260,24 +260,24 @@ impl Default for TextureAspects { } bitflags! { - #[derive(Default, Serialize, Deserialize)] - pub struct TextureUsage: u32 { - const TRANSFER_SRC = 1; - const TRANSFER_DST = 2; - const SAMPLED = 4; - const STORAGE = 8; - const COLOR_ATTACHMENT = 16; - const DEPTH_STENCIL_ATTACHMENT = 32; - const INPUT_ATTACHMENT = 64; - - // modifiers - const BY_REGION = 128; - - const NONE = 0; - const ALL_READ = Self::TRANSFER_SRC.bits | Self::SAMPLED.bits | Self::INPUT_ATTACHMENT.bits; - const ALL_WRITE = Self::TRANSFER_DST.bits | Self::STORAGE.bits | Self::COLOR_ATTACHMENT.bits | Self::DEPTH_STENCIL_ATTACHMENT.bits; - const ALL_ATTACHMENTS = Self::COLOR_ATTACHMENT.bits | Self::DEPTH_STENCIL_ATTACHMENT.bits | Self::INPUT_ATTACHMENT.bits; - } + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] + pub struct TextureUsage: u32 { + const TRANSFER_SRC = 1; + const TRANSFER_DST = 2; + const SAMPLED = 4; + const STORAGE = 8; + const COLOR_ATTACHMENT = 16; + const DEPTH_STENCIL_ATTACHMENT = 32; + const INPUT_ATTACHMENT = 64; + + // modifiers + const BY_REGION = 128; + + const NONE = 0; + const ALL_READ = Self::TRANSFER_SRC.bits() | Self::SAMPLED.bits() | Self::INPUT_ATTACHMENT.bits(); + const ALL_WRITE = Self::TRANSFER_DST.bits() | Self::STORAGE.bits() | Self::COLOR_ATTACHMENT.bits() | Self::DEPTH_STENCIL_ATTACHMENT.bits(); + const ALL_ATTACHMENTS = Self::COLOR_ATTACHMENT.bits() | Self::DEPTH_STENCIL_ATTACHMENT.bits() | Self::INPUT_ATTACHMENT.bits(); + } } impl TextureUsage { diff --git a/examples/android/Cargo.toml b/examples/android/Cargo.toml index 81b64d9..dc3cfba 100644 --- a/examples/android/Cargo.toml +++ b/examples/android/Cargo.toml @@ -20,5 +20,5 @@ pulz-window = { path = "../../crates/window" } pulz-window-winit = { path = "../../crates/window-winit", features = ["android-game-activity"]} log = "0.4" -android_logger = "0.12" +android_logger = "0.13" tracing = { workspace = true, features = ["log"] } diff --git a/examples/android/build.gradle b/examples/android/build.gradle index 10cd045..3178ca6 100644 --- a/examples/android/build.gradle +++ b/examples/android/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '7.4.0' apply false - id 'com.android.library' version '7.4.0' apply false + id 'com.android.application' version '7.4.2' apply false + id 'com.android.library' version '7.4.2' apply false id 'org.jetbrains.kotlin.android' version '1.7.21' apply false id "org.mozilla.rust-android-gradle.rust-android" version "0.9.3" } diff --git a/examples/android/src/lib.rs b/examples/android/src/lib.rs index 12d1f2a..e82999b 100644 --- a/examples/android/src/lib.rs +++ b/examples/android/src/lib.rs @@ -43,7 +43,9 @@ pub fn android_main(app: AndroidApp) { use winit::platform::android::EventLoopBuilderExtAndroid; // #[cfg(debug_assertions)] // std::env::set_var("RUST_BACKTRACE", "1"); - android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Info)); + android_logger::init_once( + android_logger::Config::default().with_max_level(log::LevelFilter::Info), + ); let event_loop = EventLoopBuilder::new().with_android_app(app).build(); let (resources, _window, window_system) = init(&event_loop); diff --git a/run-wasm/Cargo.toml b/run-wasm/Cargo.toml index b96e090..e8fc52f 100644 --- a/run-wasm/Cargo.toml +++ b/run-wasm/Cargo.toml @@ -8,4 +8,4 @@ edition.workspace = true repository.workspace = true [dependencies] -cargo-run-wasm = "0.3.0" +cargo-run-wasm = "0.3.2" From 6be66fd6b02262465e5b855459aecdf574d89b1d Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sat, 1 Apr 2023 17:26:12 +0200 Subject: [PATCH 17/36] calculate graph resource assignments --- crates/render-ash/src/graph.rs | 79 ++++--- crates/render-ash/src/lib.rs | 104 ++++++-- crates/render-ash/src/swapchain.rs | 125 +++++++--- crates/render/src/graph/access.rs | 24 +- crates/render/src/graph/builder.rs | 74 ++++-- crates/render/src/graph/mod.rs | 22 +- crates/render/src/graph/pass/builder.rs | 23 +- crates/render/src/graph/resources.rs | 250 ++++++++++++++++++-- crates/render/src/pipeline/graphics_pass.rs | 8 +- 9 files changed, 559 insertions(+), 150 deletions(-) diff --git a/crates/render-ash/src/graph.rs b/crates/render-ash/src/graph.rs index ab37b42..b4df8cf 100644 --- a/crates/render-ash/src/graph.rs +++ b/crates/render-ash/src/graph.rs @@ -1,25 +1,27 @@ -use std::sync::Arc; - use ash::vk::{self}; use pulz_render::{ draw::DrawPhases, - graph::{pass::PipelineBindPoint, resources::ResourceAssignments, PassIndex, RenderGraph}, + graph::{ + pass::PipelineBindPoint, resources::GraphBackend, PassIndex, RenderGraph, + RenderGraphAssignments, + }, pipeline::{GraphicsPass, GraphicsPassDescriptorWithTextures}, + texture::{TextureDimensions, TextureFormat}, }; +use pulz_window::WindowsMirror; use crate::{ - device::AshDevice, drop_guard::Guard, encoder::{AshCommandPool, SubmissionGroup}, resources::AshResources, + swapchain::SurfaceSwapchain, Result, }; pub struct AshRenderGraph { - device: Arc, - hash: u64, topo: Vec, barriers: Vec, + assignments: RenderGraphAssignments, } #[derive(Default)] @@ -41,35 +43,27 @@ unsafe impl Sync for Barrier {} impl AshRenderGraph { #[inline] - pub fn new(device: &Arc) -> Self { + pub const fn new() -> Self { Self { - device: device.clone(), - hash: 0, topo: Vec::new(), barriers: Vec::new(), + assignments: RenderGraphAssignments::new(), } } - fn reset(&mut self) { - // TODO: caching of render-passes: don't destroy & recreate on every update! - for topo in &mut self.topo { - for (_, pass, fb) in topo.render_passes.drain(..) { - unsafe { - self.device.destroy_framebuffer(fb, None); - self.device.destroy_render_pass(pass, None); - } - } - } - - self.topo.clear(); - self.barriers.clear(); - } - - pub fn update(&mut self, src_graph: &RenderGraph, res: &mut AshResources) -> Result { + pub fn update( + &mut self, + src_graph: &RenderGraph, + res: &mut AshResources, + surfaces: &WindowsMirror, + ) -> Result { // TODO: update render-pass, if resource-formats changed // TODO: update framebuffer if render-pass or dimensions changed - if src_graph.was_updated() || self.hash != src_graph.hash() { - self.force_update(src_graph, res)?; + if self + .assignments + .update(src_graph, &mut AshGraphBackend { res, surfaces }) + { + self.do_update(src_graph, res)?; Ok(true) } else { Ok(false) @@ -81,7 +75,8 @@ impl AshRenderGraph { descr: &GraphicsPassDescriptorWithTextures, render_pass: vk::RenderPass, ) -> Result> { - // TODO: caching? + // TODO: caching + // TODO: destroy (if not caching) let image_views: Vec<_> = descr.textures.iter().map(|&t| res[t].1).collect(); let create_info = vk::FramebufferCreateInfo::builder() .render_pass(render_pass) @@ -96,15 +91,14 @@ impl AshRenderGraph { } } - pub fn force_update(&mut self, src: &RenderGraph, res: &mut AshResources) -> Result<()> { - self.reset(); - self.hash = src.hash(); + fn do_update(&mut self, src: &RenderGraph, res: &mut AshResources) -> Result<()> { + self.topo.clear(); + self.barriers.clear(); let num_topological_groups = src.get_num_topological_groups(); self.topo .resize_with(num_topological_groups, Default::default); - let texture_assignments = ResourceAssignments::new(); for topo_index in 0..num_topological_groups { let topo_group = &mut self.topo[topo_index]; for pass in src.get_topological_group(topo_index) { @@ -113,8 +107,8 @@ impl AshRenderGraph { // TODO: no unwrap / error handling let pass_descr = GraphicsPassDescriptorWithTextures::from_graph( src, + &self.assignments, pass, - &texture_assignments, ) .unwrap(); let graphics_pass = @@ -199,8 +193,21 @@ impl AshRenderGraph { } } -impl Drop for AshRenderGraph { - fn drop(&mut self) { - self.reset() +struct AshGraphBackend<'a> { + res: &'a mut AshResources, + surfaces: &'a WindowsMirror, +} + +impl GraphBackend for AshGraphBackend<'_> { + fn get_surface( + &mut self, + window_id: pulz_window::WindowId, + ) -> ( + pulz_render::texture::Texture, + TextureFormat, + TextureDimensions, + ) { + let swapchain = self.surfaces.get(window_id).expect("swapchain not initialized"); + (swapchain.texture_id(), swapchain.texture_format(), TextureDimensions::D2(swapchain.size())) } } diff --git a/crates/render-ash/src/lib.rs b/crates/render-ash/src/lib.rs index 44109b0..0cc762e 100644 --- a/crates/render-ash/src/lib.rs +++ b/crates/render-ash/src/lib.rs @@ -25,7 +25,7 @@ #![doc(html_no_source)] #![doc = include_str!("../README.md")] -use std::{ffi::CStr, sync::Arc}; +use std::{backtrace::Backtrace, ffi::CStr, sync::Arc}; use ash::vk::{self, PipelineStageFlags}; use bitflags::bitflags; @@ -55,6 +55,37 @@ use pulz_window::{ RawWindow, Window, WindowId, Windows, WindowsMirror, }; +// wrapper object for printing backtrace, until provide() is stable +pub struct VkError { + result: vk::Result, + backtrace: Backtrace, +} + +impl From for VkError { + fn from(result: vk::Result) -> Self { + Self { + result, + backtrace: Backtrace::capture(), + } + } +} +impl std::error::Error for VkError {} +impl std::fmt::Debug for VkError { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ::fmt(&self, f) + } +} +impl std::fmt::Display for VkError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}\nVkResult backtrace:\n{}", + self.result, self.backtrace + ) + } +} + #[derive(Error, Debug)] #[non_exhaustive] pub enum Error { @@ -89,7 +120,7 @@ pub enum Error { SwapchainImageAlreadyAcquired, #[error("Vulkan Error")] - VkError(#[from] vk::Result), + VkError(#[from] VkError), #[error("Allocation Error")] AllocationError(#[from] gpu_alloc::AllocationError), @@ -113,11 +144,16 @@ impl From for Error { Self::ExtensionNotSupported(e.0) } } - +impl From for Error { + #[inline] + fn from(e: vk::Result) -> Self { + Self::from(VkError::from(e)) + } +} impl From<&vk::Result> for Error { #[inline] fn from(e: &vk::Result) -> Self { - Self::VkError(*e) + Self::from(*e) } } @@ -142,13 +178,14 @@ impl Drop for AshRendererFull { } } -bitflags!( +bitflags! { /// Instance initialization flags. + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)] pub struct AshRendererFlags: u32 { /// Generate debug information in shaders and objects. const DEBUG = 1 << 0; } -); +} struct Frame { // TODO: multi-threaded command recording: CommandPool per thread @@ -156,6 +193,7 @@ struct Frame { finished_fence: vk::Fence, // signaled ad end of command-cueue, waited at beginning of frame finished_semaphore: vk::Semaphore, // semaphore used for presenting to the swapchain retired_swapchains: Vec, + retired_image_views: Vec, } impl Frame { @@ -176,10 +214,15 @@ impl Frame { finished_fence: finished_fence.take(), finished_semaphore: finished_semaphore.take(), retired_swapchains: Vec::new(), + retired_image_views: Vec::new(), }) } unsafe fn reset(&mut self, device: &AshDevice) -> Result<(), vk::Result> { + for image_view in self.retired_image_views.drain(..) { + device.destroy_image_view(image_view, None); + } + if let Ok(ext_swapchain) = device.ext_swapchain() { for swapchain in self.retired_swapchains.drain(..) { ext_swapchain.destroy_swapchain(swapchain, None); @@ -214,14 +257,13 @@ impl Drop for Frame { impl AshRendererFull { fn from_device(device: Arc) -> Self { let res = AshResources::new(&device); - let graph = AshRenderGraph::new(&device); Self { device, res, frames: Vec::with_capacity(Frame::NUM_FRAMES_IN_FLIGHT), current_frame: 0, surfaces: WindowsMirror::new(), - graph, + graph: AshRenderGraph::new(), } } @@ -233,7 +275,8 @@ impl AshRendererFull { continue; }; //TODO: re-create also the surface, when SURFACE_LOST was returned in earlier calls. - //TODO: better resize check (don't compare size, but use a 'dirty'-flag) + //TODO: better resize check (don't compare size, but use a 'dirty'-flag, or listener) + //TODO: sync if window.size != surface_swapchain.size() { info!( "surface sized changed: {} => {}", @@ -252,6 +295,7 @@ impl AshRendererFull { .unwrap(); } } + //TODO: sync for window_id in to_remove { self.surfaces.remove(window_id); } @@ -301,25 +345,27 @@ impl AshRendererFull { &mut self, submission_group: &mut SubmissionGroup, ) -> Result<()> { - let count = self.get_num_unacquired_swapchains(); - if count == 0 { + let unaquired_swapchains: Vec<_> = self + .surfaces + .iter() + .filter_map(|(id, s)| if s.is_acquired() { None } else { Some(id) }) + .collect(); + if unaquired_swapchains.is_empty() { return Ok(()); } // TODO: try to clear with empty render-pass let _span = tracing::trace_span!("ClearImages").entered(); - let frame = &mut self.frames[self.current_frame]; - let mut encoder = frame.command_pool.encoder()?; - encoder.begin_debug_label("ClearImages"); - let mut images = Vec::with_capacity(count); - for (_window_id, surface_swapchain) in &mut self.surfaces { - if !surface_swapchain.is_acquired() { - let sem = encoder.request_semaphore()?; - submission_group.wait(sem, PipelineStageFlags::TRANSFER); - if let Some(img) = surface_swapchain.acquire_next_image(0, sem)? { - images.push((img.image, surface_swapchain.clear_value())); - } + let mut images = Vec::with_capacity(unaquired_swapchains.len()); + for window_id in unaquired_swapchains.iter().copied() { + let sem = self.frames[self.current_frame] + .command_pool + .request_semaphore()?; + submission_group.wait(sem, PipelineStageFlags::TRANSFER); + if let Some(texture) = self.acquire_swapchain_image(window_id, 0, sem)? { + let image = self.res.textures[texture].0; + images.push((image, self.surfaces[window_id].clear_value())); } } @@ -342,6 +388,11 @@ impl AshRendererFull { .build() }) .collect::>(); + + let frame = &mut self.frames[self.current_frame]; + let mut encoder = frame.command_pool.encoder()?; + encoder.begin_debug_label("ClearImages"); + unsafe { encoder.pipeline_barrier( vk::PipelineStageFlags::TOP_OF_PIPE, @@ -426,7 +477,9 @@ impl AshRendererFull { self.reconfigure_swapchains(windows); // TODO: maybe graph needs to consider updated swapchain format & dimensions? - self.graph.update(src_graph, &mut self.res).unwrap(); + self.graph + .update(src_graph, &mut self.res, &self.surfaces) + .unwrap(); let mut submission_group = self.begin_frame().unwrap(); self.render_frame(&mut submission_group, src_graph, draw_phases) @@ -477,10 +530,9 @@ impl AshRenderer { window_raw: &dyn RawWindow, ) -> Result<&mut AshRendererFull> { if let AshRendererInner::Full(renderer) = &mut self.0 { - renderer.surfaces.remove(window_id); // replaces old surface let surface = renderer.device.instance().new_surface(window_raw)?; let swapchain = renderer.device.new_swapchain(surface, window_descriptor)?; - renderer.surfaces.insert(window_id, swapchain); + renderer.insert_swapchain(swapchain, window_id)?; } else { let AshRendererInner::Early { instance, .. } = &self.0 else { unreachable!() @@ -489,7 +541,7 @@ impl AshRenderer { let device = instance.new_device(surface.raw())?; let swapchain = device.new_swapchain(surface, window_descriptor)?; let mut renderer = AshRendererFull::from_device(device); - renderer.surfaces.insert(window_id, swapchain); + renderer.insert_swapchain(swapchain, window_id)?; self.0 = AshRendererInner::Full(renderer); } let AshRendererInner::Full(renderer) = &mut self.0 else { diff --git a/crates/render-ash/src/swapchain.rs b/crates/render-ash/src/swapchain.rs index 7610b9e..cd02b77 100644 --- a/crates/render-ash/src/swapchain.rs +++ b/crates/render-ash/src/swapchain.rs @@ -1,16 +1,17 @@ use std::sync::Arc; use ash::{extensions::khr, vk}; -use pulz_render::{math::uvec2, texture::Texture}; +use pulz_render::{math::uvec2, texture::{Texture, TextureFormat}}; use pulz_window::{RawWindow, Size2, Window, WindowId}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; use slotmap::Key; +use tracing::debug; use crate::{ device::AshDevice, drop_guard::{Destroy, Guard}, instance::AshInstance, - AshRendererFull, Error, Result, + AshRendererFull, Error, Result, convert::VkInto, }; pub struct Surface { @@ -358,6 +359,24 @@ impl SwapchainSupportDetail { } self.present_modes.first().copied().unwrap() } + + pub fn preferred_composite_alpha(&self) -> vk::CompositeAlphaFlagsKHR { + if self + .capabilities + .supported_composite_alpha + .contains(vk::CompositeAlphaFlagsKHR::OPAQUE) + { + return vk::CompositeAlphaFlagsKHR::OPAQUE; + } + if self + .capabilities + .supported_composite_alpha + .contains(vk::CompositeAlphaFlagsKHR::INHERIT) + { + return vk::CompositeAlphaFlagsKHR::INHERIT; + } + self.capabilities.supported_composite_alpha + } } pub struct SurfaceSwapchain { @@ -370,10 +389,11 @@ pub struct SurfaceSwapchain { present_mode: vk::PresentModeKHR, image_usage: vk::ImageUsageFlags, images: Vec, - //image_views: Vec, + image_views: Vec, texture_id: Texture, acquired_image: u32, retired_swapchains: Vec, + retired_image_views: Vec, } impl AshDevice { @@ -398,10 +418,11 @@ impl AshDevice { // TODO: custom usage image_usage: vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::TRANSFER_DST, images: Vec::new(), - //image_views: Vec::new(), + image_views: Vec::new(), texture_id: Texture::null(), acquired_image: !0, retired_swapchains: Vec::new(), + retired_image_views: Vec::new(), }; swapchain.configure()?; Ok(swapchain) @@ -419,6 +440,16 @@ impl SurfaceSwapchain { self.surface_format.format } + #[inline] + pub fn texture_format(&self) -> TextureFormat { + self.surface_format.format.vk_into() + } + + #[inline] + pub fn texture_id(&self) -> Texture { + self.texture_id + } + #[inline] pub fn present_mode(&self) -> vk::PresentModeKHR { self.present_mode @@ -590,7 +621,7 @@ impl SurfaceSwapchain { }, ) .pre_transform(swapchain_support_info.capabilities.current_transform) - .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) + .composite_alpha(swapchain_support_info.preferred_composite_alpha()) .present_mode(self.present_mode) .clipped(true) .old_swapchain(old_swapchain) @@ -601,11 +632,34 @@ impl SurfaceSwapchain { }; self.images = unsafe { ext_swapchain.get_swapchain_images(self.swapchain_raw)? }; + self.retired_image_views.append(&mut self.image_views); + for image in &self.images { + unsafe { + let image_view = self.device.create_image_view( + &vk::ImageViewCreateInfo::builder() + .image(*image) + .view_type(vk::ImageViewType::TYPE_2D) + .format(self.surface_format.format) + .subresource_range( + vk::ImageSubresourceRange::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(1) + .build(), + ) + .build(), + None, + )?; + self.image_views.push(image_view); + } + } Ok(()) } - pub(crate) fn acquire_next_image( + fn acquire_next_image( &mut self, timeout: u64, signal_semaphore: vk::Semaphore, @@ -657,7 +711,7 @@ impl SurfaceSwapchain { Ok(Some(AcquiredSwapchainImage { index, image: self.images[index as usize], - //image_view: self.image_views[index as usize], + image_view: self.image_views[index as usize], suboptimal, })) } @@ -668,30 +722,29 @@ impl Drop for SurfaceSwapchain { if self.swapchain_raw != vk::SwapchainKHR::null() || self.surface_raw != vk::SurfaceKHR::null() || !self.retired_swapchains.is_empty() + || !self.retired_image_views.is_empty() { unsafe { - self.device.device_wait_idle(); + self.device.device_wait_idle().unwrap(); } } - if !self.retired_swapchains.is_empty() { - let ext_swapchain = self.device.ext_swapchain().unwrap(); - for r in self.retired_swapchains.drain(..) { - unsafe { - ext_swapchain.destroy_swapchain(r, None); - } + for image_view in self.image_views.drain(..) { + unsafe { + self.device.destroy_image_view(image_view, None); + } + } + // don't destroy images obtained from get_swapchain_images! They are destroyed together with the swapchain object. + + let ext_swapchain = self.device.ext_swapchain().unwrap(); + for r in self.retired_swapchains.drain(..) { + unsafe { + ext_swapchain.destroy_swapchain(r, None); } } - // for image_view in self.image_views.drain(..) { - // unsafe { - // self.device.destroy_image_view(image_view, None); - // } - // } - // don't destroy images obtained from get_swapchain_images. They are destroyed together with the swapchain object. self.images.clear(); if self.swapchain_raw != vk::SwapchainKHR::null() { - let ext_swapchain = self.device.ext_swapchain().unwrap(); unsafe { ext_swapchain.destroy_swapchain(self.swapchain_raw, None); } @@ -707,6 +760,22 @@ impl Drop for SurfaceSwapchain { } impl AshRendererFull { + pub(crate) fn insert_swapchain( + &mut self, + swapchain: SurfaceSwapchain, + window_id: WindowId, + ) -> Result<()> { + if let Some(old_swapchain) = self.surfaces.insert(window_id, swapchain) { + self.res.textures.remove(old_swapchain.texture_id); + } + let surface = &mut self.surfaces[window_id]; + surface.texture_id = self + .res + .textures + .insert((vk::Image::null(), vk::ImageView::null())); + Ok(()) + } + pub(crate) fn acquire_swapchain_image( &mut self, window_id: WindowId, @@ -731,13 +800,6 @@ impl AshRendererFull { .count() } - pub(crate) fn get_num_unacquired_swapchains(&self) -> usize { - self.surfaces - .iter() - .filter(|(_, s)| !s.is_acquired()) - .count() - } - pub(crate) fn present_acquired_swapchain_images( &mut self, wait_semaphores: &[vk::Semaphore], @@ -809,7 +871,10 @@ impl AshRendererFull { for (_, surface_swapchain) in &mut self.surfaces { frame .retired_swapchains - .append(&mut surface_swapchain.retired_swapchains) + .append(&mut surface_swapchain.retired_swapchains); + frame + .retired_image_views + .append(&mut surface_swapchain.retired_image_views); } } } @@ -817,6 +882,6 @@ impl AshRendererFull { pub struct AcquiredSwapchainImage { index: u32, pub image: vk::Image, - //pub image_view: vk::ImageView, + pub image_view: vk::ImageView, pub suboptimal: bool, } diff --git a/crates/render/src/graph/access.rs b/crates/render/src/graph/access.rs index 6af482b..5e2fd2b 100644 --- a/crates/render/src/graph/access.rs +++ b/crates/render/src/graph/access.rs @@ -16,6 +16,7 @@ pub trait ResourceAccess: Copy + Eq + Hash { type Usage: Copy + Clone + Debug + + Default + Eq + BitOr + BitOrAssign @@ -27,29 +28,46 @@ pub trait ResourceAccess: Copy + Eq + Hash { + Sub + Hash; - type Meta; + type Format: PartialEq + Debug + Copy; + type Size: PartialEq + Copy; fn check_usage_is_pass_compatible(combined_usage: Self::Usage); + + fn default_format(usage: Self::Usage) -> Self::Format; } impl ResourceAccess for Texture { type Usage = TextureUsage; - type Meta = (TextureFormat, TextureDimensions, u8); + type Format = TextureFormat; + type Size = TextureDimensions; fn check_usage_is_pass_compatible(combined_usage: Self::Usage) { if combined_usage.is_non_attachment() { panic!("Can't use texture as non-attachment resource multiple times in the same pass"); } } + + #[inline] + fn default_format(usage: Self::Usage) -> Self::Format { + if usage.contains(TextureUsage::DEPTH_STENCIL_ATTACHMENT) { + TextureFormat::Depth24PlusStencil8 + } else { + TextureFormat::Rgba8UnormSrgb + } + } } impl ResourceAccess for Buffer { type Usage = BufferUsage; - type Meta = usize; + type Format = (); + type Size = usize; fn check_usage_is_pass_compatible(_combined_usage: Self::Usage) { panic!("Can't use buffer multiple times in the same pass"); } + + #[inline] + fn default_format(usage: Self::Usage) -> Self::Format {} } bitflags! { diff --git a/crates/render/src/graph/builder.rs b/crates/render/src/graph/builder.rs index 6100771..615bd61 100644 --- a/crates/render/src/graph/builder.rs +++ b/crates/render/src/graph/builder.rs @@ -1,40 +1,29 @@ use std::{ - collections::hash_map::DefaultHasher, + collections::{hash_map::DefaultHasher, VecDeque}, hash::{Hash, Hasher}, }; +use pulz_bitset::BitSet; use tracing::{debug, trace}; use super::{ - access::ResourceAccess, deps::DependencyMatrix, resources::Slot, RenderGraph, - RenderGraphBuilder, + access::ResourceAccess, + deps::DependencyMatrix, + resources::{GetExternalResource, Slot}, + RenderGraph, RenderGraphBuilder, }; use crate::{buffer::Buffer, texture::Texture}; -pub trait GetExternalResource { - fn get_external_resource(&self) -> (R, R::Meta); -} - -impl GetExternalResource for F -where - R: ResourceAccess, - F: Fn() -> (R, R::Meta), -{ - fn get_external_resource(&self) -> (R, R::Meta) { - self() - } -} - pub trait GraphImport { type Resource: ResourceAccess; - fn import(&self) -> Box>; + fn import(&self) -> Box + 'static>; } pub trait GraphExport { type Resource: ResourceAccess; - fn export(&self) -> Box>; + fn export(&self) -> Box + 'static>; } impl RenderGraphBuilder { @@ -59,6 +48,8 @@ impl RenderGraphBuilder { E: GraphExport, { let f = export_to.export(); + let last_written_by_pass = slot.last_written_by.0; + self.passes[last_written_by_pass as usize].active = true; self.textures.export(slot, f) } @@ -67,6 +58,8 @@ impl RenderGraphBuilder { E: GraphExport, { let f = export_to.export(); + let last_written_by_pass = slot.last_written_by.0; + self.passes[last_written_by_pass as usize].active = true; self.buffers.export(slot, f) } @@ -131,29 +124,60 @@ impl RenderGraph { swap_graph_data(builder, self); self.hash = builder_hash; - // TODO: detect unused nodes / dead-stripping + let active = self.build_active_set(); - let mut m = self.build_dependency_matrix(); + let mut m = self.build_dependency_matrix(&active); m.remove_self_references(); self.passes_topo_order = m.into_topological_order(); debug!("Topological order: {:?}", self.passes_topo_order); - // TODO: resource aliasing (e.g. share Image resource when ) + self.update_resource_topo_group_range(); self.init = true; } - fn build_dependency_matrix(&self) -> DependencyMatrix { + fn build_active_set(&mut self) -> BitSet { + let mut todo = VecDeque::new(); + let mut active = BitSet::with_capacity_for(self.passes.len()); + for p in &self.passes { + if p.active { + todo.push_back(p.index); + active.insert(p.index as usize); + } + } + while let Some(next) = todo.pop_front() { + let p = &self.passes[next as usize]; + p.textures.mark_deps(&mut active, &mut todo); + p.buffers.mark_deps(&mut active, &mut todo); + } + active + } + + fn build_dependency_matrix(&self, active: &BitSet) -> DependencyMatrix { let mut m = DependencyMatrix::new(self.passes.len()); // TODO: only mark used nodes for p in &self.passes { - p.textures.mark_pass_dependency_matrix(&mut m, p.index); - p.buffers.mark_pass_dependency_matrix(&mut m, p.index); + if active.contains(p.index as usize) { + p.textures.mark_pass_dependency_matrix(&mut m, p.index); + p.buffers.mark_pass_dependency_matrix(&mut m, p.index); + } } m } + + fn update_resource_topo_group_range(&mut self) { + for (i, group) in self.passes_topo_order.iter().enumerate() { + for p in group.iter().copied() { + let p = &self.passes[p]; + p.textures + .update_resource_topo_group_range(&mut self.textures, i as u16); + p.buffers + .update_resource_topo_group_range(&mut self.buffers, i as u16); + } + } + } } fn swap_graph_data(builder: &mut RenderGraphBuilder, dest: &mut RenderGraph) { diff --git a/crates/render/src/graph/mod.rs b/crates/render/src/graph/mod.rs index e36d59e..aa91280 100644 --- a/crates/render/src/graph/mod.rs +++ b/crates/render/src/graph/mod.rs @@ -1,6 +1,6 @@ use self::{ pass::{run::PassExec, PipelineBindPoint}, - resources::{ResourceDeps, ResourceSet}, + resources::{ResourceAssignments, ResourceDeps, ResourceSet}, }; use crate::{ buffer::Buffer, @@ -40,6 +40,7 @@ pub struct PassDescription { buffers: ResourceDeps, begin_subpasses: usize, end_subpasses: usize, // exclusive! + active: bool, } pub struct RenderGraph { @@ -63,6 +64,13 @@ pub struct RenderGraphBuilder { passes: Vec, } +pub struct RenderGraphAssignments { + hash: u64, + was_updated: bool, + texture_assignments: ResourceAssignments, + buffer_assignments: ResourceAssignments, +} + impl RenderGraph { #[inline] const fn new() -> Self { @@ -137,6 +145,18 @@ impl RenderGraphBuilder { } } +impl RenderGraphAssignments { + #[inline] + pub const fn new() -> Self { + Self { + hash: 0, + was_updated: false, + texture_assignments: ResourceAssignments::new(), + buffer_assignments: ResourceAssignments::new(), + } + } +} + impl Default for RenderGraphBuilder { #[inline] fn default() -> Self { diff --git a/crates/render/src/graph/pass/builder.rs b/crates/render/src/graph/pass/builder.rs index 65bb858..73a8ac4 100644 --- a/crates/render/src/graph/pass/builder.rs +++ b/crates/render/src/graph/pass/builder.rs @@ -8,7 +8,7 @@ use crate::{ resources::{ResourceDeps, Slot, SlotAccess, WriteSlot}, PassDescription, PassIndex, RenderGraphBuilder, SubPassIndex, }, - texture::{Texture, TextureUsage}, + texture::{Texture, TextureDimensions, TextureFormat, TextureUsage}, }; impl RenderGraphBuilder { @@ -28,6 +28,7 @@ impl RenderGraphBuilder { buffers: ResourceDeps::new(), begin_subpasses, end_subpasses: begin_subpasses, + active: false, }; let output = pass.build(PassGroupBuilder( @@ -116,6 +117,11 @@ impl PassBuilderIntern<'_> { pub struct PassGroupBuilder<'a, Q>(PassBuilderIntern<'a>, PhantomData); impl PassGroupBuilder<'_, Q> { + #[inline] + pub fn set_active(&mut self) { + self.0.pass.active = true; + } + #[inline] pub(super) fn push_sub_pass>(&mut self, sub_pass: P) -> P::Output { let mut descr = SubPassDescription::new(self.0.pass.index, sub_pass.type_name()); @@ -149,6 +155,21 @@ pub struct PassBuilder<'a, Q> { } impl PassBuilder<'_, Q> { + #[inline] + pub fn set_texture_format(&mut self, slot: &Slot, format: TextureFormat) { + self.base.graph.textures.set_format(slot, format); + } + + #[inline] + pub fn set_texture_size(&mut self, slot: &Slot, size: TextureDimensions) { + self.base.graph.textures.set_size(slot, size); + } + + #[inline] + pub fn set_buffer_size(&mut self, slot: &Slot, size: usize) { + self.base.graph.buffers.set_size(slot, size); + } + #[inline] pub fn reads_texture(&mut self, texture: Slot, stages: ShaderStage) { self.base diff --git a/crates/render/src/graph/resources.rs b/crates/render/src/graph/resources.rs index 09eeceb..eac083e 100644 --- a/crates/render/src/graph/resources.rs +++ b/crates/render/src/graph/resources.rs @@ -1,21 +1,25 @@ use std::{ + collections::VecDeque, hash::{self, Hash}, marker::PhantomData, ops::Deref, }; use pulz_assets::Handle; +use pulz_bitset::BitSet; use pulz_window::WindowId; use super::{ access::{ResourceAccess, Stage}, - builder::{GetExternalResource, GraphExport, GraphImport}, + builder::{GraphExport, GraphImport}, deps::DependencyMatrix, - PassIndex, ResourceIndex, SubPassIndex, PASS_UNDEFINED, SUBPASS_UNDEFINED, + PassIndex, RenderGraph, RenderGraphAssignments, ResourceIndex, SubPassIndex, PASS_UNDEFINED, + SUBPASS_UNDEFINED, }; use crate::{ + buffer::Buffer, camera::RenderTarget, - texture::{Image, Texture}, + texture::{Image, Texture, TextureDimensions, TextureFormat}, }; #[derive(Copy, Clone)] @@ -103,23 +107,44 @@ enum ResourceVariant { Export, } -struct Resource { +struct Resource { first_written: SubPassIndex, last_written: SubPassIndex, + first_topo_group: u16, + last_topo_group: u16, + usage: R::Usage, + format: Option, + size: Option, variant: ResourceVariant, extern_res: Option>>, } #[derive(Hash)] -pub(super) struct ResourceSet(Vec>); +pub(super) struct ResourceSet(Vec>); -impl ResourceSet { +impl ResourceSet { pub fn len(&self) -> usize { self.0.len() } } -impl Hash for Resource { +impl Resource { + #[inline] + fn is_active(&self) -> bool { + self.first_topo_group <= self.last_topo_group + } + + #[inline] + fn format_or_default(&self) -> R::Format { + if let Some(f) = self.format { + f + } else { + R::default_format(self.usage) + } + } +} + +impl Hash for Resource { fn hash(&self, state: &mut H) { self.first_written.hash(state); self.last_written.hash(state); @@ -145,7 +170,7 @@ pub struct ResourceDep { usage: R::Usage, } -impl ResourceSet { +impl ResourceSet { #[inline] pub(super) const fn new() -> Self { Self(Vec::new()) @@ -160,12 +185,30 @@ impl ResourceSet { self.0.push(Resource { first_written: SUBPASS_UNDEFINED, last_written: SUBPASS_UNDEFINED, + first_topo_group: !0, + last_topo_group: 0, + usage: Default::default(), + format: None, + size: None, variant: ResourceVariant::Transient, extern_res: None, }); WriteSlot::new(index, SUBPASS_UNDEFINED) } + pub(super) fn set_format(&mut self, slot: &Slot, format: R::Format) { + let slot = &mut self.0[slot.index as usize]; + if let Some(old_format) = &slot.format { + assert_eq!(old_format, &format, "incompatible format"); + } + slot.format = Some(format); + } + + pub(super) fn set_size(&mut self, slot: &Slot, size: R::Size) { + let slot = &mut self.0[slot.index as usize]; + slot.size = Some(size); + } + pub(super) fn writes(&mut self, slot: WriteSlot, new_pass: SubPassIndex) -> WriteSlot { let r = &mut self.0[slot.0.index as usize]; let last_written_by_pass = r.last_written; @@ -236,6 +279,17 @@ impl ResourceDeps { Self(Vec::new()) } + pub(super) fn mark_deps(&self, marks: &mut BitSet, todo: &mut VecDeque) { + for dep in &self.0 { + let pass_index = dep.src_pass(); + if pass_index != !0 { + if marks.insert(pass_index as usize) { + todo.push_back(pass_index); + } + } + } + } + pub(super) fn mark_pass_dependency_matrix(&self, m: &mut DependencyMatrix, to_pass: PassIndex) { for dep in &self.0 { let pass_index = dep.src_pass(); @@ -245,6 +299,22 @@ impl ResourceDeps { } } + pub(super) fn update_resource_topo_group_range( + &self, + res: &mut ResourceSet, + group_index: u16, + ) { + for dep in &self.0 { + let r = &mut res.0[dep.resource_index() as usize]; + if r.first_topo_group > group_index { + r.first_topo_group = group_index; + } + if r.last_topo_group < group_index { + r.last_topo_group = group_index; + } + } + } + pub(super) fn access( &mut self, slot: &Slot, @@ -321,38 +391,168 @@ impl ResourceDep { } } -pub struct ResourceAssignments(Vec>); +enum ResourceAssignment { + Undefined, + Inactive, + Extern(R, R::Format, R::Size), + Transient(R::Format, u16), +} + +pub(super) struct ResourceAssignments { + assignments: Vec>, + transient_physical: Vec<(R::Format, u16)>, +} impl ResourceAssignments { #[inline] pub const fn new() -> Self { - Self(Vec::new()) + Self { + assignments: Vec::new(), + transient_physical: Vec::new(), + } } - #[inline] - pub fn get(&self, resource_index: ResourceIndex) -> Option<(R, &R::Meta)> { - if let Some(Some((r, m))) = self.0.get(resource_index as usize) { - Some((*r, m)) - } else { - None + pub fn clear(&mut self) { + self.assignments.clear(); + self.transient_physical.clear() + } + + fn get(&self, idx: ResourceIndex) -> Option<(R, R::Format, R::Size)> { + match self.assignments.get(idx as usize)? { + ResourceAssignment::Extern(r, f, s) => Some((*r, *f, *s)), + ResourceAssignment::Transient(_, p) => { + let (f, _) = self.transient_physical[*p as usize]; + todo!("implement get physical ({f:?})") + } + _ => None, } } - pub(super) fn assign_external_resources(&mut self, res: &ResourceSet) { - assert_eq!(res.0.len(), self.0.len()); - for (i, r) in res.0.iter().enumerate() { - if let Some(ext_res) = &r.extern_res { - self.0[i] = Some(ext_res.get_external_resource()); + fn assign_resources(&mut self, res: &ResourceSet, backend: &mut dyn GraphBackend) -> bool { + let mut changed = false; + if self.assignments.len() < res.0.len() { + changed = true; + self.assignments + .resize_with(res.0.len(), || ResourceAssignment::Undefined); + } + for (a, r) in self.assignments.iter_mut().zip(res.0.iter()) { + if !r.is_active() { + *a = ResourceAssignment::Inactive; + } else if let Some(ext_res) = &r.extern_res { + let (id, format, size) = ext_res.get(backend); + if !changed { + if let ResourceAssignment::Extern(_, old_format, old_size) = *a { + changed = old_format != format || old_size != size; + } else { + changed = true; + } + } + *a = ResourceAssignment::Extern(id, format, size); + } else if let ResourceAssignment::Transient(f, _) = a { + let format = r.format_or_default(); + changed |= f != &format; + *f = format; + } else { + changed = true; + let format = r.format_or_default(); + *a = ResourceAssignment::Transient(format, !0); + } + } + changed + } + + fn assign_physical(&mut self, res: &ResourceSet) { + self.transient_physical.clear(); + let mut res_sorted: Vec<_> = res.0.iter().enumerate().collect(); + res_sorted.sort_by_key(|&(_, r)| r.first_topo_group); + for (i, r) in res_sorted { + if let ResourceAssignment::Transient(format, p) = &mut self.assignments[i] { + *p = !0; + for (j, (phys_format, last_topo_group)) in + self.transient_physical.iter_mut().enumerate() + { + if *format == *phys_format && *last_topo_group < r.first_topo_group { + *last_topo_group = r.last_topo_group; + *p = j as u16; + } + } + if *p != !0 { + *p = self.transient_physical.len() as u16; + self.transient_physical.push((*format, r.last_topo_group)); + } + // TODO: calc. max size! } } } } +impl RenderGraphAssignments { + pub fn clear(&mut self) { + self.hash = 0; + self.was_updated = false; + self.texture_assignments.clear(); + self.buffer_assignments.clear(); + } + + pub fn update(&mut self, graph: &RenderGraph, backend: &mut dyn GraphBackend) -> bool { + self.was_updated = graph.was_updated; + if self.hash != graph.hash { + self.hash = graph.hash; + self.was_updated = true; + } + self.was_updated |= self + .texture_assignments + .assign_resources(&graph.textures, backend); + self.was_updated |= self + .buffer_assignments + .assign_resources(&graph.buffers, backend); + + if self.was_updated { + self.texture_assignments.assign_physical(&graph.textures); + self.buffer_assignments.assign_physical(&graph.buffers); + } + + self.was_updated + } + + pub(crate) fn get_texture( + &self, + idx: ResourceIndex, + ) -> Option<(Texture, TextureFormat, u8, TextureDimensions)> { + let (r, f, s) = self.texture_assignments.get(idx)?; + Some((r, f, 1, s)) + } + + pub(crate) fn get_buffer(&self, idx: ResourceIndex) -> Option<(Buffer, usize)> { + let (r, _, s) = self.buffer_assignments.get(idx)?; + Some((r, s)) + } +} + +pub trait GetExternalResource { + fn get(&self, backend: &mut dyn GraphBackend) -> (R, R::Format, R::Size); +} + +impl GetExternalResource for F +where + R: ResourceAccess, + F: for<'l, 'r> Fn(&'l mut dyn GraphBackend) -> (R, R::Format, R::Size), +{ + fn get(&self, backend: &mut dyn GraphBackend) -> (R, R::Format, R::Size) { + self(backend) + } +} + +pub trait GraphBackend { + fn get_surface(&mut self, window: WindowId) -> (Texture, TextureFormat, TextureDimensions); +} + impl GraphImport for Handle { type Resource = Texture; fn import(&self) -> Box> { - todo!("import image handle") + let handle = *self; + Box::new(move |_backend: &mut dyn GraphBackend| todo!("import image handle {:?}", handle)) } } @@ -360,7 +560,8 @@ impl GraphExport for Handle { type Resource = Texture; fn export(&self) -> Box> { - todo!("export image handle") + let handle = *self; + Box::new(move |_backend: &mut dyn GraphBackend| todo!("export image handle {:?}", handle)) } } @@ -368,7 +569,8 @@ impl GraphExport for WindowId { type Resource = Texture; fn export(&self) -> Box> { - todo!("export swapchain image") + let window = *self; + Box::new(move |backend: &mut dyn GraphBackend| backend.get_surface(window)) } } diff --git a/crates/render/src/pipeline/graphics_pass.rs b/crates/render/src/pipeline/graphics_pass.rs index 7a93c6b..2d2e98a 100644 --- a/crates/render/src/pipeline/graphics_pass.rs +++ b/crates/render/src/pipeline/graphics_pass.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::{ graph::{ - pass::PipelineBindPoint, resources::ResourceAssignments, PassDescription, RenderGraph, + pass::PipelineBindPoint, PassDescription, RenderGraph, RenderGraphAssignments, ResourceIndex, }, texture::{Texture, TextureFormat, TextureUsage}, @@ -100,8 +100,8 @@ pub struct GraphicsPassDescriptorWithTextures { impl GraphicsPassDescriptorWithTextures { pub fn from_graph( graph: &RenderGraph, + assignments: &RenderGraphAssignments, pass: &PassDescription, - assignments: &ResourceAssignments, ) -> Option { if pass.bind_point() != PipelineBindPoint::Graphics { return None; @@ -120,8 +120,8 @@ impl GraphicsPassDescriptorWithTextures { for i in attachment_indices.iter().copied() { let a = &pass.textures()[i as usize]; let resource_index = a.resource_index(); - let (tex, &(format, dim, samples)) = assignments - .get(resource_index) + let (tex, format, samples, dim) = assignments + .get_texture(resource_index) .expect("unassigned resource"); let dim = dim.subimage_extents(); if size == USize2::ZERO { From 480cb3105594575351f7b3c24ee2f94c3feec92d Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Thu, 4 May 2023 19:37:22 +0200 Subject: [PATCH 18/36] updates & fmt --- Cargo.toml | 4 +- crates/render-ash/src/graph.rs | 11 +- crates/render-ash/src/swapchain.rs | 8 +- crates/render-wgpu/Cargo.toml | 6 +- crates/render-wgpu/src/convert.rs | 4 +- crates/render/Cargo.toml | 2 +- crates/render/macros/src/binding_layout.rs | 123 ++++++++++++++------- crates/render/macros/src/lib.rs | 2 +- crates/render/src/shader/mod.rs | 10 +- examples/android/src/lib.rs | 5 +- 10 files changed, 121 insertions(+), 54 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bd13fe1..2f13324 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ typemap = "0.3" anyhow = "1.0" thiserror = "1.0" tracing = "0.1" -bitflags = "2.0" +bitflags = "2.2" fnv = "1.0" radsort = "0.1" bytemuck = "1.13" @@ -38,7 +38,7 @@ blocking = "1.3" threadpool = "1.8" backtrace = "0.3" atomic_refcell = "0.1" -palette = { version = "0.6", default-features = false } +palette = { version = "0.7", default-features = false } image = { version = "0.24", default-features = false } encase = { version = "0.5", features = ["glam"], default-features = false } encase_derive_impl = { version = "0.5" } diff --git a/crates/render-ash/src/graph.rs b/crates/render-ash/src/graph.rs index b4df8cf..a9e17f3 100644 --- a/crates/render-ash/src/graph.rs +++ b/crates/render-ash/src/graph.rs @@ -207,7 +207,14 @@ impl GraphBackend for AshGraphBackend<'_> { TextureFormat, TextureDimensions, ) { - let swapchain = self.surfaces.get(window_id).expect("swapchain not initialized"); - (swapchain.texture_id(), swapchain.texture_format(), TextureDimensions::D2(swapchain.size())) + let swapchain = self + .surfaces + .get(window_id) + .expect("swapchain not initialized"); + ( + swapchain.texture_id(), + swapchain.texture_format(), + TextureDimensions::D2(swapchain.size()), + ) } } diff --git a/crates/render-ash/src/swapchain.rs b/crates/render-ash/src/swapchain.rs index cd02b77..6a336c5 100644 --- a/crates/render-ash/src/swapchain.rs +++ b/crates/render-ash/src/swapchain.rs @@ -1,17 +1,21 @@ use std::sync::Arc; use ash::{extensions::khr, vk}; -use pulz_render::{math::uvec2, texture::{Texture, TextureFormat}}; +use pulz_render::{ + math::uvec2, + texture::{Texture, TextureFormat}, +}; use pulz_window::{RawWindow, Size2, Window, WindowId}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; use slotmap::Key; use tracing::debug; use crate::{ + convert::VkInto, device::AshDevice, drop_guard::{Destroy, Guard}, instance::AshInstance, - AshRendererFull, Error, Result, convert::VkInto, + AshRendererFull, Error, Result, }; pub struct Surface { diff --git a/crates/render-wgpu/Cargo.toml b/crates/render-wgpu/Cargo.toml index 41ec3b5..fd28d2b 100644 --- a/crates/render-wgpu/Cargo.toml +++ b/crates/render-wgpu/Cargo.toml @@ -15,7 +15,7 @@ pulz-render = { path = "../render" } thiserror = { workspace = true } tracing = { workspace = true } slotmap = { workspace = true } -wgpu = "0.15" +wgpu = "0.16" raw-window-handle = { workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] @@ -23,7 +23,7 @@ pollster = "0.3" [dev-dependencies] anyhow = { workspace = true } -naga = "0.11" +naga = "0.12" pulz-window-winit = { path = "../window-winit" } pulz-render-pipeline-core = { path = "../render-pipeline-core" } @@ -32,7 +32,7 @@ tracing-subscriber = { workspace = true } async-std = { workspace = true } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wgpu = { version = "0.15" , features = ["webgl"] } +wgpu = { version = "0.16" , features = ["webgl"] } tracing-wasm = { workspace = true } tracing-log = { workspace = true } console_error_panic_hook = { workspace = true } diff --git a/crates/render-wgpu/src/convert.rs b/crates/render-wgpu/src/convert.rs index 94c4d0b..acf8d79 100644 --- a/crates/render-wgpu/src/convert.rs +++ b/crates/render-wgpu/src/convert.rs @@ -99,8 +99,8 @@ pub fn convert_texture_view_descriptor(val: &TextureDescriptor) -> wgpu::Texture pub fn convert_image_data_layout(image: &ImageDataLayout) -> wgpu::ImageDataLayout { wgpu::ImageDataLayout { offset: 0, - bytes_per_row: std::num::NonZeroU32::new(image.bytes_per_row), - rows_per_image: std::num::NonZeroU32::new(image.rows_per_image), + bytes_per_row: Some(image.bytes_per_row), + rows_per_image: Some(image.rows_per_image), } } diff --git a/crates/render/Cargo.toml b/crates/render/Cargo.toml index d4e6ed1..112a044 100644 --- a/crates/render/Cargo.toml +++ b/crates/render/Cargo.toml @@ -19,7 +19,7 @@ pulz-render-macros = { path = "macros" } pulz-functional-utils = { version = "0.1.0-alpha", path = "../functional-utils" } atomic_refcell = { workspace = true } -serde = { worspace = true } +serde = { workspace = true } ambassador = { workspace = true } typemap = { workspace = true } dynsequence = { workspace = true } diff --git a/crates/render/macros/src/binding_layout.rs b/crates/render/macros/src/binding_layout.rs index 7780b1c..c119de5 100644 --- a/crates/render/macros/src/binding_layout.rs +++ b/crates/render/macros/src/binding_layout.rs @@ -1,7 +1,7 @@ -use darling::{FromMeta, ToTokens}; +use darling::{export::NestedMeta, Error, Result, ToTokens, FromMeta}; use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, TokenStreamExt}; -use syn::{Attribute, DeriveInput, Error, Lit, Meta, NestedMeta, Result}; +use syn::{Attribute, Data, DeriveInput, Expr, Fields, Lit, LitInt, Meta}; use crate::utils::resolve_render_crate; @@ -45,6 +45,28 @@ pub fn derive_as_binding_layout(input: DeriveInput) -> Result { let mut layouts = Vec::new(); + let input_struct = match input.data { + Data::Struct(input_struct) => input_struct, + Data::Enum(_) => return Err(Error::unsupported_shape_with_expected("enum", &"struct")), + Data::Union(_) => return Err(Error::unsupported_shape_with_expected("union", &"struct")), + }; + match input_struct.fields { + Fields::Unit => {} + Fields::Named(_fields) => { + // TODO: named fields + } + Fields::Unnamed(fields) => { + if fields.unnamed.len() == 1 { + // TODO: newtype / wrapper + } else { + return Err(Error::unsupported_shape_with_expected( + "tuple", + &"named struct", + )); + } + } + } + if let Some(BindingLayoutArgs { binding_type, binding_index, @@ -52,10 +74,7 @@ pub fn derive_as_binding_layout(input: DeriveInput) -> Result { }) = BindingLayoutArgs::from_attributes(&input.attrs)? { if binding_type != BindingType::Uniform { - return Err(Error::new( - ident.span(), - "only uniform is allowed on struct", - )); + return Err(Error::custom("only uniform is allowed on struct").with_span(&ident)); } layouts.push(quote! { @@ -77,7 +96,7 @@ pub fn derive_as_binding_layout(input: DeriveInput) -> Result { #[derive(Clone, Eq, PartialEq, Debug)] pub struct BindingLayoutArgs { binding_type: BindingType, - binding_index: Lit, + binding_index: LitInt, options: BindingLayoutOptions, } @@ -85,52 +104,80 @@ pub struct BindingLayoutArgs { pub struct BindingLayoutOptions {} impl BindingLayoutArgs { - fn from_attributes(attribs: &[Attribute]) -> Result> { + fn from_attributes(attrs: &[Attribute]) -> Result> { + let mut errors = Error::accumulator(); let mut binding_type = None; let mut binding_index = None; - let mut options = BindingLayoutOptions::default(); - for attr in attribs { - if let Some(ident) = attr.path.get_ident() { + let mut meta_list = Vec::new(); + for attr in attrs { + if let Some(ident) = attr.path().get_ident() { if let Some(t) = BindingType::from_ident(ident) { - if binding_type.is_some() { - return Err(Error::new( - ident.span(), - "only a single attribute is allowed", - )); + if binding_type.is_none() { + binding_type = Some(t); + } else if binding_type != Some(t) { + errors.push( + Error::custom("only a single attribute is allowed").with_span(ident), + ); } - binding_type = Some(t); - match attr.parse_meta()? { + match &attr.meta { Meta::List(meta) => { - let path = meta.path; - let mut it = meta.nested.into_iter(); - if let Some(NestedMeta::Lit(lit)) = it.next() { - binding_index = Some(lit); - let more_args: Vec<_> = it.collect(); - options = BindingLayoutOptions::from_list(&more_args)?; + let mut items = darling::export::NestedMeta::parse_meta_list( + meta.tokens.to_token_stream(), + )?; + if let Some(NestedMeta::Lit(Lit::Int(index))) = items.first() { + if binding_index.is_none() { + binding_index = Some(index.clone()); + } else { + errors.push( + Error::duplicate_field("binding_index").with_span(index), + ); + } + meta_list.extend_from_slice(&items[1..]); } else { - return Err(Error::new_spanned(path, "a binding-index is missing")); + meta_list.append(&mut items); } } Meta::NameValue(meta) => { - binding_index = Some(meta.lit); - } - Meta::Path(path) => { - return Err(Error::new_spanned(path, "a binding-index is missing")); + if binding_index.is_none() { + if let Some(index) = errors.handle(parse_index(&meta.value)) { + binding_index = Some(index); + } + } else { + errors.push( + Error::duplicate_field("binding_index").with_span(&meta.value), + ); + } } + Meta::Path(path) => {} } - // TODO: parse index } } } - if let Some(binding_type) = binding_type { - Ok(Some(BindingLayoutArgs { - binding_type, - binding_index: binding_index.unwrap(), - options, - })) + errors.finish()?; + let Some(binding_type) = binding_type else { + return Ok(None); + }; + let Some(binding_index) = binding_index else { + return Err(Error::missing_field("binding_index")); + }; + let options = BindingLayoutOptions::from_list(&meta_list)?; + Ok(Some(Self { + binding_type, + binding_index, + options, + })) + } +} + +fn parse_index(expr: &Expr) -> Result { + if let Expr::Lit(lit) = expr { + if let Lit::Int(index) = &lit.lit { + Ok(index.clone()) } else { - Ok(None) + Err(Error::unexpected_lit_type(&lit.lit)) } + } else { + Err(Error::unexpected_expr_type(expr)) } } @@ -239,7 +286,7 @@ mod tests { args, Some(BindingLayoutArgs { binding_type: BindingType::Uniform, - binding_index: Lit::Int(LitInt::new("2", Span::call_site())), + binding_index: LitInt::new("2", Span::call_site()), options: BindingLayoutOptions {}, }) ); diff --git a/crates/render/macros/src/lib.rs b/crates/render/macros/src/lib.rs index 3e01e9b..c06496e 100644 --- a/crates/render/macros/src/lib.rs +++ b/crates/render/macros/src/lib.rs @@ -15,7 +15,7 @@ mod compile_shader; pub fn derive_as_binding_layout(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); binding_layout::derive_as_binding_layout(input) - .unwrap_or_else(|err| err.to_compile_error()) + .unwrap_or_else(|err| err.write_errors()) .into() } diff --git a/crates/render/src/shader/mod.rs b/crates/render/src/shader/mod.rs index d3d1af5..8016485 100644 --- a/crates/render/src/shader/mod.rs +++ b/crates/render/src/shader/mod.rs @@ -2,10 +2,16 @@ use std::borrow::Cow; mod preprocessor; -pub use ::encase::*; -pub use ::pulz_render_macros::ShaderType; use serde::{Deserialize, Serialize}; +mod encase { + pub use ::encase::{private, ShaderSize, ShaderType}; +} +pub use ::pulz_render_macros::ShaderType; + +#[doc(hidden)] +pub use self::encase::*; + crate::backend::define_gpu_resource!(ShaderModule, ShaderModuleDescriptor<'l>); #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] diff --git a/examples/android/src/lib.rs b/examples/android/src/lib.rs index e82999b..5d9e35b 100644 --- a/examples/android/src/lib.rs +++ b/examples/android/src/lib.rs @@ -9,10 +9,12 @@ use pulz_window::{WindowDescriptor, WindowId}; use pulz_window_winit::{winit, WinitWindowModule, WinitWindowSystem}; use winit::{ event_loop::{EventLoop, EventLoopBuilder, EventLoopWindowTarget}, - platform::android::activity::AndroidApp, window::Window, }; +#[cfg(target_os = "android")] +use platform::android::activity::AndroidApp; + fn init(event_loop: &EventLoopWindowTarget<()>) -> (Resources, Rc, WinitWindowSystem) { info!("Initializing..."); let mut resources = Resources::new(); @@ -38,6 +40,7 @@ fn setup_demo_scene(resources: &mut Resources, window: WindowId) { .insert(RenderTarget::Window(window)); } +#[cfg(target_os = "android")] #[no_mangle] pub fn android_main(app: AndroidApp) { use winit::platform::android::EventLoopBuilderExtAndroid; From 4ab5191291cdc1dcbab8a437059c5d90dab00f2e Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Thu, 4 May 2023 19:38:04 +0200 Subject: [PATCH 19/36] exclude benches from tests --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 0445f2c..d20463b 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -38,7 +38,7 @@ jobs: cargo build --workspace --all-targets - name: Run tests run: | - cargo test --workspace --all-targets + cargo test --workspace --all-targets --exclude benches check: name: Rustfmt & Clippy From 944dfcbad148ba4b664c0ea6b901f5920073740d Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Fri, 5 May 2023 16:26:05 +0200 Subject: [PATCH 20/36] update window-winit docs to use doc_auto_cfg feature --- crates/window-winit/Cargo.toml | 5 ++ .../examples/window-winit-demo.rs | 2 +- crates/window-winit/src/lib.rs | 85 +++++++++++-------- 3 files changed, 54 insertions(+), 38 deletions(-) diff --git a/crates/window-winit/Cargo.toml b/crates/window-winit/Cargo.toml index e0a5a32..0320c30 100644 --- a/crates/window-winit/Cargo.toml +++ b/crates/window-winit/Cargo.toml @@ -32,3 +32,8 @@ tracing-log = { workspace = true } console_error_panic_hook = { workspace = true } wasm-bindgen = { workspace = true } web-sys = { workspace = true, features = ["HtmlCanvasElement"] } + +[package.metadata.docs.rs] +all-features = true +targets = ["x86_64-unknown-linux-gnu"] +rustdoc-args = ["--cfg", "docsrs"] diff --git a/crates/window-winit/examples/window-winit-demo.rs b/crates/window-winit/examples/window-winit-demo.rs index 6e99bd0..749777e 100644 --- a/crates/window-winit/examples/window-winit-demo.rs +++ b/crates/window-winit/examples/window-winit-demo.rs @@ -37,7 +37,7 @@ fn main() { #[cfg(target_arch = "wasm32")] fn main() { use wasm_bindgen::prelude::*; - use winit::platform::web::WindowExtWebSys; + use winit::platform::web::*; console_error_panic_hook::set_once(); tracing_log::LogTracer::init().expect("unable to create log-tracer"); diff --git a/crates/window-winit/src/lib.rs b/crates/window-winit/src/lib.rs index ebf9873..1dedd56 100644 --- a/crates/window-winit/src/lib.rs +++ b/crates/window-winit/src/lib.rs @@ -21,6 +21,7 @@ // clippy::missing_panics_doc, clippy::wildcard_imports )] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc(html_logo_url = "https://raw.githubusercontent.com/HellButcher/pulz/master/docs/logo.png")] #![doc(html_no_source)] #![doc = include_str!("../README.md")] @@ -69,15 +70,6 @@ impl WinitWindows { self.windows.get(id).map(Deref::deref) } - fn update_window_descriptor( - window_descriptor: &mut WindowDescriptor, - winit_window: &winit::window::Window, - ) { - window_descriptor.scale_factor = winit_window.scale_factor(); - let phys_size: [u32; 2] = winit_window.inner_size().into(); - window_descriptor.size = phys_size.into(); - } - fn close(&mut self, window_id: WindowId) -> bool { let Some(window) = self.windows.remove(window_id) else { return false; @@ -96,6 +88,22 @@ impl std::ops::Index for WinitWindows { } } +fn update_window_descriptor( + window_descriptor: &mut WindowDescriptor, + winit_window: &winit::window::Window, +) { + window_descriptor.scale_factor = winit_window.scale_factor(); + let phys_size: [u32; 2] = winit_window.inner_size().into(); + window_descriptor.size = phys_size.into(); +} + +fn descriptor_from_window(winit_window: &WinitWindow) -> WindowDescriptor { + let mut descriptor = WindowDescriptor::new(); + descriptor.title = winit_window.title().into(); + update_window_descriptor(&mut descriptor, winit_window); + descriptor +} + fn builder_for_descriptor(descriptor: &WindowDescriptor) -> winit::window::WindowBuilder { let mut builder = winit::window::WindowBuilder::new().with_title(descriptor.title.to_owned()); @@ -137,14 +145,13 @@ impl WinitWindowModule { ) -> Result { let builder = builder_for_descriptor(&descriptor); let window = Rc::new(builder.build(event_loop)?); - WinitWindows::update_window_descriptor(&mut descriptor, &window); + update_window_descriptor(&mut descriptor, &window); Ok(Self { descriptor, window }) } pub fn from_window(window: impl Into>) -> Self { let window: Rc = window.into(); - let mut descriptor = WindowDescriptor::default(); - WinitWindows::update_window_descriptor(&mut descriptor, &window); + let descriptor = descriptor_from_window(&window); Self { descriptor, window } } } @@ -168,14 +175,14 @@ impl WinitWindowSystemMut<'_> { ) -> Result<(WindowId, Rc), OsError> { let builder = builder_for_descriptor(&descriptor); let window = Rc::new(builder.build(event_loop)?); - WinitWindows::update_window_descriptor(&mut descriptor, &window); + update_window_descriptor(&mut descriptor, &window); let window_id = self.add_winit_window_with_descriptor(descriptor, window.clone()); Ok((window_id, window)) } - pub fn add_winit_window(&mut self, window: Rc) -> WindowId { - let mut descriptor = WindowDescriptor::default(); - WinitWindows::update_window_descriptor(&mut descriptor, &window); + pub fn add_winit_window(&mut self, window: impl Into>) -> WindowId { + let window: Rc = window.into(); + let descriptor = descriptor_from_window(&window); self.add_winit_window_with_descriptor(descriptor, window) } @@ -239,7 +246,7 @@ impl WinitWindowSystemMut<'_> { } else { let builder = builder_for_descriptor(window_descr); let winit_window = Rc::new(builder.build(event_loop)?); - WinitWindows::update_window_descriptor(window_descr, &winit_window); + update_window_descriptor(window_descr, &winit_window); self.winit_windows.insert(window_id, winit_window.clone()); self.listeners .call_on_created(res, window_id, window_descr, &winit_window); @@ -386,7 +393,8 @@ impl WinitWindowSystem { target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", - target_os = "openbsd" + target_os = "openbsd", + doc ))] pub fn run_return( &mut self, @@ -411,26 +419,29 @@ impl WinitWindowSystem { result } - #[cfg(target_arch = "wasm32")] + #[cfg(any(target_arch = "wasm32", doc))] pub fn spawn(mut self, mut resources: Resources, event_loop: EventLoop) { - use winit::platform::web::EventLoopExtWebSys; - - let schedule_id = resources.init_unsend::(); - let mut schedule = resources.remove_id(schedule_id).unwrap(); - - let event_loop_span = tracing::trace_span!("EventLoop"); - - event_loop.spawn(move |event, event_loop, control_flow| { - let span = event_loop_span.enter(); - self.handle_event( - &mut resources, - &mut schedule, - event, - event_loop, - control_flow, - ); - drop(span); - }) + #[cfg(target_arch = "wasm32")] + { + use winit::platform::web::EventLoopExtWebSys; + + let schedule_id = resources.init_unsend::(); + let mut schedule = resources.remove_id(schedule_id).unwrap(); + + let event_loop_span = tracing::trace_span!("EventLoop"); + + event_loop.spawn(move |event, event_loop, control_flow| { + let span = event_loop_span.enter(); + self.handle_event( + &mut resources, + &mut schedule, + event, + event_loop, + control_flow, + ); + drop(span); + }) + } } } From d2147c3dec2f6939db0518152a6eb9df85e0c138 Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sat, 6 May 2023 17:34:10 +0200 Subject: [PATCH 21/36] enable avx on x86(_64) + fmt --- .cargo/config.toml | 7 ++++--- crates/render-wgpu/src/lib.rs | 11 +++++++---- crates/render/macros/src/binding_layout.rs | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 586c1ed..2846269 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,8 +1,9 @@ +[target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'] +rustflags = ["-C", "target-feature=+avx"] + [target.wasm32-unknown-unknown] -rustflags = [ - "--cfg=web_sys_unstable_apis" -] +rustflags = ["--cfg=web_sys_unstable_apis"] [alias] run-wasm = "run --release --package run-wasm --" diff --git a/crates/render-wgpu/src/lib.rs b/crates/render-wgpu/src/lib.rs index 7314c36..2ca3db4 100644 --- a/crates/render-wgpu/src/lib.rs +++ b/crates/render-wgpu/src/lib.rs @@ -25,8 +25,6 @@ #![doc(html_no_source)] #![doc = include_str!("../README.md")] -use std::rc::Rc; - use convert::ConversionError; use graph::WgpuRenderGraph; use pulz_ecs::prelude::*; @@ -205,6 +203,7 @@ impl WgpuRendererFull { #[allow(clippy::large_enum_variant)] enum WgpuRendererInner { #[cfg(not(target_arch = "wasm32"))] + // for delaying full initialization until first surface is created. Early { instance: wgpu::Instance, }, @@ -228,11 +227,16 @@ impl WgpuRenderer { } #[cfg(target_arch = "wasm32")] { + // full-initialization on wasm32: + // Adapter-initialization is async, and we can not block on wasm. + // Delay of initialisation is not needed on wasm, because we don't + // depend on the surface being created for getting a compatible adapter. let adapter = Self::default_adapter(&instance, None).await?; let renderer = Self::for_adapter(instance, adapter).await?; return Ok(Self(WgpuRendererInner::Full(renderer))); } #[cfg(not(target_arch = "wasm32"))] + // Delay full initialisation until first surface is created. Ok(Self(WgpuRendererInner::Early { instance })) } @@ -262,6 +266,7 @@ impl WgpuRenderer { let surface = Surface::create(&renderer.instance, window_descriptor, window_raw)?; renderer.surfaces.insert(window_id, surface); } else { + // Delayed initialization #[cfg(not(target_arch = "wasm32"))] { let WgpuRendererInner::Early { instance } = std::mem::replace(&mut self.0, WgpuRendererInner::Tmp) else { @@ -324,8 +329,6 @@ impl WindowSystemListener for WgpuRendererInitWindowSystemListener { .init_window(window_id, window_desc, window_raw) .unwrap(); } - - #[cfg(not(target_arch = "wasm32"))] fn on_resumed(&self, res: &Resources) { let Some(mut renderer) = res.borrow_res_mut_id(self.0) else { return }; renderer.init().unwrap(); diff --git a/crates/render/macros/src/binding_layout.rs b/crates/render/macros/src/binding_layout.rs index c119de5..3e0684b 100644 --- a/crates/render/macros/src/binding_layout.rs +++ b/crates/render/macros/src/binding_layout.rs @@ -1,4 +1,4 @@ -use darling::{export::NestedMeta, Error, Result, ToTokens, FromMeta}; +use darling::{export::NestedMeta, Error, FromMeta, Result, ToTokens}; use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, TokenStreamExt}; use syn::{Attribute, Data, DeriveInput, Expr, Fields, Lit, LitInt, Meta}; From e726b0c79aff5add26cf63e2842cdadf9e43f2cb Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sat, 6 May 2023 19:55:56 +0200 Subject: [PATCH 22/36] removed typemap dependenc + fmt + clippy fixes --- Cargo.toml | 1 - crates/render-ash/src/lib.rs | 4 +- crates/render-ash/src/resources/mod.rs | 64 ++++---------- .../render-ash/src/resources/resource_impl.rs | 16 ++-- crates/render-ash/src/resources/traits.rs | 4 +- crates/render-ash/src/swapchain.rs | 1 - crates/render-wgpu/src/convert.rs | 2 +- crates/render-wgpu/src/graph.rs | 6 +- crates/render-wgpu/src/lib.rs | 6 +- crates/render/Cargo.toml | 1 - crates/render/src/draw.rs | 57 ++++++++---- crates/render/src/graph/access.rs | 2 +- crates/render/src/graph/resources.rs | 6 +- crates/render/src/utils.rs | 86 ++++++++++++++++--- examples/android/src/lib.rs | 5 +- 15 files changed, 155 insertions(+), 106 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2f13324..fb47ad1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,6 @@ glam = "0.23" serde = "1.0" raw-window-handle = "0.5" slotmap = "1.0" -typemap = "0.3" anyhow = "1.0" thiserror = "1.0" tracing = "0.1" diff --git a/crates/render-ash/src/lib.rs b/crates/render-ash/src/lib.rs index 0cc762e..0a91be3 100644 --- a/crates/render-ash/src/lib.rs +++ b/crates/render-ash/src/lib.rs @@ -73,7 +73,7 @@ impl std::error::Error for VkError {} impl std::fmt::Debug for VkError { #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - ::fmt(&self, f) + ::fmt(self, f) } } impl std::fmt::Display for VkError { @@ -390,7 +390,7 @@ impl AshRendererFull { .collect::>(); let frame = &mut self.frames[self.current_frame]; - let mut encoder = frame.command_pool.encoder()?; + let encoder = frame.command_pool.encoder()?; encoder.begin_debug_label("ClearImages"); unsafe { diff --git a/crates/render-ash/src/resources/mod.rs b/crates/render-ash/src/resources/mod.rs index b99976e..460a0e7 100644 --- a/crates/render-ash/src/resources/mod.rs +++ b/crates/render-ash/src/resources/mod.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, hash::Hasher, ops::Index, sync::Arc}; +use std::{ops::Index, sync::Arc}; use ash::vk; use pulz_render::{ @@ -9,6 +9,7 @@ use pulz_render::{ }, shader::ShaderModule, texture::Texture, + utils::hash::U64HashMap, }; use slotmap::SlotMap; @@ -23,50 +24,17 @@ use self::{ traits::{AshGpuResourceCreate, AshGpuResourceRemove}, }; -#[derive(Default)] -struct PreHashedHasherHasher(u64); -type PreHashedHasherBuildHasher = std::hash::BuildHasherDefault; - -impl Hasher for PreHashedHasherHasher { - #[inline] - fn finish(&self) -> u64 { - self.0 - } - - #[inline] - fn write(&mut self, bytes: &[u8]) { - let mut hash = self.0; - for byte in bytes.iter() { - hash <<= 8; - hash |= *byte as u64; - } - self.0 = hash; - } - - #[inline] - fn write_u64(&mut self, i: u64) { - self.0 = i; - } - - #[inline] - fn write_i64(&mut self, i: i64) { - self.0 = i as u64; - } -} - -type PreHashedU64Map = HashMap; - pub struct AshResources { device: Arc, record: Option>, pipeline_cache: vk::PipelineCache, - graphics_passes_cache: PreHashedU64Map, - shader_modules_cache: PreHashedU64Map, - bind_group_layouts_cache: PreHashedU64Map, - pipeline_layouts_cache: PreHashedU64Map, - graphics_pipelines_cache: PreHashedU64Map, - compute_pipelines_cache: PreHashedU64Map, - ray_tracing_pipelines_cache: PreHashedU64Map, + graphics_passes_cache: U64HashMap, + shader_modules_cache: U64HashMap, + bind_group_layouts_cache: U64HashMap, + pipeline_layouts_cache: U64HashMap, + graphics_pipelines_cache: U64HashMap, + compute_pipelines_cache: U64HashMap, + ray_tracing_pipelines_cache: U64HashMap, pub graphics_passes: SlotMap, pub shader_modules: SlotMap, pub bind_group_layouts: SlotMap, @@ -83,13 +51,13 @@ impl AshResources { Self { device: device.clone(), record: None, - graphics_passes_cache: HashMap::default(), - shader_modules_cache: HashMap::default(), - bind_group_layouts_cache: HashMap::default(), - pipeline_layouts_cache: HashMap::default(), - graphics_pipelines_cache: HashMap::default(), - compute_pipelines_cache: HashMap::default(), - ray_tracing_pipelines_cache: HashMap::default(), + graphics_passes_cache: U64HashMap::default(), + shader_modules_cache: U64HashMap::default(), + bind_group_layouts_cache: U64HashMap::default(), + pipeline_layouts_cache: U64HashMap::default(), + graphics_pipelines_cache: U64HashMap::default(), + compute_pipelines_cache: U64HashMap::default(), + ray_tracing_pipelines_cache: U64HashMap::default(), pipeline_cache: vk::PipelineCache::null(), graphics_passes: SlotMap::with_key(), shader_modules: SlotMap::with_key(), diff --git a/crates/render-ash/src/resources/resource_impl.rs b/crates/render-ash/src/resources/resource_impl.rs index db46dcc..0a3c8c3 100644 --- a/crates/render-ash/src/resources/resource_impl.rs +++ b/crates/render-ash/src/resources/resource_impl.rs @@ -12,7 +12,7 @@ use slotmap::SlotMap; use super::{ traits::{AshGpuResource, AshGpuResourceCached, AshGpuResourceCreate, AshGpuResourceRemove}, - AshResources, PreHashedU64Map, + AshResources, U64HashMap, }; use crate::{ convert::{CreateInfoConverter2, CreateInfoConverter6, VkInto}, @@ -101,7 +101,7 @@ impl AshGpuResource for GraphicsPass { } impl AshGpuResourceCached for GraphicsPass { #[inline] - fn get_hashs_mut(res: &mut AshResources) -> &mut PreHashedU64Map { + fn get_hashs_mut(res: &mut AshResources) -> &mut U64HashMap { &mut res.graphics_passes_cache } } @@ -131,7 +131,7 @@ impl AshGpuResource for ShaderModule { } impl AshGpuResourceCached for ShaderModule { #[inline] - fn get_hashs_mut(res: &mut AshResources) -> &mut PreHashedU64Map { + fn get_hashs_mut(res: &mut AshResources) -> &mut U64HashMap { &mut res.shader_modules_cache } } @@ -161,7 +161,7 @@ impl AshGpuResource for BindGroupLayout { } impl AshGpuResourceCached for BindGroupLayout { #[inline] - fn get_hashs_mut(res: &mut AshResources) -> &mut PreHashedU64Map { + fn get_hashs_mut(res: &mut AshResources) -> &mut U64HashMap { &mut res.bind_group_layouts_cache } } @@ -191,7 +191,7 @@ impl AshGpuResource for PipelineLayout { } impl AshGpuResourceCached for PipelineLayout { #[inline] - fn get_hashs_mut(res: &mut AshResources) -> &mut PreHashedU64Map { + fn get_hashs_mut(res: &mut AshResources) -> &mut U64HashMap { &mut res.pipeline_layouts_cache } } @@ -233,7 +233,7 @@ impl AshGpuResource for GraphicsPipeline { } impl AshGpuResourceCached for GraphicsPipeline { #[inline] - fn get_hashs_mut(res: &mut AshResources) -> &mut PreHashedU64Map { + fn get_hashs_mut(res: &mut AshResources) -> &mut U64HashMap { &mut res.graphics_pipelines_cache } } @@ -275,7 +275,7 @@ impl AshGpuResource for ComputePipeline { } impl AshGpuResourceCached for ComputePipeline { #[inline] - fn get_hashs_mut(res: &mut AshResources) -> &mut PreHashedU64Map { + fn get_hashs_mut(res: &mut AshResources) -> &mut U64HashMap { &mut res.compute_pipelines_cache } } @@ -315,7 +315,7 @@ impl AshGpuResource for RayTracingPipeline { } impl AshGpuResourceCached for RayTracingPipeline { #[inline] - fn get_hashs_mut(res: &mut AshResources) -> &mut PreHashedU64Map { + fn get_hashs_mut(res: &mut AshResources) -> &mut U64HashMap { &mut res.ray_tracing_pipelines_cache } } diff --git a/crates/render-ash/src/resources/traits.rs b/crates/render-ash/src/resources/traits.rs index dc5de68..bb57dbd 100644 --- a/crates/render-ash/src/resources/traits.rs +++ b/crates/render-ash/src/resources/traits.rs @@ -3,7 +3,7 @@ use std::hash::{Hash, Hasher}; use pulz_render::backend::GpuResource; use slotmap::SlotMap; -use super::{replay::AsResourceRecord, AshResources, PreHashedU64Map}; +use super::{replay::AsResourceRecord, AshResources, U64HashMap}; use crate::{device::AshDevice, Result}; pub trait AshGpuResource: GpuResource + 'static { @@ -33,7 +33,7 @@ where hash_one(descr) } - fn get_hashs_mut(res: &mut AshResources) -> &mut PreHashedU64Map; + fn get_hashs_mut(res: &mut AshResources) -> &mut U64HashMap; } pub trait AshGpuResourceCreate: AshGpuResource { diff --git a/crates/render-ash/src/swapchain.rs b/crates/render-ash/src/swapchain.rs index 6a336c5..d42bc53 100644 --- a/crates/render-ash/src/swapchain.rs +++ b/crates/render-ash/src/swapchain.rs @@ -8,7 +8,6 @@ use pulz_render::{ use pulz_window::{RawWindow, Size2, Window, WindowId}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; use slotmap::Key; -use tracing::debug; use crate::{ convert::VkInto, diff --git a/crates/render-wgpu/src/convert.rs b/crates/render-wgpu/src/convert.rs index acf8d79..d4d6306 100644 --- a/crates/render-wgpu/src/convert.rs +++ b/crates/render-wgpu/src/convert.rs @@ -446,7 +446,7 @@ fn convert_fragment_state<'l, 'r: 'l>( targets_tmp.reserve_exact(state.targets.len()); for target in state.targets.as_ref() { - targets_tmp.push(convert_color_target_state(&target)?); + targets_tmp.push(convert_color_target_state(target)?); } Ok(wgpu::FragmentState { diff --git a/crates/render-wgpu/src/graph.rs b/crates/render-wgpu/src/graph.rs index 63272c7..9b9dc13 100644 --- a/crates/render-wgpu/src/graph.rs +++ b/crates/render-wgpu/src/graph.rs @@ -9,16 +9,16 @@ impl WgpuRenderGraph { Self } - pub fn update(&mut self, src_graph: &RenderGraph) { + pub fn update(&mut self, _src_graph: &RenderGraph) { todo!() } pub fn execute( &self, - src_graph: &RenderGraph, + _src_graph: &RenderGraph, encoder: wgpu::CommandEncoder, ) -> [wgpu::CommandBuffer; 1] { - let mut encoder = WgpuCommandEncoder(encoder); + let _encoder = WgpuCommandEncoder(encoder); todo!(); // TODO [encoder.finish()] diff --git a/crates/render-wgpu/src/lib.rs b/crates/render-wgpu/src/lib.rs index 2ca3db4..7a05842 100644 --- a/crates/render-wgpu/src/lib.rs +++ b/crates/render-wgpu/src/lib.rs @@ -192,10 +192,10 @@ impl WgpuRendererFull { } fn run(&mut self, windows: &Windows, src_graph: &RenderGraph, _draw_phases: &DrawPhases) { - self.reconfigure_surfaces(&windows); - self.graph.update(&src_graph); + self.reconfigure_surfaces(windows); + self.graph.update(src_graph); self.aquire_swapchain_images(); - self.run_graph(&src_graph); + self.run_graph(src_graph); self.present_swapchain_images(); } } diff --git a/crates/render/Cargo.toml b/crates/render/Cargo.toml index 112a044..949907f 100644 --- a/crates/render/Cargo.toml +++ b/crates/render/Cargo.toml @@ -21,7 +21,6 @@ pulz-functional-utils = { version = "0.1.0-alpha", path = "../functional-utils" atomic_refcell = { workspace = true } serde = { workspace = true } ambassador = { workspace = true } -typemap = { workspace = true } dynsequence = { workspace = true } palette = { workspace = true, features = ["std"], default-features = false } image = { workspace = true, default-features = false } diff --git a/crates/render/src/draw.rs b/crates/render/src/draw.rs index 9cb5db8..0aeb386 100644 --- a/crates/render/src/draw.rs +++ b/crates/render/src/draw.rs @@ -1,12 +1,16 @@ -use std::{hash::Hash, marker::PhantomData, ops::Deref}; +use std::{ + any::{Any, TypeId}, + hash::Hash, + marker::PhantomData, + ops::Deref, +}; use atomic_refcell::AtomicRefCell; use dynsequence::{dyn_sequence, DynSequence}; +use fnv::FnvHashMap as HashMap; use pulz_ecs::{prelude::*, resource::ResState, system::param::SystemParam}; -use crate::{backend::CommandEncoder, RenderSystemPhase}; - -type HashMap = std::collections::HashMap; +use crate::{backend::CommandEncoder, utils::hash::TypeIdHashMap, RenderSystemPhase}; pub type DrawContext<'a> = &'a mut (dyn CommandEncoder + 'a); @@ -65,11 +69,21 @@ pub trait PhaseItem: Send + Sync + Sized + 'static { #[doc(hidden)] pub struct DrawQueue(crossbeam_queue::SegQueue<(I::TargetKey, PhaseData)>); -struct KeyType(PhantomData); -impl typemap::Key for KeyType { - type Value = AtomicRefCell>>; -} +struct PhaseDataMap(AtomicRefCell>>); +impl PhaseDataMap { + fn new() -> Self { + Self(AtomicRefCell::new(HashMap::default())) + } + + fn new_any() -> Box { + Box::new(Self::new()) + } + + fn get(&self, target_key: I::TargetKey) -> Option>> { + atomic_refcell::AtomicRef::filter_map(self.0.borrow(), |v| v.get(&target_key)) + } +} pub struct PhaseData { drawables: DynDrawables, items: Vec>, @@ -171,28 +185,39 @@ impl PhaseDraw<'_> { } } -pub struct DrawPhases(typemap::ShareMap); +pub struct DrawPhases(TypeIdHashMap>); + +impl DrawPhases { + pub fn new() -> Self { + Self(TypeIdHashMap::default()) + } +} + impl Default for DrawPhases { #[inline] fn default() -> Self { - Self(typemap::ShareMap::custom()) + Self::new() } } impl DrawPhases { + fn get_map(&self) -> Option<&PhaseDataMap> { + self.0 + .get(&TypeId::of::>())? + .downcast_ref::>() + } + pub fn get( &self, target_key: I::TargetKey, ) -> Option>> { - self.0 - .get::>() - .and_then(|v| atomic_refcell::AtomicRef::filter_map(v.borrow(), |v| v.get(&target_key))) + self.get_map::()?.get(target_key) } fn register(&mut self) { self.0 - .entry::>() - .or_insert_with(Default::default); + .entry(TypeId::of::>()) + .or_insert_with(PhaseDataMap::::new_any); } } @@ -225,7 +250,7 @@ impl Default for DrawQueue { } fn collect_and_sort_draws_system(queue: &mut DrawQueue, phases: &DrawPhases) { - let mut phase_map = phases.0.get::>().unwrap().borrow_mut(); + let mut phase_map = phases.get_map::().unwrap().0.borrow_mut(); // clear sequences for phase_data in phase_map.values_mut() { diff --git a/crates/render/src/graph/access.rs b/crates/render/src/graph/access.rs index 5e2fd2b..d9b7a97 100644 --- a/crates/render/src/graph/access.rs +++ b/crates/render/src/graph/access.rs @@ -67,7 +67,7 @@ impl ResourceAccess for Buffer { } #[inline] - fn default_format(usage: Self::Usage) -> Self::Format {} + fn default_format(_usage: Self::Usage) -> Self::Format {} } bitflags! { diff --git a/crates/render/src/graph/resources.rs b/crates/render/src/graph/resources.rs index eac083e..c4b0d37 100644 --- a/crates/render/src/graph/resources.rs +++ b/crates/render/src/graph/resources.rs @@ -282,10 +282,8 @@ impl ResourceDeps { pub(super) fn mark_deps(&self, marks: &mut BitSet, todo: &mut VecDeque) { for dep in &self.0 { let pass_index = dep.src_pass(); - if pass_index != !0 { - if marks.insert(pass_index as usize) { - todo.push_back(pass_index); - } + if pass_index != !0 && marks.insert(pass_index as usize) { + todo.push_back(pass_index); } } } diff --git a/crates/render/src/utils.rs b/crates/render/src/utils.rs index 1522311..7b1f45d 100644 --- a/crates/render/src/utils.rs +++ b/crates/render/src/utils.rs @@ -1,13 +1,67 @@ +pub mod hash { + + #[derive(Default)] + pub struct SimpleU64Hasher(u64); + pub type SimpleU64BuildHasher = std::hash::BuildHasherDefault; + + impl std::hash::Hasher for SimpleU64Hasher { + #[inline] + fn finish(&self) -> u64 { + self.0 + } + + #[inline] + fn write(&mut self, _bytes: &[u8]) { + panic!("PreHashedHasherHasher should only be used with u64") + } + + #[inline] + fn write_u64(&mut self, i: u64) { + self.0 = i; + } + + #[inline] + fn write_i64(&mut self, i: i64) { + self.0 = i as u64; + } + } + + pub type U64HashMap = std::collections::HashMap; + pub type TypeIdHashMap = + std::collections::HashMap; +} + pub mod serde_slots { - use std::{cell::RefCell, marker::PhantomData}; + use std::{ + any::{Any, TypeId}, + cell::RefCell, + marker::PhantomData, + }; - use fnv::FnvHashMap; + use super::hash::{TypeIdHashMap, U64HashMap}; - thread_local! { static CURENT_MAPPER: RefCell = RefCell::new(typemap::TypeMap::new()) } + thread_local! { + static CURENT_MAPPER: RefCell>> = RefCell::new(TypeIdHashMap::default()); + } - struct SlotTypeKey(PhantomData); - impl typemap::Key for SlotTypeKey { - type Value = FnvHashMap; + struct TypedEntry(U64HashMap); + + impl TypedEntry { + fn new() -> Self { + Self(U64HashMap::default()) + } + fn new_box_any() -> Box + where + K: 'static, + { + Box::new(Self::new()) + } + fn define(&mut self, k: u64, v: K) -> Option { + self.0.insert(k, v) + } + fn resolve(&self, k: u64) -> Option<&K> { + self.0.get(&k) + } } pub struct SlotDeserializationMapper { _private: (), @@ -19,13 +73,21 @@ pub mod serde_slots { pub fn define(&mut self, old: u64, new: K) -> Option { CURENT_MAPPER.with(|m| { m.borrow_mut() - .entry::>() - .or_insert_with(Default::default) - .insert(old, new) + .entry(TypeId::of::>()) + .or_insert_with(TypedEntry::::new_box_any) + .downcast_mut::>() + .expect("inconsistend typeid map") + .define(old, new) }) } pub fn resolve(&self, old: u64) -> Option { - CURENT_MAPPER.with(|m| m.borrow().get::>()?.get(&old).copied()) + CURENT_MAPPER.with(|m| { + m.borrow() + .get(&TypeId::of::>())? + .downcast_ref::>()? + .resolve(old) + .copied() + }) } pub fn with(f: F) -> R @@ -36,8 +98,8 @@ pub mod serde_slots { assert!(is_empty, "nested calls are not allowed"); let mut tmp = Self::INSTANCE; let r = f(&mut tmp); - CURENT_MAPPER - .with(|m| std::mem::replace(&mut *m.borrow_mut(), typemap::TypeMap::new())); + // free the map + CURENT_MAPPER.with(|m| std::mem::take(&mut *m.borrow_mut())); r } } diff --git a/examples/android/src/lib.rs b/examples/android/src/lib.rs index 5d9e35b..be02e91 100644 --- a/examples/android/src/lib.rs +++ b/examples/android/src/lib.rs @@ -1,6 +1,8 @@ use std::rc::Rc; use log::info; +#[cfg(target_os = "android")] +use platform::android::activity::AndroidApp; use pulz_ecs::prelude::*; use pulz_render::camera::{Camera, RenderTarget}; use pulz_render_ash::AshRenderer; @@ -12,9 +14,6 @@ use winit::{ window::Window, }; -#[cfg(target_os = "android")] -use platform::android::activity::AndroidApp; - fn init(event_loop: &EventLoopWindowTarget<()>) -> (Resources, Rc, WinitWindowSystem) { info!("Initializing..."); let mut resources = Resources::new(); From 7741b5f8638ec6e36279c9d3d71f280bf500196c Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Thu, 28 Dec 2023 19:20:28 +0100 Subject: [PATCH 23/36] [render] allocate images, updates, assign physical ressources --- Cargo.toml | 30 +- crates/render-ash/Cargo.toml | 9 +- crates/render-ash/examples/render-ash-demo.rs | 9 +- crates/render-ash/src/alloc.rs | 130 ++++ crates/render-ash/src/device.rs | 56 +- crates/render-ash/src/drop_guard.rs | 6 + crates/render-ash/src/graph.rs | 177 +++-- crates/render-ash/src/instance.rs | 2 +- crates/render-ash/src/lib.rs | 171 ++--- crates/render-ash/src/resources/mod.rs | 154 +++-- .../render-ash/src/resources/resource_impl.rs | 201 ++++-- crates/render-ash/src/resources/traits.rs | 60 +- crates/render-ash/src/swapchain.rs | 400 ++++++------ crates/render-pipeline-core/Cargo.toml | 1 + .../src/deferred_3d/mod.rs | 5 +- crates/render-wgpu/Cargo.toml | 7 +- .../render-wgpu/examples/render-wgpu-demo.rs | 14 +- crates/render-wgpu/src/lib.rs | 114 ++-- crates/render-wgpu/src/surface.rs | 19 +- crates/render/src/backend.rs | 24 + crates/render/src/camera.rs | 103 +-- crates/render/src/draw.rs | 4 +- crates/render/src/graph/access.rs | 57 +- crates/render/src/graph/builder.rs | 32 +- crates/render/src/graph/mod.rs | 48 +- crates/render/src/graph/pass/builder.rs | 8 +- crates/render/src/graph/resources.rs | 609 +++++++++++++----- crates/render/src/lib.rs | 22 +- crates/render/src/pipeline/graphics_pass.rs | 7 +- crates/render/src/surface.rs | 104 +++ crates/render/src/view.rs | 76 --- crates/window-winit/Cargo.toml | 3 +- .../examples/window-winit-demo.rs | 6 +- crates/window-winit/src/lib.rs | 177 +++-- crates/window/Cargo.toml | 2 +- crates/window/src/lib.rs | 8 +- crates/window/src/listener.rs | 63 +- crates/window/src/window.rs | 5 - 38 files changed, 1718 insertions(+), 1205 deletions(-) create mode 100644 crates/render-ash/src/alloc.rs create mode 100644 crates/render/src/surface.rs delete mode 100644 crates/render/src/view.rs diff --git a/Cargo.toml b/Cargo.toml index fb47ad1..72948c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,31 +19,45 @@ edition = "2024" rust-version = "1.86" [workspace.dependencies] -glam = "0.23" +glam = "0.24" serde = "1.0" -raw-window-handle = "0.5" +raw-window-handle = "0.5.2" slotmap = "1.0" anyhow = "1.0" thiserror = "1.0" tracing = "0.1" -bitflags = "2.2" +bitflags = "2.4" fnv = "1.0" radsort = "0.1" -bytemuck = "1.13" +bytemuck = "1.14" +blink-alloc = "0.3" downcast-rs = "1.2" -dynsequence = { version = "0.1.0-alpha.3" } +dynsequence = { version = "0.1.0-alpha.4" } ambassador = "0.3" -blocking = "1.3" +blocking = "1.5" threadpool = "1.8" backtrace = "0.3" +gametime = "0.3" atomic_refcell = "0.1" palette = { version = "0.7", default-features = false } image = { version = "0.24", default-features = false } -encase = { version = "0.5", features = ["glam"], default-features = false } -encase_derive_impl = { version = "0.5" } +encase = { version = "0.6", features = ["glam"], default-features = false } +encase_derive_impl = { version = "0.6" } crossbeam-utils = "0.8" +crossbeam-queue = "0.3" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing-wasm = "0.2" +tracing-log = "0.2" +console_error_panic_hook = "0.1" +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" +js-sys = "0.3" +web-sys = "0.3" +ndk = "0.8" +ndk-glue = "0.7" darling = "0.20" proc-macro2 = "1.0" syn = "2.0" quote = "1.0" proc-macro-crate = "3.3" + diff --git a/crates/render-ash/Cargo.toml b/crates/render-ash/Cargo.toml index 980ef4a..afd4f43 100644 --- a/crates/render-ash/Cargo.toml +++ b/crates/render-ash/Cargo.toml @@ -12,6 +12,7 @@ pulz-ecs = { path = "../ecs" } pulz-window = { path = "../window" } pulz-render = { path = "../render" } pulz-bitset = { path = "../bitset" } +pulz-assets = { path = "../assets" } thiserror = { workspace = true } tracing = { workspace = true } @@ -22,14 +23,16 @@ fnv = { workspace = true } raw-window-handle = { workspace = true } ash = "0.37" scratchbuffer = "0.1.0-alpha.1" -gpu-alloc = "0.5" -gpu-alloc-ash = "0.5" +gpu-alloc = "0.6" +gpu-alloc-ash = "0.6" gpu-descriptor = "0.2" crossbeam-queue = { workspace = true } +[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] +raw-window-metal = "0.3" + [dev-dependencies] anyhow = { workspace = true } pulz-window-winit = { path = "../window-winit" } pulz-render-pipeline-core = { path = "../render-pipeline-core" } tracing-subscriber = { workspace = true } -async-std = { workspace = true } diff --git a/crates/render-ash/examples/render-ash-demo.rs b/crates/render-ash/examples/render-ash-demo.rs index f04e95e..573f502 100644 --- a/crates/render-ash/examples/render-ash-demo.rs +++ b/crates/render-ash/examples/render-ash-demo.rs @@ -15,14 +15,15 @@ fn init() -> (Resources, EventLoop<()>, Rc, WinitWindowSystem) { info!("Initializing..."); let mut resources = Resources::new(); resources.install(CoreShadingModule); - resources.install(AshRenderer::new().unwrap()); - let event_loop = EventLoop::new(); + let event_loop = EventLoop::new().unwrap(); let (window_system, window_id, window) = WinitWindowModule::new(WindowDescriptor::default(), &event_loop) .unwrap() .install(&mut resources); + resources.install(AshRenderer::new().unwrap()); + // let mut schedule = resources.remove::().unwrap(); // schedule.init(&mut resources); // schedule.debug_dump_if_env(None).unwrap(); @@ -51,9 +52,9 @@ fn main() { .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE) .init(); - let (resources, event_loop, _window, window_system) = init(); + let (mut resources, event_loop, _window, window_system) = init(); - window_system.run(resources, event_loop); + window_system.run(&mut resources, event_loop).unwrap(); } #[cfg(target_arch = "wasm32")] diff --git a/crates/render-ash/src/alloc.rs b/crates/render-ash/src/alloc.rs new file mode 100644 index 0000000..f723a85 --- /dev/null +++ b/crates/render-ash/src/alloc.rs @@ -0,0 +1,130 @@ +use std::{mem::ManuallyDrop, sync::Arc}; + +use ash::vk; + +use crate::{ + device::AshDevice, + instance::{AshInstance, VK_API_VERSION}, + Result, +}; + +type GpuAllocator = gpu_alloc::GpuAllocator; +pub type GpuMemoryBlock = gpu_alloc::MemoryBlock; +pub use gpu_alloc::AllocationError; + +pub struct AshAllocator { + device: Arc, + gpu_allocator: GpuAllocator, +} + +impl AshAllocator { + pub fn new(device: &Arc) -> Result { + let gpu_alloc_props = unsafe { + gpu_alloc_ash::device_properties( + device.instance(), + VK_API_VERSION, + device.physical_device(), + )? + }; + + // TODO: Config + let gpu_alloc_config = gpu_alloc::Config::i_am_potato(); + + Ok(Self { + device: device.clone(), + gpu_allocator: GpuAllocator::new(gpu_alloc_config, gpu_alloc_props), + }) + } + + #[inline] + pub fn instance(&self) -> &AshInstance { + &self.device.instance() + } + + #[inline] + pub fn instance_arc(&self) -> Arc { + self.device.instance_arc() + } + + #[inline] + pub fn device(&self) -> &AshDevice { + &self.device + } + + #[inline] + pub fn device_arc(&self) -> Arc { + self.device.clone() + } + + #[inline] + pub unsafe fn alloc( + &mut self, + request: gpu_alloc::Request, + ) -> Result, AllocationError> { + let block = self + .gpu_allocator + .alloc(gpu_alloc_ash::AshMemoryDevice::wrap(&self.device), request)?; + Ok(AshMemoryBlockGuard { + allocator: self, + block: ManuallyDrop::new(block), + }) + } + + #[inline] + pub unsafe fn alloc_with_dedicated( + &mut self, + request: gpu_alloc::Request, + dedicated: gpu_alloc::Dedicated, + ) -> Result, AllocationError> { + let block = self.gpu_allocator.alloc_with_dedicated( + gpu_alloc_ash::AshMemoryDevice::wrap(&self.device), + request, + dedicated, + )?; + Ok(AshMemoryBlockGuard { + allocator: self, + block: ManuallyDrop::new(block), + }) + } + + #[inline] + pub unsafe fn dealloc(&mut self, block: GpuMemoryBlock) { + self.gpu_allocator + .dealloc(gpu_alloc_ash::AshMemoryDevice::wrap(&self.device), block) + } +} + +pub struct AshMemoryBlockGuard<'a> { + block: ManuallyDrop, + allocator: &'a mut AshAllocator, +} + +impl AshMemoryBlockGuard<'_> { + #[inline] + pub fn take(mut self) -> GpuMemoryBlock { + let block = unsafe { ManuallyDrop::take(&mut self.block) }; + std::mem::forget(self); + block + } +} +impl Drop for AshMemoryBlockGuard<'_> { + fn drop(&mut self) { + unsafe { + self.allocator.dealloc(ManuallyDrop::take(&mut self.block)); + } + } +} + +impl std::ops::Deref for AshMemoryBlockGuard<'_> { + type Target = GpuMemoryBlock; + #[inline] + fn deref(&self) -> &Self::Target { + &self.block + } +} +impl std::ops::DerefMut for AshMemoryBlockGuard<'_> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.block + } +} diff --git a/crates/render-ash/src/device.rs b/crates/render-ash/src/device.rs index 4181ca7..9f13bcf 100644 --- a/crates/render-ash/src/device.rs +++ b/crates/render-ash/src/device.rs @@ -4,21 +4,13 @@ use ash::{extensions::khr, vk}; use pulz_render::graph::pass::PipelineBindPoint; use tracing::{debug, info, warn}; -use crate::{ - instance::{AshInstance, VK_API_VERSION}, - Error, ErrorNoExtension, Result, -}; - -pub type GpuAllocator = gpu_alloc::GpuAllocator; -pub type GpuMemoryBlock = gpu_alloc::MemoryBlock; -pub use gpu_alloc::AllocationError; +use crate::{instance::AshInstance, Error, ErrorNoExtension, Result}; pub struct AshDevice { + device_raw: ash::Device, instance: Arc, physical_device: vk::PhysicalDevice, - device_raw: ash::Device, device_extensions: Vec<&'static CStr>, - gpu_allocator: GpuAllocator, ext_swapchain: Option, ext_sync2: Option, ext_raytracing_pipeline: Option, @@ -51,24 +43,17 @@ impl AshDevice { indices: QueueFamilyIndices, device_extensions: Vec<&'static CStr>, ) -> Result> { - let gpu_alloc_props = - unsafe { gpu_alloc_ash::device_properties(instance, VK_API_VERSION, physical_device)? }; - let (device_raw, queues) = instance.create_logical_device( physical_device, indices, device_extensions.iter().copied(), )?; - // TODO: Config - let gpu_alloc_config = gpu_alloc::Config::i_am_potato(); - let mut device = Self { instance: instance.clone(), physical_device, device_raw, device_extensions, - gpu_allocator: GpuAllocator::new(gpu_alloc_config, gpu_alloc_props), ext_swapchain: None, ext_sync2: None, ext_raytracing_pipeline: None, @@ -93,6 +78,11 @@ impl AshDevice { &self.instance } + #[inline] + pub fn instance_arc(&self) -> Arc { + self.instance.clone() + } + #[inline] pub fn physical_device(&self) -> vk::PhysicalDevice { self.physical_device @@ -131,38 +121,6 @@ impl AshDevice { .ok_or(ErrorNoExtension(khr::RayTracingPipeline::name())) } - #[inline] - pub unsafe fn alloc( - &mut self, - request: gpu_alloc::Request, - ) -> Result { - self.gpu_allocator.alloc( - gpu_alloc_ash::AshMemoryDevice::wrap(&self.device_raw), - request, - ) - } - - #[inline] - pub unsafe fn alloc_with_dedicated( - &mut self, - request: gpu_alloc::Request, - dedicated: gpu_alloc::Dedicated, - ) -> Result { - self.gpu_allocator.alloc_with_dedicated( - gpu_alloc_ash::AshMemoryDevice::wrap(&self.device_raw), - request, - dedicated, - ) - } - - #[inline] - pub unsafe fn dealloc(&mut self, block: GpuMemoryBlock) { - self.gpu_allocator.dealloc( - gpu_alloc_ash::AshMemoryDevice::wrap(&self.device_raw), - block, - ) - } - #[inline] pub unsafe fn object_name(&self, handle: H, name: &str) { if let Ok(debug_utils) = self.instance.ext_debug_utils() { diff --git a/crates/render-ash/src/drop_guard.rs b/crates/render-ash/src/drop_guard.rs index 67e8d33..5c34fb9 100644 --- a/crates/render-ash/src/drop_guard.rs +++ b/crates/render-ash/src/drop_guard.rs @@ -91,6 +91,11 @@ impl<'a, D: Destroy> Guard<'a, D> { pub fn as_ref(&self) -> &D { &self.item } + + #[inline] + pub fn as_mut(&mut self) -> &mut D { + &mut self.item + } } impl<'a, I, D: Destroy + std::ops::Index> std::ops::Index for Guard<'a, D> { @@ -184,6 +189,7 @@ impl_create_destroy! { vk::Semaphore : (destroy_semaphore, create_semaphore vk::SemaphoreCreateInfo), vk::Event : (destroy_event, create_event vk::EventCreateInfo), vk::CommandPool : (destroy_command_pool, create_command_pool vk::CommandPoolCreateInfo), + vk::Buffer : (destroy_buffer, create_buffer vk::BufferCreateInfo), vk::Image : (destroy_image, create_image vk::ImageCreateInfo), vk::ImageView : (destroy_image_view, create_image_view vk::ImageViewCreateInfo), vk::Framebuffer : (destroy_framebuffer, create_framebuffer vk::FramebufferCreateInfo), diff --git a/crates/render-ash/src/graph.rs b/crates/render-ash/src/graph.rs index a9e17f3..dbbbfc7 100644 --- a/crates/render-ash/src/graph.rs +++ b/crates/render-ash/src/graph.rs @@ -1,36 +1,47 @@ -use ash::vk::{self}; +use ash::vk::{self, PipelineStageFlags}; +use pulz_assets::Handle; use pulz_render::{ + backend::PhysicalResourceResolver, + buffer::{Buffer, BufferUsage}, + camera::RenderTarget, draw::DrawPhases, graph::{ - pass::PipelineBindPoint, resources::GraphBackend, PassIndex, RenderGraph, - RenderGraphAssignments, + pass::PipelineBindPoint, + resources::{PhysicalResource, PhysicalResources}, + PassIndex, RenderGraph, }, + math::USize2, pipeline::{GraphicsPass, GraphicsPassDescriptorWithTextures}, - texture::{TextureDimensions, TextureFormat}, + texture::{Texture, TextureDescriptor, TextureDimensions, TextureFormat, TextureUsage}, }; use pulz_window::WindowsMirror; +use tracing::debug; use crate::{ + convert::VkInto, drop_guard::Guard, encoder::{AshCommandPool, SubmissionGroup}, resources::AshResources, - swapchain::SurfaceSwapchain, + swapchain::AshSurfaceSwapchain, Result, }; pub struct AshRenderGraph { + physical_resources: PhysicalResources, topo: Vec, barriers: Vec, - assignments: RenderGraphAssignments, + hash: u64, + physical_resources_hash: u64, } -#[derive(Default)] +#[derive(Default, Debug)] pub struct TopoGroup { - render_passes: Vec<(PassIndex, vk::RenderPass, vk::Framebuffer)>, // pass-index - compute_passes: Vec, // sub-pass-index - ray_tracing_passes: Vec, // sub-pass-index + render_passes: Vec<(PassIndex, vk::RenderPass, vk::Framebuffer, USize2)>, // pass-index + compute_passes: Vec, // sub-pass-index + ray_tracing_passes: Vec, // sub-pass-index } +#[derive(Debug)] pub struct Barrier { image: Vec, buffer: Vec, @@ -41,29 +52,113 @@ pub struct Barrier { unsafe impl Send for Barrier {} unsafe impl Sync for Barrier {} +struct AshPhysicalResourceResolver<'a> { + submission_group: &'a mut SubmissionGroup, + res: &'a mut AshResources, + command_pool: &'a mut AshCommandPool, + surfaces: &'a mut WindowsMirror, +} + +impl PhysicalResourceResolver for AshPhysicalResourceResolver<'_> { + fn resolve_render_target( + &mut self, + render_target: &RenderTarget, + ) -> Option> { + match render_target { + RenderTarget::Image(_i) => todo!("implement resolve_render_target (image)"), + RenderTarget::Window(w) => { + let surface = self.surfaces.get_mut(*w).expect("resolve window"); + assert!(!surface.is_acquired()); + + let sem = self + .command_pool + .request_semaphore() + .expect("request semaphore"); + self.submission_group + .wait(sem, PipelineStageFlags::TRANSFER); + let aquired_texture = surface + .acquire_next_image(self.res, 0, sem) + .expect("aquire failed") + .expect("aquire failed(2)"); + + Some(PhysicalResource { + resource: aquired_texture.texture, + format: surface.texture_format(), + // TODO: usage + usage: TextureUsage::ALL_ATTACHMENTS, + size: TextureDimensions::D2(surface.size()), + }) + } + } + } + + fn resolve_buffer(&mut self, _handle: &Handle) -> Option> { + todo!("implement resolve_buffer") + } + + fn create_transient_texture( + &mut self, + format: TextureFormat, + dimensions: TextureDimensions, + usage: TextureUsage, + ) -> Option { + let t = self + .res + .create::(&TextureDescriptor { + format, + dimensions, + usage, + ..Default::default() + }) + .ok()?; + // TODO: destroy texture + // TODO: reuse textures + Some(t) + } + + fn create_transient_buffer(&mut self, _size: usize, _usage: BufferUsage) -> Option { + // TODO: reuse textures + todo!("implement create_transient_buffer") + } +} + impl AshRenderGraph { #[inline] pub const fn new() -> Self { Self { + physical_resources: PhysicalResources::new(), topo: Vec::new(), barriers: Vec::new(), - assignments: RenderGraphAssignments::new(), + hash: 0, + physical_resources_hash: 0, } } pub fn update( &mut self, src_graph: &RenderGraph, + submission_group: &mut SubmissionGroup, res: &mut AshResources, - surfaces: &WindowsMirror, + command_pool: &mut AshCommandPool, + surfaces: &mut WindowsMirror, ) -> Result { + let mut resolver = AshPhysicalResourceResolver { + submission_group, + res, + command_pool, + surfaces, + }; // TODO: update render-pass, if resource-formats changed // TODO: update framebuffer if render-pass or dimensions changed - if self - .assignments - .update(src_graph, &mut AshGraphBackend { res, surfaces }) - { + let formats_changed = self + .physical_resources + .assign_physical(src_graph, &mut resolver); + if src_graph.was_updated() || src_graph.hash() != self.hash || formats_changed { self.do_update(src_graph, res)?; + debug!( + "graph updated: topo={:?}, barriers={:?}", + self.topo, self.barriers + ); Ok(true) } else { Ok(false) @@ -92,6 +187,7 @@ impl AshRenderGraph { } fn do_update(&mut self, src: &RenderGraph, res: &mut AshResources) -> Result<()> { + self.hash = src.hash(); self.topo.clear(); self.barriers.clear(); @@ -107,7 +203,7 @@ impl AshRenderGraph { // TODO: no unwrap / error handling let pass_descr = GraphicsPassDescriptorWithTextures::from_graph( src, - &self.assignments, + &self.physical_resources, pass, ) .unwrap(); @@ -115,9 +211,12 @@ impl AshRenderGraph { res.create::(&pass_descr.graphics_pass)?; let render_pass = res[graphics_pass]; let framebuf = Self::create_framebuffer(res, &pass_descr, render_pass)?; - topo_group - .render_passes - .push((pass.index(), render_pass, framebuf.take())); + topo_group.render_passes.push(( + pass.index(), + render_pass, + framebuf.take(), + pass_descr.size, + )); } PipelineBindPoint::Compute => { let range = pass.sub_pass_range(); @@ -144,9 +243,10 @@ impl AshRenderGraph { draw_phases: &DrawPhases, ) -> Result<()> { let mut encoder = command_pool.encoder()?; + //let mut clear_values = Vec::new(); for (topo_index, topo) in self.topo.iter().enumerate() { // render-passes - for &(pass_index, render_pass, fb) in &topo.render_passes { + for &(pass_index, render_pass, fb, size) in &topo.render_passes { let pass = src_graph.get_pass(pass_index).unwrap(); let has_multiple_subpass = pass.sub_pass_range().len() > 1; if has_multiple_subpass { @@ -154,11 +254,18 @@ impl AshRenderGraph { } unsafe { // TODO: caching of render-pass & framebuffer - // TODO: clear-values, ... + // TODO: clear-values, render-area, ... encoder.begin_render_pass( &vk::RenderPassBeginInfo::builder() .render_pass(render_pass) .framebuffer(fb) + //.clear_values(&clear_values) + .render_area( + vk::Rect2D::builder() + .offset(vk::Offset2D { x: 0, y: 0 }) + .extent(size.vk_into()) + .build(), + ) .build(), vk::SubpassContents::INLINE, ); @@ -192,29 +299,3 @@ impl AshRenderGraph { Ok(()) } } - -struct AshGraphBackend<'a> { - res: &'a mut AshResources, - surfaces: &'a WindowsMirror, -} - -impl GraphBackend for AshGraphBackend<'_> { - fn get_surface( - &mut self, - window_id: pulz_window::WindowId, - ) -> ( - pulz_render::texture::Texture, - TextureFormat, - TextureDimensions, - ) { - let swapchain = self - .surfaces - .get(window_id) - .expect("swapchain not initialized"); - ( - swapchain.texture_id(), - swapchain.texture_format(), - TextureDimensions::D2(swapchain.size()), - ) - } -} diff --git a/crates/render-ash/src/instance.rs b/crates/render-ash/src/instance.rs index a737318..34ef478 100644 --- a/crates/render-ash/src/instance.rs +++ b/crates/render-ash/src/instance.rs @@ -13,8 +13,8 @@ pub const ENGINE_VERSION: u32 = parse_version(env!("CARGO_PKG_VERSION")); pub const VK_API_VERSION: u32 = vk::API_VERSION_1_1; pub struct AshInstance { - entry: ash::Entry, instance_raw: ash::Instance, + entry: ash::Entry, instance_extensions: Vec<&'static CStr>, ext_debug_utils: Option, ext_surface: Option, diff --git a/crates/render-ash/src/lib.rs b/crates/render-ash/src/lib.rs index 0a91be3..d63feb3 100644 --- a/crates/render-ash/src/lib.rs +++ b/crates/render-ash/src/lib.rs @@ -25,7 +25,7 @@ #![doc(html_no_source)] #![doc = include_str!("../README.md")] -use std::{backtrace::Backtrace, ffi::CStr, sync::Arc}; +use std::{backtrace::Backtrace, ffi::CStr, rc::Rc, sync::Arc}; use ash::vk::{self, PipelineStageFlags}; use bitflags::bitflags; @@ -37,8 +37,8 @@ use pulz_ecs::prelude::*; use pulz_render::{draw::DrawPhases, graph::RenderGraph, RenderModule, RenderSystemPhase}; use resources::AshResources; use thiserror::Error; -use tracing::info; +mod alloc; mod convert; mod debug_utils; mod device; @@ -51,8 +51,8 @@ mod shader; mod swapchain; use pulz_window::{ - listener::{WindowSystemListener, WindowSystemListeners}, - RawWindow, Window, WindowId, Windows, WindowsMirror, + listener::WindowSystemListener, HasRawWindowAndDisplayHandle, Window, WindowId, Windows, + WindowsMirror, }; // wrapper object for printing backtrace, until provide() is stable @@ -164,7 +164,7 @@ struct AshRendererFull { res: AshResources, frames: Vec, current_frame: usize, - surfaces: WindowsMirror, + surfaces: WindowsMirror, graph: AshRenderGraph, } @@ -173,8 +173,11 @@ impl Drop for AshRendererFull { unsafe { self.device.device_wait_idle().unwrap(); } + for (_, swapchain) in self.surfaces.drain() { + swapchain.destroy_with_surface(&mut self.res).unwrap(); + } self.frames.clear(); - self.res.clear_all(); + self.res.clear_all().unwrap(); } } @@ -192,8 +195,6 @@ struct Frame { command_pool: AshCommandPool, finished_fence: vk::Fence, // signaled ad end of command-cueue, waited at beginning of frame finished_semaphore: vk::Semaphore, // semaphore used for presenting to the swapchain - retired_swapchains: Vec, - retired_image_views: Vec, } impl Frame { @@ -213,24 +214,11 @@ impl Frame { command_pool, finished_fence: finished_fence.take(), finished_semaphore: finished_semaphore.take(), - retired_swapchains: Vec::new(), - retired_image_views: Vec::new(), }) } - unsafe fn reset(&mut self, device: &AshDevice) -> Result<(), vk::Result> { - for image_view in self.retired_image_views.drain(..) { - device.destroy_image_view(image_view, None); - } - - if let Ok(ext_swapchain) = device.ext_swapchain() { - for swapchain in self.retired_swapchains.drain(..) { - ext_swapchain.destroy_swapchain(swapchain, None); - } - } - + unsafe fn reset(&mut self, _device: &AshDevice) -> Result<(), vk::Result> { self.command_pool.reset()?; - Ok(()) } } @@ -239,11 +227,6 @@ impl Drop for Frame { fn drop(&mut self) { unsafe { let device = self.command_pool.device(); - if let Ok(ext_swapchain) = device.ext_swapchain() { - for swapchain in self.retired_swapchains.drain(..) { - ext_swapchain.destroy_swapchain(swapchain, None); - } - } if self.finished_fence != vk::Fence::null() { device.destroy_fence(self.finished_fence, None); } @@ -255,50 +238,16 @@ impl Drop for Frame { } impl AshRendererFull { - fn from_device(device: Arc) -> Self { - let res = AshResources::new(&device); - Self { + fn from_device(device: Arc) -> Result { + let res = AshResources::new(&device, Frame::NUM_FRAMES_IN_FLIGHT)?; + Ok(Self { device, res, frames: Vec::with_capacity(Frame::NUM_FRAMES_IN_FLIGHT), current_frame: 0, surfaces: WindowsMirror::new(), graph: AshRenderGraph::new(), - } - } - - fn reconfigure_swapchains(&mut self, windows: &Windows) { - let mut to_remove = Vec::new(); - for (window_id, surface_swapchain) in self.surfaces.iter_mut() { - let Some(window) = windows.get(window_id) else { - to_remove.push(window_id); - continue; - }; - //TODO: re-create also the surface, when SURFACE_LOST was returned in earlier calls. - //TODO: better resize check (don't compare size, but use a 'dirty'-flag, or listener) - //TODO: sync - if window.size != surface_swapchain.size() { - info!( - "surface sized changed: {} => {}", - surface_swapchain.size(), - window.size - ); - surface_swapchain - .configure_with( - window.size, - if window.vsync { - vk::PresentModeKHR::MAILBOX - } else { - vk::PresentModeKHR::IMMEDIATE - }, - ) - .unwrap(); - } - } - //TODO: sync - for window_id in to_remove { - self.surfaces.remove(window_id); - } + }) } fn begin_frame(&mut self) -> Result { @@ -320,6 +269,7 @@ impl AshRendererFull { // cleanup old frame unsafe { frame.reset(&self.device)?; + self.res.next_frame_and_clear_garbage(); } Ok(SubmissionGroup::new()) @@ -334,8 +284,24 @@ impl AshRendererFull { let _span = tracing::trace_span!("RunGraph").entered(); let frame = &mut self.frames[self.current_frame]; - self.graph - .execute(src_graph, submission_group, &mut frame.command_pool, phases)?; + let span_update = tracing::trace_span!("Update").entered(); + self.graph.update( + src_graph, + submission_group, + &mut self.res, + &mut frame.command_pool, + &mut self.surfaces, + )?; + drop(span_update); + + let span_exec = tracing::trace_span!("Execute").entered(); + self.graph.execute( + src_graph, + submission_group, + &mut frame.command_pool, + phases, + )?; + drop(span_exec); Ok(()) } @@ -395,7 +361,7 @@ impl AshRendererFull { unsafe { encoder.pipeline_barrier( - vk::PipelineStageFlags::TOP_OF_PIPE, + vk::PipelineStageFlags::TRANSFER | vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT, vk::PipelineStageFlags::TRANSFER, &[], &[], @@ -430,7 +396,7 @@ impl AshRendererFull { unsafe { encoder.pipeline_barrier( vk::PipelineStageFlags::TRANSFER, - vk::PipelineStageFlags::BOTTOM_OF_PIPE, + vk::PipelineStageFlags::TRANSFER | vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT, &[], &[], &barriers, @@ -475,13 +441,9 @@ impl AshRendererFull { fn run(&mut self, windows: &mut Windows, src_graph: &RenderGraph, draw_phases: &DrawPhases) { self.reconfigure_swapchains(windows); - // TODO: maybe graph needs to consider updated swapchain format & dimensions? - - self.graph - .update(src_graph, &mut self.res, &self.surfaces) - .unwrap(); let mut submission_group = self.begin_frame().unwrap(); + self.render_frame(&mut submission_group, src_graph, draw_phases) .unwrap(); self.end_frame(submission_group).unwrap(); @@ -514,7 +476,7 @@ impl AshRenderer { fn init(&mut self) -> Result<&mut AshRendererFull> { if let AshRendererInner::Early { instance, .. } = &self.0 { let device = instance.new_device(vk::SurfaceKHR::null())?; - let renderer = AshRendererFull::from_device(device); + let renderer = AshRendererFull::from_device(device)?; self.0 = AshRendererInner::Full(renderer); } let AshRendererInner::Full(renderer) = &mut self.0 else { @@ -527,21 +489,22 @@ impl AshRenderer { &mut self, window_id: WindowId, window_descriptor: &Window, - window_raw: &dyn RawWindow, + window: Rc, ) -> Result<&mut AshRendererFull> { if let AshRendererInner::Full(renderer) = &mut self.0 { - let surface = renderer.device.instance().new_surface(window_raw)?; - let swapchain = renderer.device.new_swapchain(surface, window_descriptor)?; - renderer.insert_swapchain(swapchain, window_id)?; + let device = renderer.device.clone(); + // SAVETY: window is kept alive + let surface = unsafe { device.instance().new_surface(&*window)? }; + renderer.init_swapchain(window_id, window_descriptor, window, surface)?; } else { let AshRendererInner::Early { instance, .. } = &self.0 else { unreachable!() }; - let surface = instance.new_surface(&window_raw)?; + // SAVETY: window is kept alive + let surface = unsafe { instance.new_surface(&*window)? }; let device = instance.new_device(surface.raw())?; - let swapchain = device.new_swapchain(surface, window_descriptor)?; - let mut renderer = AshRendererFull::from_device(device); - renderer.insert_swapchain(swapchain, window_id)?; + let mut renderer = AshRendererFull::from_device(device)?; + renderer.init_swapchain(window_id, window_descriptor, window, surface)?; self.0 = AshRendererInner::Full(renderer); } let AshRendererInner::Full(renderer) = &mut self.0 else { @@ -559,34 +522,29 @@ impl AshRenderer { } } -struct AshRendererInitWindowSystemListener(ResourceId); - -impl WindowSystemListener for AshRendererInitWindowSystemListener { +impl WindowSystemListener for AshRenderer { fn on_created( - &self, - res: &Resources, + &mut self, window_id: WindowId, window_desc: &Window, - window_raw: &dyn RawWindow, + window: Rc, ) { - let Some(mut renderer) = res.borrow_res_mut_id(self.0) else { return }; - renderer - .init_window(window_id, window_desc, window_raw) - .unwrap(); + self.init_window(window_id, window_desc, window).unwrap(); } - fn on_resumed(&self, res: &Resources) { - let Some(mut renderer) = res.borrow_res_mut_id(self.0) else { return }; - renderer.init().unwrap(); + fn on_resumed(&mut self) { + self.init().unwrap(); } - fn on_closed(&self, res: &Resources, window_id: WindowId) { - let Some(mut renderer) = res.borrow_res_mut_id(self.0) else { return }; - let AshRendererInner::Full(renderer) = &mut renderer.0 else { return }; - renderer.surfaces.remove(window_id); + fn on_closed(&mut self, window_id: WindowId) { + let AshRendererInner::Full(renderer) = &mut self.0 else { + return; + }; + renderer.destroy_swapchain(window_id).unwrap(); } - fn on_suspended(&self, res: &Resources) { - let Some(mut renderer) = res.borrow_res_mut_id(self.0) else { return }; - let AshRendererInner::Full(renderer) = &mut renderer.0 else { return }; - renderer.surfaces.clear(); + fn on_suspended(&mut self) { + let AshRendererInner::Full(renderer) = &mut self.0 else { + return; + }; + renderer.destroy_all_swapchains().unwrap(); } } @@ -598,11 +556,8 @@ impl ModuleWithOutput for AshRenderer { } fn install_resources(self, res: &mut Resources) -> &mut Self { - let listeners_id = res.init_unsend::(); let resource_id = res.insert_unsend(self); - res.get_mut_id(listeners_id) - .unwrap() - .insert(AshRendererInitWindowSystemListener(resource_id)); + res.init_meta_id::(resource_id); res.get_mut_id(resource_id).unwrap() } diff --git a/crates/render-ash/src/resources/mod.rs b/crates/render-ash/src/resources/mod.rs index 460a0e7..0383267 100644 --- a/crates/render-ash/src/resources/mod.rs +++ b/crates/render-ash/src/resources/mod.rs @@ -13,7 +13,12 @@ use pulz_render::{ }; use slotmap::SlotMap; -use crate::{device::AshDevice, Result}; +use crate::{ + alloc::{AshAllocator, GpuMemoryBlock}, + device::AshDevice, + instance::AshInstance, + Result, +}; mod replay; mod resource_impl; @@ -21,11 +26,12 @@ mod traits; use self::{ replay::RecordResource, - traits::{AshGpuResourceCreate, AshGpuResourceRemove}, + traits::{ + AshGpuResource, AshGpuResourceCollection, AshGpuResourceCreate, AshGpuResourceRemove, + }, }; - pub struct AshResources { - device: Arc, + pub alloc: AshAllocator, record: Option>, pipeline_cache: vk::PipelineCache, graphics_passes_cache: U64HashMap, @@ -42,14 +48,28 @@ pub struct AshResources { pub graphics_pipelines: SlotMap, pub compute_pipelines: SlotMap, pub ray_tracing_pipelines: SlotMap, - pub buffers: SlotMap, - pub textures: SlotMap, + pub buffers: SlotMap)>, + pub textures: SlotMap)>, + frame_garbage: Vec, + current_frame: usize, +} + +#[derive(Debug, Default)] +pub struct AshFrameGarbage { + pub buffers: Vec, + pub images: Vec, + pub image_views: Vec, + pub swapchains: Vec, + pub memory: Vec, } impl AshResources { - pub fn new(device: &Arc) -> Self { - Self { - device: device.clone(), + pub fn new(device: &Arc, num_frames_in_flight: usize) -> Result { + let alloc = AshAllocator::new(device)?; + let mut frame_garbage = Vec::with_capacity(num_frames_in_flight); + frame_garbage.resize_with(num_frames_in_flight, AshFrameGarbage::default); + Ok(Self { + alloc, record: None, graphics_passes_cache: U64HashMap::default(), shader_modules_cache: U64HashMap::default(), @@ -68,12 +88,19 @@ impl AshResources { ray_tracing_pipelines: SlotMap::with_key(), buffers: SlotMap::with_key(), textures: SlotMap::with_key(), - } + frame_garbage, + current_frame: 0, + }) + } + + #[inline] + pub fn instance(&self) -> &AshInstance { + &self.alloc.instance() } #[inline] pub fn device(&self) -> &AshDevice { - &self.device + &self.alloc.device() } pub fn with_pipeline_cache(mut self, initial_data: &[u8]) -> Result { @@ -84,11 +111,12 @@ impl AshResources { pub fn set_pipeline_cache(&mut self, initial_data: &[u8]) -> Result<()> { unsafe { if self.pipeline_cache != vk::PipelineCache::null() { - self.device + self.alloc + .device() .destroy_pipeline_cache(self.pipeline_cache, None); self.pipeline_cache = vk::PipelineCache::null(); } - self.pipeline_cache = self.device.create_pipeline_cache( + self.pipeline_cache = self.alloc.device().create_pipeline_cache( &vk::PipelineCacheCreateInfo::builder() .initial_data(initial_data) .build(), @@ -103,7 +131,10 @@ impl AshResources { return Ok(Vec::new()); } unsafe { - let data = self.device.get_pipeline_cache_data(self.pipeline_cache)?; + let data = self + .alloc + .device() + .get_pipeline_cache_data(self.pipeline_cache)?; Ok(data) } } @@ -117,56 +148,105 @@ impl AshResources { } #[inline] - pub fn get_raw(&self, key: R) -> Option + pub fn get_raw(&self, key: R) -> Option<&R::Raw> where R: AshGpuResourceCreate, { - R::get_raw(self, key).copied() + R::slotmap(self).get(key) } #[inline] - pub fn clear(&mut self) + pub fn destroy(&mut self, key: R) -> bool where - R: AshGpuResourceCreate, + R: AshGpuResourceRemove, { - R::clear(self) + R::destroy(self, key) + } + + pub(crate) fn clear_garbage(&mut self) -> Result<()> { + unsafe { + self.alloc.device().device_wait_idle()?; + for garbage in &mut self.frame_garbage { + garbage.clear_frame(&mut self.alloc); + } + } + Ok(()) + } + + pub(crate) fn clear_all(&mut self) -> Result<()> { + self.clear_garbage()?; + // SAFETY: clear save, because clear garbage waits until device is idle + unsafe { + self.ray_tracing_pipelines.clear_destroy(&mut self.alloc); + self.ray_tracing_pipelines_cache.clear(); + self.compute_pipelines.clear_destroy(&mut self.alloc); + self.compute_pipelines_cache.clear(); + self.graphics_pipelines.clear_destroy(&mut self.alloc); + self.graphics_pipelines_cache.clear(); + self.pipeline_layouts.clear_destroy(&mut self.alloc); + self.pipeline_layouts_cache.clear(); + self.bind_group_layouts.clear_destroy(&mut self.alloc); + self.bind_group_layouts_cache.clear(); + self.shader_modules.clear_destroy(&mut self.alloc); + self.shader_modules_cache.clear(); + self.graphics_passes.clear_destroy(&mut self.alloc); + self.graphics_passes_cache.clear(); + self.textures.clear_destroy(&mut self.alloc); + self.buffers.clear_destroy(&mut self.alloc); + } + Ok(()) } #[inline] - pub fn remove(&mut self, key: R) -> bool - where - R: AshGpuResourceRemove, - { - R::remove(self, key) + pub(crate) fn current_frame_garbage_mut(&mut self) -> &mut AshFrameGarbage { + &mut self.frame_garbage[self.current_frame] } - pub fn clear_all(&mut self) { - self.clear::(); - self.clear::(); - self.clear::(); - self.clear::(); - self.clear::(); - self.clear::(); - self.clear::(); - self.clear::(); + /// # SAFETY + /// caller must ensure, that the next frame has finished + pub(crate) unsafe fn next_frame_and_clear_garbage(&mut self) { + self.current_frame = (self.current_frame + 1) % self.frame_garbage.len(); + self.frame_garbage[self.current_frame].clear_frame(&mut self.alloc); + } +} + +impl AshFrameGarbage { + unsafe fn clear_frame(&mut self, alloc: &mut AshAllocator) { + let device = alloc.device(); + self.image_views + .drain(..) + .for_each(|r| device.destroy_image_view(r, None)); + self.images + .drain(..) + .for_each(|r| device.destroy_image(r, None)); + self.buffers + .drain(..) + .for_each(|r| device.destroy_buffer(r, None)); + if let Ok(ext_swapchain) = device.ext_swapchain() { + self.swapchains + .drain(..) + .for_each(|r| ext_swapchain.destroy_swapchain(r, None)); + } + self.memory.drain(..).for_each(|r| alloc.dealloc(r)); } } -impl Index for AshResources { +impl Index for AshResources { type Output = R::Raw; #[inline] fn index(&self, index: R) -> &Self::Output { - R::get_raw(self, index).expect("invalid resource") + R::slotmap(self).get(index).expect("invalid resource") } } impl Drop for AshResources { #[inline] fn drop(&mut self) { - self.clear_all(); + self.clear_all().unwrap(); if self.pipeline_cache != vk::PipelineCache::null() { unsafe { - self.device + self.alloc + .device() .destroy_pipeline_cache(self.pipeline_cache, None); } } diff --git a/crates/render-ash/src/resources/resource_impl.rs b/crates/render-ash/src/resources/resource_impl.rs index 0a3c8c3..87ae2b6 100644 --- a/crates/render-ash/src/resources/resource_impl.rs +++ b/crates/render-ash/src/resources/resource_impl.rs @@ -15,15 +15,14 @@ use super::{ AshResources, U64HashMap, }; use crate::{ + alloc::{AshAllocator, GpuMemoryBlock}, convert::{CreateInfoConverter2, CreateInfoConverter6, VkInto}, - device::AshDevice, shader::compie_into_spv, Result, }; impl AshGpuResource for Buffer { - type Raw = vk::Buffer; - + type Raw = (vk::Buffer, Option); fn slotmap(res: &AshResources) -> &SlotMap { &res.buffers } @@ -31,25 +30,47 @@ impl AshGpuResource for Buffer { fn slotmap_mut(res: &mut AshResources) -> &mut SlotMap { &mut res.buffers } - - unsafe fn create_raw(res: &AshResources, descr: &Self::Descriptor<'_>) -> Result { + unsafe fn create_raw( + res: &mut AshResources, + descr: &Self::Descriptor<'_>, + ) -> Result { + let alloc = &mut res.alloc; + let device = alloc.device_arc(); let create_info: vk::BufferCreateInfo = descr.vk_into(); - let raw = res.device.create_buffer(&create_info, None)?; - Ok(raw) - } - - unsafe fn destroy_raw(device: &AshDevice, raw: Self::Raw) { - if raw != vk::Buffer::null() { - device.destroy_buffer(raw, None); + let buf = device.create(&create_info)?; + let mreq = device.get_buffer_memory_requirements(buf.raw()); + let mem = alloc.alloc(gpu_alloc::Request { + size: mreq.size, + align_mask: mreq.alignment, + usage: gpu_alloc::UsageFlags::FAST_DEVICE_ACCESS, + memory_types: mreq.memory_type_bits, + })?; + device.bind_buffer_memory(buf.raw(), *mem.memory(), mem.offset())?; + Ok((buf.take(), Some(mem.take()))) + } + unsafe fn destroy_raw(alloc: &mut AshAllocator, (buf, mem): Self::Raw) { + if buf != vk::Buffer::null() { + alloc.device().destroy_buffer(buf, None); + } + if let Some(mem) = mem { + alloc.dealloc(mem); } } } impl AshGpuResourceCreate for Buffer {} -impl AshGpuResourceRemove for Buffer {} +impl AshGpuResourceRemove for Buffer { + fn put_to_garbage(garbage: &mut super::AshFrameGarbage, (buf, mem): Self::Raw) { + if buf != vk::Buffer::null() { + garbage.buffers.push(buf); + } + if let Some(mem) = mem { + garbage.memory.push(mem); + } + } +} impl AshGpuResource for Texture { - type Raw = (vk::Image, vk::ImageView); - + type Raw = (vk::Image, vk::ImageView, Option); fn slotmap(res: &AshResources) -> &SlotMap { &res.textures } @@ -57,26 +78,54 @@ impl AshGpuResource for Texture { fn slotmap_mut(res: &mut AshResources) -> &mut SlotMap { &mut res.textures } - - unsafe fn create_raw(res: &AshResources, descr: &Self::Descriptor<'_>) -> Result { + unsafe fn create_raw( + res: &mut AshResources, + descr: &Self::Descriptor<'_>, + ) -> Result { + let alloc = &mut res.alloc; + let device = alloc.device_arc(); let img_create_info: vk::ImageCreateInfo = descr.vk_into(); - let img = res.device.create(&img_create_info)?; - let view_create_info: vk::ImageViewCreateInfo = descr.vk_into(); - let view = res.device.create(&view_create_info)?; - Ok((img.take(), view.take())) + let img = device.create(&img_create_info)?; + let mreq = device.get_image_memory_requirements(img.raw()); + let mem = alloc.alloc(gpu_alloc::Request { + size: mreq.size, + align_mask: mreq.alignment, + usage: gpu_alloc::UsageFlags::FAST_DEVICE_ACCESS, + memory_types: mreq.memory_type_bits, + })?; + device.bind_image_memory(img.raw(), *mem.memory(), mem.offset())?; + let mut view_create_info: vk::ImageViewCreateInfo = descr.vk_into(); + view_create_info.image = img.raw(); + let view = device.create(&view_create_info)?; + Ok((img.take(), view.take(), Some(mem.take()))) } - unsafe fn destroy_raw(device: &AshDevice, (img, view): Self::Raw) { + unsafe fn destroy_raw(alloc: &mut AshAllocator, (img, view, mem): Self::Raw) { if view != vk::ImageView::null() { - device.destroy(view); + alloc.device().destroy(view); } if img != vk::Image::null() { - device.destroy(img); + alloc.device().destroy(img); + } + if let Some(mem) = mem { + alloc.dealloc(mem); } } } impl AshGpuResourceCreate for Texture {} -impl AshGpuResourceRemove for Texture {} +impl AshGpuResourceRemove for Texture { + fn put_to_garbage(garbage: &mut super::AshFrameGarbage, (image, image_view, mem): Self::Raw) { + if image != vk::Image::null() { + garbage.images.push(image); + } + if image_view != vk::ImageView::null() { + garbage.image_views.push(image_view); + } + if let Some(mem) = mem { + garbage.memory.push(mem); + } + } +} impl AshGpuResource for GraphicsPass { type Raw = vk::RenderPass; @@ -86,16 +135,18 @@ impl AshGpuResource for GraphicsPass { fn slotmap_mut(res: &mut AshResources) -> &mut SlotMap { &mut res.graphics_passes } - - unsafe fn create_raw(res: &AshResources, descr: &Self::Descriptor<'_>) -> Result { + unsafe fn create_raw( + res: &mut AshResources, + descr: &Self::Descriptor<'_>, + ) -> Result { let mut conv = CreateInfoConverter6::new(); let create_info = conv.graphics_pass(descr); - let raw = res.device.create(create_info)?; + let raw = res.device().create(create_info)?; Ok(raw.take()) } - unsafe fn destroy_raw(device: &AshDevice, raw: Self::Raw) { + unsafe fn destroy_raw(alloc: &mut AshAllocator, raw: Self::Raw) { if raw != vk::RenderPass::null() { - device.destroy(raw); + alloc.device().destroy(raw); } } } @@ -113,19 +164,21 @@ impl AshGpuResource for ShaderModule { fn slotmap_mut(res: &mut AshResources) -> &mut SlotMap { &mut res.shader_modules } - - unsafe fn create_raw(res: &AshResources, descr: &Self::Descriptor<'_>) -> Result { + unsafe fn create_raw( + res: &mut AshResources, + descr: &Self::Descriptor<'_>, + ) -> Result { let code = compie_into_spv(&descr.source)?; let create_info = vk::ShaderModuleCreateInfo::builder().code(&code).build(); - let raw = res.device.create(&create_info)?; + let raw = res.device().create(&create_info)?; if let Some(label) = descr.label { - res.device.object_name(raw.raw(), label); + res.device().object_name(raw.raw(), label); } Ok(raw.take()) } - unsafe fn destroy_raw(device: &AshDevice, raw: Self::Raw) { + unsafe fn destroy_raw(alloc: &mut AshAllocator, raw: Self::Raw) { if raw != vk::ShaderModule::null() { - device.destroy(raw); + alloc.device().destroy(raw); } } } @@ -144,18 +197,21 @@ impl AshGpuResource for BindGroupLayout { &mut res.bind_group_layouts } - unsafe fn create_raw(res: &AshResources, descr: &Self::Descriptor<'_>) -> Result { + unsafe fn create_raw( + res: &mut AshResources, + descr: &Self::Descriptor<'_>, + ) -> Result { let mut conv = CreateInfoConverter2::new(); let create_info = conv.bind_group_layout(descr); - let raw = res.device.create(create_info)?; + let raw = res.device().create(create_info)?; if let Some(label) = descr.label { - res.device.object_name(raw.raw(), label); + res.device().object_name(raw.raw(), label); } Ok(raw.take()) } - unsafe fn destroy_raw(device: &AshDevice, raw: Self::Raw) { + unsafe fn destroy_raw(alloc: &mut AshAllocator, raw: Self::Raw) { if raw != vk::DescriptorSetLayout::null() { - device.destroy(raw); + alloc.device().destroy(raw); } } } @@ -174,18 +230,21 @@ impl AshGpuResource for PipelineLayout { &mut res.pipeline_layouts } - unsafe fn create_raw(res: &AshResources, descr: &Self::Descriptor<'_>) -> Result { + unsafe fn create_raw( + res: &mut AshResources, + descr: &Self::Descriptor<'_>, + ) -> Result { let mut conv = CreateInfoConverter2::new(); let create_info = conv.pipeline_layout(res, descr); - let raw = res.device.create(create_info)?; + let raw = res.device().create(create_info)?; if let Some(label) = descr.label { - res.device.object_name(raw.raw(), label); + res.device().object_name(raw.raw(), label); } Ok(raw.take()) } - unsafe fn destroy_raw(device: &AshDevice, raw: Self::Raw) { + unsafe fn destroy_raw(alloc: &mut AshAllocator, raw: Self::Raw) { if raw != vk::PipelineLayout::null() { - device.destroy(raw); + alloc.device().destroy(raw); } } } @@ -204,30 +263,33 @@ impl AshGpuResource for GraphicsPipeline { &mut res.graphics_pipelines } - unsafe fn create_raw(res: &AshResources, descr: &Self::Descriptor<'_>) -> Result { + unsafe fn create_raw( + res: &mut AshResources, + descr: &Self::Descriptor<'_>, + ) -> Result { let mut conv = CreateInfoConverter2::new(); let create_infos = conv.graphics_pipeline_descriptor(res, std::slice::from_ref(descr)); match res - .device + .device() .create_graphics_pipelines(res.pipeline_cache, create_infos, None) { Ok(raw) => { - let raw = res.device.hold(raw[0]); + let raw = res.device().hold(raw[0]); if let Some(label) = descr.label { - res.device.object_name(raw.raw(), label); + res.device().object_name(raw.raw(), label); } Ok(raw.take()) } Err((pipelines, e)) => { - res.device.destroy(pipelines); + res.device().destroy(pipelines); Err(e.into()) } } } - unsafe fn destroy_raw(device: &AshDevice, raw: Self::Raw) { + unsafe fn destroy_raw(alloc: &mut AshAllocator, raw: Self::Raw) { if raw != vk::Pipeline::null() { - device.destroy_pipeline(raw, None); + alloc.device().destroy_pipeline(raw, None); } } } @@ -246,30 +308,33 @@ impl AshGpuResource for ComputePipeline { &mut res.compute_pipelines } - unsafe fn create_raw(res: &AshResources, descr: &Self::Descriptor<'_>) -> Result { + unsafe fn create_raw( + res: &mut AshResources, + descr: &Self::Descriptor<'_>, + ) -> Result { let mut conv = CreateInfoConverter2::new(); let create_infos = conv.compute_pipeline_descriptor(res, std::slice::from_ref(descr)); match res - .device + .device() .create_compute_pipelines(res.pipeline_cache, create_infos, None) { Ok(raw) => { - let raw = res.device.hold(raw[0]); + let raw = res.device().hold(raw[0]); if let Some(label) = descr.label { - res.device.object_name(raw.raw(), label); + res.device().object_name(raw.raw(), label); } Ok(raw.take()) } Err((pipelines, e)) => { - res.device.destroy(pipelines); + res.device().destroy(pipelines); Err(e.into()) } } } - unsafe fn destroy_raw(device: &AshDevice, raw: Self::Raw) { + unsafe fn destroy_raw(alloc: &mut AshAllocator, raw: Self::Raw) { if raw != vk::Pipeline::null() { - device.destroy_pipeline(raw, None); + alloc.device().destroy_pipeline(raw, None); } } } @@ -289,9 +354,11 @@ impl AshGpuResource for RayTracingPipeline { fn slotmap_mut(res: &mut AshResources) -> &mut SlotMap { &mut res.ray_tracing_pipelines } - - unsafe fn create_raw(res: &AshResources, descr: &Self::Descriptor<'_>) -> Result { - let ext = res.device.ext_raytracing_pipeline()?; + unsafe fn create_raw( + res: &mut AshResources, + descr: &Self::Descriptor<'_>, + ) -> Result { + let ext = res.device().ext_raytracing_pipeline()?; let mut conv = CreateInfoConverter2::new(); let create_infos = conv.ray_tracing_pipeline_descriptor(res, std::slice::from_ref(descr)); let raw = ext.create_ray_tracing_pipelines( @@ -300,16 +367,16 @@ impl AshGpuResource for RayTracingPipeline { create_infos, None, )?; - let raw = res.device.hold(raw[0]); + let raw = res.device().hold(raw[0]); if let Some(label) = descr.label { - res.device.object_name(raw.raw(), label); + res.device().object_name(raw.raw(), label); } Ok(raw.take()) } - unsafe fn destroy_raw(device: &AshDevice, raw: Self::Raw) { + unsafe fn destroy_raw(res: &mut AshAllocator, raw: Self::Raw) { if raw != vk::Pipeline::null() { - device.destroy_pipeline(raw, None); + res.device().destroy_pipeline(raw, None); } } } diff --git a/crates/render-ash/src/resources/traits.rs b/crates/render-ash/src/resources/traits.rs index bb57dbd..b009b22 100644 --- a/crates/render-ash/src/resources/traits.rs +++ b/crates/render-ash/src/resources/traits.rs @@ -3,20 +3,19 @@ use std::hash::{Hash, Hasher}; use pulz_render::backend::GpuResource; use slotmap::SlotMap; -use super::{replay::AsResourceRecord, AshResources, U64HashMap}; -use crate::{device::AshDevice, Result}; +use super::{replay::AsResourceRecord, AshFrameGarbage, AshResources, U64HashMap}; +use crate::{alloc::AshAllocator, Result}; pub trait AshGpuResource: GpuResource + 'static { - type Raw: Copy; - - fn slotmap(res: &AshResources) -> &SlotMap; - fn slotmap_mut(res: &mut AshResources) -> &mut SlotMap; - + type Raw; unsafe fn create_raw( - res: &AshResources, + alloc: &mut AshResources, descriptor: &Self::Descriptor<'_>, ) -> Result; - unsafe fn destroy_raw(device: &AshDevice, raw: Self::Raw); + unsafe fn destroy_raw(alloc: &mut AshAllocator, raw: Self::Raw); + + fn slotmap(res: &AshResources) -> &SlotMap; + fn slotmap_mut(res: &mut AshResources) -> &mut SlotMap; } fn hash_one(value: &T) -> u64 { @@ -37,10 +36,6 @@ where } pub trait AshGpuResourceCreate: AshGpuResource { - #[inline] - fn get_raw(res: &AshResources, key: Self) -> Option<&Self::Raw> { - Self::slotmap(res).get(key) - } #[inline] fn create(res: &mut AshResources, descr: &Self::Descriptor<'_>) -> Result { unsafe { @@ -49,20 +44,38 @@ pub trait AshGpuResourceCreate: AshGpuResource { Ok(key) } } - fn clear(res: &mut AshResources) { - let device = res.device.clone(); - for (_key, raw) in Self::slotmap_mut(res).drain() { +} + +pub trait AshGpuResourceCollection { + type Resource: AshGpuResource; + unsafe fn clear_destroy(&mut self, alloc: &mut AshAllocator); + unsafe fn destroy(&mut self, key: Self::Resource, alloc: &mut AshAllocator) -> bool; +} +impl AshGpuResourceCollection for SlotMap { + type Resource = R; + unsafe fn clear_destroy(&mut self, alloc: &mut AshAllocator) { + for (_key, raw) in self.drain() { unsafe { - Self::destroy_raw(&device, raw); + R::destroy_raw(alloc, raw); } } } + unsafe fn destroy(&mut self, key: Self::Resource, alloc: &mut AshAllocator) -> bool { + if let Some(raw) = self.remove(key) { + R::destroy_raw(alloc, raw); + true + } else { + false + } + } } pub trait AshGpuResourceRemove: AshGpuResource { - fn remove(res: &mut AshResources, key: Self) -> bool { + fn put_to_garbage(garbage: &mut AshFrameGarbage, raw: Self::Raw); + fn destroy(res: &mut AshResources, key: Self) -> bool { if let Some(raw) = Self::slotmap_mut(res).remove(key) { - unsafe { Self::destroy_raw(&res.device, raw) } + let garbage = res.current_frame_garbage_mut(); + Self::put_to_garbage(garbage, raw); true } else { false @@ -90,13 +103,4 @@ where } Ok(key) } - fn clear(res: &mut AshResources) { - Self::get_hashs_mut(res).clear(); - let device = res.device.clone(); - for (_key, raw) in Self::slotmap_mut(res).drain() { - unsafe { - Self::destroy_raw(&device, raw); - } - } - } } diff --git a/crates/render-ash/src/swapchain.rs b/crates/render-ash/src/swapchain.rs index d42bc53..dac1ad2 100644 --- a/crates/render-ash/src/swapchain.rs +++ b/crates/render-ash/src/swapchain.rs @@ -1,44 +1,44 @@ -use std::sync::Arc; +use std::rc::Rc; use ash::{extensions::khr, vk}; use pulz_render::{ - math::uvec2, + math::{uvec2, USize2}, texture::{Texture, TextureFormat}, }; -use pulz_window::{RawWindow, Size2, Window, WindowId}; +use pulz_window::{ + HasRawWindowAndDisplayHandle, Size2, Window, WindowDescriptor, WindowId, Windows, +}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; -use slotmap::Key; +use tracing::info; use crate::{ convert::VkInto, device::AshDevice, drop_guard::{Destroy, Guard}, instance::AshInstance, + resources::AshResources, AshRendererFull, Error, Result, }; -pub struct Surface { - instance: Arc, - surface_raw: vk::SurfaceKHR, -} - -impl Drop for Surface { - fn drop(&mut self) { - if self.surface_raw != vk::SurfaceKHR::null() { - let ext_surface = self.instance.ext_surface().unwrap(); - unsafe { - ext_surface.destroy_surface(self.surface_raw, None); - } +impl Destroy for vk::SurfaceKHR { + type Context = AshInstance; + #[inline] + unsafe fn destroy(self, instance: &AshInstance) { + if self != Self::null() { + let ext_surface = instance.ext_surface().unwrap(); + ext_surface.destroy_surface(self, None); } } } -impl Destroy for vk::SurfaceKHR { - type Context = AshInstance; +impl Destroy for vk::SwapchainKHR { + type Context = AshDevice; #[inline] - unsafe fn destroy(self, instance: &AshInstance) { - let ext_surface = instance.ext_surface().unwrap(); - ext_surface.destroy_surface(self, None); + unsafe fn destroy(self, device: &AshDevice) { + if self != Self::null() { + let ext_swapchain = device.ext_swapchain().unwrap(); + ext_swapchain.destroy_swapchain(self, None); + } } } @@ -163,56 +163,16 @@ impl AshInstance { Ok(surface) } - #[cfg(any(target_os = "macos", target_os = "ios"))] - unsafe fn create_surface_apple(&self, view: *mut c_void) -> Result { - use ash::extensions::ext; - use core_graphics_types::{base::CGFloat, geometry::CGRect}; - use objc::{ - class, msg_send, - runtime::{Object, BOOL, YES}, - sel, sel_impl, - }; - - // early check extension - if !self.has_instance_extension(ext::MetalSurface::name()) { - return Err(Error::ExtensionNotSupported(ext::MetalSurface::name())); - } - - let layer = unsafe { - let view = view as *mut Object; - let existing: *mut Object = msg_send![view, layer]; - let layer_class = class!(CAMetalLayer); - - if !existing.is_null() && msg_send![existing, isKindOfClass: layer_class] == YES { - existing - } else { - let layer: *mut Object = msg_send![layer_class, new]; - let _: () = msg_send![view, setLayer: layer]; - let bounds: CGRect = msg_send![view, bounds]; - let () = msg_send![layer, setBounds: bounds]; - - let window: *mut Object = msg_send![view, window]; - if !window.is_null() { - let scale_factor: CGFloat = msg_send![window, backingScaleFactor]; - let () = msg_send![layer, setContentsScale: scale_factor]; - } - layer - } - }; - - self.create_surface_metal(layer as *mut _) - } - /// SAFETY: display and window handles must be valid for the complete lifetime of surface unsafe fn create_surface_raw( &self, - display_handle: RawDisplayHandle, - window_handle: RawWindowHandle, + raw_display_handle: RawDisplayHandle, + raw_window_handle: RawWindowHandle, ) -> Result { // check for surface-extension self.ext_surface()?; - match (display_handle, window_handle) { + match (raw_display_handle, raw_window_handle) { #[cfg(all( unix, not(target_os = "android"), @@ -249,12 +209,22 @@ impl AshInstance { self.create_surface_win32(w.hinstance, w.hwnd)? } #[cfg(target_os = "macos")] - (RawDisplayHandle::AppKit(_), RawWindowHandle::AppKit(w)) => { - self.create_surface_apple(w.ns_view)? + (RawDisplayHandle::AppKit(_), RawWindowHandle::AppKit(h)) => { + use raw_window_metal::{appkit, Layer}; + let layer = match appkit::metal_layer_from_handle(h) { + Layer::Existing(layer) | Layer::Allocated(layer) => layer.cast(), + Layer::None => return Err(vk::Result::ERROR_INITIALIZATION_FAILED), + }; + self.create_surface_metal(layer)? } #[cfg(target_os = "ios")] (RawDisplayHandle::UiKit(_), RawWindowHandle::UiKit(w)) => { - self.create_surface_apple(w.ui_view)? + use raw_window_metal::{uikit, Layer}; + let layer = match uikit::metal_layer_from_handle(h) { + Layer::Existing(layer) | Layer::Allocated(layer) => layer.cast(), + Layer::None => return Err(vk::Result::ERROR_INITIALIZATION_FAILED), + }; + self.create_surface_metal(layer)? } _ => Err(Error::UnsupportedWindowSystem), @@ -262,10 +232,12 @@ impl AshInstance { } /// SAFETY: display and window handles must be valid for the complete lifetime of surface - pub(crate) fn new_surface(&self, window: &dyn RawWindow) -> Result> { - let surface_raw = unsafe { - self.create_surface_raw(window.raw_display_handle(), window.raw_window_handle())? - }; + pub(crate) unsafe fn new_surface( + &self, + window: &dyn HasRawWindowAndDisplayHandle, + ) -> Result> { + let surface_raw = + self.create_surface_raw(window.raw_display_handle(), window.raw_window_handle())?; Ok(Guard::new(self, surface_raw)) } } @@ -382,8 +354,7 @@ impl SwapchainSupportDetail { } } -pub struct SurfaceSwapchain { - device: Arc, +pub struct AshSurfaceSwapchain { surface_raw: vk::SurfaceKHR, swapchain_raw: vk::SwapchainKHR, size: Size2, @@ -393,46 +364,42 @@ pub struct SurfaceSwapchain { image_usage: vk::ImageUsageFlags, images: Vec, image_views: Vec, - texture_id: Texture, + textures: Vec, acquired_image: u32, - retired_swapchains: Vec, - retired_image_views: Vec, + window: Rc, // for keeping ownership } -impl AshDevice { - pub(crate) fn new_swapchain( - self: &Arc, - surface: Guard<'_, vk::SurfaceKHR>, - window_descriptor: &Window, - ) -> Result { - let (image_count, present_mode) = if window_descriptor.vsync { +impl AshSurfaceSwapchain { + fn window_swapchain_config(window: &WindowDescriptor) -> (u32, vk::PresentModeKHR, USize2) { + let (image_count, present_mode) = if window.vsync { (3, vk::PresentModeKHR::MAILBOX) } else { (2, vk::PresentModeKHR::IMMEDIATE) }; - let mut swapchain = SurfaceSwapchain { - device: self.clone(), - surface_raw: surface.take(), + (image_count, present_mode, window.size) + } + + fn new_unconfigured( + window: Rc, + surface_raw: vk::SurfaceKHR, + ) -> Self { + AshSurfaceSwapchain { + surface_raw, swapchain_raw: vk::SwapchainKHR::null(), - size: window_descriptor.size, - image_count, + size: USize2::new(0, 0), + image_count: 0, surface_format: Default::default(), - present_mode, + present_mode: vk::PresentModeKHR::IMMEDIATE, // TODO: custom usage image_usage: vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::TRANSFER_DST, images: Vec::new(), image_views: Vec::new(), - texture_id: Texture::null(), + textures: Vec::new(), acquired_image: !0, - retired_swapchains: Vec::new(), - retired_image_views: Vec::new(), - }; - swapchain.configure()?; - Ok(swapchain) + window, + } } -} -impl SurfaceSwapchain { #[inline] pub fn size(&self) -> Size2 { self.size @@ -448,11 +415,6 @@ impl SurfaceSwapchain { self.surface_format.format.vk_into() } - #[inline] - pub fn texture_id(&self) -> Texture { - self.texture_id - } - #[inline] pub fn present_mode(&self) -> vk::PresentModeKHR { self.present_mode @@ -529,26 +491,56 @@ impl SurfaceSwapchain { self.acquired_image != !0 } - pub fn configure_with( - &mut self, - suggested_size: Size2, - suggested_present_mode: vk::PresentModeKHR, - ) -> Result<()> { - self.size = suggested_size; + pub fn put_to_garbage(&mut self, res: &mut AshResources) -> vk::SwapchainKHR { + let old_swapchain = self.swapchain_raw; + if old_swapchain != vk::SwapchainKHR::null() { + self.swapchain_raw = vk::SwapchainKHR::null(); + for texture_id in self.textures.drain(..) { + res.textures.remove(texture_id); // forget texture without destroy! + } + let garbage = res.current_frame_garbage_mut(); + garbage.image_views.append(&mut self.image_views); + garbage.swapchains.push(old_swapchain); + self.images.clear(); // images owned by swapchain! + } + old_swapchain + } + + pub fn destroy_with_surface(mut self, res: &mut AshResources) -> Result<()> { + self.put_to_garbage(res); + res.clear_garbage().unwrap(); + // SAFETY: clear garbage waits for the device to be idle + unsafe { + let surface = self.surface_raw; + if surface != vk::SurfaceKHR::null() { + self.surface_raw = vk::SurfaceKHR::null(); + if let Ok(ext_surface) = res.device().instance().ext_surface() { + ext_surface.destroy_surface(surface, None); + } + } + } + Ok(()) + } + + fn configure_with(&mut self, res: &mut AshResources, window: &WindowDescriptor) -> Result<()> { + let (suggested_image_count, suggested_present_mode, suggessted_size) = + Self::window_swapchain_config(window); + self.image_count = suggested_image_count; + self.size = suggessted_size; self.present_mode = suggested_present_mode; - self.configure() + self.configure(res) } - pub fn configure(&mut self) -> Result<()> { + fn configure(&mut self, res: &mut AshResources) -> Result<()> { + // check swapchain support + res.device().ext_swapchain()?; + // TODO: also reconfigure on resize, and when presenting results in `Outdated/Lost` // TODO: pass swapchain format to graph - let ext_swapchain = self.device.ext_swapchain()?; - - let swapchain_support_info = self - .device + let swapchain_support_info = res .instance() - .query_swapchain_support(self.surface_raw, self.device.physical_device()) + .query_swapchain_support(self.surface_raw, res.device().physical_device()) .ok_or(Error::NoSwapchainSupport)?; if !swapchain_support_info @@ -593,17 +585,13 @@ impl SurfaceSwapchain { } let shared_queue_family_indices = [ - self.device.queues().graphics_family, - self.device.queues().present_family, + res.device().queues().graphics_family, + res.device().queues().present_family, ]; - let old_swapchain = self.swapchain_raw; - if old_swapchain != vk::SwapchainKHR::null() { - // swapchain is retired, even if `create_swapchain` fails - self.swapchain_raw = vk::SwapchainKHR::null(); - self.retired_swapchains.push(old_swapchain); - } + // old swapchain is retired, even if `create_swapchain` fails + let old_swapchain = self.put_to_garbage(res); self.swapchain_raw = unsafe { - ext_swapchain.create_swapchain( + res.device().ext_swapchain()?.create_swapchain( &vk::SwapchainCreateInfoKHR::builder() .surface(self.surface_raw) .min_image_count(self.image_count) @@ -634,13 +622,21 @@ impl SurfaceSwapchain { )? }; - self.images = unsafe { ext_swapchain.get_swapchain_images(self.swapchain_raw)? }; - self.retired_image_views.append(&mut self.image_views); - for image in &self.images { + debug_assert_ne!(vk::SwapchainKHR::null(), self.swapchain_raw); + debug_assert_eq!(0, self.images.len()); + debug_assert_eq!(0, self.image_views.len()); + debug_assert_eq!(0, self.textures.len()); + self.images = unsafe { + res.device() + .ext_swapchain()? + .get_swapchain_images(self.swapchain_raw)? + }; + + for image in self.images.iter().copied() { unsafe { - let image_view = self.device.create_image_view( + let image_view = res.device().create_image_view( &vk::ImageViewCreateInfo::builder() - .image(*image) + .image(image) .view_type(vk::ImageViewType::TYPE_2D) .format(self.surface_format.format) .subresource_range( @@ -656,20 +652,20 @@ impl SurfaceSwapchain { None, )?; self.image_views.push(image_view); + let texture = res.textures.insert((image, image_view, None)); + self.textures.push(texture); } } Ok(()) } - fn acquire_next_image( + pub(crate) fn acquire_next_image( &mut self, + res: &mut AshResources, timeout: u64, signal_semaphore: vk::Semaphore, ) -> Result> { - let device = self.device.clone(); - let ext_swapchain = device.ext_swapchain()?; - if self.is_acquired() { return Err(Error::SwapchainImageAlreadyAcquired); } @@ -677,7 +673,7 @@ impl SurfaceSwapchain { // TODO: better sync mechanism let result = unsafe { - match ext_swapchain.acquire_next_image( + match res.device().ext_swapchain()?.acquire_next_image( self.swapchain_raw, timeout, signal_semaphore, @@ -685,8 +681,8 @@ impl SurfaceSwapchain { ) { Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => { // re-configure and re-acquire - self.configure()?; - ext_swapchain.acquire_next_image( + self.configure(res)?; + res.device().ext_swapchain()?.acquire_next_image( self.swapchain_raw, timeout, signal_semaphore, @@ -715,70 +711,80 @@ impl SurfaceSwapchain { index, image: self.images[index as usize], image_view: self.image_views[index as usize], + texture: self.textures[index as usize], suboptimal, })) } } -impl Drop for SurfaceSwapchain { +impl Drop for AshSurfaceSwapchain { fn drop(&mut self) { - if self.swapchain_raw != vk::SwapchainKHR::null() - || self.surface_raw != vk::SurfaceKHR::null() - || !self.retired_swapchains.is_empty() - || !self.retired_image_views.is_empty() - { - unsafe { - self.device.device_wait_idle().unwrap(); - } - } - - for image_view in self.image_views.drain(..) { - unsafe { - self.device.destroy_image_view(image_view, None); - } - } - // don't destroy images obtained from get_swapchain_images! They are destroyed together with the swapchain object. - - let ext_swapchain = self.device.ext_swapchain().unwrap(); - for r in self.retired_swapchains.drain(..) { - unsafe { - ext_swapchain.destroy_swapchain(r, None); - } - } - - self.images.clear(); - if self.swapchain_raw != vk::SwapchainKHR::null() { - unsafe { - ext_swapchain.destroy_swapchain(self.swapchain_raw, None); - } - } - - if self.surface_raw != vk::SurfaceKHR::null() { - let ext_surface = self.device.instance().ext_surface().unwrap(); - unsafe { - ext_surface.destroy_surface(self.surface_raw, None); - } - } + assert_eq!(0, self.images.len()); + assert_eq!(0, self.image_views.len()); + assert_eq!(0, self.textures.len()); + assert_eq!(vk::SwapchainKHR::null(), self.swapchain_raw); + assert_eq!(vk::SurfaceKHR::null(), self.surface_raw); } } impl AshRendererFull { - pub(crate) fn insert_swapchain( + pub(crate) fn init_swapchain( &mut self, - swapchain: SurfaceSwapchain, window_id: WindowId, - ) -> Result<()> { - if let Some(old_swapchain) = self.surfaces.insert(window_id, swapchain) { - self.res.textures.remove(old_swapchain.texture_id); + window_descriptor: &Window, + window: Rc, + surface: Guard<'_, vk::SurfaceKHR>, + ) -> Result<&mut AshSurfaceSwapchain> { + assert!(self + .surfaces + .insert( + window_id, + AshSurfaceSwapchain::new_unconfigured(window, surface.take()) + ) + .is_none()); + let swapchain = self.surfaces.get_mut(window_id).unwrap(); + swapchain.configure_with(&mut self.res, window_descriptor)?; + Ok(swapchain) + } + + pub(crate) fn destroy_swapchain(&mut self, window_id: WindowId) -> Result<()> { + let Some(swapchain) = self.surfaces.remove(window_id) else { + return Err(Error::WindowNotAvailable); + }; + swapchain.destroy_with_surface(&mut self.res)?; + Ok(()) + } + + pub(crate) fn destroy_all_swapchains(&mut self) -> Result<()> { + for (_window_id, swapchain) in self.surfaces.drain() { + swapchain.destroy_with_surface(&mut self.res)?; } - let surface = &mut self.surfaces[window_id]; - surface.texture_id = self - .res - .textures - .insert((vk::Image::null(), vk::ImageView::null())); Ok(()) } + pub(crate) fn reconfigure_swapchains(&mut self, windows: &Windows) { + self.surfaces.retain(|window_id, surface_swapchain| { + let Some(window) = windows.get(window_id) else { + surface_swapchain.put_to_garbage(&mut self.res); + return false; + }; + //TODO: re-create also the surface, when SURFACE_LOST was returned in earlier calls. + //TODO: better resize check (don't compare size, but use a 'dirty'-flag, or listener) + //TODO: sync + if window.size != surface_swapchain.size() { + info!( + "surface sized changed: {} => {}", + surface_swapchain.size(), + window.size + ); + surface_swapchain + .configure_with(&mut self.res, &window) + .unwrap(); + } + true + }); + } + pub(crate) fn acquire_swapchain_image( &mut self, window_id: WindowId, @@ -787,13 +793,9 @@ impl AshRendererFull { ) -> Result> { let _ = tracing::trace_span!("AquireImage").entered(); let surface_swapchain = self.surfaces.get_mut(window_id).expect("swapchain"); - // TODO: create surface & swapchain on demand? Ok(surface_swapchain - .acquire_next_image(timeout, signal_semaphore)? - .map(|_swapchain_image| { - //TODO: associate texture - todo!() - })) + .acquire_next_image(&mut self.res, timeout, signal_semaphore)? + .map(|swapchain_image| swapchain_image.texture)) } pub(crate) fn get_num_acquired_swapchains(&self) -> usize { @@ -848,7 +850,6 @@ impl AshRendererFull { | Err(vk::Result::ERROR_OUT_OF_DATE_KHR) | Err(vk::Result::ERROR_SURFACE_LOST_KHR) => (), Err(e) => { - self.retire_swapchains(); return Err(e.into()); } } @@ -858,33 +859,20 @@ impl AshRendererFull { .zip(acquired_surface_swapchains.into_iter()) { if result == vk::Result::SUBOPTIMAL_KHR || result == vk::Result::ERROR_OUT_OF_DATE_KHR { - surface_swapchain.configure()?; + surface_swapchain.configure(&mut self.res)?; } else if result == vk::Result::ERROR_SURFACE_LOST_KHR { // TODO: re-create surface and re-configure swapchain } } - self.retire_swapchains(); - Ok(()) } - - fn retire_swapchains(&mut self) { - let frame = &mut self.frames[self.current_frame]; - for (_, surface_swapchain) in &mut self.surfaces { - frame - .retired_swapchains - .append(&mut surface_swapchain.retired_swapchains); - frame - .retired_image_views - .append(&mut surface_swapchain.retired_image_views); - } - } } pub struct AcquiredSwapchainImage { index: u32, pub image: vk::Image, pub image_view: vk::ImageView, + pub texture: Texture, pub suboptimal: bool, } diff --git a/crates/render-pipeline-core/Cargo.toml b/crates/render-pipeline-core/Cargo.toml index 80d54f5..faf334c 100644 --- a/crates/render-pipeline-core/Cargo.toml +++ b/crates/render-pipeline-core/Cargo.toml @@ -14,3 +14,4 @@ pulz-ecs = { path = "../ecs" } pulz-render = { path = "../render" } radsort = { workspace = true } +tracing = { workspace = true } diff --git a/crates/render-pipeline-core/src/deferred_3d/mod.rs b/crates/render-pipeline-core/src/deferred_3d/mod.rs index e524d92..fe3145b 100644 --- a/crates/render-pipeline-core/src/deferred_3d/mod.rs +++ b/crates/render-pipeline-core/src/deferred_3d/mod.rs @@ -10,6 +10,7 @@ use pulz_render::{ resources::{Slot, WriteSlot}, RenderGraphBuilder, }, + math::Mat4, texture::Texture, RenderSystemPhase, }; @@ -22,9 +23,10 @@ impl DeferredShadingModule { builder: &mut RenderGraphBuilder, cams_qry: Query<'_, (&Camera, &RenderTarget, Entity)>, ) { - for (_camera, render_target, entity) in cams_qry { + for (camera, render_target, entity) in cams_qry { let output = builder.add_pass(DeferredShadingPass { view_camera: entity, + projection: camera.projection_matrix, }); builder.export_texture(output.read(), render_target); @@ -45,6 +47,7 @@ impl Module for DeferredShadingModule { pub struct DeferredShadingPass { view_camera: Entity, + projection: Mat4, } impl PassGroup for DeferredShadingPass { diff --git a/crates/render-wgpu/Cargo.toml b/crates/render-wgpu/Cargo.toml index fd28d2b..07aba72 100644 --- a/crates/render-wgpu/Cargo.toml +++ b/crates/render-wgpu/Cargo.toml @@ -15,7 +15,7 @@ pulz-render = { path = "../render" } thiserror = { workspace = true } tracing = { workspace = true } slotmap = { workspace = true } -wgpu = "0.16" +wgpu = "0.18" raw-window-handle = { workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] @@ -23,16 +23,15 @@ pollster = "0.3" [dev-dependencies] anyhow = { workspace = true } -naga = "0.12" +naga = "0.14" pulz-window-winit = { path = "../window-winit" } pulz-render-pipeline-core = { path = "../render-pipeline-core" } [target.'cfg(not(target_os = "unknown"))'.dev-dependencies] tracing-subscriber = { workspace = true } -async-std = { workspace = true } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wgpu = { version = "0.16" , features = ["webgl"] } +wgpu = { version = "0.18" , features = ["webgl"] } tracing-wasm = { workspace = true } tracing-log = { workspace = true } console_error_panic_hook = { workspace = true } diff --git a/crates/render-wgpu/examples/render-wgpu-demo.rs b/crates/render-wgpu/examples/render-wgpu-demo.rs index 5cfc4a2..e480c52 100644 --- a/crates/render-wgpu/examples/render-wgpu-demo.rs +++ b/crates/render-wgpu/examples/render-wgpu-demo.rs @@ -15,14 +15,15 @@ async fn init() -> (Resources, EventLoop<()>, Rc, WinitWindowSystem) { info!("Initializing..."); let mut resources = Resources::new(); resources.install(CoreShadingModule); - resources.install(WgpuRenderer::new().await.unwrap()); - let event_loop = EventLoop::new(); + let event_loop = EventLoop::new().unwrap(); let (window_system, window_id, window) = WinitWindowModule::new(WindowDescriptor::default(), &event_loop) .unwrap() .install(&mut resources); + WgpuRenderer::new().await.unwrap().install(&mut resources); + // let mut schedule = resources.remove::().unwrap(); // schedule.init(&mut resources); // schedule.debug_dump_if_env(None).unwrap(); @@ -43,8 +44,7 @@ fn setup_demo_scene(resources: &mut Resources, window: WindowId) { } #[cfg(not(target_arch = "wasm32"))] -#[async_std::main] -async fn main() { +fn main() { // todo: run blocking! use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter}; let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); @@ -53,9 +53,11 @@ async fn main() { .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE) .init(); - let (resources, event_loop, _window, window_system) = init().await; + pollster::block_on(async move { + let (mut resources, event_loop, _window, window_system) = init().await; - window_system.run(resources, event_loop); + window_system.run(&mut resources, event_loop).unwrap(); + }) } #[cfg(target_arch = "wasm32")] diff --git a/crates/render-wgpu/src/lib.rs b/crates/render-wgpu/src/lib.rs index 7a05842..6f051a9 100644 --- a/crates/render-wgpu/src/lib.rs +++ b/crates/render-wgpu/src/lib.rs @@ -25,13 +25,15 @@ #![doc(html_no_source)] #![doc = include_str!("../README.md")] +use std::rc::Rc; + use convert::ConversionError; use graph::WgpuRenderGraph; use pulz_ecs::prelude::*; use pulz_render::{draw::DrawPhases, graph::RenderGraph, RenderModule, RenderSystemPhase}; use pulz_window::{ - listener::{WindowSystemListener, WindowSystemListeners}, - RawWindow, Window, WindowId, Windows, WindowsMirror, + listener::WindowSystemListener, HasRawWindowAndDisplayHandle, Window, WindowId, Windows, + WindowsMirror, }; use resources::WgpuResources; use surface::Surface; @@ -85,30 +87,6 @@ struct WgpuRendererFull { tmp_surface_textures: Vec, } -fn backend_bits_from_env_or_default() -> wgpu::Backends { - wgpu::util::backend_bits_from_env().unwrap_or(wgpu::Backends::PRIMARY | wgpu::Backends::GL) -} - -async fn initialize_adapter_from_env_or_default( - instance: &wgpu::Instance, - backend_bits: wgpu::Backends, - compatible_surface: Option<&wgpu::Surface>, -) -> Option { - match wgpu::util::initialize_adapter_from_env(instance, backend_bits) { - Some(a) => Some(a), - None => { - let power_preference = wgpu::util::power_preference_from_env().unwrap_or_default(); - instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference, - force_fallback_adapter: false, - compatible_surface, - }) - .await - } - } -} - impl WgpuRendererFull { async fn for_adapter(instance: wgpu::Instance, adapter: wgpu::Adapter) -> Result { let trace_dir = std::env::var("WGPU_TRACE"); @@ -216,15 +194,11 @@ pub struct WgpuRenderer(WgpuRendererInner); impl WgpuRenderer { pub async fn new() -> Result { - let backends = backend_bits_from_env_or_default(); + let backends = wgpu::util::backend_bits_from_env().unwrap_or(wgpu::Backends::PRIMARY); let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends, ..Default::default() }); - if let Some(adapter) = wgpu::util::initialize_adapter_from_env(&instance, backends) { - let renderer = WgpuRendererFull::for_adapter(instance, adapter).await?; - return Ok(Self(WgpuRendererInner::Full(renderer))); - } #[cfg(target_arch = "wasm32")] { // full-initialization on wasm32: @@ -240,41 +214,33 @@ impl WgpuRenderer { Ok(Self(WgpuRendererInner::Early { instance })) } - async fn default_adapter( - instance: &wgpu::Instance, - compatible_surface: Option<&wgpu::Surface>, - ) -> Result { - let power_preference = wgpu::util::power_preference_from_env().unwrap_or_default(); - instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference, - force_fallback_adapter: false, - compatible_surface, - }) - .await - .ok_or(Error::NoAdapter) - } - fn init_window( &mut self, window_id: WindowId, window_descriptor: &Window, - window_raw: &dyn RawWindow, + window: Rc, ) -> Result<&mut WgpuRendererFull> { if let WgpuRendererInner::Full(renderer) = &mut self.0 { renderer.surfaces.remove(window_id); // replaces old surface - let surface = Surface::create(&renderer.instance, window_descriptor, window_raw)?; + let surface = Surface::create(&renderer.instance, window_descriptor, window)?; renderer.surfaces.insert(window_id, surface); } else { // Delayed initialization #[cfg(not(target_arch = "wasm32"))] { - let WgpuRendererInner::Early { instance } = std::mem::replace(&mut self.0, WgpuRendererInner::Tmp) else { + let WgpuRendererInner::Early { instance } = + std::mem::replace(&mut self.0, WgpuRendererInner::Tmp) + else { panic!("unexpected state"); }; - let surface = Surface::create(&instance, window_descriptor, window_raw)?; + let surface = Surface::create(&instance, window_descriptor, window)?; let mut renderer = pollster::block_on(async { - let adapter = Self::default_adapter(&instance, Some(&surface)).await?; + let adapter = wgpu::util::initialize_adapter_from_env_or_default( + &instance, + Some(&surface), + ) + .await + .ok_or(Error::NoAdapter)?; WgpuRendererFull::for_adapter(instance, adapter).await })?; renderer.surfaces.insert(window_id, surface); @@ -290,11 +256,15 @@ impl WgpuRenderer { fn init(&mut self) -> Result<&mut WgpuRendererFull> { #[cfg(not(target_arch = "wasm32"))] if !matches!(self.0, WgpuRendererInner::Full { .. }) { - let WgpuRendererInner::Early { instance } = std::mem::replace(&mut self.0, WgpuRendererInner::Tmp) else { + let WgpuRendererInner::Early { instance } = + std::mem::replace(&mut self.0, WgpuRendererInner::Tmp) + else { panic!("unexpected state"); }; let renderer = pollster::block_on(async { - let adapter = Self::default_adapter(&instance, None).await?; + let adapter = wgpu::util::initialize_adapter_from_env_or_default(&instance, None) + .await + .ok_or(Error::NoAdapter)?; WgpuRendererFull::for_adapter(instance, adapter).await })?; self.0 = WgpuRendererInner::Full(renderer); @@ -314,33 +284,28 @@ impl WgpuRenderer { } } -struct WgpuRendererInitWindowSystemListener(ResourceId); - -impl WindowSystemListener for WgpuRendererInitWindowSystemListener { +impl WindowSystemListener for WgpuRenderer { fn on_created( - &self, - res: &Resources, + &mut self, window_id: WindowId, window_desc: &Window, - window_raw: &dyn RawWindow, + window: Rc, ) { - let Some(mut renderer) = res.borrow_res_mut_id(self.0) else { return }; - renderer - .init_window(window_id, window_desc, window_raw) - .unwrap(); + self.init_window(window_id, window_desc, window).unwrap(); } - fn on_resumed(&self, res: &Resources) { - let Some(mut renderer) = res.borrow_res_mut_id(self.0) else { return }; - renderer.init().unwrap(); + fn on_resumed(&mut self) { + self.init().unwrap(); } - fn on_closed(&self, res: &Resources, window_id: WindowId) { - let Some(mut renderer) = res.borrow_res_mut_id(self.0) else { return }; - let WgpuRendererInner::Full(renderer) = &mut renderer.0 else { return }; + fn on_closed(&mut self, window_id: WindowId) { + let WgpuRendererInner::Full(renderer) = &mut self.0 else { + return; + }; renderer.surfaces.remove(window_id); } - fn on_suspended(&self, res: &Resources) { - let Some(mut renderer) = res.borrow_res_mut_id(self.0) else { return }; - let WgpuRendererInner::Full(renderer) = &mut renderer.0 else { return }; + fn on_suspended(&mut self) { + let WgpuRendererInner::Full(renderer) = &mut self.0 else { + return; + }; renderer.surfaces.clear(); } } @@ -353,11 +318,8 @@ impl ModuleWithOutput for WgpuRenderer { } fn install_resources(self, res: &mut Resources) -> &mut Self { - let listeners_id = res.init_unsend::(); let resource_id = res.insert_unsend(self); - res.get_mut_id(listeners_id) - .unwrap() - .insert(WgpuRendererInitWindowSystemListener(resource_id)); + res.init_meta_id::(resource_id); res.get_mut_id(resource_id).unwrap() } diff --git a/crates/render-wgpu/src/surface.rs b/crates/render-wgpu/src/surface.rs index f00b339..ad6be6d 100644 --- a/crates/render-wgpu/src/surface.rs +++ b/crates/render-wgpu/src/surface.rs @@ -1,6 +1,9 @@ -use std::ops::{Deref, DerefMut}; +use std::{ + ops::{Deref, DerefMut}, + rc::Rc, +}; -use pulz_window::{RawWindow, Size2, Window}; +use pulz_window::{HasRawWindowAndDisplayHandle, Size2, Window}; use tracing::info; pub struct Surface { @@ -8,20 +11,22 @@ pub struct Surface { size: Size2, vsync: bool, format: wgpu::TextureFormat, + window: Rc, } impl Surface { pub fn create( instance: &wgpu::Instance, - window: &Window, - window_handle: &dyn RawWindow, + window_descriptor: &Window, + window: Rc, ) -> Result { - let surface = unsafe { instance.create_surface(&window_handle)? }; + let surface = unsafe { instance.create_surface(&*window)? }; Ok(Self { surface, - size: window.size, - vsync: window.vsync, + size: window_descriptor.size, + vsync: window_descriptor.vsync, format: wgpu::TextureFormat::Rgba8UnormSrgb, + window, }) } diff --git a/crates/render/src/backend.rs b/crates/render/src/backend.rs index 378a453..f45a98d 100644 --- a/crates/render/src/backend.rs +++ b/crates/render/src/backend.rs @@ -1,3 +1,12 @@ +use pulz_assets::Handle; + +use crate::{ + buffer::{Buffer, BufferUsage}, + camera::RenderTarget, + graph::resources::PhysicalResource, + texture::{Texture, TextureDimensions, TextureFormat, TextureUsage}, +}; + pub trait GpuResource: slotmap::Key { type Descriptor<'l>; } @@ -22,3 +31,18 @@ pub trait CommandEncoder { fn push_debug_group(&mut self, label: &str); fn pop_debug_group(&mut self); } + +pub trait PhysicalResourceResolver { + fn resolve_render_target( + &mut self, + render_target: &RenderTarget, + ) -> Option>; + fn resolve_buffer(&mut self, handle: &Handle) -> Option>; + fn create_transient_texture( + &mut self, + format: TextureFormat, + size: TextureDimensions, + usage: TextureUsage, + ) -> Option; + fn create_transient_buffer(&mut self, size: usize, usage: BufferUsage) -> Option; +} diff --git a/crates/render/src/camera.rs b/crates/render/src/camera.rs index 33d7221..c60f737 100644 --- a/crates/render/src/camera.rs +++ b/crates/render/src/camera.rs @@ -1,9 +1,12 @@ use pulz_assets::{Assets, Handle}; use pulz_ecs::prelude::*; -use pulz_transform::math::{size2, Mat4, Size2, USize2}; -use pulz_window::{Window, WindowId, Windows}; +use pulz_transform::math::{size2, Mat4, Size2}; +use pulz_window::WindowId; -use crate::texture::Image; +use crate::{ + surface::{Surface, WindowSurfaces}, + texture::Image, +}; trait AsProjectionMatrix { fn as_projection_matrix(&self) -> Mat4; @@ -211,7 +214,6 @@ pub struct Camera { pub order: isize, zorder_optimization: bool, pub projection_matrix: Mat4, - target_info: Option, } impl Camera { @@ -220,95 +222,40 @@ impl Camera { order: 0, zorder_optimization: false, projection_matrix: Mat4::IDENTITY, - target_info: None, } } - pub fn to_logical_size(&self, physical_size: USize2) -> Size2 { - let scale_factor = self.target_info.as_ref().map_or(1.0, |t| t.scale_factor); - (physical_size.as_dvec2() / scale_factor).as_vec2() - } - - #[inline] - pub fn logical_target_size(&self) -> Option { - self.target_info - .as_ref() - .map(|t| self.to_logical_size(t.physical_size)) - } - - #[inline] - pub fn physical_target_size(&self) -> Option { - self.target_info.as_ref().map(|t| t.physical_size) - } } -#[derive(Component)] +#[derive(Component, Copy, Clone, Debug)] pub enum RenderTarget { Window(WindowId), Image(Handle), } -#[derive(Copy, Clone, PartialEq)] -struct RenderTargetInfo { - pub physical_size: USize2, - pub scale_factor: f64, -} - -impl RenderTargetInfo { - #[inline] - fn from_window(window: &Window) -> Self { - Self { - physical_size: window.size, - scale_factor: window.scale_factor, +impl RenderTarget { + pub fn resolve(&self, surfaces: &WindowSurfaces, _images: &Assets) -> Option { + match self { + RenderTarget::Window(window_id) => surfaces.get(*window_id).copied(), + RenderTarget::Image(_image_handle) => todo!("surface from image asset"), } } +} - #[inline] - fn from_image(image: &Image) -> Self { - Self { - physical_size: image.descriptor.dimensions.subimage_extents(), - scale_factor: 1.0, +pub fn update_projections_from_render_targets( + window_surfaces: &'_ WindowSurfaces, + images: &'_ Assets, + mut projections: Query<'_, (&'_ mut Projection, &'_ RenderTarget)>, +) { + for (projection, render_target) in projections.iter() { + if let Some(surface) = render_target.resolve(&window_surfaces, &images) { + projection.update_viewport(surface.logical_size()); } } } -pub fn update_cameras( - windows: Res<'_, Windows>, - images: Res<'_, Assets>, - mut cameras: Query< - '_, - ( - &'_ mut Camera, - Option<&'_ mut Projection>, - Option<&'_ RenderTarget>, - ), - >, -) { - for (camera, projection, render_target) in cameras.iter() { - let target_info = match render_target { - None => None, - Some(&RenderTarget::Window(window_id)) => { - windows.get(window_id).map(RenderTargetInfo::from_window) - } - Some(&RenderTarget::Image(image_handle)) => { - images.get(image_handle).map(RenderTargetInfo::from_image) - } - }; - let changed = target_info != camera.target_info; - if changed { - camera.target_info = target_info; - - if let Some(target_info) = target_info { - let logical_size = - (target_info.physical_size.as_dvec2() / target_info.scale_factor).as_vec2(); - // TODO: viewport size? - - // update projection - if let Some(projection) = projection { - projection.update_viewport(logical_size); - camera.zorder_optimization = projection.zorder_optimization(); - camera.projection_matrix = projection.as_projection_matrix(); - } - } - } +pub fn update_cameras_from_projections(mut cameras: Query<'_, (&'_ mut Camera, &'_ Projection)>) { + for (camera, projection) in cameras.iter() { + camera.zorder_optimization = projection.zorder_optimization(); + camera.projection_matrix = projection.as_projection_matrix(); } } diff --git a/crates/render/src/draw.rs b/crates/render/src/draw.rs index 0aeb386..02d7da2 100644 --- a/crates/render/src/draw.rs +++ b/crates/render/src/draw.rs @@ -8,7 +8,7 @@ use std::{ use atomic_refcell::AtomicRefCell; use dynsequence::{dyn_sequence, DynSequence}; use fnv::FnvHashMap as HashMap; -use pulz_ecs::{prelude::*, resource::ResState, system::param::SystemParam}; +use pulz_ecs::{prelude::*, resource::ResState, system::data::SystemData}; use crate::{backend::CommandEncoder, utils::hash::TypeIdHashMap, RenderSystemPhase}; @@ -299,7 +299,7 @@ impl Drop for DrawTarget<'_, I> { } } -impl SystemParam for Draw<'_, I> { +impl SystemData for Draw<'_, I> { type State = ResState>; type Fetch<'r> = Res<'r, DrawQueue>; type Item<'a> = Draw<'a, I>; diff --git a/crates/render/src/graph/access.rs b/crates/render/src/graph/access.rs index d9b7a97..e1aab8b 100644 --- a/crates/render/src/graph/access.rs +++ b/crates/render/src/graph/access.rs @@ -5,13 +5,14 @@ use std::{ }; use bitflags::bitflags; +use pulz_assets::Handle; use crate::{ buffer::{Buffer, BufferUsage}, + camera::RenderTarget, texture::{Texture, TextureDimensions, TextureFormat, TextureUsage}, }; - -pub trait ResourceAccess: Copy + Eq + Hash { +pub trait ResourceAccess: Copy + Eq + Default + Hash { // Bitflags! type Usage: Copy + Clone @@ -28,18 +29,21 @@ pub trait ResourceAccess: Copy + Eq + Hash { + Sub + Hash; - type Format: PartialEq + Debug + Copy; - type Size: PartialEq + Copy; + type Format: PartialEq + Debug + Copy + Hash; + type Size: PartialEq + Copy + Debug; + type ExternHandle: Debug; fn check_usage_is_pass_compatible(combined_usage: Self::Usage); fn default_format(usage: Self::Usage) -> Self::Format; + fn merge_size_max(a: Self::Size, b: Self::Size) -> Option; } impl ResourceAccess for Texture { type Usage = TextureUsage; type Format = TextureFormat; type Size = TextureDimensions; + type ExternHandle = RenderTarget; fn check_usage_is_pass_compatible(combined_usage: Self::Usage) { if combined_usage.is_non_attachment() { @@ -55,12 +59,52 @@ impl ResourceAccess for Texture { TextureFormat::Rgba8UnormSrgb } } + + #[inline] + fn merge_size_max(a: Self::Size, b: Self::Size) -> Option { + use TextureDimensions::*; + match (a, b) { + (D1(a), D1(b)) => Some(D1(a.max(b))), + (D2(a), D2(b)) => Some(D2(a.max(b))), + ( + D2Array { + size: a1, + array_len: a2, + }, + D2Array { + size: b1, + array_len: b2, + }, + ) => Some(D2Array { + size: a1.max(b1), + array_len: a2.max(b2), + }), + (Cube(a), Cube(b)) => Some(Cube(a.max(b))), + ( + CubeArray { + size: a1, + array_len: a2, + }, + CubeArray { + size: b1, + array_len: b2, + }, + ) => Some(CubeArray { + size: a1.max(b1), + array_len: a2.max(b2), + }), + (D3(a), D3(b)) => Some(D3(a.max(b))), + + _ => None, + } + } } impl ResourceAccess for Buffer { type Usage = BufferUsage; type Format = (); type Size = usize; + type ExternHandle = Handle; fn check_usage_is_pass_compatible(_combined_usage: Self::Usage) { panic!("Can't use buffer multiple times in the same pass"); @@ -68,6 +112,11 @@ impl ResourceAccess for Buffer { #[inline] fn default_format(_usage: Self::Usage) -> Self::Format {} + + #[inline] + fn merge_size_max(a: usize, b: usize) -> Option { + Some(a.max(b)) + } } bitflags! { diff --git a/crates/render/src/graph/builder.rs b/crates/render/src/graph/builder.rs index 615bd61..5fe4cd9 100644 --- a/crates/render/src/graph/builder.rs +++ b/crates/render/src/graph/builder.rs @@ -9,27 +9,23 @@ use tracing::{debug, trace}; use super::{ access::ResourceAccess, deps::DependencyMatrix, - resources::{GetExternalResource, Slot}, + resources::{ExtendedResourceData, Slot}, RenderGraph, RenderGraphBuilder, }; use crate::{buffer::Buffer, texture::Texture}; -pub trait GraphImport { - type Resource: ResourceAccess; - - fn import(&self) -> Box + 'static>; +pub trait GraphImport { + fn import(&self) -> R::ExternHandle; } -pub trait GraphExport { - type Resource: ResourceAccess; - - fn export(&self) -> Box + 'static>; +pub trait GraphExport { + fn export(&self) -> R::ExternHandle; } impl RenderGraphBuilder { pub fn import_texture(&mut self, import_from: &I) -> Slot where - I: GraphImport, + I: GraphImport, { let f = import_from.import(); self.textures.import(f) @@ -37,7 +33,7 @@ impl RenderGraphBuilder { pub fn import_buffer(&mut self, import_from: &I) -> Slot where - I: GraphImport, + I: GraphImport, { let f = import_from.import(); self.buffers.import(f) @@ -45,7 +41,7 @@ impl RenderGraphBuilder { pub fn export_texture(&mut self, slot: Slot, export_to: &E) where - E: GraphExport, + E: GraphExport, { let f = export_to.export(); let last_written_by_pass = slot.last_written_by.0; @@ -55,7 +51,7 @@ impl RenderGraphBuilder { pub fn export_buffer(&mut self, slot: Slot, export_to: &E) where - E: GraphExport, + E: GraphExport, { let f = export_to.export(); let last_written_by_pass = slot.last_written_by.0; @@ -88,7 +84,9 @@ impl RenderGraph { self.init = false; self.was_updated = true; self.textures.reset(); + self.textures_ext.clear(); self.buffers.reset(); + self.buffers_ext.clear(); self.subpasses.clear(); self.subpasses_exec.clear(); self.passes.clear(); @@ -168,13 +166,17 @@ impl RenderGraph { } fn update_resource_topo_group_range(&mut self) { + self.textures_ext + .resize_with(self.textures.len(), ExtendedResourceData::new); + self.buffers_ext + .resize_with(self.buffers.len(), ExtendedResourceData::new); for (i, group) in self.passes_topo_order.iter().enumerate() { for p in group.iter().copied() { let p = &self.passes[p]; p.textures - .update_resource_topo_group_range(&mut self.textures, i as u16); + .update_resource_topo_group_range(&mut self.textures_ext, i as u16); p.buffers - .update_resource_topo_group_range(&mut self.buffers, i as u16); + .update_resource_topo_group_range(&mut self.buffers_ext, i as u16); } } } diff --git a/crates/render/src/graph/mod.rs b/crates/render/src/graph/mod.rs index aa91280..e16afa6 100644 --- a/crates/render/src/graph/mod.rs +++ b/crates/render/src/graph/mod.rs @@ -1,6 +1,8 @@ +use core::fmt; + use self::{ pass::{run::PassExec, PipelineBindPoint}, - resources::{ResourceAssignments, ResourceDeps, ResourceSet}, + resources::{ExtendedResourceData, ResourceDeps, ResourceSet}, }; use crate::{ buffer::Buffer, @@ -22,7 +24,7 @@ type SubPassIndex = (u16, u16); const PASS_UNDEFINED: PassIndex = !0; const SUBPASS_UNDEFINED: SubPassIndex = (!0, !0); -#[derive(Hash)] +#[derive(Hash, Debug)] pub struct SubPassDescription { pass_index: PassIndex, name: &'static str, @@ -31,7 +33,7 @@ pub struct SubPassDescription { input_attachments: Vec, } -#[derive(Hash)] +#[derive(Hash, Debug)] pub struct PassDescription { index: PassIndex, name: &'static str, @@ -48,7 +50,9 @@ pub struct RenderGraph { hash: u64, was_updated: bool, textures: ResourceSet, + textures_ext: Vec, buffers: ResourceSet, + buffers_ext: Vec, subpasses: Vec, subpasses_exec: Vec>, passes: Vec, @@ -64,13 +68,6 @@ pub struct RenderGraphBuilder { passes: Vec, } -pub struct RenderGraphAssignments { - hash: u64, - was_updated: bool, - texture_assignments: ResourceAssignments, - buffer_assignments: ResourceAssignments, -} - impl RenderGraph { #[inline] const fn new() -> Self { @@ -79,7 +76,9 @@ impl RenderGraph { hash: 0, was_updated: false, textures: ResourceSet::new(), + textures_ext: Vec::new(), buffers: ResourceSet::new(), + buffers_ext: Vec::new(), subpasses: Vec::new(), subpasses_exec: Vec::new(), passes: Vec::new(), @@ -145,18 +144,6 @@ impl RenderGraphBuilder { } } -impl RenderGraphAssignments { - #[inline] - pub const fn new() -> Self { - Self { - hash: 0, - was_updated: false, - texture_assignments: ResourceAssignments::new(), - buffer_assignments: ResourceAssignments::new(), - } - } -} - impl Default for RenderGraphBuilder { #[inline] fn default() -> Self { @@ -171,6 +158,23 @@ impl Default for RenderGraph { } } +impl fmt::Debug for RenderGraph { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("RenderGraph") + .field("init", &self.init) + .field("hash", &self.hash) + .field("was_updated", &self.was_updated) + .field("textures", &self.textures) + .field("textures_ext", &self.textures_ext) + .field("buffers", &self.buffers) + .field("buffers_ext", &self.buffers_ext) + .field("subpasses", &self.subpasses) + .field("passes", &self.passes) + .field("passes_topo_order", &self.passes_topo_order) + .finish() + } +} + impl SubPassDescription { #[inline] pub const fn pass_index(&self) -> PassIndex { diff --git a/crates/render/src/graph/pass/builder.rs b/crates/render/src/graph/pass/builder.rs index 73a8ac4..c15d91f 100644 --- a/crates/render/src/graph/pass/builder.rs +++ b/crates/render/src/graph/pass/builder.rs @@ -75,7 +75,7 @@ impl PassBuilderIntern<'_> { "trying to write to a texture multiple times in the same sub-pass" ); self.pass.textures.access(&slot, true, stages, usage); - self.graph.textures.writes(slot, current_subpass) + self.graph.textures.writes(slot, current_subpass, usage) } fn reads_texture_intern(&mut self, slot: Slot, stages: Stage, usage: TextureUsage) { @@ -85,7 +85,7 @@ impl PassBuilderIntern<'_> { "trying to read and write a texture in the same sub-pass" ); self.pass.textures.access(&slot, false, stages, usage); - self.graph.textures.reads(slot); + self.graph.textures.reads(slot, usage); } fn writes_buffer_intern( @@ -100,7 +100,7 @@ impl PassBuilderIntern<'_> { "trying to write to a buffer multiple times in the same sub-pass" ); self.pass.buffers.access(&slot, true, stages, usage); - self.graph.buffers.writes(slot, current_subpass) + self.graph.buffers.writes(slot, current_subpass, usage) } fn reads_buffer_intern(&mut self, slot: Slot, stages: Stage, usage: BufferUsage) { @@ -110,7 +110,7 @@ impl PassBuilderIntern<'_> { "trying to read and write a buffer in the same sub-pass" ); self.pass.buffers.access(&slot, false, stages, usage); - self.graph.buffers.reads(slot); + self.graph.buffers.reads(slot, usage); } } diff --git a/crates/render/src/graph/resources.rs b/crates/render/src/graph/resources.rs index c4b0d37..468b611 100644 --- a/crates/render/src/graph/resources.rs +++ b/crates/render/src/graph/resources.rs @@ -1,8 +1,9 @@ use std::{ collections::VecDeque, - hash::{self, Hash}, + hash::{Hash, Hasher}, marker::PhantomData, ops::Deref, + usize, }; use pulz_assets::Handle; @@ -13,13 +14,14 @@ use super::{ access::{ResourceAccess, Stage}, builder::{GraphExport, GraphImport}, deps::DependencyMatrix, - PassIndex, RenderGraph, RenderGraphAssignments, ResourceIndex, SubPassIndex, PASS_UNDEFINED, - SUBPASS_UNDEFINED, + PassDescription, PassIndex, RenderGraph, ResourceIndex, SubPassDescription, SubPassIndex, + PASS_UNDEFINED, SUBPASS_UNDEFINED, }; use crate::{ - buffer::Buffer, + backend::PhysicalResourceResolver, + buffer::{Buffer, BufferUsage}, camera::RenderTarget, - texture::{Image, Texture, TextureDimensions, TextureFormat}, + texture::{Image, Texture, TextureDimensions, TextureFormat, TextureUsage}, }; #[derive(Copy, Clone)] @@ -107,33 +109,29 @@ enum ResourceVariant { Export, } +#[derive(Debug)] struct Resource { first_written: SubPassIndex, last_written: SubPassIndex, - first_topo_group: u16, - last_topo_group: u16, usage: R::Usage, format: Option, size: Option, variant: ResourceVariant, - extern_res: Option>>, + extern_assignment: Option, } -#[derive(Hash)] -pub(super) struct ResourceSet(Vec>); +#[derive(Debug)] +pub(super) struct ExtendedResourceData { + pub first_topo_group: u16, + pub last_topo_group: u16, +} -impl ResourceSet { - pub fn len(&self) -> usize { - self.0.len() - } +#[derive(Debug)] +pub(super) struct ResourceSet { + resources: Vec>, } impl Resource { - #[inline] - fn is_active(&self) -> bool { - self.first_topo_group <= self.last_topo_group - } - #[inline] fn format_or_default(&self) -> R::Format { if let Some(f) = self.format { @@ -144,24 +142,48 @@ impl Resource { } } +impl ExtendedResourceData { + #[inline] + pub const fn new() -> Self { + Self { + first_topo_group: !0, + last_topo_group: 0, + } + } + + #[inline] + fn is_active(&self) -> bool { + self.first_topo_group <= self.last_topo_group + } +} + +impl Hash for ResourceSet { + fn hash(&self, state: &mut H) { + self.resources.hash(state); + // ignore transients + } +} + impl Hash for Resource { - fn hash(&self, state: &mut H) { + fn hash(&self, state: &mut H) { self.first_written.hash(state); self.last_written.hash(state); self.variant.hash(state); - // ignore extern_res! + self.format.hash(state); + // ignore size and extern assignment! } } +#[derive(Debug)] pub struct ResourceDeps(Vec>); impl Hash for ResourceDeps { - fn hash(&self, state: &mut H) { + fn hash(&self, state: &mut H) { Hash::hash_slice(&self.0, state); } } -#[derive(Hash)] +#[derive(Hash, Debug)] pub struct ResourceDep { index: ResourceIndex, last_written_by_pass: PassIndex, @@ -173,31 +195,35 @@ pub struct ResourceDep { impl ResourceSet { #[inline] pub(super) const fn new() -> Self { - Self(Vec::new()) + Self { + resources: Vec::new(), + } + } + + pub fn len(&self) -> usize { + self.resources.len() } pub(super) fn reset(&mut self) { - self.0.clear(); + self.resources.clear(); } pub(super) fn create(&mut self) -> WriteSlot { - let index = self.0.len() as ResourceIndex; - self.0.push(Resource { + let index = self.resources.len() as ResourceIndex; + self.resources.push(Resource { first_written: SUBPASS_UNDEFINED, last_written: SUBPASS_UNDEFINED, - first_topo_group: !0, - last_topo_group: 0, usage: Default::default(), format: None, size: None, variant: ResourceVariant::Transient, - extern_res: None, + extern_assignment: None, }); WriteSlot::new(index, SUBPASS_UNDEFINED) } pub(super) fn set_format(&mut self, slot: &Slot, format: R::Format) { - let slot = &mut self.0[slot.index as usize]; + let slot = &mut self.resources[slot.index as usize]; if let Some(old_format) = &slot.format { assert_eq!(old_format, &format, "incompatible format"); } @@ -205,17 +231,23 @@ impl ResourceSet { } pub(super) fn set_size(&mut self, slot: &Slot, size: R::Size) { - let slot = &mut self.0[slot.index as usize]; + let slot = &mut self.resources[slot.index as usize]; slot.size = Some(size); } - pub(super) fn writes(&mut self, slot: WriteSlot, new_pass: SubPassIndex) -> WriteSlot { - let r = &mut self.0[slot.0.index as usize]; + pub(super) fn writes( + &mut self, + slot: WriteSlot, + new_pass: SubPassIndex, + usage: R::Usage, + ) -> WriteSlot { + let r = &mut self.resources[slot.0.index as usize]; let last_written_by_pass = r.last_written; assert_eq!( last_written_by_pass, slot.0.last_written_by, "resource also written by an other pass (slot out of sync)" ); + r.usage |= usage; if new_pass != last_written_by_pass { r.last_written = new_pass; if r.first_written.0 == PASS_UNDEFINED { @@ -225,38 +257,47 @@ impl ResourceSet { WriteSlot::new(slot.0.index, new_pass) } - pub(super) fn reads(&mut self, slot: Slot) { + pub(super) fn reads(&mut self, slot: Slot, usage: R::Usage) { assert_ne!( slot.last_written_by.0, PASS_UNDEFINED, "resource was not yet written!" ); - let r = &self.0[slot.index as usize]; + let r = &mut self.resources[slot.index as usize]; let last_written_by_pass = r.last_written; // TODO: allow usage of older slots for reading (Write>Read>Write) assert_eq!( last_written_by_pass, slot.last_written_by, "resource also written by an other pass (slot out of sync)" ); + r.usage |= usage; } - pub(super) fn import(&mut self, f: Box>) -> Slot { + pub(super) fn import(&mut self, extern_resource: R::ExternHandle) -> Slot { let slot = self.create(); - let r = &mut self.0[slot.index as usize]; + let r = &mut self.resources[slot.index as usize]; r.variant = ResourceVariant::Import; - r.extern_res = Some(f); + r.extern_assignment = Some(extern_resource); slot.read() } - pub(super) fn export(&mut self, slot: Slot, f: Box>) { - let r = &mut self.0[slot.index as usize]; + pub(super) fn export(&mut self, slot: Slot, extern_resource: R::ExternHandle) { + let r = &mut self.resources[slot.index as usize]; assert_eq!( ResourceVariant::Transient, r.variant, "resource can be exported only once" ); + assert_eq!( + None, r.format, + "format of slot must be undefined for exports. Export target format will be used." + ); + assert_eq!( + None, r.size, + "size of slot must be undefined for exports. Export target size will be used." + ); // TODO: allow multiple exports by copying resource? r.variant = ResourceVariant::Export; - r.extern_res = Some(f); + r.extern_assignment = Some(extern_resource); } } @@ -299,16 +340,16 @@ impl ResourceDeps { pub(super) fn update_resource_topo_group_range( &self, - res: &mut ResourceSet, + res: &mut [ExtendedResourceData], group_index: u16, ) { for dep in &self.0 { - let r = &mut res.0[dep.resource_index() as usize]; - if r.first_topo_group > group_index { - r.first_topo_group = group_index; + let d = &mut res[dep.resource_index() as usize]; + if d.first_topo_group > group_index { + d.first_topo_group = group_index; } - if r.last_topo_group < group_index { - r.last_topo_group = group_index; + if d.last_topo_group < group_index { + d.last_topo_group = group_index; } } } @@ -389,196 +430,418 @@ impl ResourceDep { } } -enum ResourceAssignment { - Undefined, - Inactive, - Extern(R, R::Format, R::Size), - Transient(R::Format, u16), +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct PhysicalResource { + pub resource: R, + pub format: R::Format, + pub size: R::Size, + pub usage: R::Usage, +} + +impl Hash for PhysicalResource { + fn hash(&self, state: &mut H) { + self.format.hash(state); + // ignore resource and size + } +} + +#[derive(Hash, Debug)] +struct TransientResource { + physical: PhysicalResource, + first_topo_group: u16, + last_topo_group: u16, +} + +#[derive(Copy, Clone, Hash, Debug)] +enum ExternalOrTransient { + None, + External(u16), + Transient(u16), } -pub(super) struct ResourceAssignments { - assignments: Vec>, - transient_physical: Vec<(R::Format, u16)>, +#[derive(Hash, Debug)] +struct PhysicalResourceSet { + assignments: Vec, + assignment_sizes: Vec>, + externals: Vec>, + transients: Vec>, } -impl ResourceAssignments { +trait ResolveExtern { + fn resolve_extern(&mut self, handle: &R::ExternHandle) -> Option>; +} +trait CreateTransient { + fn create_transient(&mut self, format: R::Format, size: R::Size, usage: R::Usage) -> Option; +} + +impl PhysicalResourceSet { #[inline] - pub const fn new() -> Self { + pub(super) const fn new() -> Self { Self { assignments: Vec::new(), - transient_physical: Vec::new(), + assignment_sizes: Vec::new(), + externals: Vec::new(), + transients: Vec::new(), } } - pub fn clear(&mut self) { + pub fn len(&self) -> usize { + self.assignments.len() + } + + fn reset(&mut self, resources: &ResourceSet) { self.assignments.clear(); - self.transient_physical.clear() + self.assignment_sizes.clear(); + self.externals.clear(); + self.transients.clear(); + self.assignments + .resize_with(resources.len(), || ExternalOrTransient::None); + for res in resources.resources.iter() { + self.assignment_sizes.push(res.size); + } } - fn get(&self, idx: ResourceIndex) -> Option<(R, R::Format, R::Size)> { - match self.assignments.get(idx as usize)? { - ResourceAssignment::Extern(r, f, s) => Some((*r, *f, *s)), - ResourceAssignment::Transient(_, p) => { - let (f, _) = self.transient_physical[*p as usize]; - todo!("implement get physical ({f:?})") + fn get_physical(&self, idx: ResourceIndex) -> Option<&PhysicalResource> { + match self.assignments.get(idx as usize).copied()? { + ExternalOrTransient::None => None, + ExternalOrTransient::External(e) => self.externals.get(e as usize), + ExternalOrTransient::Transient(t) => { + self.transients.get(t as usize).map(|t| &t.physical) } - _ => None, } } - fn assign_resources(&mut self, res: &ResourceSet, backend: &mut dyn GraphBackend) -> bool { - let mut changed = false; - if self.assignments.len() < res.0.len() { - changed = true; - self.assignments - .resize_with(res.0.len(), || ResourceAssignment::Undefined); - } - for (a, r) in self.assignments.iter_mut().zip(res.0.iter()) { - if !r.is_active() { - *a = ResourceAssignment::Inactive; - } else if let Some(ext_res) = &r.extern_res { - let (id, format, size) = ext_res.get(backend); - if !changed { - if let ResourceAssignment::Extern(_, old_format, old_size) = *a { - changed = old_format != format || old_size != size; - } else { - changed = true; - } + fn get_or_create_transient( + transients: &mut Vec>, + format: R::Format, + size: R::Size, + usage: R::Usage, + first_topo_group: u16, + last_topo_group: u16, + ) -> u16 { + for (j, p) in transients.iter_mut().enumerate() { + if format == p.physical.format && p.last_topo_group < first_topo_group { + if let Some(s) = R::merge_size_max(p.physical.size, size) { + p.physical.size = s; + p.physical.usage |= usage; + p.last_topo_group = last_topo_group; + return j as u16; } - *a = ResourceAssignment::Extern(id, format, size); - } else if let ResourceAssignment::Transient(f, _) = a { - let format = r.format_or_default(); - changed |= f != &format; - *f = format; - } else { - changed = true; - let format = r.format_or_default(); - *a = ResourceAssignment::Transient(format, !0); } } - changed + let index = transients.len(); + transients.push(TransientResource { + physical: PhysicalResource { + resource: R::default(), + format, + size, + usage, + }, + first_topo_group, + last_topo_group, + }); + index as u16 } - fn assign_physical(&mut self, res: &ResourceSet) { - self.transient_physical.clear(); - let mut res_sorted: Vec<_> = res.0.iter().enumerate().collect(); - res_sorted.sort_by_key(|&(_, r)| r.first_topo_group); - for (i, r) in res_sorted { - if let ResourceAssignment::Transient(format, p) = &mut self.assignments[i] { - *p = !0; - for (j, (phys_format, last_topo_group)) in - self.transient_physical.iter_mut().enumerate() - { - if *format == *phys_format && *last_topo_group < r.first_topo_group { - *last_topo_group = r.last_topo_group; - *p = j as u16; - } + fn assign_externals>( + &mut self, + resources: &ResourceSet, + resources_data: &[ExtendedResourceData], + backend: &mut B, + ) { + // assign externals + for (i, r) in resources.resources.iter().enumerate() { + if !resources_data[i].is_active() { + continue; + } + if let Some(extern_handle) = &r.extern_assignment { + if let Some(ext) = backend.resolve_extern(extern_handle) { + // TODO: check usage compatible? + let external_index = self.externals.len() as u16; + self.assignments[i] = ExternalOrTransient::External(external_index); + self.assignment_sizes[i] = Some(ext.size); + self.externals.push(ext); + } else { + panic!( + "unable to resolve external resource {:?}, first_written={:?}", + i, r.first_written + ); } - if *p != !0 { - *p = self.transient_physical.len() as u16; - self.transient_physical.push((*format, r.last_topo_group)); + } + } + } + + fn assign_transient>( + &mut self, + resources: &ResourceSet, + resources_data: &[ExtendedResourceData], + backend: &mut B, + ) { + let mut res_sorted: Vec<_> = (0..resources.len()).collect(); + res_sorted.sort_by_key(|&r| resources_data[r].first_topo_group); + + // pre-assign transients + for &i in res_sorted.iter() { + let r = &resources.resources[i]; + let d = &resources_data[i]; + if d.is_active() && matches!(self.assignments[i], ExternalOrTransient::None) { + if r.usage == R::Usage::default() { + panic!( + "transient usage is empty, {:?}, {:?}, {}, {}, {:?}, {:?}", + r.size, + r.format, + d.first_topo_group, + d.last_topo_group, + r.first_written, + r.usage + ); } - // TODO: calc. max size! + let transient_index = Self::get_or_create_transient( + &mut self.transients, + r.format_or_default(), + self.assignment_sizes[i].expect("missing size"), + r.usage, + d.first_topo_group, + d.last_topo_group, + ); + self.assignments[i] = ExternalOrTransient::Transient(transient_index); } } + + for trans in self.transients.iter_mut() { + trans.physical.resource = backend + .create_transient( + trans.physical.format, + trans.physical.size, + trans.physical.usage, + ) + .expect("unable to create transient"); // TODO: error + } } } -impl RenderGraphAssignments { - pub fn clear(&mut self) { - self.hash = 0; - self.was_updated = false; - self.texture_assignments.clear(); - self.buffer_assignments.clear(); +impl PhysicalResourceSet { + fn derive_framebuffer_sizes( + &mut self, + passes: &[PassDescription], + subpasses: &[SubPassDescription], + ) { + for pass in passes { + if pass.active { + self.derive_framebuffer_size_for_pass(pass, &subpasses[pass.sub_pass_range()]); + } + } } - pub fn update(&mut self, graph: &RenderGraph, backend: &mut dyn GraphBackend) -> bool { - self.was_updated = graph.was_updated; - if self.hash != graph.hash { - self.hash = graph.hash; - self.was_updated = true; + fn derive_framebuffer_size_for_pass( + &mut self, + pass: &PassDescription, + subpasses: &[SubPassDescription], + ) { + let mut pass_size = None; + let mut empty = true; + for p in subpasses { + for r_index in p + .color_attachments + .iter() + .copied() + .chain(p.depth_stencil_attachment.iter().copied()) + .chain(p.input_attachments.iter().copied()) + { + empty = false; + if let Some(s) = self.assignment_sizes[r_index as usize] { + if let Some(s2) = pass_size { + assert_eq!(s2, s, "pass attachments have to be the same size"); + } else { + pass_size = Some(s); + } + } + } + } + if empty { + return; + } + if pass_size.is_none() { + panic!( + "unable to derive framebuffer size for pass {:?}, physical_resource_set={:?}", + pass.name, self + ); + } + + for p in subpasses { + for r_index in p + .color_attachments + .iter() + .copied() + .chain(p.depth_stencil_attachment.iter().copied()) + .chain(p.input_attachments.iter().copied()) + { + let s = &mut self.assignment_sizes[r_index as usize]; + if s.is_none() { + *s = pass_size; + } + } } - self.was_updated |= self - .texture_assignments - .assign_resources(&graph.textures, backend); - self.was_updated |= self - .buffer_assignments - .assign_resources(&graph.buffers, backend); - - if self.was_updated { - self.texture_assignments.assign_physical(&graph.textures); - self.buffer_assignments.assign_physical(&graph.buffers); + } +} + +#[derive(Hash, Debug)] +pub struct PhysicalResources { + textures: PhysicalResourceSet, + buffers: PhysicalResourceSet, + hash: u64, +} + +impl PhysicalResources { + pub const fn new() -> Self { + Self { + textures: PhysicalResourceSet::new(), + buffers: PhysicalResourceSet::new(), + hash: 0, } + } - self.was_updated + pub fn assign_physical( + &mut self, + graph: &RenderGraph, + backend: &mut B, + ) -> bool { + self.textures.reset(&graph.textures); + self.buffers.reset(&graph.buffers); + + self.textures + .assign_externals(&graph.textures, &graph.textures_ext, backend); + self.buffers + .assign_externals(&graph.buffers, &graph.buffers_ext, backend); + + self.textures + .derive_framebuffer_sizes(&graph.passes, &graph.subpasses); + + self.textures + .assign_transient(&graph.textures, &graph.textures_ext, backend); + self.buffers + .assign_transient(&graph.buffers, &graph.buffers_ext, backend); + + let new_hash = { + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + self.hash(&mut hasher); + hasher.finish() + }; + let changed = self.hash != new_hash; + self.hash = new_hash; + changed } pub(crate) fn get_texture( &self, idx: ResourceIndex, ) -> Option<(Texture, TextureFormat, u8, TextureDimensions)> { - let (r, f, s) = self.texture_assignments.get(idx)?; - Some((r, f, 1, s)) + let r = self.textures.get_physical(idx)?; + Some((r.resource, r.format, 1, r.size)) } pub(crate) fn get_buffer(&self, idx: ResourceIndex) -> Option<(Buffer, usize)> { - let (r, _, s) = self.buffer_assignments.get(idx)?; - Some((r, s)) + let r = self.buffers.get_physical(idx)?; + Some((r.resource, r.size)) } } -pub trait GetExternalResource { - fn get(&self, backend: &mut dyn GraphBackend) -> (R, R::Format, R::Size); +impl> GraphImport for &T { + fn import(&self) -> R::ExternHandle { + T::import(self) + } } -impl GetExternalResource for F -where - R: ResourceAccess, - F: for<'l, 'r> Fn(&'l mut dyn GraphBackend) -> (R, R::Format, R::Size), -{ - fn get(&self, backend: &mut dyn GraphBackend) -> (R, R::Format, R::Size) { - self(backend) +impl> GraphExport for &T { + fn export(&self) -> R::ExternHandle { + T::export(self) } } -pub trait GraphBackend { - fn get_surface(&mut self, window: WindowId) -> (Texture, TextureFormat, TextureDimensions); +impl> GraphImport for &mut T { + fn import(&self) -> R::ExternHandle { + T::import(self) + } } -impl GraphImport for Handle { - type Resource = Texture; +impl> GraphExport for &mut T { + fn export(&self) -> R::ExternHandle { + T::export(self) + } +} - fn import(&self) -> Box> { - let handle = *self; - Box::new(move |_backend: &mut dyn GraphBackend| todo!("import image handle {:?}", handle)) +impl GraphImport for Handle { + fn import(&self) -> RenderTarget { + RenderTarget::Image(*self) } } -impl GraphExport for Handle { - type Resource = Texture; +impl GraphExport for Handle { + fn export(&self) -> RenderTarget { + RenderTarget::Image(*self) + } +} - fn export(&self) -> Box> { - let handle = *self; - Box::new(move |_backend: &mut dyn GraphBackend| todo!("export image handle {:?}", handle)) +impl GraphExport for WindowId { + fn export(&self) -> RenderTarget { + RenderTarget::Window(*self) } } -impl GraphExport for WindowId { - type Resource = Texture; +impl GraphExport for RenderTarget { + fn export(&self) -> RenderTarget { + *self + } +} - fn export(&self) -> Box> { - let window = *self; - Box::new(move |backend: &mut dyn GraphBackend| backend.get_surface(window)) +impl GraphImport for Handle { + fn import(&self) -> Handle { + *self } } -impl GraphExport for RenderTarget { - type Resource = Texture; +impl GraphExport for Handle { + fn export(&self) -> Handle { + *self + } +} - fn export(&self) -> Box> { - match self { - Self::Image(i) => i.export(), - Self::Window(w) => w.export(), - } +impl ResolveExtern for B +where + B: PhysicalResourceResolver, +{ + fn resolve_extern(&mut self, handle: &RenderTarget) -> Option> { + self.resolve_render_target(handle) + } +} + +impl ResolveExtern for B +where + B: PhysicalResourceResolver, +{ + fn resolve_extern(&mut self, handle: &Handle) -> Option> { + self.resolve_buffer(handle) + } +} + +impl CreateTransient for B +where + B: PhysicalResourceResolver, +{ + fn create_transient( + &mut self, + format: TextureFormat, + size: TextureDimensions, + usage: TextureUsage, + ) -> Option { + self.create_transient_texture(format, size, usage) + } +} +impl CreateTransient for B +where + B: PhysicalResourceResolver, +{ + fn create_transient(&mut self, _format: (), size: usize, usage: BufferUsage) -> Option { + self.create_transient_buffer(size, usage) } } diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index 5aa761c..2c28bcf 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -25,7 +25,7 @@ #![doc(html_no_source)] #![doc = include_str!("../README.md")] -use camera::{Camera, RenderTarget}; +use camera::{Camera, Projection, RenderTarget}; use graph::{RenderGraph, RenderGraphBuilder}; use pulz_assets::Assets; use pulz_ecs::{ @@ -42,9 +42,8 @@ pub mod graph; pub mod mesh; pub mod pipeline; pub mod shader; +pub mod surface; pub mod texture; -pub mod view; - pub mod utils; pub use pulz_window as window; @@ -56,9 +55,11 @@ pub mod color { #[doc(hidden)] pub use ::encase as __encase; pub use pulz_transform::math; +use surface::WindowSurfaces; define_label_enum! { pub enum RenderSystemPhase: SystemPhase { + UpdateCamera, Sorting, BuildGraph, UpdateGraph, @@ -72,8 +73,9 @@ impl Module for RenderModule { fn install_once(&self, res: &mut Resources) { Assets::::install_into(res); - res.init_unsend::(); - res.init_unsend::(); + res.init::(); + res.init::(); + res.init::(); // TODO: //res.init::(); //render_graph::graph::RenderGraph::install_into(res, schedule); @@ -81,15 +83,25 @@ impl Module for RenderModule { let mut world = res.world_mut(); world.init::(); world.init::(); + world.init::(); } fn install_systems(schedule: &mut Schedule) { schedule.add_phase_chain([ + RenderSystemPhase::UpdateCamera, RenderSystemPhase::Sorting, RenderSystemPhase::BuildGraph, RenderSystemPhase::UpdateGraph, RenderSystemPhase::Render, ]); + // update projection and camera + schedule + .add_system(camera::update_projections_from_render_targets) + .after(CoreSystemPhase::Update) + .before(RenderSystemPhase::UpdateCamera); + schedule + .add_system(camera::update_cameras_from_projections) + .into_phase(RenderSystemPhase::UpdateCamera); // SORTING after update UPDATE schedule.add_phase_dependency(CoreSystemPhase::Update, RenderSystemPhase::Sorting); schedule diff --git a/crates/render/src/pipeline/graphics_pass.rs b/crates/render/src/pipeline/graphics_pass.rs index 2d2e98a..a9bbb7d 100644 --- a/crates/render/src/pipeline/graphics_pass.rs +++ b/crates/render/src/pipeline/graphics_pass.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::{ graph::{ - pass::PipelineBindPoint, PassDescription, RenderGraph, RenderGraphAssignments, + pass::PipelineBindPoint, resources::PhysicalResources, PassDescription, RenderGraph, ResourceIndex, }, texture::{Texture, TextureFormat, TextureUsage}, @@ -42,6 +42,7 @@ pub struct AttachmentDescriptor { pub format: TextureFormat, pub usage: TextureUsage, //pub initial_layout: TextureLayout, + //pub final_layout: TextureLayout, pub samples: u8, } @@ -100,7 +101,7 @@ pub struct GraphicsPassDescriptorWithTextures { impl GraphicsPassDescriptorWithTextures { pub fn from_graph( graph: &RenderGraph, - assignments: &RenderGraphAssignments, + physical_resources: &PhysicalResources, pass: &PassDescription, ) -> Option { if pass.bind_point() != PipelineBindPoint::Graphics { @@ -120,7 +121,7 @@ impl GraphicsPassDescriptorWithTextures { for i in attachment_indices.iter().copied() { let a = &pass.textures()[i as usize]; let resource_index = a.resource_index(); - let (tex, format, samples, dim) = assignments + let (tex, format, samples, dim) = physical_resources .get_texture(resource_index) .expect("unassigned resource"); let dim = dim.subimage_extents(); diff --git a/crates/render/src/surface.rs b/crates/render/src/surface.rs new file mode 100644 index 0000000..a8aeab6 --- /dev/null +++ b/crates/render/src/surface.rs @@ -0,0 +1,104 @@ +use fnv::FnvHashMap as HashMap; +use pulz_transform::math::{Size2, USize2}; +use pulz_window::{WindowId, WindowsMirror}; + +use crate::texture::{Texture, TextureFormat}; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Surface { + pub texture: Texture, + pub format: TextureFormat, + pub physical_size: USize2, + pub scale_factor: f64, +} + +impl Surface { + #[inline] + pub fn to_logical_size(&self, physical_size: USize2) -> Size2 { + (physical_size.as_dvec2() / self.scale_factor).as_vec2() + } + + #[inline] + pub fn logical_size(&self) -> Size2 { + self.to_logical_size(self.physical_size) + } + + #[inline] + pub fn physical_size(&self) -> USize2 { + self.physical_size + } +} + +pub type Iter<'a, T = Surface> = slotmap::secondary::Iter<'a, WindowId, T>; +pub type IterMut<'a, T = Surface> = slotmap::secondary::IterMut<'a, WindowId, T>; + +pub struct WindowSurfaces { + surfaces: WindowsMirror, + by_texture: HashMap, +} + +impl WindowSurfaces { + pub fn new() -> Self { + Self { + surfaces: WindowsMirror::new(), + by_texture: HashMap::default(), + } + } + + #[inline] + pub fn len(&self) -> usize { + self.surfaces.len() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.surfaces.is_empty() + } + + #[inline] + pub fn get(&self, id: WindowId) -> Option<&Surface> { + self.surfaces.get(id) + } + + #[inline] + pub fn get_mut(&mut self, id: WindowId) -> Option<&mut Surface> { + self.surfaces.get_mut(id) + } + + #[inline] + pub fn remove(&mut self, id: WindowId) -> bool { + self.surfaces.remove(id).is_some() + } + + #[inline] + pub fn iter(&self) -> Iter<'_> { + self.surfaces.iter() + } + + #[inline] + pub fn iter_mut(&mut self) -> IterMut<'_> { + self.surfaces.iter_mut() + } +} + +impl Default for WindowSurfaces { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl std::ops::Index for WindowSurfaces { + type Output = Surface; + #[inline] + fn index(&self, id: WindowId) -> &Self::Output { + &self.surfaces[id] + } +} + +impl std::ops::IndexMut for WindowSurfaces { + #[inline] + fn index_mut(&mut self, id: WindowId) -> &mut Self::Output { + &mut self.surfaces[id] + } +} diff --git a/crates/render/src/view.rs b/crates/render/src/view.rs deleted file mode 100644 index 7868872..0000000 --- a/crates/render/src/view.rs +++ /dev/null @@ -1,76 +0,0 @@ -use std::ops::Range; - -use pulz_ecs::Component; -use pulz_transform::{ - components::GlobalTransform, - math::{USize2, UVec2, Vec2, Vec3}, -}; - -use crate::{math::Mat4, shader::ShaderType, texture::Texture}; - -pub struct View { - pub projection: Mat4, - pub transform: GlobalTransform, - pub size: USize2, -} - -#[derive(Clone, ShaderType)] -struct ViewUniform { - view_proj: Mat4, - inverse_view_projection: Mat4, - view: Mat4, - inverse_view: Mat4, - projection: Mat4, - inverse_projection: Mat4, - world_position: Vec3, - size: Vec2, -} - -impl View { - #[inline] - fn to_uniform(&self) -> ViewUniform { - let projection = self.projection; - let inverse_projection = projection.inverse(); - let view = self.transform.to_matrix(); - let inverse_view = view.inverse(); - let view_proj = projection * inverse_view; - let inverse_view_projection = view * inverse_projection; - let world_position = self.transform.translation; - let size = [self.size.x as f32, self.size.y as f32]; - ViewUniform { - view_proj, - inverse_view_projection, - view, - inverse_view, - projection, - inverse_projection, - world_position, - size: size.into(), - } - } -} - -#[derive(Component)] -pub struct ViewTarget { - pub target: Texture, - pub sampled: Option, - pub depth: Option, -} - -#[derive(Component)] -pub struct Viewport { - pub position: UVec2, - pub size: USize2, - pub depth: Range, -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub struct Msaa { - pub samples: u32, -} - -impl Default for Msaa { - fn default() -> Self { - Self { samples: 4 } - } -} diff --git a/crates/window-winit/Cargo.toml b/crates/window-winit/Cargo.toml index 0320c30..99a56af 100644 --- a/crates/window-winit/Cargo.toml +++ b/crates/window-winit/Cargo.toml @@ -21,7 +21,8 @@ pulz-input = { path = "../input" } fnv = { workspace = true } tracing = { workspace = true } -winit = { version = "0.28", default-features = false } +winit = { version = "0.29.7", default-features = false, features = ["rwh_05"] } +raw-window-handle = { workspace = true, features = ["std"] } [target.'cfg(not(target_os = "unknown"))'.dev-dependencies] tracing-subscriber = { workspace = true } diff --git a/crates/window-winit/examples/window-winit-demo.rs b/crates/window-winit/examples/window-winit-demo.rs index 749777e..d6fe5ba 100644 --- a/crates/window-winit/examples/window-winit-demo.rs +++ b/crates/window-winit/examples/window-winit-demo.rs @@ -10,7 +10,7 @@ fn init() -> (Resources, EventLoop<()>, Rc, WinitWindowSystem) { info!("Initializing..."); let mut resources = Resources::new(); - let event_loop = EventLoop::new(); + let event_loop = EventLoop::new().unwrap(); let (window_system, _window_id, window) = WinitWindowModule::new(WindowDescriptor::default(), &event_loop) @@ -29,9 +29,9 @@ fn main() { .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE) .init(); - let (resources, event_loop, _window, window_system) = init(); + let (mut resources, event_loop, _window, window_system) = init(); - window_system.run(resources, event_loop); + window_system.run(&mut resources, event_loop).unwrap(); } #[cfg(target_arch = "wasm32")] diff --git a/crates/window-winit/src/lib.rs b/crates/window-winit/src/lib.rs index 1dedd56..4cd200a 100644 --- a/crates/window-winit/src/lib.rs +++ b/crates/window-winit/src/lib.rs @@ -31,13 +31,13 @@ use std::{ops::Deref, rc::Rc}; use fnv::FnvHashMap; use pulz_ecs::prelude::*; use pulz_window::{ - listener::WindowSystemListeners, Size2, WindowDescriptor, WindowId, Windows, WindowsMirror, + listener::WindowSystemListener, Size2, WindowDescriptor, WindowId, Windows, WindowsMirror, }; use tracing::{debug, info, warn}; pub use winit; use winit::{ dpi::PhysicalSize, - error::OsError, + error::{EventLoopError, OsError}, event::{Event, StartCause, WindowEvent}, event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, window::{Window as WinitWindow, WindowId as WinitWindowId}, @@ -122,14 +122,13 @@ fn builder_for_descriptor(descriptor: &WindowDescriptor) -> winit::window::Windo pub struct WinitWindowSystem { windows_id: ResourceId, - listeners_id: ResourceId, winit_windows_id: ResourceId, + schedule_id: ResourceId, active: bool, } pub struct WinitWindowSystemMut<'l> { windows: ResMut<'l, Windows>, - listeners: ResMut<'l, WindowSystemListeners>, winit_windows: ResMut<'l, WinitWindows>, } @@ -206,27 +205,24 @@ impl WinitWindowSystemMut<'_> { &mut self, res: &Resources, window_id: WinitWindowId, - event: WindowEvent<'_>, + event: WindowEvent, ) { if let Some(window_id) = self.winit_windows.window_id_map.get(&window_id).copied() { if matches!(event, WindowEvent::Destroyed) { + debug!("Window {:?} destroyed", window_id); self.close(res, window_id); } else if let Some(window) = self.windows.get_mut(window_id) { match event { WindowEvent::CloseRequested => { + debug!("Window {:?} close requested", window_id); window.close_requested = true; } WindowEvent::Resized(size) => { let phys_size: [u32; 2] = size.into(); window.size = phys_size.into(); } - WindowEvent::ScaleFactorChanged { - scale_factor, - new_inner_size, - } => { - let phys_size: [u32; 2] = (*new_inner_size).into(); + WindowEvent::ScaleFactorChanged { scale_factor, .. } => { window.scale_factor = scale_factor; - window.size = phys_size.into(); } _ => {} } @@ -234,25 +230,22 @@ impl WinitWindowSystemMut<'_> { } } - fn handle_created( - &mut self, - res: &Resources, - event_loop: &EventLoopWindowTarget, - ) -> Result<(), OsError> { + fn handle_created(&mut self, res: &Resources, event_loop: &EventLoopWindowTarget) { while let Some((window_id, window_descr)) = self.windows.pop_next_created_window() { - if let Some(winit_window) = self.winit_windows.get(window_id) { - self.listeners - .call_on_created(res, window_id, window_descr, winit_window); + let winit_window = if let Some(w) = self.winit_windows.windows.get(window_id) { + Rc::clone(w) } else { let builder = builder_for_descriptor(window_descr); - let winit_window = Rc::new(builder.build(event_loop)?); + let winit_window = + Rc::new(builder.build(event_loop).expect("unable to create window")); update_window_descriptor(window_descr, &winit_window); self.winit_windows.insert(window_id, winit_window.clone()); - self.listeners - .call_on_created(res, window_id, window_descr, &winit_window); + winit_window }; + res.foreach_meta_mut(move |l: &mut dyn WindowSystemListener| { + l.on_created(window_id, &window_descr, winit_window.clone()); + }); } - Ok(()) } // close all windows where the close_requested flag is not cleared @@ -276,7 +269,8 @@ impl WinitWindowSystemMut<'_> { fn close(&mut self, res: &Resources, window_id: WindowId) -> bool { if self.winit_windows.get(window_id).is_some() { - self.listeners.call_on_closed(res, window_id); + debug!("Window {:?} closing", window_id); + res.foreach_meta_mut(|l: &mut dyn WindowSystemListener| l.on_closed(window_id)); self.windows.close(window_id); self.winit_windows.close(window_id); true @@ -289,12 +283,12 @@ impl WinitWindowSystemMut<'_> { impl WinitWindowSystem { fn install(res: &mut Resources) -> Self { let windows_id = res.init::(); - let listeners_id = res.init_unsend::(); let winit_windows_id = res.init_unsend::(); + let schedule_id = res.init_unsend::(); Self { windows_id, - listeners_id, winit_windows_id, + schedule_id, active: false, } } @@ -302,87 +296,90 @@ impl WinitWindowSystem { fn as_mut<'l>(&self, res: &'l Resources) -> WinitWindowSystemMut<'l> { WinitWindowSystemMut { windows: res.borrow_res_mut_id(self.windows_id).unwrap(), - listeners: res.borrow_res_mut_id(self.listeners_id).unwrap(), winit_windows: res.borrow_res_mut_id(self.winit_windows_id).unwrap(), } } - fn listeners_mut<'l>(&self, res: &'l Resources) -> ResMut<'l, WindowSystemListeners> { - res.borrow_res_mut_id(self.listeners_id).unwrap() - } - pub fn handle_event( &mut self, - resources: &mut Resources, - schedule: &mut Schedule, - event: Event<'_, T>, + res: &mut Resources, + event: Event, event_loop: &EventLoopWindowTarget, - control_flow: &mut ControlFlow, ) { - *control_flow = winit::event_loop::ControlFlow::Poll; - match event { Event::NewEvents(StartCause::Init) => { info!("event loop started..."); } Event::Resumed => { info!("resumed"); - self.active = true; - let mut s = self.as_mut(resources); - s.handle_created(resources, event_loop).unwrap(); - s.listeners.call_on_resumed(resources); - *control_flow = winit::event_loop::ControlFlow::Poll; + if !self.active { + self.active = true; + let mut s = self.as_mut(res); + s.handle_created(res, event_loop); + res.foreach_meta_mut(|l: &mut dyn WindowSystemListener| l.on_resumed()); + } + event_loop.set_control_flow(ControlFlow::Poll); } Event::WindowEvent { window_id, event } => { - self.as_mut(resources) - .handle_window_event(resources, window_id, event); + self.as_mut(res).handle_window_event(res, window_id, event); } Event::Suspended => { info!("suspended"); - self.active = false; - self.listeners_mut(resources).call_on_suspended(resources); - *control_flow = winit::event_loop::ControlFlow::Wait; + if self.active { + self.active = false; + res.foreach_meta_mut(|l: &mut dyn WindowSystemListener| l.on_suspended()); + } + event_loop.set_control_flow(ControlFlow::Wait); } - Event::MainEventsCleared => { + Event::AboutToWait => { if self.active { - self.as_mut(resources) - .handle_created(resources, event_loop) - .unwrap(); - schedule.run(resources); - if self.as_mut(resources).handle_close(resources) { + self.as_mut(res).handle_created(res, event_loop); + let mut schedule = res.remove_id(self.schedule_id).unwrap(); + schedule.run(res); + res.insert_again(schedule); + if self.as_mut(res).handle_close(res) { // all windows closed - *control_flow = winit::event_loop::ControlFlow::Exit; + event_loop.exit(); } } } - Event::LoopDestroyed => { + Event::LoopExiting => { info!("event loop ended"); if self.active { self.active = false; - self.listeners_mut(resources).call_on_suspended(resources); + res.foreach_meta_mut(|l: &mut dyn WindowSystemListener| l.on_suspended()); } } _ => {} } } - pub fn run(mut self, mut resources: Resources, event_loop: EventLoop) -> ! { - let schedule_id = resources.init_unsend::(); - let mut schedule = resources.remove_id(schedule_id).unwrap(); - + pub fn event_handler<'a, T>( + &'a mut self, + res: &'a mut Resources, + ) -> impl FnMut(Event, &EventLoopWindowTarget) + 'a { let event_loop_span = tracing::trace_span!("EventLoop"); - - event_loop.run(move |event, event_loop, control_flow| { + move |event, event_loop| { let span = event_loop_span.enter(); - self.handle_event( - &mut resources, - &mut schedule, - event, - event_loop, - control_flow, - ); + self.handle_event(res, event, event_loop); drop(span); - }) + } + } + + #[cfg(any(not(target_arch = "wasm32"), doc))] + pub fn run( + mut self, + resources: &mut Resources, + event_loop: EventLoop, + ) -> Result<(), EventLoopError> { + #[cfg(not(target_arch = "wasm32"))] + { + event_loop.run(self.event_handler(resources)) + } + #[cfg(target_arch = "wasm32")] + { + Ok(()) + } } #[cfg(any( @@ -396,26 +393,14 @@ impl WinitWindowSystem { target_os = "openbsd", doc ))] - pub fn run_return( + pub fn pump_events( &mut self, resources: &mut Resources, event_loop: &mut EventLoop, - ) -> i32 { - use winit::platform::run_return::EventLoopExtRunReturn; - - let schedule_id = resources.init_unsend::(); - let mut schedule = resources.remove_id(schedule_id).unwrap(); - - let event_loop_span = tracing::trace_span!("EventLoop"); - - let result = event_loop.run_return(|event, event_loop, control_flow| { - let span = event_loop_span.enter(); - self.handle_event(resources, &mut schedule, event, event_loop, control_flow); - drop(span); - }); - - resources.insert_again(schedule); - + timeout: Option, + ) -> winit::platform::pump_events::PumpStatus { + use winit::platform::pump_events::EventLoopExtPumpEvents; + let result = event_loop.pump_events(timeout, self.event_handler(resources)); result } @@ -424,23 +409,7 @@ impl WinitWindowSystem { #[cfg(target_arch = "wasm32")] { use winit::platform::web::EventLoopExtWebSys; - - let schedule_id = resources.init_unsend::(); - let mut schedule = resources.remove_id(schedule_id).unwrap(); - - let event_loop_span = tracing::trace_span!("EventLoop"); - - event_loop.spawn(move |event, event_loop, control_flow| { - let span = event_loop_span.enter(); - self.handle_event( - &mut resources, - &mut schedule, - event, - event_loop, - control_flow, - ); - drop(span); - }) + event_loop.spawn(self.handler(&mut resources)) } } } diff --git a/crates/window/Cargo.toml b/crates/window/Cargo.toml index 928cefc..7487bcc 100644 --- a/crates/window/Cargo.toml +++ b/crates/window/Cargo.toml @@ -9,7 +9,7 @@ readme = "README.md" [dependencies] pulz-ecs = { path = "../ecs" } +raw-window-handle = { workspace = true } slotmap = { workspace = true } glam = { workspace = true, features = ["mint", "bytemuck"] } -raw-window-handle = { workspace = true, features = ["alloc"]} diff --git a/crates/window/src/lib.rs b/crates/window/src/lib.rs index d9bc576..927d9df 100644 --- a/crates/window/src/lib.rs +++ b/crates/window/src/lib.rs @@ -32,8 +32,10 @@ mod window; pub type Point2 = glam::IVec2; pub type Size2 = glam::UVec2; -pub use raw_window_handle::{ - HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, -}; +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; pub use crate::window::*; + +pub trait HasRawWindowAndDisplayHandle: HasRawWindowHandle + HasRawDisplayHandle {} + +impl HasRawWindowAndDisplayHandle for T where T: HasRawWindowHandle + HasRawDisplayHandle {} diff --git a/crates/window/src/listener.rs b/crates/window/src/listener.rs index 8ab4332..e3d3989 100644 --- a/crates/window/src/listener.rs +++ b/crates/window/src/listener.rs @@ -1,63 +1,20 @@ -use pulz_ecs::resource::Resources; -use slotmap::SlotMap; +use std::rc::Rc; -use crate::{RawWindow, Window, WindowId}; +use pulz_ecs::impl_any_cast; + +use crate::{HasRawWindowAndDisplayHandle, Window, WindowId}; pub trait WindowSystemListener: 'static { fn on_created( - &self, - _res: &Resources, + &mut self, _window_id: WindowId, _window_desc: &Window, - _window_raw: &dyn RawWindow, + _window: Rc, ) { } - fn on_resumed(&self, _res: &Resources) {} - fn on_closed(&self, _res: &Resources, _window_id: WindowId) {} - fn on_suspended(&self, _res: &Resources) {} -} - -slotmap::new_key_type! { - pub struct WindowSystemListenerId; + fn on_closed(&mut self, _window_id: WindowId) {} + fn on_resumed(&mut self) {} + fn on_suspended(&mut self) {} } -#[derive(Default)] -pub struct WindowSystemListeners(SlotMap>); - -impl WindowSystemListeners { - #[inline] - pub fn insert(&mut self, l: impl WindowSystemListener) -> WindowSystemListenerId { - self.0.insert(Box::new(l)) - } - - #[inline] - pub fn remove(&mut self, id: WindowSystemListenerId) -> bool { - self.0.remove(id).is_some() - } - pub fn call_on_created( - &self, - res: &Resources, - window_id: WindowId, - window_descr: &Window, - window_raw: &dyn RawWindow, - ) { - for (_, l) in self.0.iter() { - l.on_created(res, window_id, window_descr, window_raw); - } - } - pub fn call_on_resumed(&self, res: &Resources) { - for (_, l) in self.0.iter() { - l.on_resumed(res); - } - } - pub fn call_on_closed(&self, res: &Resources, window_id: WindowId) { - for (_, l) in self.0.iter() { - l.on_closed(res, window_id); - } - } - pub fn call_on_suspended(&self, res: &Resources) { - for (_, l) in self.0.iter() { - l.on_suspended(res); - } - } -} +impl_any_cast!(dyn WindowSystemListener); diff --git a/crates/window/src/window.rs b/crates/window/src/window.rs index 112bcfa..98fc495 100644 --- a/crates/window/src/window.rs +++ b/crates/window/src/window.rs @@ -5,7 +5,6 @@ use std::{ }; use pulz_ecs::Component; -use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use slotmap::{new_key_type, SlotMap}; use crate::Size2; @@ -167,7 +166,3 @@ pub enum WindowCommand { SetTitle(Cow<'static, String>), Close, } - -pub trait RawWindow: HasRawWindowHandle + HasRawDisplayHandle {} - -impl RawWindow for W where W: HasRawWindowHandle + HasRawDisplayHandle {} From a34dd28913bf769a9befee59599c0ecaee0e942c Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Thu, 28 Dec 2023 19:50:30 +0100 Subject: [PATCH 24/36] [render] fix graph physical resources hash --- crates/render-ash/src/graph.rs | 7 ++++--- crates/render/src/graph/resources.rs | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/render-ash/src/graph.rs b/crates/render-ash/src/graph.rs index dbbbfc7..9d0f443 100644 --- a/crates/render-ash/src/graph.rs +++ b/crates/render-ash/src/graph.rs @@ -156,8 +156,8 @@ impl AshRenderGraph { if src_graph.was_updated() || src_graph.hash() != self.hash || formats_changed { self.do_update(src_graph, res)?; debug!( - "graph updated: topo={:?}, barriers={:?}", - self.topo, self.barriers + "graph updated: topo={:?}, barriers={:?}, formats_changed={:?}", + self.topo, self.barriers, formats_changed, ); Ok(true) } else { @@ -289,8 +289,9 @@ impl AshRenderGraph { } // TODO: compute passes, raytracing-passes - if let Some(_barrier) = self.barriers.get(topo_index) { + if let Some(barrier) = self.barriers.get(topo_index) { // TODO: add barriers + todo!("implement barriers {barrier:?}"); } } diff --git a/crates/render/src/graph/resources.rs b/crates/render/src/graph/resources.rs index 468b611..18da545 100644 --- a/crates/render/src/graph/resources.rs +++ b/crates/render/src/graph/resources.rs @@ -724,7 +724,8 @@ impl PhysicalResources { let new_hash = { let mut hasher = std::collections::hash_map::DefaultHasher::new(); - self.hash(&mut hasher); + self.textures.hash(&mut hasher); + self.buffers.hash(&mut hasher); hasher.finish() }; let changed = self.hash != new_hash; From cf1c92bea4901370790c3c6cd5794a072dac3c19 Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Thu, 28 Dec 2023 20:49:05 +0100 Subject: [PATCH 25/36] [render] fix aquire-image timeout --- crates/render-ash/src/graph.rs | 2 +- crates/render-ash/src/lib.rs | 6 +++--- crates/render-ash/src/swapchain.rs | 23 ++++------------------- 3 files changed, 8 insertions(+), 23 deletions(-) diff --git a/crates/render-ash/src/graph.rs b/crates/render-ash/src/graph.rs index 9d0f443..2800bc1 100644 --- a/crates/render-ash/src/graph.rs +++ b/crates/render-ash/src/graph.rs @@ -77,7 +77,7 @@ impl PhysicalResourceResolver for AshPhysicalResourceResolver<'_> { self.submission_group .wait(sem, PipelineStageFlags::TRANSFER); let aquired_texture = surface - .acquire_next_image(self.res, 0, sem) + .acquire_next_image(self.res, sem) .expect("aquire failed") .expect("aquire failed(2)"); diff --git a/crates/render-ash/src/lib.rs b/crates/render-ash/src/lib.rs index d63feb3..0ab7b2b 100644 --- a/crates/render-ash/src/lib.rs +++ b/crates/render-ash/src/lib.rs @@ -329,9 +329,9 @@ impl AshRendererFull { .command_pool .request_semaphore()?; submission_group.wait(sem, PipelineStageFlags::TRANSFER); - if let Some(texture) = self.acquire_swapchain_image(window_id, 0, sem)? { - let image = self.res.textures[texture].0; - images.push((image, self.surfaces[window_id].clear_value())); + let surface = &mut self.surfaces[window_id]; + if let Some(acquired) = surface.acquire_next_image(&mut self.res, sem)? { + images.push((acquired.image, surface.clear_value())); } } diff --git a/crates/render-ash/src/swapchain.rs b/crates/render-ash/src/swapchain.rs index dac1ad2..89c5644 100644 --- a/crates/render-ash/src/swapchain.rs +++ b/crates/render-ash/src/swapchain.rs @@ -9,7 +9,7 @@ use pulz_window::{ HasRawWindowAndDisplayHandle, Size2, Window, WindowDescriptor, WindowId, Windows, }; use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; -use tracing::info; +use tracing::{info, debug}; use crate::{ convert::VkInto, @@ -532,6 +532,7 @@ impl AshSurfaceSwapchain { } fn configure(&mut self, res: &mut AshResources) -> Result<()> { + debug!("re-creating swapchain, recreate={:?}", self.swapchain_raw != vk::SwapchainKHR::null()); // check swapchain support res.device().ext_swapchain()?; @@ -663,19 +664,16 @@ impl AshSurfaceSwapchain { pub(crate) fn acquire_next_image( &mut self, res: &mut AshResources, - timeout: u64, signal_semaphore: vk::Semaphore, ) -> Result> { if self.is_acquired() { return Err(Error::SwapchainImageAlreadyAcquired); } - // TODO: better sync mechanism - let result = unsafe { match res.device().ext_swapchain()?.acquire_next_image( self.swapchain_raw, - timeout, + !0, signal_semaphore, vk::Fence::null(), ) { @@ -684,7 +682,7 @@ impl AshSurfaceSwapchain { self.configure(res)?; res.device().ext_swapchain()?.acquire_next_image( self.swapchain_raw, - timeout, + !0, signal_semaphore, vk::Fence::null(), ) @@ -785,19 +783,6 @@ impl AshRendererFull { }); } - pub(crate) fn acquire_swapchain_image( - &mut self, - window_id: WindowId, - timeout: u64, - signal_semaphore: vk::Semaphore, - ) -> Result> { - let _ = tracing::trace_span!("AquireImage").entered(); - let surface_swapchain = self.surfaces.get_mut(window_id).expect("swapchain"); - Ok(surface_swapchain - .acquire_next_image(&mut self.res, timeout, signal_semaphore)? - .map(|swapchain_image| swapchain_image.texture)) - } - pub(crate) fn get_num_acquired_swapchains(&self) -> usize { self.surfaces .iter() From 95edc82ab777b2c29f3b4e4748638b8463650ad3 Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Thu, 28 Dec 2023 22:14:45 +0100 Subject: [PATCH 26/36] [render] keep ownership of window --- crates/render-ash/src/lib.rs | 8 ++--- crates/render-ash/src/swapchain.rs | 51 ++++++++++++++++++++---------- 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/crates/render-ash/src/lib.rs b/crates/render-ash/src/lib.rs index 0ab7b2b..113a02d 100644 --- a/crates/render-ash/src/lib.rs +++ b/crates/render-ash/src/lib.rs @@ -172,9 +172,9 @@ impl Drop for AshRendererFull { fn drop(&mut self) { unsafe { self.device.device_wait_idle().unwrap(); - } - for (_, swapchain) in self.surfaces.drain() { - swapchain.destroy_with_surface(&mut self.res).unwrap(); + for (_, swapchain) in self.surfaces.drain() { + swapchain.destroy_with_surface(&mut self.res).unwrap(); + } } self.frames.clear(); self.res.clear_all().unwrap(); @@ -330,7 +330,7 @@ impl AshRendererFull { .request_semaphore()?; submission_group.wait(sem, PipelineStageFlags::TRANSFER); let surface = &mut self.surfaces[window_id]; - if let Some(acquired) = surface.acquire_next_image(&mut self.res, sem)? { + if let Some(acquired) = surface.acquire_next_image(&mut self.res, sem)? { images.push((acquired.image, surface.clear_value())); } } diff --git a/crates/render-ash/src/swapchain.rs b/crates/render-ash/src/swapchain.rs index 89c5644..fbcbb3e 100644 --- a/crates/render-ash/src/swapchain.rs +++ b/crates/render-ash/src/swapchain.rs @@ -9,7 +9,7 @@ use pulz_window::{ HasRawWindowAndDisplayHandle, Size2, Window, WindowDescriptor, WindowId, Windows, }; use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; -use tracing::{info, debug}; +use tracing::{debug, info}; use crate::{ convert::VkInto, @@ -506,17 +506,27 @@ impl AshSurfaceSwapchain { old_swapchain } - pub fn destroy_with_surface(mut self, res: &mut AshResources) -> Result<()> { - self.put_to_garbage(res); - res.clear_garbage().unwrap(); - // SAFETY: clear garbage waits for the device to be idle - unsafe { - let surface = self.surface_raw; - if surface != vk::SurfaceKHR::null() { - self.surface_raw = vk::SurfaceKHR::null(); - if let Ok(ext_surface) = res.device().instance().ext_surface() { - ext_surface.destroy_surface(surface, None); - } + /// #SAFETY: there must not be any outstanding operations + pub unsafe fn destroy_with_surface(mut self, res: &mut AshResources) -> Result<()> { + let swapchain = self.swapchain_raw; + for texture_id in self.textures.drain(..) { + res.textures.remove(texture_id); // forget texture without destroy! + } + for image_view in self.image_views.drain(..) { + res.device().destroy_image_view(image_view, None); + } + self.images.clear(); // images owned by swapchain! + if swapchain != vk::SwapchainKHR::null() { + self.swapchain_raw = vk::SwapchainKHR::null(); + res.device() + .ext_swapchain()? + .destroy_swapchain(swapchain, None); + } + let surface = self.surface_raw; + if surface != vk::SurfaceKHR::null() { + self.surface_raw = vk::SurfaceKHR::null(); + if let Ok(ext_surface) = res.device().instance().ext_surface() { + ext_surface.destroy_surface(surface, None); } } Ok(()) @@ -532,7 +542,10 @@ impl AshSurfaceSwapchain { } fn configure(&mut self, res: &mut AshResources) -> Result<()> { - debug!("re-creating swapchain, recreate={:?}", self.swapchain_raw != vk::SwapchainKHR::null()); + debug!( + "re-creating swapchain, recreate={:?}", + self.swapchain_raw != vk::SwapchainKHR::null() + ); // check swapchain support res.device().ext_swapchain()?; @@ -749,13 +762,19 @@ impl AshRendererFull { let Some(swapchain) = self.surfaces.remove(window_id) else { return Err(Error::WindowNotAvailable); }; - swapchain.destroy_with_surface(&mut self.res)?; + unsafe { + self.device.device_wait_idle()?; + swapchain.destroy_with_surface(&mut self.res)?; + } Ok(()) } pub(crate) fn destroy_all_swapchains(&mut self) -> Result<()> { - for (_window_id, swapchain) in self.surfaces.drain() { - swapchain.destroy_with_surface(&mut self.res)?; + unsafe { + self.device.device_wait_idle()?; + for (_window_id, swapchain) in self.surfaces.drain() { + swapchain.destroy_with_surface(&mut self.res)?; + } } Ok(()) } From 2c038a29f19fd5812c60a1bd0d6a3c7d78e5ecb2 Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Fri, 29 Dec 2023 01:16:17 +0100 Subject: [PATCH 27/36] [render] cleanup render graph in currect order --- crates/render-ash/src/graph.rs | 184 ++++++++++++++------ crates/render-ash/src/lib.rs | 10 +- crates/render-ash/src/resources/mod.rs | 39 ++++- crates/render-ash/src/swapchain.rs | 4 +- crates/render/src/graph/resources.rs | 4 +- crates/render/src/pipeline/graphics_pass.rs | 11 +- 6 files changed, 175 insertions(+), 77 deletions(-) diff --git a/crates/render-ash/src/graph.rs b/crates/render-ash/src/graph.rs index 2800bc1..c0e14f4 100644 --- a/crates/render-ash/src/graph.rs +++ b/crates/render-ash/src/graph.rs @@ -1,3 +1,8 @@ +use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, +}; + use ash::vk::{self, PipelineStageFlags}; use pulz_assets::Handle; use pulz_render::{ @@ -8,18 +13,18 @@ use pulz_render::{ graph::{ pass::PipelineBindPoint, resources::{PhysicalResource, PhysicalResources}, - PassIndex, RenderGraph, + PassDescription, PassIndex, RenderGraph, }, math::USize2, - pipeline::{GraphicsPass, GraphicsPassDescriptorWithTextures}, + pipeline::{ExtendedGraphicsPassDescriptor, GraphicsPass}, texture::{Texture, TextureDescriptor, TextureDimensions, TextureFormat, TextureUsage}, + utils::hash::U64HashMap, }; use pulz_window::WindowsMirror; use tracing::debug; use crate::{ convert::VkInto, - drop_guard::Guard, encoder::{AshCommandPool, SubmissionGroup}, resources::AshResources, swapchain::AshSurfaceSwapchain, @@ -31,14 +36,23 @@ pub struct AshRenderGraph { topo: Vec, barriers: Vec, hash: u64, - physical_resources_hash: u64, } #[derive(Default, Debug)] pub struct TopoGroup { - render_passes: Vec<(PassIndex, vk::RenderPass, vk::Framebuffer, USize2)>, // pass-index - compute_passes: Vec, // sub-pass-index - ray_tracing_passes: Vec, // sub-pass-index + render_passes: Vec, // pass-index + compute_passes: Vec, // sub-pass-index + ray_tracing_passes: Vec, // sub-pass-index +} + +#[derive(Debug)] +struct TopoRenderPass { + index: PassIndex, + render_pass: vk::RenderPass, + framebuffer: vk::Framebuffer, + attachment_resource_indices: Vec, + //framebuffers_cache: U64HashMap, + size: USize2, } #[derive(Debug)] @@ -112,16 +126,97 @@ impl PhysicalResourceResolver for AshPhysicalResourceResolver<'_> { }) .ok()?; // TODO: destroy texture + self.res.current_frame_garbage_mut().texture_handles.push(t); // TODO: reuse textures Some(t) } fn create_transient_buffer(&mut self, _size: usize, _usage: BufferUsage) -> Option { - // TODO: reuse textures + // TODO: destroy buffers + // TODO: reuse buffers todo!("implement create_transient_buffer") } } +impl TopoRenderPass { + fn from_graph( + res: &mut AshResources, + src: &RenderGraph, + phys: &PhysicalResources, + pass: &PassDescription, + ) -> Result { + let pass_descr = ExtendedGraphicsPassDescriptor::from_graph(src, phys, pass).unwrap(); + let graphics_pass = res.create::(&pass_descr.graphics_pass)?; + let render_pass = res[graphics_pass]; + Ok(TopoRenderPass { + index: pass.index(), + render_pass, + framebuffer: vk::Framebuffer::null(), + attachment_resource_indices: pass_descr.resource_indices, + //framebuffers_cache: U64HashMap::default(), + size: pass_descr.size, + }) + } + + fn cleanup(&mut self, res: &mut AshResources) { + if self.framebuffer != vk::Framebuffer::null() { + res.current_frame_garbage_mut() + .framebuffers + .push(self.framebuffer); + self.framebuffer = vk::Framebuffer::null(); + } + /* + for (_, fb) in self.framebuffers_cache.drain() { + unsafe { + res.device().destroy_framebuffer(fb, None); + } + } + */ + } + + fn update_framebuffer<'d>( + &mut self, + res: &'d mut AshResources, + phys: &PhysicalResources, + ) -> Result<()> { + self.cleanup(res); + + let mut image_views = Vec::with_capacity(self.attachment_resource_indices.len()); + for &i in &self.attachment_resource_indices { + let (tex, _format, _samples, dim) = phys.get_texture(i).expect("unassigned resource"); + let dim = dim.subimage_extents(); + if dim != self.size { + // TODO: handle size changed! + // TODO: error handling + panic!("all framebuffer textures need to have the same dimensions"); + } + image_views.push(res[tex].1); + } + + /* + let mut hasher = DefaultHasher::new(); + image_views.hash(&mut hasher); + let key = hasher.finish(); + if let Some(fb) = self.framebuffers_cache.get(&key).copied() { + self.framebuffer = fb; + return Ok(()); + } + */ + + let create_info = vk::FramebufferCreateInfo::builder() + .render_pass(self.render_pass) + // TODO + .attachments(&image_views) + .width(self.size.x) + .height(self.size.y) + .layers(1); + let fb = unsafe { res.device().create(&create_info.build())? }; + self.framebuffer = fb.take(); + //self.framebuffers_cache.insert(key, self.framebuffer); + Ok(()) + } +} + impl AshRenderGraph { #[inline] pub const fn new() -> Self { @@ -130,7 +225,15 @@ impl AshRenderGraph { topo: Vec::new(), barriers: Vec::new(), hash: 0, - physical_resources_hash: 0, + } + } + + pub fn cleanup(&mut self, res: &mut AshResources) { + self.hash = 0; + for mut topo in self.topo.drain(..) { + for mut topo_render_pass in &mut topo.render_passes.drain(..) { + topo_render_pass.cleanup(res); + } } } @@ -159,36 +262,17 @@ impl AshRenderGraph { "graph updated: topo={:?}, barriers={:?}, formats_changed={:?}", self.topo, self.barriers, formats_changed, ); + self.do_update_framebuffers(res)?; Ok(true) } else { + self.do_update_framebuffers(res)?; Ok(false) } } - fn create_framebuffer<'d>( - res: &'d mut AshResources, - descr: &GraphicsPassDescriptorWithTextures, - render_pass: vk::RenderPass, - ) -> Result> { - // TODO: caching - // TODO: destroy (if not caching) - let image_views: Vec<_> = descr.textures.iter().map(|&t| res[t].1).collect(); - let create_info = vk::FramebufferCreateInfo::builder() - .render_pass(render_pass) - // TODO - .attachments(&image_views) - .width(descr.size.x) - .height(descr.size.y) - .layers(1); - unsafe { - let fb = res.device().create(&create_info.build())?; - Ok(fb) - } - } - fn do_update(&mut self, src: &RenderGraph, res: &mut AshResources) -> Result<()> { + self.cleanup(res); self.hash = src.hash(); - self.topo.clear(); self.barriers.clear(); let num_topological_groups = src.get_num_topological_groups(); @@ -200,23 +284,12 @@ impl AshRenderGraph { for pass in src.get_topological_group(topo_index) { match pass.bind_point() { PipelineBindPoint::Graphics => { - // TODO: no unwrap / error handling - let pass_descr = GraphicsPassDescriptorWithTextures::from_graph( + topo_group.render_passes.push(TopoRenderPass::from_graph( + res, src, &self.physical_resources, pass, - ) - .unwrap(); - let graphics_pass = - res.create::(&pass_descr.graphics_pass)?; - let render_pass = res[graphics_pass]; - let framebuf = Self::create_framebuffer(res, &pass_descr, render_pass)?; - topo_group.render_passes.push(( - pass.index(), - render_pass, - framebuf.take(), - pass_descr.size, - )); + )?); } PipelineBindPoint::Compute => { let range = pass.sub_pass_range(); @@ -235,6 +308,15 @@ impl AshRenderGraph { Ok(()) } + fn do_update_framebuffers(&mut self, res: &mut AshResources) -> Result<()> { + for topo in &mut self.topo { + for topo_render_pass in &mut topo.render_passes { + topo_render_pass.update_framebuffer(res, &self.physical_resources)?; + } + } + Ok(()) + } + pub fn execute( &self, src_graph: &RenderGraph, @@ -246,24 +328,24 @@ impl AshRenderGraph { //let mut clear_values = Vec::new(); for (topo_index, topo) in self.topo.iter().enumerate() { // render-passes - for &(pass_index, render_pass, fb, size) in &topo.render_passes { - let pass = src_graph.get_pass(pass_index).unwrap(); + for topo_render_pass in &topo.render_passes { + let pass = src_graph.get_pass(topo_render_pass.index).unwrap(); let has_multiple_subpass = pass.sub_pass_range().len() > 1; if has_multiple_subpass { encoder.begin_debug_label(pass.name()); } unsafe { - // TODO: caching of render-pass & framebuffer + // TODO: caching of framebuffer // TODO: clear-values, render-area, ... encoder.begin_render_pass( &vk::RenderPassBeginInfo::builder() - .render_pass(render_pass) - .framebuffer(fb) + .render_pass(topo_render_pass.render_pass) + .framebuffer(topo_render_pass.framebuffer) //.clear_values(&clear_values) .render_area( vk::Rect2D::builder() .offset(vk::Offset2D { x: 0, y: 0 }) - .extent(size.vk_into()) + .extent(topo_render_pass.size.vk_into()) .build(), ) .build(), diff --git a/crates/render-ash/src/lib.rs b/crates/render-ash/src/lib.rs index 113a02d..709dc17 100644 --- a/crates/render-ash/src/lib.rs +++ b/crates/render-ash/src/lib.rs @@ -170,14 +170,10 @@ struct AshRendererFull { impl Drop for AshRendererFull { fn drop(&mut self) { - unsafe { - self.device.device_wait_idle().unwrap(); - for (_, swapchain) in self.surfaces.drain() { - swapchain.destroy_with_surface(&mut self.res).unwrap(); - } - } + self.graph.cleanup(&mut self.res); + self.res.wait_idle_and_clear_all().unwrap(); + self.destroy_all_swapchains().unwrap(); self.frames.clear(); - self.res.clear_all().unwrap(); } } diff --git a/crates/render-ash/src/resources/mod.rs b/crates/render-ash/src/resources/mod.rs index 0383267..b9e51f7 100644 --- a/crates/render-ash/src/resources/mod.rs +++ b/crates/render-ash/src/resources/mod.rs @@ -56,9 +56,12 @@ pub struct AshResources { #[derive(Debug, Default)] pub struct AshFrameGarbage { + pub texture_handles: Vec, + pub buffer_handles: Vec, pub buffers: Vec, pub images: Vec, pub image_views: Vec, + pub framebuffers: Vec, pub swapchains: Vec, pub memory: Vec, } @@ -163,18 +166,18 @@ impl AshResources { R::destroy(self, key) } - pub(crate) fn clear_garbage(&mut self) -> Result<()> { + pub(crate) fn wait_idle_and_clear_garbage(&mut self) -> Result<()> { unsafe { self.alloc.device().device_wait_idle()?; - for garbage in &mut self.frame_garbage { - garbage.clear_frame(&mut self.alloc); + for frame in 0..self.frame_garbage.len() { + self.clear_garbage(frame); } } Ok(()) } - pub(crate) fn clear_all(&mut self) -> Result<()> { - self.clear_garbage()?; + pub(crate) fn wait_idle_and_clear_all(&mut self) -> Result<()> { + self.wait_idle_and_clear_garbage()?; // SAFETY: clear save, because clear garbage waits until device is idle unsafe { self.ray_tracing_pipelines.clear_destroy(&mut self.alloc); @@ -202,17 +205,39 @@ impl AshResources { &mut self.frame_garbage[self.current_frame] } + pub(crate) unsafe fn clear_garbage(&mut self, frame: usize) { + let garbage = &mut self.frame_garbage[frame]; + let mut textures = std::mem::take(&mut garbage.texture_handles); + for texture in textures.drain(..) { + if let Some(raw) = self.textures.remove(texture) { + Texture::put_to_garbage(garbage, raw) + } + } + garbage.texture_handles = textures; + let mut buffers = std::mem::take(&mut garbage.buffer_handles); + for buffer in buffers.drain(..) { + if let Some(raw) = self.buffers.remove(buffer) { + Buffer::put_to_garbage(garbage, raw) + } + } + garbage.buffer_handles = buffers; + garbage.clear_frame(&mut self.alloc); + } + /// # SAFETY /// caller must ensure, that the next frame has finished pub(crate) unsafe fn next_frame_and_clear_garbage(&mut self) { self.current_frame = (self.current_frame + 1) % self.frame_garbage.len(); - self.frame_garbage[self.current_frame].clear_frame(&mut self.alloc); + self.clear_garbage(self.current_frame); } } impl AshFrameGarbage { unsafe fn clear_frame(&mut self, alloc: &mut AshAllocator) { let device = alloc.device(); + self.framebuffers + .drain(..) + .for_each(|r| device.destroy_framebuffer(r, None)); self.image_views .drain(..) .for_each(|r| device.destroy_image_view(r, None)); @@ -242,7 +267,7 @@ impl Index for AshResources { impl Drop for AshResources { #[inline] fn drop(&mut self) { - self.clear_all().unwrap(); + self.wait_idle_and_clear_all().unwrap(); if self.pipeline_cache != vk::PipelineCache::null() { unsafe { self.alloc diff --git a/crates/render-ash/src/swapchain.rs b/crates/render-ash/src/swapchain.rs index fbcbb3e..c1becbd 100644 --- a/crates/render-ash/src/swapchain.rs +++ b/crates/render-ash/src/swapchain.rs @@ -762,16 +762,16 @@ impl AshRendererFull { let Some(swapchain) = self.surfaces.remove(window_id) else { return Err(Error::WindowNotAvailable); }; + self.res.wait_idle_and_clear_garbage()?; unsafe { - self.device.device_wait_idle()?; swapchain.destroy_with_surface(&mut self.res)?; } Ok(()) } pub(crate) fn destroy_all_swapchains(&mut self) -> Result<()> { + self.res.wait_idle_and_clear_garbage()?; unsafe { - self.device.device_wait_idle()?; for (_window_id, swapchain) in self.surfaces.drain() { swapchain.destroy_with_surface(&mut self.res)?; } diff --git a/crates/render/src/graph/resources.rs b/crates/render/src/graph/resources.rs index 18da545..fcb9e72 100644 --- a/crates/render/src/graph/resources.rs +++ b/crates/render/src/graph/resources.rs @@ -733,7 +733,7 @@ impl PhysicalResources { changed } - pub(crate) fn get_texture( + pub fn get_texture( &self, idx: ResourceIndex, ) -> Option<(Texture, TextureFormat, u8, TextureDimensions)> { @@ -741,7 +741,7 @@ impl PhysicalResources { Some((r.resource, r.format, 1, r.size)) } - pub(crate) fn get_buffer(&self, idx: ResourceIndex) -> Option<(Buffer, usize)> { + pub fn get_buffer(&self, idx: ResourceIndex) -> Option<(Buffer, usize)> { let r = self.buffers.get_physical(idx)?; Some((r.resource, r.size)) } diff --git a/crates/render/src/pipeline/graphics_pass.rs b/crates/render/src/pipeline/graphics_pass.rs index a9bbb7d..d395b66 100644 --- a/crates/render/src/pipeline/graphics_pass.rs +++ b/crates/render/src/pipeline/graphics_pass.rs @@ -91,14 +91,13 @@ impl GraphicsPassDescriptor { } } -pub struct GraphicsPassDescriptorWithTextures { +pub struct ExtendedGraphicsPassDescriptor { pub graphics_pass: GraphicsPassDescriptor, pub resource_indices: Vec, - pub textures: Vec, pub size: USize2, } -impl GraphicsPassDescriptorWithTextures { +impl ExtendedGraphicsPassDescriptor { pub fn from_graph( graph: &RenderGraph, physical_resources: &PhysicalResources, @@ -116,12 +115,11 @@ impl GraphicsPassDescriptorWithTextures { let mut attachments = Vec::with_capacity(attachment_indices.len()); let mut load_store_ops = Vec::with_capacity(attachment_indices.len()); - let mut textures = Vec::with_capacity(attachment_indices.len()); let mut size = USize2::ZERO; for i in attachment_indices.iter().copied() { let a = &pass.textures()[i as usize]; let resource_index = a.resource_index(); - let (tex, format, samples, dim) = physical_resources + let (_tex, format, samples, dim) = physical_resources .get_texture(resource_index) .expect("unassigned resource"); let dim = dim.subimage_extents(); @@ -132,8 +130,6 @@ impl GraphicsPassDescriptorWithTextures { panic!("all framebuffer textures need to have the same dimensions"); } - textures.push(tex); - attachments.push(AttachmentDescriptor { format, samples, @@ -195,7 +191,6 @@ impl GraphicsPassDescriptorWithTextures { Some(Self { graphics_pass, resource_indices: attachment_indices, - textures, size, }) } From a44ab282044b8466d829318a3d608528cdde96a5 Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sat, 20 Jan 2024 18:54:01 +0100 Subject: [PATCH 28/36] fix(deps): various updates --- Cargo.toml | 8 +- crates/render-ash/Cargo.toml | 2 +- crates/render-ash/src/alloc.rs | 2 +- crates/render-ash/src/convert.rs | 112 ++++++++++++++++++-- crates/render-ash/src/graph.rs | 43 +++++--- crates/render-ash/src/instance.rs | 7 ++ crates/render-ash/src/lib.rs | 33 +++--- crates/render-ash/src/resources/mod.rs | 4 +- crates/render-ash/src/swapchain.rs | 100 +++++------------ crates/render-wgpu/Cargo.toml | 6 +- crates/render-wgpu/src/lib.rs | 14 ++- crates/render-wgpu/src/surface.rs | 55 +++++----- crates/render/macros/src/binding_layout.rs | 2 +- crates/render/macros/src/compile_shader.rs | 22 ++-- crates/render/macros/src/lib.rs | 4 +- crates/render/src/camera.rs | 6 +- crates/render/src/graph/access.rs | 2 +- crates/render/src/graph/mod.rs | 22 +++- crates/render/src/graph/resources.rs | 29 ++++- crates/render/src/pipeline/graphics_pass.rs | 57 ++++++---- crates/render/src/texture/descriptor.rs | 23 +++- crates/window-winit/Cargo.toml | 2 +- crates/window-winit/src/lib.rs | 2 +- crates/window/src/lib.rs | 6 +- crates/window/src/listener.rs | 4 +- examples/android/src/lib.rs | 5 +- 26 files changed, 362 insertions(+), 210 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 72948c8..b359562 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,9 @@ edition = "2024" rust-version = "1.86" [workspace.dependencies] -glam = "0.24" +glam = "0.25" serde = "1.0" -raw-window-handle = "0.5.2" +raw-window-handle = "0.6" slotmap = "1.0" anyhow = "1.0" thiserror = "1.0" @@ -41,8 +41,8 @@ gametime = "0.3" atomic_refcell = "0.1" palette = { version = "0.7", default-features = false } image = { version = "0.24", default-features = false } -encase = { version = "0.6", features = ["glam"], default-features = false } -encase_derive_impl = { version = "0.6" } +encase = { version = "0.7", features = ["glam"], default-features = false } +encase_derive_impl = { version = "0.7" } crossbeam-utils = "0.8" crossbeam-queue = "0.3" tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/crates/render-ash/Cargo.toml b/crates/render-ash/Cargo.toml index afd4f43..d17594f 100644 --- a/crates/render-ash/Cargo.toml +++ b/crates/render-ash/Cargo.toml @@ -29,7 +29,7 @@ gpu-descriptor = "0.2" crossbeam-queue = { workspace = true } [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] -raw-window-metal = "0.3" +raw-window-metal = "0.4" [dev-dependencies] anyhow = { workspace = true } diff --git a/crates/render-ash/src/alloc.rs b/crates/render-ash/src/alloc.rs index f723a85..60f01cd 100644 --- a/crates/render-ash/src/alloc.rs +++ b/crates/render-ash/src/alloc.rs @@ -38,7 +38,7 @@ impl AshAllocator { #[inline] pub fn instance(&self) -> &AshInstance { - &self.device.instance() + self.device.instance() } #[inline] diff --git a/crates/render-ash/src/convert.rs b/crates/render-ash/src/convert.rs index 5c5949f..cea9d2e 100644 --- a/crates/render-ash/src/convert.rs +++ b/crates/render-ash/src/convert.rs @@ -11,7 +11,10 @@ use pulz_render::{ PrimitiveTopology, RayTracingPipelineDescriptor, StencilFaceState, StencilOperation, StoreOp, VertexFormat, }, - texture::{TextureAspects, TextureDescriptor, TextureDimensions, TextureFormat, TextureUsage}, + texture::{ + TextureAspects, TextureDescriptor, TextureDimensions, TextureFormat, TextureLayout, + TextureUsage, + }, }; use scratchbuffer::ScratchBuffer; @@ -397,6 +400,82 @@ impl VkFrom for TextureFormat { } } +pub fn default_clear_value_for_format(format: vk::Format) -> vk::ClearValue { + match format { + // Depth and stencil formats + vk::Format::S8_UINT + | vk::Format::D16_UNORM + | vk::Format::X8_D24_UNORM_PACK32 + | vk::Format::D24_UNORM_S8_UINT + | vk::Format::D32_SFLOAT => vk::ClearValue { + depth_stencil: vk::ClearDepthStencilValue { + depth: 1.0, + stencil: 0, + }, + }, + + _ => vk::ClearValue { + color: default_clear_color_value_for_format(format), + }, + } +} + +pub fn default_clear_color_value_for_format(format: vk::Format) -> vk::ClearColorValue { + match format { + vk::Format::R8_SINT + | vk::Format::R8G8_SINT + | vk::Format::R8G8B8_SINT + | vk::Format::B8G8R8_SINT + | vk::Format::R8G8B8A8_SINT + | vk::Format::B8G8R8A8_SINT + | vk::Format::A8B8G8R8_SINT_PACK32 + | vk::Format::A2R10G10B10_SINT_PACK32 + | vk::Format::A2B10G10R10_SINT_PACK32 + | vk::Format::R16_SINT + | vk::Format::R16G16_SINT + | vk::Format::R16G16B16_SINT + | vk::Format::R16G16B16A16_SINT + | vk::Format::R32_SINT + | vk::Format::R32G32_SINT + | vk::Format::R32G32B32_SINT + | vk::Format::R32G32B32A32_SINT + | vk::Format::R64_SINT + | vk::Format::R64G64_SINT + | vk::Format::R64G64B64_SINT + | vk::Format::R64G64B64A64_SINT => vk::ClearColorValue { + int32: [i32::MIN, i32::MIN, i32::MIN, i32::MAX], + }, + + vk::Format::R8_UINT + | vk::Format::R8G8_UINT + | vk::Format::R8G8B8_UINT + | vk::Format::B8G8R8_UINT + | vk::Format::R8G8B8A8_UINT + | vk::Format::B8G8R8A8_UINT + | vk::Format::A8B8G8R8_UINT_PACK32 + | vk::Format::A2R10G10B10_UINT_PACK32 + | vk::Format::A2B10G10R10_UINT_PACK32 + | vk::Format::R16_UINT + | vk::Format::R16G16_UINT + | vk::Format::R16G16B16_UINT + | vk::Format::R16G16B16A16_UINT + | vk::Format::R32_UINT + | vk::Format::R32G32_UINT + | vk::Format::R32G32B32_UINT + | vk::Format::R32G32B32A32_UINT + | vk::Format::R64_UINT + | vk::Format::R64G64_UINT + | vk::Format::R64G64B64_UINT + | vk::Format::R64G64B64A64_UINT => vk::ClearColorValue { + uint32: [0, 0, 0, u32::MAX], + }, + + _ => vk::ClearColorValue { + float32: [0.0, 0.0, 0.0, 1.0], + }, + } +} + impl VkFrom for vk::Format { #[inline] fn from(val: &VertexFormat) -> Self { @@ -470,6 +549,27 @@ impl VkFrom for vk::ImageUsageFlags { } } +impl VkFrom for vk::ImageLayout { + #[inline] + fn from(val: &TextureLayout) -> Self { + match val { + TextureLayout::Undefined => Self::UNDEFINED, + TextureLayout::General => Self::GENERAL, + TextureLayout::TransferSrc => Self::TRANSFER_SRC_OPTIMAL, + TextureLayout::TransferDst => Self::TRANSFER_DST_OPTIMAL, + TextureLayout::Preinitialized => Self::PREINITIALIZED, + TextureLayout::ShaderReadOnly | TextureLayout::InputAttachment => { + Self::SHADER_READ_ONLY_OPTIMAL + } + TextureLayout::ColorAttachment => Self::COLOR_ATTACHMENT_OPTIMAL, + TextureLayout::DepthStencilAttachment => Self::DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + TextureLayout::Present => Self::PRESENT_SRC_KHR, + + _ => Self::GENERAL, + } + } +} + pub fn into_texture_usage_read_access(usage: TextureUsage) -> vk::AccessFlags { let mut result = vk::AccessFlags::empty(); if usage.contains(TextureUsage::TRANSFER_SRC) | usage.contains(TextureUsage::TRANSFER_DST) { @@ -867,8 +967,8 @@ impl CreateInfoConverter6 { .stencil_load_op(load_store_ops.load_op.vk_into()) .stencil_store_op(load_store_ops.store_op.vk_into()) // TODO: initial_layout if load-op == LOAD - .initial_layout(vk::ImageLayout::UNDEFINED) - .final_layout(vk::ImageLayout::GENERAL) + .initial_layout(a.initial_layout.vk_into()) + .final_layout(a.final_layout.vk_into()) .build(), ); attachment_dep_data.push((vk::SUBPASS_EXTERNAL, vk::AccessFlags::NONE)); @@ -883,7 +983,7 @@ impl CreateInfoConverter6 { let dst = i as u32; for &a in sp.input_attachments() { let a = a as usize; - attachments[a].final_layout = vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL; + //attachments[a].final_layout = vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL; let (src, src_access) = attachment_dep_data[a]; if src != vk::SUBPASS_EXTERNAL { let dep = get_or_create_subpass_dep(sp_deps, src, dst, src_access); @@ -894,7 +994,7 @@ impl CreateInfoConverter6 { } for &a in sp.color_attachments() { let a = a as usize; - attachments[a].final_layout = vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL; + //attachments[a].final_layout = vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL; let (src, src_access) = attachment_dep_data[a]; if src != vk::SUBPASS_EXTERNAL { let dep = get_or_create_subpass_dep(sp_deps, src, dst, src_access); @@ -906,7 +1006,7 @@ impl CreateInfoConverter6 { } if let Some(a) = sp.depth_stencil_attachment() { let a = a as usize; - attachments[a].final_layout = vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + //attachments[a].final_layout = vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL; let (src, src_access) = attachment_dep_data[a]; if src != vk::SUBPASS_EXTERNAL { let dep = get_or_create_subpass_dep(sp_deps, src, dst, src_access); diff --git a/crates/render-ash/src/graph.rs b/crates/render-ash/src/graph.rs index c0e14f4..c012f9c 100644 --- a/crates/render-ash/src/graph.rs +++ b/crates/render-ash/src/graph.rs @@ -1,7 +1,4 @@ -use std::{ - collections::hash_map::DefaultHasher, - hash::{Hash, Hasher}, -}; +use std::hash::Hash; use ash::vk::{self, PipelineStageFlags}; use pulz_assets::Handle; @@ -17,14 +14,15 @@ use pulz_render::{ }, math::USize2, pipeline::{ExtendedGraphicsPassDescriptor, GraphicsPass}, - texture::{Texture, TextureDescriptor, TextureDimensions, TextureFormat, TextureUsage}, - utils::hash::U64HashMap, + texture::{ + Texture, TextureDescriptor, TextureDimensions, TextureFormat, TextureLayout, TextureUsage, + }, }; use pulz_window::WindowsMirror; use tracing::debug; use crate::{ - convert::VkInto, + convert::{default_clear_value_for_format, VkInto}, encoder::{AshCommandPool, SubmissionGroup}, resources::AshResources, swapchain::AshSurfaceSwapchain, @@ -98,8 +96,7 @@ impl PhysicalResourceResolver for AshPhysicalResourceResolver<'_> { Some(PhysicalResource { resource: aquired_texture.texture, format: surface.texture_format(), - // TODO: usage - usage: TextureUsage::ALL_ATTACHMENTS, + usage: TextureUsage::PRESENT, size: TextureDimensions::D2(surface.size()), }) } @@ -143,12 +140,15 @@ impl TopoRenderPass { res: &mut AshResources, src: &RenderGraph, phys: &PhysicalResources, + current_texture_layouts: &mut [TextureLayout], pass: &PassDescription, ) -> Result { - let pass_descr = ExtendedGraphicsPassDescriptor::from_graph(src, phys, pass).unwrap(); + let pass_descr = + ExtendedGraphicsPassDescriptor::from_graph(src, phys, current_texture_layouts, pass) + .unwrap(); let graphics_pass = res.create::(&pass_descr.graphics_pass)?; let render_pass = res[graphics_pass]; - Ok(TopoRenderPass { + Ok(Self { index: pass.index(), render_pass, framebuffer: vk::Framebuffer::null(), @@ -174,9 +174,9 @@ impl TopoRenderPass { */ } - fn update_framebuffer<'d>( + fn update_framebuffer( &mut self, - res: &'d mut AshResources, + res: &mut AshResources, phys: &PhysicalResources, ) -> Result<()> { self.cleanup(res); @@ -279,6 +279,10 @@ impl AshRenderGraph { self.topo .resize_with(num_topological_groups, Default::default); + let mut current_texture_layouts = Vec::new(); + current_texture_layouts.resize(src.get_num_textures(), TextureLayout::Undefined); + // TODO: get initial layout of external textures + for topo_index in 0..num_topological_groups { let topo_group = &mut self.topo[topo_index]; for pass in src.get_topological_group(topo_index) { @@ -288,6 +292,7 @@ impl AshRenderGraph { res, src, &self.physical_resources, + &mut current_texture_layouts, pass, )?); } @@ -325,7 +330,7 @@ impl AshRenderGraph { draw_phases: &DrawPhases, ) -> Result<()> { let mut encoder = command_pool.encoder()?; - //let mut clear_values = Vec::new(); + let mut clear_values = Vec::new(); for (topo_index, topo) in self.topo.iter().enumerate() { // render-passes for topo_render_pass in &topo.render_passes { @@ -334,6 +339,14 @@ impl AshRenderGraph { if has_multiple_subpass { encoder.begin_debug_label(pass.name()); } + clear_values.clear(); + for &i in &topo_render_pass.attachment_resource_indices { + let (_tex, format, _samples, _dim) = + self.physical_resources.get_texture(i).unwrap(); + let format: vk::Format = format.vk_into(); + let clear_value = default_clear_value_for_format(format); + clear_values.push(clear_value); + } unsafe { // TODO: caching of framebuffer // TODO: clear-values, render-area, ... @@ -341,7 +354,7 @@ impl AshRenderGraph { &vk::RenderPassBeginInfo::builder() .render_pass(topo_render_pass.render_pass) .framebuffer(topo_render_pass.framebuffer) - //.clear_values(&clear_values) + .clear_values(&clear_values) .render_area( vk::Rect2D::builder() .offset(vk::Offset2D { x: 0, y: 0 }) diff --git a/crates/render-ash/src/instance.rs b/crates/render-ash/src/instance.rs index 34ef478..9be1b4a 100644 --- a/crates/render-ash/src/instance.rs +++ b/crates/render-ash/src/instance.rs @@ -18,6 +18,7 @@ pub struct AshInstance { instance_extensions: Vec<&'static CStr>, ext_debug_utils: Option, ext_surface: Option, + flags: AshRendererFlags, } impl Deref for AshInstance { @@ -40,6 +41,7 @@ impl AshInstance { instance_extensions, ext_debug_utils: None, ext_surface: None, + flags, }; if instance.has_instance_extension(DebugUtils::name()) { @@ -84,6 +86,11 @@ impl AshInstance { .as_ref() .ok_or(ErrorNoExtension(ext::DebugUtils::name())) } + + #[inline] + pub fn flags(&self) -> AshRendererFlags { + self.flags + } } impl Drop for AshInstance { diff --git a/crates/render-ash/src/lib.rs b/crates/render-ash/src/lib.rs index 709dc17..636ea3b 100644 --- a/crates/render-ash/src/lib.rs +++ b/crates/render-ash/src/lib.rs @@ -29,6 +29,7 @@ use std::{backtrace::Backtrace, ffi::CStr, rc::Rc, sync::Arc}; use ash::vk::{self, PipelineStageFlags}; use bitflags::bitflags; +use convert::default_clear_color_value_for_format; use device::AshDevice; use encoder::{AshCommandPool, SubmissionGroup}; use graph::AshRenderGraph; @@ -51,7 +52,7 @@ mod shader; mod swapchain; use pulz_window::{ - listener::WindowSystemListener, HasRawWindowAndDisplayHandle, Window, WindowId, Windows, + listener::WindowSystemListener, HasWindowAndDisplayHandle, Window, WindowId, Windows, WindowsMirror, }; @@ -291,12 +292,8 @@ impl AshRendererFull { drop(span_update); let span_exec = tracing::trace_span!("Execute").entered(); - self.graph.execute( - src_graph, - submission_group, - &mut frame.command_pool, - phases, - )?; + self.graph + .execute(src_graph, submission_group, &mut frame.command_pool, phases)?; drop(span_exec); Ok(()) @@ -327,7 +324,10 @@ impl AshRendererFull { submission_group.wait(sem, PipelineStageFlags::TRANSFER); let surface = &mut self.surfaces[window_id]; if let Some(acquired) = surface.acquire_next_image(&mut self.res, sem)? { - images.push((acquired.image, surface.clear_value())); + images.push(( + acquired.image, + default_clear_color_value_for_format(surface.format()), + )); } } @@ -424,6 +424,8 @@ impl AshRendererFull { .signal(frame.finished_semaphore) .submit(&self.device, frame.finished_fence)?; + // TODO: tranform images to PRESENT layout + self.present_acquired_swapchain_images(&[frame.finished_semaphore])?; } @@ -448,10 +450,7 @@ impl AshRendererFull { #[allow(clippy::large_enum_variant)] enum AshRendererInner { - Early { - instance: Arc, - flags: AshRendererFlags, - }, + Early(Arc), Full(AshRendererFull), } @@ -466,11 +465,11 @@ impl AshRenderer { #[inline] pub fn with_flags(flags: AshRendererFlags) -> Result { let instance = AshInstance::new(flags)?; - Ok(Self(AshRendererInner::Early { instance, flags })) + Ok(Self(AshRendererInner::Early(instance))) } fn init(&mut self) -> Result<&mut AshRendererFull> { - if let AshRendererInner::Early { instance, .. } = &self.0 { + if let AshRendererInner::Early(instance) = &self.0 { let device = instance.new_device(vk::SurfaceKHR::null())?; let renderer = AshRendererFull::from_device(device)?; self.0 = AshRendererInner::Full(renderer); @@ -485,7 +484,7 @@ impl AshRenderer { &mut self, window_id: WindowId, window_descriptor: &Window, - window: Rc, + window: Rc, ) -> Result<&mut AshRendererFull> { if let AshRendererInner::Full(renderer) = &mut self.0 { let device = renderer.device.clone(); @@ -493,7 +492,7 @@ impl AshRenderer { let surface = unsafe { device.instance().new_surface(&*window)? }; renderer.init_swapchain(window_id, window_descriptor, window, surface)?; } else { - let AshRendererInner::Early { instance, .. } = &self.0 else { + let AshRendererInner::Early(instance) = &self.0 else { unreachable!() }; // SAVETY: window is kept alive @@ -523,7 +522,7 @@ impl WindowSystemListener for AshRenderer { &mut self, window_id: WindowId, window_desc: &Window, - window: Rc, + window: Rc, ) { self.init_window(window_id, window_desc, window).unwrap(); } diff --git a/crates/render-ash/src/resources/mod.rs b/crates/render-ash/src/resources/mod.rs index b9e51f7..edc1bef 100644 --- a/crates/render-ash/src/resources/mod.rs +++ b/crates/render-ash/src/resources/mod.rs @@ -98,12 +98,12 @@ impl AshResources { #[inline] pub fn instance(&self) -> &AshInstance { - &self.alloc.instance() + self.alloc.instance() } #[inline] pub fn device(&self) -> &AshDevice { - &self.alloc.device() + self.alloc.device() } pub fn with_pipeline_cache(mut self, initial_data: &[u8]) -> Result { diff --git a/crates/render-ash/src/swapchain.rs b/crates/render-ash/src/swapchain.rs index c1becbd..a83ea9c 100644 --- a/crates/render-ash/src/swapchain.rs +++ b/crates/render-ash/src/swapchain.rs @@ -5,9 +5,7 @@ use pulz_render::{ math::{uvec2, USize2}, texture::{Texture, TextureFormat}, }; -use pulz_window::{ - HasRawWindowAndDisplayHandle, Size2, Window, WindowDescriptor, WindowId, Windows, -}; +use pulz_window::{HasWindowAndDisplayHandle, Size2, Window, WindowDescriptor, WindowId, Windows}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; use tracing::{debug, info}; @@ -179,18 +177,20 @@ impl AshInstance { not(target_os = "macos"), not(target_os = "ios") ))] - (RawDisplayHandle::Xlib(d), RawWindowHandle::Xlib(w)) => { - self.create_surface_xlib(d.display as *mut _, w.window) - } + (RawDisplayHandle::Xlib(d), RawWindowHandle::Xlib(w)) => self.create_surface_xlib( + d.display.ok_or(Error::WindowNotAvailable)?.as_ptr().cast(), + w.window, + ), #[cfg(all( unix, not(target_os = "android"), not(target_os = "macos"), not(target_os = "ios") ))] - (RawDisplayHandle::Xcb(d), RawWindowHandle::Xcb(w)) => { - self.create_surface_xcb(d.connection, w.window) - } + (RawDisplayHandle::Xcb(d), RawWindowHandle::Xcb(w)) => self.create_surface_xcb( + d.connection.ok_or(Error::WindowNotAvailable)?.as_ptr(), + w.window.get(), + ), #[cfg(all( unix, not(target_os = "android"), @@ -198,7 +198,7 @@ impl AshInstance { not(target_os = "ios") ))] (RawDisplayHandle::Wayland(d), RawWindowHandle::Wayland(w)) => { - self.create_surface_wayland(d.display, w.surface) + self.create_surface_wayland(d.display.as_ptr(), w.surface.as_ptr()) } #[cfg(target_os = "android")] (RawDisplayHandle::Android(_), RawWindowHandle::AndroidNdk(w)) => { @@ -234,10 +234,18 @@ impl AshInstance { /// SAFETY: display and window handles must be valid for the complete lifetime of surface pub(crate) unsafe fn new_surface( &self, - window: &dyn HasRawWindowAndDisplayHandle, + window: &dyn HasWindowAndDisplayHandle, ) -> Result> { - let surface_raw = - self.create_surface_raw(window.raw_display_handle(), window.raw_window_handle())?; + fn map_handle_error(e: raw_window_handle::HandleError) -> Error { + use raw_window_handle::HandleError; + match e { + HandleError::Unavailable => Error::WindowNotAvailable, + _ => Error::UnsupportedWindowSystem, + } + } + let raw_display_handle = window.display_handle().map_err(map_handle_error)?.as_raw(); + let raw_window_handle = window.window_handle().map_err(map_handle_error)?.as_raw(); + let surface_raw = self.create_surface_raw(raw_display_handle, raw_window_handle)?; Ok(Guard::new(self, surface_raw)) } } @@ -366,7 +374,7 @@ pub struct AshSurfaceSwapchain { image_views: Vec, textures: Vec, acquired_image: u32, - window: Rc, // for keeping ownership + window: Rc, // for keeping ownership } impl AshSurfaceSwapchain { @@ -380,10 +388,10 @@ impl AshSurfaceSwapchain { } fn new_unconfigured( - window: Rc, + window: Rc, surface_raw: vk::SurfaceKHR, ) -> Self { - AshSurfaceSwapchain { + Self { surface_raw, swapchain_raw: vk::SwapchainKHR::null(), size: USize2::new(0, 0), @@ -430,62 +438,6 @@ impl AshSurfaceSwapchain { self.images.len() } - pub fn clear_value(&self) -> vk::ClearColorValue { - match self.surface_format.format { - vk::Format::R8_SINT - | vk::Format::R8G8_SINT - | vk::Format::R8G8B8_SINT - | vk::Format::B8G8R8_SINT - | vk::Format::R8G8B8A8_SINT - | vk::Format::B8G8R8A8_SINT - | vk::Format::A8B8G8R8_SINT_PACK32 - | vk::Format::A2R10G10B10_SINT_PACK32 - | vk::Format::A2B10G10R10_SINT_PACK32 - | vk::Format::R16_SINT - | vk::Format::R16G16_SINT - | vk::Format::R16G16B16_SINT - | vk::Format::R16G16B16A16_SINT - | vk::Format::R32_SINT - | vk::Format::R32G32_SINT - | vk::Format::R32G32B32_SINT - | vk::Format::R32G32B32A32_SINT - | vk::Format::R64_SINT - | vk::Format::R64G64_SINT - | vk::Format::R64G64B64_SINT - | vk::Format::R64G64B64A64_SINT => vk::ClearColorValue { - int32: [i32::MIN, i32::MIN, i32::MIN, i32::MAX], - }, - - vk::Format::R8_UINT - | vk::Format::R8G8_UINT - | vk::Format::R8G8B8_UINT - | vk::Format::B8G8R8_UINT - | vk::Format::R8G8B8A8_UINT - | vk::Format::B8G8R8A8_UINT - | vk::Format::A8B8G8R8_UINT_PACK32 - | vk::Format::A2R10G10B10_UINT_PACK32 - | vk::Format::A2B10G10R10_UINT_PACK32 - | vk::Format::R16_UINT - | vk::Format::R16G16_UINT - | vk::Format::R16G16B16_UINT - | vk::Format::R16G16B16A16_UINT - | vk::Format::R32_UINT - | vk::Format::R32G32_UINT - | vk::Format::R32G32B32_UINT - | vk::Format::R32G32B32A32_UINT - | vk::Format::R64_UINT - | vk::Format::R64G64_UINT - | vk::Format::R64G64B64_UINT - | vk::Format::R64G64B64A64_UINT => vk::ClearColorValue { - uint32: [0, 0, 0, u32::MAX], - }, - - _ => vk::ClearColorValue { - float32: [0.0, 0.0, 0.0, 1.0], - }, - } - } - #[inline] pub const fn is_acquired(&self) -> bool { self.acquired_image != !0 @@ -743,7 +695,7 @@ impl AshRendererFull { &mut self, window_id: WindowId, window_descriptor: &Window, - window: Rc, + window: Rc, surface: Guard<'_, vk::SurfaceKHR>, ) -> Result<&mut AshSurfaceSwapchain> { assert!(self @@ -795,7 +747,7 @@ impl AshRendererFull { window.size ); surface_swapchain - .configure_with(&mut self.res, &window) + .configure_with(&mut self.res, window) .unwrap(); } true diff --git a/crates/render-wgpu/Cargo.toml b/crates/render-wgpu/Cargo.toml index 07aba72..e6324d3 100644 --- a/crates/render-wgpu/Cargo.toml +++ b/crates/render-wgpu/Cargo.toml @@ -15,7 +15,7 @@ pulz-render = { path = "../render" } thiserror = { workspace = true } tracing = { workspace = true } slotmap = { workspace = true } -wgpu = "0.18" +wgpu = "0.19" raw-window-handle = { workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] @@ -23,7 +23,7 @@ pollster = "0.3" [dev-dependencies] anyhow = { workspace = true } -naga = "0.14" +naga = "0.19" pulz-window-winit = { path = "../window-winit" } pulz-render-pipeline-core = { path = "../render-pipeline-core" } @@ -31,7 +31,7 @@ pulz-render-pipeline-core = { path = "../render-pipeline-core" } tracing-subscriber = { workspace = true } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wgpu = { version = "0.18" , features = ["webgl"] } +wgpu = { version = "0.19" , features = ["webgl"] } tracing-wasm = { workspace = true } tracing-log = { workspace = true } console_error_panic_hook = { workspace = true } diff --git a/crates/render-wgpu/src/lib.rs b/crates/render-wgpu/src/lib.rs index 6f051a9..c22a84d 100644 --- a/crates/render-wgpu/src/lib.rs +++ b/crates/render-wgpu/src/lib.rs @@ -32,7 +32,7 @@ use graph::WgpuRenderGraph; use pulz_ecs::prelude::*; use pulz_render::{draw::DrawPhases, graph::RenderGraph, RenderModule, RenderSystemPhase}; use pulz_window::{ - listener::WindowSystemListener, HasRawWindowAndDisplayHandle, Window, WindowId, Windows, + listener::WindowSystemListener, HasWindowAndDisplayHandle, Window, WindowId, Windows, WindowsMirror, }; use resources::WgpuResources; @@ -58,6 +58,9 @@ pub enum Error { #[error("The window is not available, or it has no raw-window-handle")] WindowNotAvailable, + #[error("The used Window-System is not supported")] + UnsupportedWindowSystem, + #[error("Unable to create surface")] CreateSurfaceError(#[from] wgpu::CreateSurfaceError), @@ -94,9 +97,10 @@ impl WgpuRendererFull { .request_device( &wgpu::DeviceDescriptor { label: None, - features: wgpu::Features::empty(), + required_features: wgpu::Features::empty(), // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain. - limits: wgpu::Limits::downlevel_defaults().using_resolution(adapter.limits()), + required_limits: wgpu::Limits::downlevel_defaults() + .using_resolution(adapter.limits()), }, trace_dir.ok().as_ref().map(std::path::Path::new), ) @@ -218,7 +222,7 @@ impl WgpuRenderer { &mut self, window_id: WindowId, window_descriptor: &Window, - window: Rc, + window: Rc, ) -> Result<&mut WgpuRendererFull> { if let WgpuRendererInner::Full(renderer) = &mut self.0 { renderer.surfaces.remove(window_id); // replaces old surface @@ -289,7 +293,7 @@ impl WindowSystemListener for WgpuRenderer { &mut self, window_id: WindowId, window_desc: &Window, - window: Rc, + window: Rc, ) { self.init_window(window_id, window_desc, window).unwrap(); } diff --git a/crates/render-wgpu/src/surface.rs b/crates/render-wgpu/src/surface.rs index ad6be6d..5e5af3e 100644 --- a/crates/render-wgpu/src/surface.rs +++ b/crates/render-wgpu/src/surface.rs @@ -3,24 +3,40 @@ use std::{ rc::Rc, }; -use pulz_window::{HasRawWindowAndDisplayHandle, Size2, Window}; +use pulz_window::{HasWindowAndDisplayHandle, Size2, Window}; use tracing::info; +use crate::{Error, Result}; + pub struct Surface { - surface: wgpu::Surface, + surface: wgpu::Surface<'static>, size: Size2, vsync: bool, format: wgpu::TextureFormat, - window: Rc, + window: Rc, } impl Surface { pub fn create( instance: &wgpu::Instance, window_descriptor: &Window, - window: Rc, - ) -> Result { - let surface = unsafe { instance.create_surface(&*window)? }; + window: Rc, + ) -> Result { + fn map_handle_error(e: raw_window_handle::HandleError) -> Error { + use raw_window_handle::HandleError; + match e { + HandleError::Unavailable => Error::WindowNotAvailable, + _ => Error::UnsupportedWindowSystem, + } + } + let raw_display_handle = window.display_handle().map_err(map_handle_error)?.as_raw(); + let raw_window_handle = window.window_handle().map_err(map_handle_error)?.as_raw(); + let surface = unsafe { + instance.create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle { + raw_display_handle, + raw_window_handle, + })? + }; Ok(Self { surface, size: window_descriptor.size, @@ -57,32 +73,17 @@ impl Surface { pub fn configure(&mut self, adapter: &wgpu::Adapter, device: &wgpu::Device) { // TODO: also reconfigure on resize, and when presenting results in `Outdated/Lost` - let capabilities = self.surface.get_capabilities(adapter); - self.format = capabilities - .formats - .first() - .copied() - .expect("surface not compatible"); - let present_mode = if self.vsync { - wgpu::PresentMode::AutoVsync - } else { - wgpu::PresentMode::AutoNoVsync - }; - let surface_config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: self.format, - width: self.size.x, - height: self.size.y, - present_mode, - alpha_mode: wgpu::CompositeAlphaMode::Auto, - view_formats: vec![], - }; + let surface_config = self + .surface + .get_default_config(adapter, self.size.x, self.size.y) + .expect("surface not supported by adapter"); + self.surface.configure(device, &surface_config); } } impl Deref for Surface { - type Target = wgpu::Surface; + type Target = wgpu::Surface<'static>; #[inline] fn deref(&self) -> &Self::Target { &self.surface diff --git a/crates/render/macros/src/binding_layout.rs b/crates/render/macros/src/binding_layout.rs index 3e0684b..abaacea 100644 --- a/crates/render/macros/src/binding_layout.rs +++ b/crates/render/macros/src/binding_layout.rs @@ -148,7 +148,7 @@ impl BindingLayoutArgs { ); } } - Meta::Path(path) => {} + Meta::Path(_path) => {} } } } diff --git a/crates/render/macros/src/compile_shader.rs b/crates/render/macros/src/compile_shader.rs index f393976..e902e4a 100644 --- a/crates/render/macros/src/compile_shader.rs +++ b/crates/render/macros/src/compile_shader.rs @@ -1,15 +1,6 @@ use darling::FromMeta; use proc_macro2::TokenStream; -use syn::{AttributeArgs, LitStr, Result}; - -pub fn compile_shader_int(args: AttributeArgs) -> Result { - let args = CompileShaderArgs::from_list(&args)?; - compile_shader_with_args(args) -} - -pub fn compile_shader_with_args(args: CompileShaderArgs) -> Result { - panic!("TODO: implement: {:#?}", args); -} +use syn::{LitStr, Result}; #[derive(FromMeta, PartialEq, Eq, Debug)] pub enum TargetFormat { @@ -22,3 +13,14 @@ pub struct CompileShaderArgs { pub target_format: TargetFormat, pub source: LitStr, } + +impl CompileShaderArgs { + pub fn parse(input: TokenStream) -> Result { + let meta_list = darling::ast::NestedMeta::parse_meta_list(input)?; + let args = Self::from_list(&meta_list)?; + Ok(args) + } + pub fn compile(&self) -> Result { + panic!("TODO: implement: {:#?}", self); + } +} diff --git a/crates/render/macros/src/lib.rs b/crates/render/macros/src/lib.rs index c06496e..20f816f 100644 --- a/crates/render/macros/src/lib.rs +++ b/crates/render/macros/src/lib.rs @@ -28,8 +28,8 @@ encase_derive_impl::implement! {{ #[cfg(feature = "unstable")] #[proc_macro] pub fn compile_shader_int(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as syn::AttributeArgs); - compile_shader::compile_shader_int(input) + compile_shader::CompileShaderArgs::parse(input.into()) + .and_then(|args| args.compile()) .unwrap_or_else(|err| err.to_compile_error()) .into() } diff --git a/crates/render/src/camera.rs b/crates/render/src/camera.rs index c60f737..079001d 100644 --- a/crates/render/src/camera.rs +++ b/crates/render/src/camera.rs @@ -235,8 +235,8 @@ pub enum RenderTarget { impl RenderTarget { pub fn resolve(&self, surfaces: &WindowSurfaces, _images: &Assets) -> Option { match self { - RenderTarget::Window(window_id) => surfaces.get(*window_id).copied(), - RenderTarget::Image(_image_handle) => todo!("surface from image asset"), + Self::Window(window_id) => surfaces.get(*window_id).copied(), + Self::Image(_image_handle) => todo!("surface from image asset"), } } } @@ -247,7 +247,7 @@ pub fn update_projections_from_render_targets( mut projections: Query<'_, (&'_ mut Projection, &'_ RenderTarget)>, ) { for (projection, render_target) in projections.iter() { - if let Some(surface) = render_target.resolve(&window_surfaces, &images) { + if let Some(surface) = render_target.resolve(window_surfaces, images) { projection.update_viewport(surface.logical_size()); } } diff --git a/crates/render/src/graph/access.rs b/crates/render/src/graph/access.rs index e1aab8b..932063a 100644 --- a/crates/render/src/graph/access.rs +++ b/crates/render/src/graph/access.rs @@ -104,7 +104,7 @@ impl ResourceAccess for Buffer { type Usage = BufferUsage; type Format = (); type Size = usize; - type ExternHandle = Handle; + type ExternHandle = Handle; fn check_usage_is_pass_compatible(_combined_usage: Self::Usage) { panic!("Can't use buffer multiple times in the same pass"); diff --git a/crates/render/src/graph/mod.rs b/crates/render/src/graph/mod.rs index e16afa6..9ac6b60 100644 --- a/crates/render/src/graph/mod.rs +++ b/crates/render/src/graph/mod.rs @@ -2,7 +2,7 @@ use core::fmt; use self::{ pass::{run::PassExec, PipelineBindPoint}, - resources::{ExtendedResourceData, ResourceDeps, ResourceSet}, + resources::{ExtendedResourceData, Resource, ResourceDeps, ResourceSet}, }; use crate::{ buffer::Buffer, @@ -101,6 +101,26 @@ impl RenderGraph { self.passes_topo_order.len() } + #[inline] + pub fn get_num_textures(&self) -> usize { + self.textures.len() + } + + #[inline] + pub fn get_num_buffers(&self) -> usize { + self.buffers.len() + } + + #[inline] + pub(crate) fn get_texture_info(&self, index: usize) -> Option<&Resource> { + self.textures.get(index) + } + + #[inline] + pub(crate) fn get_buffer_info(&self, index: usize) -> Option<&Resource> { + self.buffers.get(index) + } + pub fn get_topological_group( &self, group: usize, diff --git a/crates/render/src/graph/resources.rs b/crates/render/src/graph/resources.rs index fcb9e72..c092960 100644 --- a/crates/render/src/graph/resources.rs +++ b/crates/render/src/graph/resources.rs @@ -103,14 +103,14 @@ impl WriteSlot { } #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -enum ResourceVariant { +pub enum ResourceVariant { Transient, Import, Export, } #[derive(Debug)] -struct Resource { +pub(crate) struct Resource { first_written: SubPassIndex, last_written: SubPassIndex, usage: R::Usage, @@ -140,6 +140,21 @@ impl Resource { R::default_format(self.usage) } } + + #[inline] + pub fn usage(&self) -> R::Usage { + self.usage + } + + #[inline] + pub fn size(&self) -> Option { + self.size + } + + #[inline] + pub fn variant(&self) -> ResourceVariant { + self.variant + } } impl ExtendedResourceData { @@ -222,6 +237,10 @@ impl ResourceSet { WriteSlot::new(index, SUBPASS_UNDEFINED) } + pub(super) fn get(&self, index: usize) -> Option<&Resource> { + self.resources.get(index) + } + pub(super) fn set_format(&mut self, slot: &Slot, format: R::Format) { let slot = &mut self.resources[slot.index as usize]; if let Some(old_format) = &slot.format { @@ -790,19 +809,19 @@ impl GraphExport for WindowId { } impl GraphExport for RenderTarget { - fn export(&self) -> RenderTarget { + fn export(&self) -> Self { *self } } impl GraphImport for Handle { - fn import(&self) -> Handle { + fn import(&self) -> Self { *self } } impl GraphExport for Handle { - fn export(&self) -> Handle { + fn export(&self) -> Self { *self } } diff --git a/crates/render/src/pipeline/graphics_pass.rs b/crates/render/src/pipeline/graphics_pass.rs index d395b66..93b70ae 100644 --- a/crates/render/src/pipeline/graphics_pass.rs +++ b/crates/render/src/pipeline/graphics_pass.rs @@ -6,7 +6,7 @@ use crate::{ pass::PipelineBindPoint, resources::PhysicalResources, PassDescription, RenderGraph, ResourceIndex, }, - texture::{Texture, TextureFormat, TextureUsage}, + texture::{TextureFormat, TextureLayout, TextureUsage}, }; crate::backend::define_gpu_resource!(GraphicsPass, GraphicsPassDescriptor); @@ -41,8 +41,8 @@ pub struct LoadStoreOps { pub struct AttachmentDescriptor { pub format: TextureFormat, pub usage: TextureUsage, - //pub initial_layout: TextureLayout, - //pub final_layout: TextureLayout, + pub initial_layout: TextureLayout, + pub final_layout: TextureLayout, pub samples: u8, } @@ -101,6 +101,7 @@ impl ExtendedGraphicsPassDescriptor { pub fn from_graph( graph: &RenderGraph, physical_resources: &PhysicalResources, + current_texture_layouts: &mut [TextureLayout], pass: &PassDescription, ) -> Option { if pass.bind_point() != PipelineBindPoint::Graphics { @@ -130,21 +131,29 @@ impl ExtendedGraphicsPassDescriptor { panic!("all framebuffer textures need to have the same dimensions"); } + let mut load_store = LoadStoreOps { + // TODO: provide a way to use DONT_CARE or CLEAR + load_op: LoadOp::Clear, + // TODO: is resource used in later pass? then STORE, else DONT_CARE + store_op: StoreOp::Store, + }; + if a.is_read() { + load_store.load_op = LoadOp::Load; + } else { + // overide to undefined + current_texture_layouts[resource_index as usize] = TextureLayout::Undefined; + } + let current_texture_layout = current_texture_layouts[resource_index as usize]; + attachments.push(AttachmentDescriptor { format, samples, usage: a.usage(), + initial_layout: current_texture_layout, + final_layout: current_texture_layout, }); - load_store_ops.push(LoadStoreOps { - load_op: if a.is_read() { - LoadOp::Load - } else { - // TODO: provide a way to use DONT_CARE or CLEAR - LoadOp::Clear - }, - // TODO: is resource used in later pass? then STORE, else DONT_CARE - store_op: StoreOp::Store, - }); + + load_store_ops.push(load_store); } // map attachment_indices into their actual resource indices @@ -152,10 +161,17 @@ impl ExtendedGraphicsPassDescriptor { // pass.textures() is sorted by resource-index! *i = pass.textures()[*i as usize].resource_index(); } - let map_attachment_index = |resource_index: &u16| { - attachment_indices + let mut map_attachment_index = |resource_index: &u16, mut layout: TextureLayout| { + if layout == TextureLayout::Undefined { + layout = current_texture_layouts[*resource_index as usize]; + } else { + current_texture_layouts[*resource_index as usize] = layout; + }; + let a = attachment_indices .binary_search(resource_index) - .expect("unvalid resource index") as u16 + .expect("unvalid resource index") as u16; + attachments[a as usize].final_layout = layout; + a }; let mut subpasses = Vec::with_capacity(pass.sub_pass_range().len()); @@ -164,17 +180,17 @@ impl ExtendedGraphicsPassDescriptor { let input_attachments = sp .input_attachments() .iter() - .map(map_attachment_index) + .map(|r| map_attachment_index(r, TextureLayout::InputAttachment)) .collect(); let color_attachments = sp .color_attachments() .iter() - .map(map_attachment_index) + .map(|r| map_attachment_index(r, TextureLayout::ColorAttachment)) .collect(); let depth_stencil_attachment = sp .depth_stencil_attachment() .as_ref() - .map(map_attachment_index); + .map(|r| map_attachment_index(r, TextureLayout::DepthStencilAttachment)); subpasses.push(SubpassDescriptor { input_attachments, color_attachments, @@ -182,6 +198,9 @@ impl ExtendedGraphicsPassDescriptor { }) } + // TODO: if this pass is the last pass accessing this resource (and resource not extern), then STOREOP = DON'T CARE + // TODO: if this pass is the last pass accessing this resource, and usage us PRESENT, then finalLayout=PRESENT + let graphics_pass = GraphicsPassDescriptor { attachments, load_store_ops, diff --git a/crates/render/src/texture/descriptor.rs b/crates/render/src/texture/descriptor.rs index 0d83f27..d3797cd 100644 --- a/crates/render/src/texture/descriptor.rs +++ b/crates/render/src/texture/descriptor.rs @@ -269,12 +269,13 @@ bitflags! { const COLOR_ATTACHMENT = 16; const DEPTH_STENCIL_ATTACHMENT = 32; const INPUT_ATTACHMENT = 64; + const PRESENT = 128; // modifiers - const BY_REGION = 128; + const BY_REGION = 256; const NONE = 0; - const ALL_READ = Self::TRANSFER_SRC.bits() | Self::SAMPLED.bits() | Self::INPUT_ATTACHMENT.bits(); + const ALL_READ = Self::TRANSFER_SRC.bits() | Self::SAMPLED.bits() | Self::INPUT_ATTACHMENT.bits() | Self::PRESENT.bits(); const ALL_WRITE = Self::TRANSFER_DST.bits() | Self::STORAGE.bits() | Self::COLOR_ATTACHMENT.bits() | Self::DEPTH_STENCIL_ATTACHMENT.bits(); const ALL_ATTACHMENTS = Self::COLOR_ATTACHMENT.bits() | Self::DEPTH_STENCIL_ATTACHMENT.bits() | Self::INPUT_ATTACHMENT.bits(); } @@ -292,6 +293,24 @@ impl TextureUsage { } } +#[derive( + Copy, Clone, Debug, Default, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, +)] +#[non_exhaustive] +pub enum TextureLayout { + #[default] + Undefined, + General, + TransferSrc, + TransferDst, + Preinitialized, + ShaderReadOnly, + ColorAttachment, + DepthStencilAttachment, + InputAttachment, + Present, +} + #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default, Serialize, Deserialize)] pub struct ImageDataLayout { pub offset: usize, diff --git a/crates/window-winit/Cargo.toml b/crates/window-winit/Cargo.toml index 99a56af..03197a2 100644 --- a/crates/window-winit/Cargo.toml +++ b/crates/window-winit/Cargo.toml @@ -21,7 +21,7 @@ pulz-input = { path = "../input" } fnv = { workspace = true } tracing = { workspace = true } -winit = { version = "0.29.7", default-features = false, features = ["rwh_05"] } +winit = { version = "0.29.10", default-features = false, features = ["rwh_06"] } raw-window-handle = { workspace = true, features = ["std"] } [target.'cfg(not(target_os = "unknown"))'.dev-dependencies] diff --git a/crates/window-winit/src/lib.rs b/crates/window-winit/src/lib.rs index 4cd200a..a57a83c 100644 --- a/crates/window-winit/src/lib.rs +++ b/crates/window-winit/src/lib.rs @@ -243,7 +243,7 @@ impl WinitWindowSystemMut<'_> { winit_window }; res.foreach_meta_mut(move |l: &mut dyn WindowSystemListener| { - l.on_created(window_id, &window_descr, winit_window.clone()); + l.on_created(window_id, window_descr, winit_window.clone()); }); } } diff --git a/crates/window/src/lib.rs b/crates/window/src/lib.rs index 927d9df..f560105 100644 --- a/crates/window/src/lib.rs +++ b/crates/window/src/lib.rs @@ -32,10 +32,10 @@ mod window; pub type Point2 = glam::IVec2; pub type Size2 = glam::UVec2; -use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; +use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; pub use crate::window::*; -pub trait HasRawWindowAndDisplayHandle: HasRawWindowHandle + HasRawDisplayHandle {} +pub trait HasWindowAndDisplayHandle: HasWindowHandle + HasDisplayHandle {} -impl HasRawWindowAndDisplayHandle for T where T: HasRawWindowHandle + HasRawDisplayHandle {} +impl HasWindowAndDisplayHandle for T where T: HasWindowHandle + HasDisplayHandle {} diff --git a/crates/window/src/listener.rs b/crates/window/src/listener.rs index e3d3989..20f7e0a 100644 --- a/crates/window/src/listener.rs +++ b/crates/window/src/listener.rs @@ -2,14 +2,14 @@ use std::rc::Rc; use pulz_ecs::impl_any_cast; -use crate::{HasRawWindowAndDisplayHandle, Window, WindowId}; +use crate::{HasWindowAndDisplayHandle, Window, WindowId}; pub trait WindowSystemListener: 'static { fn on_created( &mut self, _window_id: WindowId, _window_desc: &Window, - _window: Rc, + _window: Rc, ) { } fn on_closed(&mut self, _window_id: WindowId) {} diff --git a/examples/android/src/lib.rs b/examples/android/src/lib.rs index be02e91..615cd4d 100644 --- a/examples/android/src/lib.rs +++ b/examples/android/src/lib.rs @@ -9,10 +9,7 @@ use pulz_render_ash::AshRenderer; use pulz_render_pipeline_core::core_3d::CoreShadingModule; use pulz_window::{WindowDescriptor, WindowId}; use pulz_window_winit::{winit, WinitWindowModule, WinitWindowSystem}; -use winit::{ - event_loop::{EventLoop, EventLoopBuilder, EventLoopWindowTarget}, - window::Window, -}; +use winit::{event_loop::EventLoopWindowTarget, window::Window}; fn init(event_loop: &EventLoopWindowTarget<()>) -> (Resources, Rc, WinitWindowSystem) { info!("Initializing..."); From 795cb33798a6b9c47acdfd6e9d2d8a4563c6bf63 Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sun, 28 Jan 2024 16:43:21 +0100 Subject: [PATCH 29/36] refactor(render): new Access bitflag, seperate from Texture/Buffer-Usage --- crates/render-ash/src/convert.rs | 321 +++++++++--------- crates/render-ash/src/debug_utils.rs | 20 +- crates/render-ash/src/graph.rs | 27 +- .../src/deferred_3d/mod.rs | 6 +- crates/render-wgpu/src/convert.rs | 41 ++- crates/render/src/backend.rs | 10 +- crates/render/src/buffer.rs | 75 +++- crates/render/src/graph/access.rs | 216 +++++++++--- crates/render/src/graph/mod.rs | 21 +- crates/render/src/graph/pass/builder.rs | 120 ++++--- crates/render/src/graph/resources.rs | 196 +++++------ crates/render/src/pipeline/graphics_pass.rs | 77 +++-- crates/render/src/texture/descriptor.rs | 88 +++-- 13 files changed, 733 insertions(+), 485 deletions(-) diff --git a/crates/render-ash/src/convert.rs b/crates/render-ash/src/convert.rs index cea9d2e..adbaa3b 100644 --- a/crates/render-ash/src/convert.rs +++ b/crates/render-ash/src/convert.rs @@ -2,7 +2,10 @@ use ash::vk; use pulz_bitset::BitSet; use pulz_render::{ buffer::{BufferDescriptor, BufferUsage}, - graph::{access::Stage, pass::PipelineBindPoint}, + graph::{ + access::{Access, Stage}, + pass::PipelineBindPoint, + }, math::{USize2, USize3}, pipeline::{ BindGroupLayoutDescriptor, BlendFactor, BlendOperation, CompareFunction, @@ -11,10 +14,7 @@ use pulz_render::{ PrimitiveTopology, RayTracingPipelineDescriptor, StencilFaceState, StencilOperation, StoreOp, VertexFormat, }, - texture::{ - TextureAspects, TextureDescriptor, TextureDimensions, TextureFormat, TextureLayout, - TextureUsage, - }, + texture::{TextureAspects, TextureDescriptor, TextureDimensions, TextureFormat, TextureUsage}, }; use scratchbuffer::ScratchBuffer; @@ -60,30 +60,42 @@ impl VkFrom for vk::BufferUsageFlags { #[inline] fn from(val: &BufferUsage) -> Self { let mut result = Self::empty(); - if val.contains(BufferUsage::TRANSFER_SRC) { - result |= Self::TRANSFER_SRC; - } - if val.contains(BufferUsage::TRANSFER_DST) { - result |= Self::TRANSFER_DST; + if val.intersects(BufferUsage::INDIRECT) { + result |= Self::INDIRECT_BUFFER; } - if val.contains(BufferUsage::INDEX) { + if val.intersects(BufferUsage::INDEX) { result |= Self::INDEX_BUFFER; } - if val.contains(BufferUsage::UNIFORM) { + if val.intersects(BufferUsage::VERTEX) { + result |= Self::VERTEX_BUFFER; + } + if val.intersects(BufferUsage::UNIFORM) { result |= Self::UNIFORM_BUFFER; } - if val.contains(BufferUsage::STORAGE) { + if val.intersects(BufferUsage::STORAGE) { result |= Self::STORAGE_BUFFER; } - if val.contains(BufferUsage::INDIRECT) { - result |= Self::INDIRECT_BUFFER; + if val.intersects(BufferUsage::UNIFORM_TEXEL) { + result |= Self::UNIFORM_TEXEL_BUFFER; + } + if val.intersects(BufferUsage::STORAGE_TEXEL) { + result |= Self::STORAGE_TEXEL_BUFFER; + } + if val.intersects(BufferUsage::TRANSFER_READ) { + result |= Self::TRANSFER_SRC; + } + if val.intersects(BufferUsage::TRANSFER_WRITE) { + result |= Self::TRANSFER_DST; + } + if val.intersects(BufferUsage::ACCELERATION_STRUCTURE_STORAGE) { + result |= Self::ACCELERATION_STRUCTURE_STORAGE_KHR; + } + if val.intersects(BufferUsage::ACCELERATION_STRUCTURE_BUILD_INPUT) { + result |= Self::ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_KHR; + } + if val.intersects(BufferUsage::SHADER_BINDING_TABLE) { + result |= Self::SHADER_BINDING_TABLE_KHR; } - // if val.contains(BufferUsage::UNIFORM_TEXEL) { - // result |= Self::UNIFORM_TEXEL_BUFFER; - // } - // if val.contains(BufferUsage::STORAGE_TEXEL) { - // result |= Self::STORAGE_TEXEL_BUFFER; - // } result } } @@ -524,127 +536,136 @@ impl VkFrom for vk::Format { impl VkFrom for vk::ImageUsageFlags { fn from(val: &TextureUsage) -> Self { let mut result = Self::empty(); - if val.contains(TextureUsage::TRANSFER_SRC) { + if val.intersects(TextureUsage::INPUT_ATTACHMENT) { + result |= Self::INPUT_ATTACHMENT; + } + if val.intersects(TextureUsage::COLOR_ATTACHMENT) { + result |= Self::COLOR_ATTACHMENT; + } + if val.intersects(TextureUsage::DEPTH_STENCIL_ATTACHMENT) { + result |= Self::DEPTH_STENCIL_ATTACHMENT; + } + if val.intersects(TextureUsage::TRANSFER_READ) { result |= Self::TRANSFER_SRC; } - if val.contains(TextureUsage::TRANSFER_DST) { + if val.intersects(TextureUsage::TRANSFER_WRITE) { result |= Self::TRANSFER_DST; } - if val.contains(TextureUsage::SAMPLED) { + if val.intersects(TextureUsage::SAMPLED) { result |= Self::SAMPLED; } - if val.contains(TextureUsage::STORAGE) { + if val.intersects(TextureUsage::STORAGE) { result |= Self::STORAGE; } - if val.contains(TextureUsage::COLOR_ATTACHMENT) { - result |= Self::COLOR_ATTACHMENT; - } - if val.contains(TextureUsage::DEPTH_STENCIL_ATTACHMENT) { - result |= Self::DEPTH_STENCIL_ATTACHMENT; - } - if val.contains(TextureUsage::INPUT_ATTACHMENT) { - result |= Self::INPUT_ATTACHMENT; - } result } } -impl VkFrom for vk::ImageLayout { +impl VkFrom for vk::ImageLayout { #[inline] - fn from(val: &TextureLayout) -> Self { - match val { - TextureLayout::Undefined => Self::UNDEFINED, - TextureLayout::General => Self::GENERAL, - TextureLayout::TransferSrc => Self::TRANSFER_SRC_OPTIMAL, - TextureLayout::TransferDst => Self::TRANSFER_DST_OPTIMAL, - TextureLayout::Preinitialized => Self::PREINITIALIZED, - TextureLayout::ShaderReadOnly | TextureLayout::InputAttachment => { - Self::SHADER_READ_ONLY_OPTIMAL - } - TextureLayout::ColorAttachment => Self::COLOR_ATTACHMENT_OPTIMAL, - TextureLayout::DepthStencilAttachment => Self::DEPTH_STENCIL_ATTACHMENT_OPTIMAL, - TextureLayout::Present => Self::PRESENT_SRC_KHR, - - _ => Self::GENERAL, + fn from(val: &Access) -> Self { + let mut num = 0; + let mut r = Self::UNDEFINED; + if val.intersects(Access::COLOR_ATTACHMENT_READ | Access::COLOR_ATTACHMENT_WRITE) { + r = Self::COLOR_ATTACHMENT_OPTIMAL; + num += 1; + } + if val.intersects(Access::DEPTH_STENCIL_ATTACHMENT_WRITE) { + r = Self::DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + // TODO: single write variants + num += 1; + } else if val.intersects(Access::DEPTH_STENCIL_ATTACHMENT_READ) { + r = Self::DEPTH_STENCIL_READ_ONLY_OPTIMAL; + num += 1; + } + if val.intersects(Access::TRANSFER_READ) { + r = Self::TRANSFER_SRC_OPTIMAL; + num += 1; + } + if val.intersects(Access::TRANSFER_WRITE) { + r = Self::TRANSFER_DST_OPTIMAL; + num += 1; + } + if val.intersects(Access::PRESENT) { + r = Self::PRESENT_SRC_KHR; + num += 1; + } + if num <= 1 { + return r; + } + if !val.intersects(Access::ANY_WRITE) { + Self::READ_ONLY_OPTIMAL + } else { + Self::GENERAL } } } -pub fn into_texture_usage_read_access(usage: TextureUsage) -> vk::AccessFlags { - let mut result = vk::AccessFlags::empty(); - if usage.contains(TextureUsage::TRANSFER_SRC) | usage.contains(TextureUsage::TRANSFER_DST) { - result |= vk::AccessFlags::TRANSFER_READ; - } - if usage.contains(TextureUsage::SAMPLED) || usage.contains(TextureUsage::STORAGE) { - result |= vk::AccessFlags::SHADER_READ; - } - if usage.contains(TextureUsage::COLOR_ATTACHMENT) { - result |= vk::AccessFlags::COLOR_ATTACHMENT_READ; - } - if usage.contains(TextureUsage::DEPTH_STENCIL_ATTACHMENT) { - result |= vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ; - } - if usage.contains(TextureUsage::INPUT_ATTACHMENT) { - result |= vk::AccessFlags::INPUT_ATTACHMENT_READ; - } - result -} - -pub fn into_texture_usage_write_access(usage: TextureUsage) -> vk::AccessFlags { - let mut result = vk::AccessFlags::empty(); - if usage.contains(TextureUsage::TRANSFER_SRC) | usage.contains(TextureUsage::TRANSFER_DST) { - result |= vk::AccessFlags::TRANSFER_WRITE; - } - if usage.contains(TextureUsage::STORAGE) { - result |= vk::AccessFlags::SHADER_WRITE; - } - if usage.contains(TextureUsage::COLOR_ATTACHMENT) { - result |= vk::AccessFlags::COLOR_ATTACHMENT_WRITE; - } - if usage.contains(TextureUsage::DEPTH_STENCIL_ATTACHMENT) { - result |= vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE; - } - result -} - -pub fn into_buffer_usage_read_access(usage: BufferUsage) -> vk::AccessFlags { - let mut result = vk::AccessFlags::empty(); - if usage.contains(BufferUsage::HOST) { - result |= vk::AccessFlags::HOST_READ; - } - if usage.contains(BufferUsage::TRANSFER_SRC) | usage.contains(BufferUsage::TRANSFER_DST) { - result |= vk::AccessFlags::TRANSFER_READ; - } - if usage.contains(BufferUsage::INDEX) { - result |= vk::AccessFlags::INDEX_READ; - } - if usage.contains(BufferUsage::VERTEX) { - result |= vk::AccessFlags::VERTEX_ATTRIBUTE_READ; - } - if usage.contains(BufferUsage::UNIFORM) { - result |= vk::AccessFlags::UNIFORM_READ; - } - if usage.contains(BufferUsage::STORAGE) { - result |= vk::AccessFlags::SHADER_READ; - } - if usage.contains(BufferUsage::INDIRECT) { - result |= vk::AccessFlags::INDIRECT_COMMAND_READ; - } - result -} - -pub fn into_buffer_usage_write_access(usage: BufferUsage) -> vk::AccessFlags { - let mut result = vk::AccessFlags::empty(); - if usage.contains(BufferUsage::HOST) { - result |= vk::AccessFlags::HOST_WRITE; - } - if usage.contains(BufferUsage::TRANSFER_SRC) | usage.contains(BufferUsage::TRANSFER_DST) { - result |= vk::AccessFlags::TRANSFER_WRITE; - } - if usage.contains(BufferUsage::STORAGE) { - result |= vk::AccessFlags::SHADER_WRITE; +impl VkFrom for vk::AccessFlags { + #[inline] + fn from(val: &Access) -> Self { + let mut result = Self::empty(); + if *val == Access::GENERAL { + return Self::MEMORY_READ | Self::MEMORY_WRITE; + } else if *val == Access::MEMORY_READ { + return Self::MEMORY_READ; + } else if *val == Access::MEMORY_WRITE { + return Self::MEMORY_WRITE; + } + if val.intersects(Access::INDIRECT_COMMAND_READ) { + result |= Self::INDIRECT_COMMAND_READ; + } + if val.intersects(Access::INDEX_READ) { + result |= Self::INDEX_READ; + } + if val.intersects(Access::VERTEX_ATTRIBUTE_READ) { + result |= Self::VERTEX_ATTRIBUTE_READ; + } + if val.intersects( + Access::COLOR_INPUT_ATTACHMENT_READ | Access::DEPTH_STENCIL_INPUT_ATTACHMENT_READ, + ) { + result |= Self::INPUT_ATTACHMENT_READ; + } + if val.intersects(Access::UNIFORM_READ) { + result |= Self::UNIFORM_READ; + } + if val.intersects(Access::SAMPLED_READ | Access::SAMPLED_READ) { + result |= Self::SHADER_READ; + } + if val.intersects(Access::COLOR_ATTACHMENT_READ) { + result |= Self::COLOR_ATTACHMENT_READ; + } + if val.intersects(Access::COLOR_ATTACHMENT_WRITE) { + result |= Self::COLOR_ATTACHMENT_WRITE; + } + if val.intersects(Access::DEPTH_STENCIL_ATTACHMENT_READ) { + result |= Self::DEPTH_STENCIL_ATTACHMENT_READ; + } + if val.intersects(Access::DEPTH_STENCIL_ATTACHMENT_WRITE) { + result |= Self::DEPTH_STENCIL_ATTACHMENT_WRITE; + } + if val.intersects(Access::TRANSFER_READ) { + result |= Self::TRANSFER_READ; + } + if val.intersects(Access::TRANSFER_WRITE) { + result |= Self::TRANSFER_WRITE; + } + if val.intersects(Access::HOST_READ) { + result |= Self::HOST_READ; + } + if val.intersects(Access::HOST_WRITE) { + result |= Self::HOST_WRITE; + } + if val.intersects( + Access::ACCELERATION_STRUCTURE_READ | Access::ACCELERATION_STRUCTURE_BUILD_READ, + ) { + result |= Self::ACCELERATION_STRUCTURE_READ_KHR; + } + if val.intersects(Access::ACCELERATION_STRUCTURE_BUILD_WRITE) { + result |= Self::ACCELERATION_STRUCTURE_WRITE_KHR; + } + result } - result } impl VkFrom for vk::PipelineBindPoint { @@ -967,8 +988,8 @@ impl CreateInfoConverter6 { .stencil_load_op(load_store_ops.load_op.vk_into()) .stencil_store_op(load_store_ops.store_op.vk_into()) // TODO: initial_layout if load-op == LOAD - .initial_layout(a.initial_layout.vk_into()) - .final_layout(a.final_layout.vk_into()) + .initial_layout(a.initial_access.vk_into()) + .final_layout(a.final_access.vk_into()) .build(), ); attachment_dep_data.push((vk::SUBPASS_EXTERNAL, vk::AccessFlags::NONE)); @@ -976,14 +997,15 @@ impl CreateInfoConverter6 { // calculate subpass deps let sp_deps = self.1.clear_and_use_as::(); + let a_refs = self.2.clear_and_use_as::(); let num_subpasses = desc.subpasses().len(); let mut attachment_usage = BitSet::with_capacity_for(num_attachments * num_subpasses); for (i, sp) in desc.subpasses().iter().enumerate() { // TODO: handle Write>Read>Write! + // TODO: also non-attachment dubpass-deps let dst = i as u32; - for &a in sp.input_attachments() { + for &(a, _u) in sp.input_attachments() { let a = a as usize; - //attachments[a].final_layout = vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL; let (src, src_access) = attachment_dep_data[a]; if src != vk::SUBPASS_EXTERNAL { let dep = get_or_create_subpass_dep(sp_deps, src, dst, src_access); @@ -991,10 +1013,15 @@ impl CreateInfoConverter6 { dep.dst_access_mask |= vk::AccessFlags::INPUT_ATTACHMENT_READ; } attachment_usage.insert(i * num_attachments + a); + + a_refs.push(vk::AttachmentReference { + attachment: a as u32, // if a==!0 => vk::ATTACHMENT_UNUSED + //layout: u.vk_into(), + layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, + }); } - for &a in sp.color_attachments() { + for &(a, _u) in sp.color_attachments() { let a = a as usize; - //attachments[a].final_layout = vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL; let (src, src_access) = attachment_dep_data[a]; if src != vk::SUBPASS_EXTERNAL { let dep = get_or_create_subpass_dep(sp_deps, src, dst, src_access); @@ -1003,10 +1030,16 @@ impl CreateInfoConverter6 { } attachment_dep_data[a] = (dst, vk::AccessFlags::COLOR_ATTACHMENT_WRITE); attachment_usage.insert(i * num_attachments + a); + + //attachments[a].final_layout = vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL; + a_refs.push(vk::AttachmentReference { + attachment: a as u32, // if a==!0 => vk::ATTACHMENT_UNUSED + //layout: u.vk_into(), + layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, + }); } - if let Some(a) = sp.depth_stencil_attachment() { + if let Some((a, _u)) = sp.depth_stencil_attachment() { let a = a as usize; - //attachments[a].final_layout = vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL; let (src, src_access) = attachment_dep_data[a]; if src != vk::SUBPASS_EXTERNAL { let dep = get_or_create_subpass_dep(sp_deps, src, dst, src_access); @@ -1016,32 +1049,16 @@ impl CreateInfoConverter6 { } attachment_dep_data[a] = (dst, vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE); attachment_usage.insert(i * num_attachments + a); - } - } - drop(attachment_dep_data); - // collect references - let a_refs = self.2.clear_and_use_as::(); - for s in desc.subpasses() { - for &a in s.input_attachments() { - a_refs.push(vk::AttachmentReference { - attachment: a as u32, // if a==!0 => vk::ATTACHMENT_UNUSED - layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, - }); - } - for &a in s.color_attachments() { - a_refs.push(vk::AttachmentReference { - attachment: a as u32, // if a==!0 => vk::ATTACHMENT_UNUSED - layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, - }); - } - if let Some(a) = s.depth_stencil_attachment() { + //attachments[a].final_layout = vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL; a_refs.push(vk::AttachmentReference { attachment: a as u32, + //layout: u.vk_into(), layout: vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL, }); } } + drop(attachment_dep_data); // preserve attachment let mut a_preserve_tmp: Vec> = Vec::new(); @@ -1104,7 +1121,6 @@ impl CreateInfoConverter6 { } subpasses.push(b.build()); } - // TODO: subpass dependencies let buf = self.5.clear_and_use_as::(); buf.reserve(1); @@ -1112,6 +1128,7 @@ impl CreateInfoConverter6 { vk::RenderPassCreateInfo::builder() .attachments(attachments.as_slice()) .subpasses(subpasses.as_slice()) + .dependencies(sp_deps.as_slice()) .build(), ); &buf.as_slice()[0] diff --git a/crates/render-ash/src/debug_utils.rs b/crates/render-ash/src/debug_utils.rs index de135fb..358bf98 100644 --- a/crates/render-ash/src/debug_utils.rs +++ b/crates/render-ash/src/debug_utils.rs @@ -35,46 +35,46 @@ unsafe extern "system" fn debug_callback( match message_severity { DebugUtilsMessageSeverityFlagsEXT::VERBOSE => { debug!( - ?message_id_name, - "Vk[{:?},#{}]: {}", + "Vk[{:?},#{},{:?}]: {}", message_type, message_id_number, + message_id_name, message.to_string_lossy() ) } DebugUtilsMessageSeverityFlagsEXT::INFO => { info!( - ?message_id_name, - "Vk[{:?},#{}]: {}", + "Vk[{:?},#{},{:?}]: {}", message_type, message_id_number, + message_id_name, message.to_string_lossy() ) } DebugUtilsMessageSeverityFlagsEXT::WARNING => { warn!( - ?message_id_name, - "Vk[{:?},#{}]: {}", + "Vk[{:?},#{},{:?}]: {}", message_type, message_id_number, + message_id_name, message.to_string_lossy() ) } DebugUtilsMessageSeverityFlagsEXT::ERROR => { error!( - ?message_id_name, - "Vk[{:?},#{}]: {}", + "Vk[{:?},#{},{:?}]: {}", message_type, message_id_number, + message_id_name, message.to_string_lossy() ) } _ => { warn!( - ?message_id_name, - "Vk[{:?},#{}]: {}", + "Vk[{:?},#{},{:?}]: {}", message_type, message_id_number, + message_id_name, message.to_string_lossy() ) } diff --git a/crates/render-ash/src/graph.rs b/crates/render-ash/src/graph.rs index c012f9c..4a72c6c 100644 --- a/crates/render-ash/src/graph.rs +++ b/crates/render-ash/src/graph.rs @@ -1,22 +1,19 @@ -use std::hash::Hash; - use ash::vk::{self, PipelineStageFlags}; use pulz_assets::Handle; use pulz_render::{ backend::PhysicalResourceResolver, - buffer::{Buffer, BufferUsage}, + buffer::Buffer, camera::RenderTarget, draw::DrawPhases, graph::{ + access::Access, pass::PipelineBindPoint, resources::{PhysicalResource, PhysicalResources}, PassDescription, PassIndex, RenderGraph, }, math::USize2, pipeline::{ExtendedGraphicsPassDescriptor, GraphicsPass}, - texture::{ - Texture, TextureDescriptor, TextureDimensions, TextureFormat, TextureLayout, TextureUsage, - }, + texture::{Texture, TextureDescriptor, TextureDimensions, TextureFormat}, }; use pulz_window::WindowsMirror; use tracing::debug; @@ -96,7 +93,7 @@ impl PhysicalResourceResolver for AshPhysicalResourceResolver<'_> { Some(PhysicalResource { resource: aquired_texture.texture, format: surface.texture_format(), - usage: TextureUsage::PRESENT, + access: Access::PRESENT, size: TextureDimensions::D2(surface.size()), }) } @@ -111,14 +108,14 @@ impl PhysicalResourceResolver for AshPhysicalResourceResolver<'_> { &mut self, format: TextureFormat, dimensions: TextureDimensions, - usage: TextureUsage, + access: Access, ) -> Option { let t = self .res .create::(&TextureDescriptor { format, dimensions, - usage, + usage: access.as_texture_usage(), ..Default::default() }) .ok()?; @@ -128,7 +125,7 @@ impl PhysicalResourceResolver for AshPhysicalResourceResolver<'_> { Some(t) } - fn create_transient_buffer(&mut self, _size: usize, _usage: BufferUsage) -> Option { + fn create_transient_buffer(&mut self, _size: usize, _access: Access) -> Option { // TODO: destroy buffers // TODO: reuse buffers todo!("implement create_transient_buffer") @@ -140,11 +137,11 @@ impl TopoRenderPass { res: &mut AshResources, src: &RenderGraph, phys: &PhysicalResources, - current_texture_layouts: &mut [TextureLayout], + current_texture_access: &mut [Access], pass: &PassDescription, ) -> Result { let pass_descr = - ExtendedGraphicsPassDescriptor::from_graph(src, phys, current_texture_layouts, pass) + ExtendedGraphicsPassDescriptor::from_graph(src, phys, current_texture_access, pass) .unwrap(); let graphics_pass = res.create::(&pass_descr.graphics_pass)?; let render_pass = res[graphics_pass]; @@ -279,8 +276,8 @@ impl AshRenderGraph { self.topo .resize_with(num_topological_groups, Default::default); - let mut current_texture_layouts = Vec::new(); - current_texture_layouts.resize(src.get_num_textures(), TextureLayout::Undefined); + let mut current_texture_access = Vec::new(); + current_texture_access.resize(src.get_num_textures(), Access::NONE); // TODO: get initial layout of external textures for topo_index in 0..num_topological_groups { @@ -292,7 +289,7 @@ impl AshRenderGraph { res, src, &self.physical_resources, - &mut current_texture_layouts, + &mut current_texture_access, pass, )?); } diff --git a/crates/render-pipeline-core/src/deferred_3d/mod.rs b/crates/render-pipeline-core/src/deferred_3d/mod.rs index fe3145b..ad70a0f 100644 --- a/crates/render-pipeline-core/src/deferred_3d/mod.rs +++ b/crates/render-pipeline-core/src/deferred_3d/mod.rs @@ -119,9 +119,9 @@ impl Pass for Composition { type Output = WriteSlot; fn build(self, mut build: PassBuilder<'_, Graphics>) -> (Self::Output, PassExec) { - build.input_attachment(self.albedo); - build.input_attachment(self.position); - build.input_attachment(self.normal); + build.color_input_attachment(self.albedo); + build.color_input_attachment(self.position); + build.color_input_attachment(self.normal); let output = build.creates_color_attachment(); (output, PassExec::noop()) } diff --git a/crates/render-wgpu/src/convert.rs b/crates/render-wgpu/src/convert.rs index d4d6306..a39eb58 100644 --- a/crates/render-wgpu/src/convert.rs +++ b/crates/render-wgpu/src/convert.rs @@ -52,24 +52,33 @@ pub fn convert_buffer_descriptor(val: &BufferDescriptor) -> wgpu::BufferDescript #[inline] fn convert_buffer_usage(val: BufferUsage) -> wgpu::BufferUsages { let mut result = wgpu::BufferUsages::empty(); - if val.contains(BufferUsage::TRANSFER_SRC) { + if val.intersects(BufferUsage::TRANSFER_READ) { result |= wgpu::BufferUsages::COPY_SRC; } - if val.contains(BufferUsage::TRANSFER_DST) { + if val.intersects(BufferUsage::TRANSFER_READ) { result |= wgpu::BufferUsages::COPY_DST; } - if val.contains(BufferUsage::INDEX) { + if val.intersects(BufferUsage::HOST_READ) { + result |= wgpu::BufferUsages::MAP_READ; + } + if val.intersects(BufferUsage::HOST_WRITE) { + result |= wgpu::BufferUsages::MAP_WRITE; + } + if val.intersects(BufferUsage::INDEX) { result |= wgpu::BufferUsages::INDEX; } - if val.contains(BufferUsage::UNIFORM) { + if val.intersects(BufferUsage::VERTEX) { + result |= wgpu::BufferUsages::VERTEX; + } + if val.intersects(BufferUsage::INDIRECT) { + result |= wgpu::BufferUsages::INDIRECT; + } + if val.intersects(BufferUsage::UNIFORM | BufferUsage::UNIFORM_TEXEL) { result |= wgpu::BufferUsages::UNIFORM; } - if val.contains(BufferUsage::STORAGE) { + if val.intersects(BufferUsage::STORAGE | BufferUsage::STORAGE_TEXEL) { result |= wgpu::BufferUsages::STORAGE; } - if val.contains(BufferUsage::INDIRECT) { - result |= wgpu::BufferUsages::INDIRECT; - } result } @@ -245,21 +254,23 @@ fn convert_vertex_format(val: VertexFormat) -> Result { #[inline] fn convert_texture_usages(val: TextureUsage) -> wgpu::TextureUsages { let mut result = wgpu::TextureUsages::empty(); - if val.contains(TextureUsage::TRANSFER_SRC) { + if val.intersects(TextureUsage::TRANSFER_READ) { result |= wgpu::TextureUsages::COPY_SRC; } - if val.contains(TextureUsage::TRANSFER_DST) { + if val.intersects(TextureUsage::TRANSFER_WRITE) { result |= wgpu::TextureUsages::COPY_DST; } - if val.contains(TextureUsage::SAMPLED) { + if val.intersects(TextureUsage::SAMPLED) { result |= wgpu::TextureUsages::TEXTURE_BINDING; } - if val.contains(TextureUsage::STORAGE) { + if val.intersects(TextureUsage::STORAGE) { result |= wgpu::TextureUsages::STORAGE_BINDING; } - if val.contains(TextureUsage::COLOR_ATTACHMENT) - || val.contains(TextureUsage::DEPTH_STENCIL_ATTACHMENT) - { + if val.intersects( + TextureUsage::COLOR_ATTACHMENT + | TextureUsage::DEPTH_STENCIL_ATTACHMENT + | TextureUsage::INPUT_ATTACHMENT, + ) { result |= wgpu::TextureUsages::RENDER_ATTACHMENT; } result diff --git a/crates/render/src/backend.rs b/crates/render/src/backend.rs index f45a98d..0d26548 100644 --- a/crates/render/src/backend.rs +++ b/crates/render/src/backend.rs @@ -1,10 +1,10 @@ use pulz_assets::Handle; use crate::{ - buffer::{Buffer, BufferUsage}, + buffer::Buffer, camera::RenderTarget, - graph::resources::PhysicalResource, - texture::{Texture, TextureDimensions, TextureFormat, TextureUsage}, + graph::{access::Access, resources::PhysicalResource}, + texture::{Texture, TextureDimensions, TextureFormat}, }; pub trait GpuResource: slotmap::Key { @@ -42,7 +42,7 @@ pub trait PhysicalResourceResolver { &mut self, format: TextureFormat, size: TextureDimensions, - usage: TextureUsage, + access: Access, ) -> Option; - fn create_transient_buffer(&mut self, size: usize, usage: BufferUsage) -> Option; + fn create_transient_buffer(&mut self, size: usize, access: Access) -> Option; } diff --git a/crates/render/src/buffer.rs b/crates/render/src/buffer.rs index 50253d8..05f607b 100644 --- a/crates/render/src/buffer.rs +++ b/crates/render/src/buffer.rs @@ -1,6 +1,8 @@ use bitflags::bitflags; use serde::{Deserialize, Serialize}; +use crate::graph::access::Access; + crate::backend::define_gpu_resource!(Buffer, BufferDescriptor); #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] @@ -23,19 +25,72 @@ impl Default for BufferDescriptor { Self::new() } } - bitflags! { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] pub struct BufferUsage: u32 { - const TRANSFER_SRC = 1; - const TRANSFER_DST = 2; - const HOST = 4; // used in combination with TRANSFER_SRC / TRANSFER_DST - const INDEX = 8; - const VERTEX = 17; - const UNIFORM = 32; - const STORAGE = 64; - const INDIRECT = 128; - + const TRANSFER_READ = 0x0001; + const TRANSFER_WRITE = 0x0002; + const HOST_READ = 0x0004; + const HOST_WRITE = 0x0008; + const INDEX = 0x0010; + const VERTEX = 0x0020; + const INDIRECT = 0x0040; + const UNIFORM = 0x0080; + const STORAGE = 0x0100; + const UNIFORM_TEXEL = 0x0200; + const STORAGE_TEXEL = 0x0400; + const ACCELERATION_STRUCTURE_BUILD_INPUT = 0x0800; + const ACCELERATION_STRUCTURE_STORAGE = 0x1000; + const SHADER_BINDING_TABLE = 0x2000; const NONE = 0; } } + +impl Access { + pub fn as_buffer_usage(self) -> BufferUsage { + let mut result = BufferUsage::NONE; + if self.intersects(Self::INDIRECT_COMMAND_READ) { + result |= BufferUsage::INDIRECT; + } + if self.intersects(Self::INDEX_READ) { + result |= BufferUsage::INDEX; + } + if self.intersects(Self::VERTEX_ATTRIBUTE_READ) { + result |= BufferUsage::VERTEX; + } + + if self.intersects(Self::SHADER_READ | Self::SHADER_WRITE) { + result |= BufferUsage::STORAGE; + } + if self.intersects(Self::UNIFORM_READ) { + result |= BufferUsage::UNIFORM; + } + + if self.intersects(Self::TRANSFER_READ) { + result |= BufferUsage::TRANSFER_READ; + } + if self.intersects(Self::TRANSFER_WRITE) { + result |= BufferUsage::TRANSFER_WRITE; + } + if self.intersects(Self::HOST_READ) { + result |= BufferUsage::HOST_READ; + } + if self.intersects(Self::HOST_WRITE) { + result |= BufferUsage::HOST_WRITE; + } + // TODO: check this + if self.intersects( + Self::ACCELERATION_STRUCTURE_BUILD_READ | Self::ACCELERATION_STRUCTURE_BUILD_WRITE, + ) { + result |= BufferUsage::ACCELERATION_STRUCTURE_STORAGE; + } + result + } +} + +impl From for BufferUsage { + #[inline] + fn from(access: Access) -> Self { + access.as_buffer_usage() + } +} diff --git a/crates/render/src/graph/access.rs b/crates/render/src/graph/access.rs index 932063a..df88eb5 100644 --- a/crates/render/src/graph/access.rs +++ b/crates/render/src/graph/access.rs @@ -1,59 +1,36 @@ -use std::{ - fmt::Debug, - hash::Hash, - ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, Sub}, -}; +use std::{fmt::Debug, hash::Hash}; use bitflags::bitflags; use pulz_assets::Handle; +use serde::{Deserialize, Serialize}; use crate::{ - buffer::{Buffer, BufferUsage}, + buffer::Buffer, camera::RenderTarget, - texture::{Texture, TextureDimensions, TextureFormat, TextureUsage}, + texture::{Texture, TextureDimensions, TextureFormat}, }; -pub trait ResourceAccess: Copy + Eq + Default + Hash { - // Bitflags! - type Usage: Copy - + Clone - + Debug - + Default - + Eq - + BitOr - + BitOrAssign - + BitAnd - + BitAndAssign - + BitXor - + BitXorAssign - + Not - + Sub - + Hash; +pub trait ResourceAccess: Copy + Eq + Default + Hash { type Format: PartialEq + Debug + Copy + Hash; type Size: PartialEq + Copy + Debug; type ExternHandle: Debug; - fn check_usage_is_pass_compatible(combined_usage: Self::Usage); - - fn default_format(usage: Self::Usage) -> Self::Format; + fn default_format(access: Access) -> Self::Format; fn merge_size_max(a: Self::Size, b: Self::Size) -> Option; } impl ResourceAccess for Texture { - type Usage = TextureUsage; type Format = TextureFormat; type Size = TextureDimensions; type ExternHandle = RenderTarget; - fn check_usage_is_pass_compatible(combined_usage: Self::Usage) { - if combined_usage.is_non_attachment() { - panic!("Can't use texture as non-attachment resource multiple times in the same pass"); - } - } - #[inline] - fn default_format(usage: Self::Usage) -> Self::Format { - if usage.contains(TextureUsage::DEPTH_STENCIL_ATTACHMENT) { + fn default_format(access: Access) -> Self::Format { + if access.intersects( + Access::DEPTH_STENCIL_ATTACHMENT_READ + | Access::DEPTH_STENCIL_ATTACHMENT_STENCIL_WRITE + | Access::DEPTH_STENCIL_INPUT_ATTACHMENT_READ, + ) { TextureFormat::Depth24PlusStencil8 } else { TextureFormat::Rgba8UnormSrgb @@ -101,17 +78,12 @@ impl ResourceAccess for Texture { } impl ResourceAccess for Buffer { - type Usage = BufferUsage; type Format = (); type Size = usize; type ExternHandle = Handle; - fn check_usage_is_pass_compatible(_combined_usage: Self::Usage) { - panic!("Can't use buffer multiple times in the same pass"); - } - #[inline] - fn default_format(_usage: Self::Usage) -> Self::Format {} + fn default_format(_access: Access) -> Self::Format {} #[inline] fn merge_size_max(a: usize, b: usize) -> Option { @@ -119,6 +91,58 @@ impl ResourceAccess for Buffer { } } +bitflags! { + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] + pub struct Access: u32 { + const INDIRECT_COMMAND_READ = 0x00000001; + const INDEX_READ = 0x00000002; + const VERTEX_ATTRIBUTE_READ = 0x00000004; + const COLOR_INPUT_ATTACHMENT_READ = 0x00000008; + const DEPTH_STENCIL_INPUT_ATTACHMENT_READ = 0x00000010; + + // combined with shader stage: 0x??000000 + const UNIFORM_READ = 0x00000020; + const SHADER_READ = 0x00000040; + const SAMPLED_READ = 0x00000080; + + const COLOR_ATTACHMENT_READ = 0x00000100; + const DEPTH_STENCIL_ATTACHMENT_READ = 0x00000200; + const TRANSFER_READ = 0x00000400; + const HOST_READ = 0x00000800; + const ACCELERATION_STRUCTURE_READ = 0x00001000; + const ACCELERATION_STRUCTURE_BUILD_READ = 0x00002000; + const PRESENT = 0x00004000; + + // combined with shader stage: 0x??000000 + const SHADER_WRITE = 0x00010000; + + const COLOR_ATTACHMENT_WRITE = 0x00020000; + const DEPTH_STENCIL_ATTACHMENT_DEPTH_WRITE = 0x00040000; + const DEPTH_STENCIL_ATTACHMENT_STENCIL_WRITE = 0x00080000; + const DEPTH_STENCIL_ATTACHMENT_WRITE = Self::DEPTH_STENCIL_ATTACHMENT_DEPTH_WRITE.bits() | Self::DEPTH_STENCIL_ATTACHMENT_STENCIL_WRITE.bits(); + const TRANSFER_WRITE = 0x00100000; + const HOST_WRITE = 0x00200000; + const ACCELERATION_STRUCTURE_BUILD_WRITE = 0x00400000; + + const VERTEX_SHADER = 0x01000000; + const TESSELLATION_CONTROL_SHADER = 0x02000000; + const TESSELLATION_EVALUATION_SHADER = 0x04000000; + const GEOMETRY_SHADER = 0x08000000; + const FRAGMENT_SHADER = 0x10000000; + const COMPUTE_SHADER = 0x20000000; + const RAY_TRACING_SHADER = 0x40000000; + + const NONE = 0; + const ANY_READ = 0x0000FFFF; + const ANY_WRITE = 0x00FF0000; + const ANY_SHADER_STAGE = 0xFF000000; + const GRAPICS_ATTACHMENTS = Self::COLOR_INPUT_ATTACHMENT_READ.bits() | Self::DEPTH_STENCIL_INPUT_ATTACHMENT_READ.bits() | Self::COLOR_ATTACHMENT_READ.bits() | Self::COLOR_ATTACHMENT_WRITE.bits() | Self::DEPTH_STENCIL_ATTACHMENT_READ.bits() | Self::DEPTH_STENCIL_ATTACHMENT_WRITE.bits(); + const MEMORY_READ = Self::ANY_SHADER_STAGE.bits() | Self::ANY_READ.bits(); + const MEMORY_WRITE = Self::ANY_SHADER_STAGE.bits() | Self::ANY_WRITE.bits(); + const GENERAL = 0xFFFFFFFF; + } +} + bitflags! { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)] pub struct Stage: u32 { @@ -164,11 +188,114 @@ bitflags! { } } +impl Access { + #[inline] + pub fn as_stage(self) -> Stage { + let mut result = Stage::NONE; + if self.intersects(Self::INDIRECT_COMMAND_READ) { + result |= Stage::DRAW_INDIRECT; + } + if self.intersects(Self::INDEX_READ | Self::VERTEX_ATTRIBUTE_READ) { + result |= Stage::VERTEX_INPUT; + } + if self.intersects( + Self::COLOR_INPUT_ATTACHMENT_READ | Self::DEPTH_STENCIL_INPUT_ATTACHMENT_READ, + ) { + result |= Stage::FRAGMENT_SHADER; + } + + if self.intersects(Self::VERTEX_SHADER) { + result |= Stage::VERTEX_SHADER; + } + if self.intersects(Self::TESSELLATION_CONTROL_SHADER) { + result |= Stage::TESSELLATION_CONTROL_SHADER; + } + if self.intersects(Self::TESSELLATION_EVALUATION_SHADER) { + result |= Stage::TESSELLATION_EVALUATION_SHADER; + } + if self.intersects(Self::GEOMETRY_SHADER) { + result |= Stage::GEOMETRY_SHADER; + } + if self.intersects(Self::FRAGMENT_SHADER) { + result |= Stage::FRAGMENT_SHADER; + } + if self.intersects(Self::COMPUTE_SHADER) { + result |= Stage::COMPUTE_SHADER; + } + if self.intersects(Self::RAY_TRACING_SHADER) { + result |= Stage::RAY_TRACING_SHADER; + } + + if self.intersects(Self::COLOR_ATTACHMENT_READ | Self::COLOR_ATTACHMENT_WRITE) { + result |= Stage::COLOR_ATTACHMENT_OUTPUT; + } + if self + .intersects(Self::DEPTH_STENCIL_ATTACHMENT_READ | Self::DEPTH_STENCIL_ATTACHMENT_WRITE) + { + result |= Stage::FRAGMENT_TESTS; + } + if self.intersects(Self::TRANSFER_READ | Self::TRANSFER_WRITE) { + result |= Stage::TRANSFER; + } + if self.intersects(Self::HOST_READ | Self::HOST_WRITE) { + result |= Stage::HOST; + } + if self.intersects( + Self::ACCELERATION_STRUCTURE_BUILD_READ | Self::ACCELERATION_STRUCTURE_BUILD_WRITE, + ) { + result |= Stage::ACCELERATION_STRUCTURE_BUILD; + } + + result + } + + #[inline] + pub const fn is_read(self) -> bool { + self.intersects(Self::ANY_READ) + } + #[inline] + pub const fn is_write(self) -> bool { + self.intersects(Self::ANY_WRITE) + } + + #[inline] + pub const fn is_graphics_attachment(self) -> bool { + self.intersects(Self::GRAPICS_ATTACHMENTS) + } +} + impl ShaderStage { #[inline] pub const fn as_stage(self) -> Stage { Stage::from_bits_truncate(self.bits()) } + + #[inline] + pub fn as_access(self) -> Access { + let mut result = Access::NONE; + if self.contains(Self::VERTEX) { + result |= Access::VERTEX_SHADER; + } + if self.contains(Self::TESSELLATION_CONTROL) { + result |= Access::TESSELLATION_CONTROL_SHADER; + } + if self.contains(Self::TESSELLATION_EVALUATION) { + result |= Access::TESSELLATION_EVALUATION_SHADER; + } + if self.contains(Self::GEOMETRY) { + result |= Access::GEOMETRY_SHADER; + } + if self.contains(Self::FRAGMENT) { + result |= Access::FRAGMENT_SHADER; + } + if self.contains(Self::COMPUTE) { + result |= Access::COMPUTE_SHADER; + } + if self.contains(Self::RAY_TRACING) { + result |= Access::RAY_TRACING_SHADER; + } + result + } } impl From for Stage { @@ -177,3 +304,10 @@ impl From for Stage { shader_state.as_stage() } } + +impl From for Access { + #[inline] + fn from(shader_state: ShaderStage) -> Self { + shader_state.as_access() + } +} diff --git a/crates/render/src/graph/mod.rs b/crates/render/src/graph/mod.rs index 9ac6b60..7a7456b 100644 --- a/crates/render/src/graph/mod.rs +++ b/crates/render/src/graph/mod.rs @@ -1,6 +1,7 @@ use core::fmt; use self::{ + access::Access, pass::{run::PassExec, PipelineBindPoint}, resources::{ExtendedResourceData, Resource, ResourceDeps, ResourceSet}, }; @@ -28,9 +29,9 @@ const SUBPASS_UNDEFINED: SubPassIndex = (!0, !0); pub struct SubPassDescription { pass_index: PassIndex, name: &'static str, - color_attachments: Vec, - depth_stencil_attachment: Option, - input_attachments: Vec, + color_attachments: Vec<(ResourceIndex, Access)>, + depth_stencil_attachment: Option<(ResourceIndex, Access)>, + input_attachments: Vec<(ResourceIndex, Access)>, } #[derive(Hash, Debug)] @@ -38,8 +39,8 @@ pub struct PassDescription { index: PassIndex, name: &'static str, bind_point: PipelineBindPoint, - textures: ResourceDeps, - buffers: ResourceDeps, + textures: ResourceDeps, + buffers: ResourceDeps, begin_subpasses: usize, end_subpasses: usize, // exclusive! active: bool, @@ -207,15 +208,15 @@ impl SubPassDescription { } #[inline] - pub fn color_attachments(&self) -> &[ResourceIndex] { + pub fn color_attachments(&self) -> &[(ResourceIndex, Access)] { &self.color_attachments } #[inline] - pub fn input_attachments(&self) -> &[ResourceIndex] { + pub fn input_attachments(&self) -> &[(ResourceIndex, Access)] { &self.input_attachments } #[inline] - pub fn depth_stencil_attachment(&self) -> Option { + pub fn depth_stencil_attachment(&self) -> Option<(ResourceIndex, Access)> { self.depth_stencil_attachment } } @@ -236,12 +237,12 @@ impl PassDescription { } #[inline] - pub const fn textures(&self) -> &ResourceDeps { + pub const fn textures(&self) -> &ResourceDeps { &self.textures } #[inline] - pub const fn buffers(&self) -> &ResourceDeps { + pub const fn buffers(&self) -> &ResourceDeps { &self.buffers } diff --git a/crates/render/src/graph/pass/builder.rs b/crates/render/src/graph/pass/builder.rs index c15d91f..c014894 100644 --- a/crates/render/src/graph/pass/builder.rs +++ b/crates/render/src/graph/pass/builder.rs @@ -2,13 +2,13 @@ use std::marker::PhantomData; use super::{Graphics, Pass, PassGroup, PipelineType, SubPassDescription}; use crate::{ - buffer::{Buffer, BufferUsage}, + buffer::Buffer, graph::{ - access::{ShaderStage, Stage}, + access::{Access, ShaderStage}, resources::{ResourceDeps, Slot, SlotAccess, WriteSlot}, PassDescription, PassIndex, RenderGraphBuilder, SubPassIndex, }, - texture::{Texture, TextureDimensions, TextureFormat, TextureUsage}, + texture::{Texture, TextureDimensions, TextureFormat}, }; impl RenderGraphBuilder { @@ -66,51 +66,49 @@ impl PassBuilderIntern<'_> { fn writes_texture_intern( &mut self, slot: WriteSlot, - stages: Stage, - usage: TextureUsage, + access: Access, ) -> WriteSlot { let current_subpass = self.current_subpass(); assert_ne!( slot.last_written_by, current_subpass, "trying to write to a texture multiple times in the same sub-pass" ); - self.pass.textures.access(&slot, true, stages, usage); - self.graph.textures.writes(slot, current_subpass, usage) + self.pass.textures.access(&slot, access); + self.graph.textures.writes(slot, current_subpass, access) } - fn reads_texture_intern(&mut self, slot: Slot, stages: Stage, usage: TextureUsage) { + fn reads_texture_intern(&mut self, slot: Slot, access: Access) { assert_ne!( slot.last_written_by, self.current_subpass(), "trying to read and write a texture in the same sub-pass" ); - self.pass.textures.access(&slot, false, stages, usage); - self.graph.textures.reads(slot, usage); + self.pass.textures.access(&slot, access); + self.graph.textures.reads(slot, access); } fn writes_buffer_intern( &mut self, slot: WriteSlot, - stages: Stage, - usage: BufferUsage, + access: Access, ) -> WriteSlot { let current_subpass = self.current_subpass(); assert_ne!( slot.last_written_by, current_subpass, "trying to write to a buffer multiple times in the same sub-pass" ); - self.pass.buffers.access(&slot, true, stages, usage); - self.graph.buffers.writes(slot, current_subpass, usage) + self.pass.buffers.access(&slot, access); + self.graph.buffers.writes(slot, current_subpass, access) } - fn reads_buffer_intern(&mut self, slot: Slot, stages: Stage, usage: BufferUsage) { + fn reads_buffer_intern(&mut self, slot: Slot, access: Access) { assert_ne!( slot.last_written_by, self.current_subpass(), "trying to read and write a buffer in the same sub-pass" ); - self.pass.buffers.access(&slot, false, stages, usage); - self.graph.buffers.reads(slot, usage); + self.pass.buffers.access(&slot, access); + self.graph.buffers.reads(slot, access); } } @@ -173,45 +171,45 @@ impl PassBuilder<'_, Q> { #[inline] pub fn reads_texture(&mut self, texture: Slot, stages: ShaderStage) { self.base - .reads_texture_intern(texture, stages.as_stage(), TextureUsage::SAMPLED) + .reads_texture_intern(texture, stages.as_access() | Access::SAMPLED_READ) } #[inline] - pub fn reads_storage_texture(&mut self, texture: Slot, stages: ShaderStage) { + pub fn reads_texture_storage(&mut self, texture: Slot, stages: ShaderStage) { self.base - .reads_texture_intern(texture, stages.as_stage(), TextureUsage::STORAGE) + .reads_texture_intern(texture, stages.as_access() | Access::SHADER_READ) } #[inline] - pub fn writes_storage_texture( + pub fn writes_texture_storage( &mut self, texture: WriteSlot, stages: ShaderStage, ) -> WriteSlot { self.base - .writes_texture_intern(texture, stages.as_stage(), TextureUsage::STORAGE) + .writes_texture_intern(texture, stages.as_access() | Access::SHADER_WRITE) } #[inline] pub fn reads_uniform_buffer(&mut self, buffer: Slot, stages: ShaderStage) { self.base - .reads_buffer_intern(buffer, stages.as_stage(), BufferUsage::UNIFORM) + .reads_buffer_intern(buffer, stages.as_access() | Access::UNIFORM_READ) } #[inline] - pub fn reads_storage_buffer(&mut self, buffer: Slot, stages: ShaderStage) { + pub fn reads_buffer(&mut self, buffer: Slot, stages: ShaderStage) { self.base - .reads_buffer_intern(buffer, stages.as_stage(), BufferUsage::STORAGE) + .reads_buffer_intern(buffer, stages.as_access() | Access::SHADER_READ) } #[inline] - pub fn writes_storage_buffer( + pub fn writes_buffer( &mut self, buffer: WriteSlot, stages: ShaderStage, ) -> WriteSlot { self.base - .writes_buffer_intern(buffer, stages.as_stage(), BufferUsage::STORAGE) + .writes_buffer_intern(buffer, stages.as_access() | Access::SHADER_WRITE) } } @@ -222,12 +220,11 @@ impl PassBuilder<'_, Graphics> { self.color_attachment(slot) } pub fn color_attachment(&mut self, texture: WriteSlot) -> WriteSlot { - self.subpass.color_attachments.push(texture.index()); - self.base.writes_texture_intern( - texture, - Stage::COLOR_ATTACHMENT_OUTPUT, - TextureUsage::COLOR_ATTACHMENT, - ) + self.subpass + .color_attachments + .push((texture.index(), Access::COLOR_ATTACHMENT_WRITE)); + self.base + .writes_texture_intern(texture, Access::COLOR_ATTACHMENT_WRITE) } #[inline] @@ -235,36 +232,57 @@ impl PassBuilder<'_, Graphics> { let slot = self.base.graph.textures.create(); self.depth_stencil_attachment(slot) } + #[inline] pub fn depth_stencil_attachment(&mut self, texture: WriteSlot) -> WriteSlot { - self.subpass + self.write_depth_stencil_attachment_intern(texture, Access::DEPTH_STENCIL_ATTACHMENT_WRITE) + } + + fn write_depth_stencil_attachment_intern( + &mut self, + texture: WriteSlot, + access: Access, + ) -> WriteSlot { + let old = self + .subpass .depth_stencil_attachment - .replace(texture.index()); + .replace((texture.index(), access)); + assert!(old.is_none(), "only one depth stencil attachment allowed"); // TODO: support early & late fragment tests - self.base.writes_texture_intern( - texture, - Stage::FRAGMENT_TESTS, - TextureUsage::DEPTH_STENCIL_ATTACHMENT, - ) + // TODO: support readonly, write depth only, write stencil only + self.base.writes_texture_intern(texture, access) } - pub fn input_attachment(&mut self, texture: Slot) { - self.subpass.input_attachments.push(texture.index()); - self.base.reads_texture_intern( - texture, - Stage::FRAGMENT_SHADER, - TextureUsage::INPUT_ATTACHMENT, - ) + #[inline] + pub fn color_input_attachment(&mut self, texture: Slot) { + self.input_attachment_intern(texture, Access::COLOR_INPUT_ATTACHMENT_READ) } #[inline] - pub fn reads_vertex_buffer(&mut self, buffer: Slot) { + pub fn depth_stencil_input_attachment(&mut self, texture: Slot) { + self.input_attachment_intern(texture, Access::DEPTH_STENCIL_INPUT_ATTACHMENT_READ) + } + + fn input_attachment_intern(&mut self, texture: Slot, access: Access) { + self.subpass + .input_attachments + .push((texture.index(), access)); + self.base.reads_texture_intern(texture, access) + } + + #[inline] + pub fn vertex_buffer(&mut self, buffer: Slot) { self.base - .reads_buffer_intern(buffer, Stage::VERTEX_INPUT, BufferUsage::VERTEX) + .reads_buffer_intern(buffer, Access::VERTEX_ATTRIBUTE_READ) + } + + #[inline] + pub fn index_buffer(&mut self, buffer: Slot) { + self.base.reads_buffer_intern(buffer, Access::INDEX_READ) } #[inline] - pub fn reads_index_buffer(&mut self, buffer: Slot) { + pub fn indirect_command_buffer(&mut self, buffer: Slot) { self.base - .reads_buffer_intern(buffer, Stage::VERTEX_INPUT, BufferUsage::INDEX) + .reads_buffer_intern(buffer, Access::INDIRECT_COMMAND_READ) } } diff --git a/crates/render/src/graph/resources.rs b/crates/render/src/graph/resources.rs index c092960..c206ac9 100644 --- a/crates/render/src/graph/resources.rs +++ b/crates/render/src/graph/resources.rs @@ -11,23 +11,28 @@ use pulz_bitset::BitSet; use pulz_window::WindowId; use super::{ - access::{ResourceAccess, Stage}, + access::{Access, ResourceAccess, Stage}, builder::{GraphExport, GraphImport}, deps::DependencyMatrix, - PassDescription, PassIndex, RenderGraph, ResourceIndex, SubPassDescription, SubPassIndex, - PASS_UNDEFINED, SUBPASS_UNDEFINED, + PassDescription, PassIndex, RenderGraph, ResourceIndex, SubPassIndex, PASS_UNDEFINED, + SUBPASS_UNDEFINED, }; use crate::{ backend::PhysicalResourceResolver, - buffer::{Buffer, BufferUsage}, + buffer::Buffer, camera::RenderTarget, - texture::{Image, Texture, TextureDimensions, TextureFormat, TextureUsage}, + texture::{Image, Texture, TextureDimensions, TextureFormat}, }; #[derive(Copy, Clone)] -pub struct Slot { +pub struct SlotRaw { pub(crate) index: ResourceIndex, pub(crate) last_written_by: SubPassIndex, +} + +#[derive(Copy, Clone)] +pub struct Slot { + raw: SlotRaw, _phantom: PhantomData R>, } @@ -35,11 +40,19 @@ impl std::fmt::Debug for Slot { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let typename = std::any::type_name::(); f.debug_tuple(&format!("Slot<{typename}>")) - .field(&self.index) + .field(&self.raw.index) .finish() } } +impl Deref for Slot { + type Target = SlotRaw; + #[inline] + fn deref(&self) -> &SlotRaw { + &self.raw + } +} + // Not Copy by intention! pub struct WriteSlot(Slot); @@ -47,11 +60,19 @@ impl std::fmt::Debug for WriteSlot { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let typename = std::any::type_name::(); f.debug_tuple(&format!("WriteSlot<{typename}>")) - .field(&self.0.index) + .field(&self.raw.index) .finish() } } +impl Deref for WriteSlot { + type Target = Slot; + #[inline] + fn deref(&self) -> &Slot { + &self.0 + } +} + pub trait SlotAccess { const WRITE: bool; fn index(&self) -> ResourceIndex; @@ -61,7 +82,7 @@ impl SlotAccess for Slot { const WRITE: bool = false; #[inline] fn index(&self) -> ResourceIndex { - self.index + self.raw.index } } @@ -69,23 +90,17 @@ impl SlotAccess for WriteSlot { const WRITE: bool = true; #[inline] fn index(&self) -> ResourceIndex { - self.0.index - } -} - -impl Deref for WriteSlot { - type Target = Slot; - #[inline] - fn deref(&self) -> &Slot { - &self.0 + self.raw.index } } impl Slot { const fn new(index: ResourceIndex, last_written_by: SubPassIndex) -> Self { Self { - index, - last_written_by, + raw: SlotRaw { + index, + last_written_by, + }, _phantom: PhantomData, } } @@ -113,7 +128,7 @@ pub enum ResourceVariant { pub(crate) struct Resource { first_written: SubPassIndex, last_written: SubPassIndex, - usage: R::Usage, + access: Access, format: Option, size: Option, variant: ResourceVariant, @@ -137,13 +152,13 @@ impl Resource { if let Some(f) = self.format { f } else { - R::default_format(self.usage) + R::default_format(self.access) } } #[inline] - pub fn usage(&self) -> R::Usage { - self.usage + pub fn access(&self) -> Access { + self.access } #[inline] @@ -183,6 +198,7 @@ impl Hash for Resource { fn hash(&self, state: &mut H) { self.first_written.hash(state); self.last_written.hash(state); + self.access.hash(state); self.variant.hash(state); self.format.hash(state); // ignore size and extern assignment! @@ -190,21 +206,19 @@ impl Hash for Resource { } #[derive(Debug)] -pub struct ResourceDeps(Vec>); +pub struct ResourceDeps(Vec); -impl Hash for ResourceDeps { +impl Hash for ResourceDeps { fn hash(&self, state: &mut H) { Hash::hash_slice(&self.0, state); } } #[derive(Hash, Debug)] -pub struct ResourceDep { +pub struct ResourceDep { index: ResourceIndex, last_written_by_pass: PassIndex, - write_access: bool, - stages: Stage, - usage: R::Usage, + access: Access, } impl ResourceSet { @@ -228,7 +242,7 @@ impl ResourceSet { self.resources.push(Resource { first_written: SUBPASS_UNDEFINED, last_written: SUBPASS_UNDEFINED, - usage: Default::default(), + access: Access::empty(), format: None, size: None, variant: ResourceVariant::Transient, @@ -258,15 +272,16 @@ impl ResourceSet { &mut self, slot: WriteSlot, new_pass: SubPassIndex, - usage: R::Usage, + access: Access, ) -> WriteSlot { + assert!(access.is_empty() || access.is_write()); let r = &mut self.resources[slot.0.index as usize]; let last_written_by_pass = r.last_written; assert_eq!( last_written_by_pass, slot.0.last_written_by, "resource also written by an other pass (slot out of sync)" ); - r.usage |= usage; + r.access |= access; if new_pass != last_written_by_pass { r.last_written = new_pass; if r.first_written.0 == PASS_UNDEFINED { @@ -276,7 +291,8 @@ impl ResourceSet { WriteSlot::new(slot.0.index, new_pass) } - pub(super) fn reads(&mut self, slot: Slot, usage: R::Usage) { + pub(super) fn reads(&mut self, slot: Slot, access: Access) { + assert!(access.is_empty() || access.is_read()); assert_ne!( slot.last_written_by.0, PASS_UNDEFINED, "resource was not yet written!" @@ -288,7 +304,7 @@ impl ResourceSet { last_written_by_pass, slot.last_written_by, "resource also written by an other pass (slot out of sync)" ); - r.usage |= usage; + r.access |= access; } pub(super) fn import(&mut self, extern_resource: R::ExternHandle) -> Slot { @@ -320,13 +336,13 @@ impl ResourceSet { } } -impl ResourceDeps { +impl ResourceDeps { #[inline] - pub fn deps(&self) -> &[ResourceDep] { + pub fn deps(&self) -> &[ResourceDep] { &self.0 } - pub fn find_by_resource_index(&self, resource_index: ResourceIndex) -> Option<&ResourceDep> { + pub fn find_by_resource_index(&self, resource_index: ResourceIndex) -> Option<&ResourceDep> { if let Ok(i) = self.0.binary_search_by_key(&resource_index, |d| d.index) { Some(&self.0[i]) } else { @@ -373,22 +389,14 @@ impl ResourceDeps { } } - pub(super) fn access( - &mut self, - slot: &Slot, - write_access: bool, - stages: Stage, - usage: R::Usage, - ) -> bool { + pub(super) fn access(&mut self, slot: &SlotRaw, access: Access) -> bool { match self.0.binary_search_by_key(&slot.index, |e| e.index) { Ok(i) => { let entry = &mut self.0[i]; assert_eq!(entry.last_written_by_pass, slot.last_written_by.0); - entry.write_access |= write_access; - entry.stages |= stages; - entry.usage |= usage; - if entry.write_access { - R::check_usage_is_pass_compatible(entry.usage); + entry.access |= access; + if access.is_write() { + //TODO: R::check_usage_is_pass_compatible(entry.usage); } false } @@ -398,9 +406,7 @@ impl ResourceDeps { ResourceDep { index: slot.index, last_written_by_pass: slot.last_written_by.0, - write_access, - stages, - usage, + access, }, ); true @@ -409,15 +415,15 @@ impl ResourceDeps { } } -impl Deref for ResourceDeps { - type Target = [ResourceDep]; +impl Deref for ResourceDeps { + type Target = [ResourceDep]; #[inline] fn deref(&self) -> &Self::Target { &self.0 } } -impl ResourceDep { +impl ResourceDep { #[inline] pub fn resource_index(&self) -> ResourceIndex { self.index @@ -430,22 +436,22 @@ impl ResourceDep { #[inline] pub fn stages(&self) -> Stage { - self.stages + self.access.as_stage() } #[inline] - pub fn usage(&self) -> R::Usage { - self.usage + pub fn access(&self) -> Access { + self.access } #[inline] pub fn is_read(&self) -> bool { - self.last_written_by_pass != !0 + self.access.is_read() } #[inline] pub fn is_write(&self) -> bool { - self.write_access + self.access.is_write() } } @@ -454,12 +460,13 @@ pub struct PhysicalResource { pub resource: R, pub format: R::Format, pub size: R::Size, - pub usage: R::Usage, + pub access: Access, } impl Hash for PhysicalResource { fn hash(&self, state: &mut H) { self.format.hash(state); + self.access.hash(state); // ignore resource and size } } @@ -490,7 +497,7 @@ trait ResolveExtern { fn resolve_extern(&mut self, handle: &R::ExternHandle) -> Option>; } trait CreateTransient { - fn create_transient(&mut self, format: R::Format, size: R::Size, usage: R::Usage) -> Option; + fn create_transient(&mut self, format: R::Format, size: R::Size, access: Access) -> Option; } impl PhysicalResourceSet { @@ -534,7 +541,7 @@ impl PhysicalResourceSet { transients: &mut Vec>, format: R::Format, size: R::Size, - usage: R::Usage, + access: Access, first_topo_group: u16, last_topo_group: u16, ) -> u16 { @@ -542,7 +549,7 @@ impl PhysicalResourceSet { if format == p.physical.format && p.last_topo_group < first_topo_group { if let Some(s) = R::merge_size_max(p.physical.size, size) { p.physical.size = s; - p.physical.usage |= usage; + p.physical.access |= access; p.last_topo_group = last_topo_group; return j as u16; } @@ -554,7 +561,7 @@ impl PhysicalResourceSet { resource: R::default(), format, size, - usage, + access, }, first_topo_group, last_topo_group, @@ -604,7 +611,7 @@ impl PhysicalResourceSet { let r = &resources.resources[i]; let d = &resources_data[i]; if d.is_active() && matches!(self.assignments[i], ExternalOrTransient::None) { - if r.usage == R::Usage::default() { + if r.access.is_empty() { panic!( "transient usage is empty, {:?}, {:?}, {}, {}, {:?}, {:?}", r.size, @@ -612,14 +619,14 @@ impl PhysicalResourceSet { d.first_topo_group, d.last_topo_group, r.first_written, - r.usage + r.access ); } let transient_index = Self::get_or_create_transient( &mut self.transients, r.format_or_default(), self.assignment_sizes[i].expect("missing size"), - r.usage, + r.access, d.first_topo_group, d.last_topo_group, ); @@ -632,7 +639,7 @@ impl PhysicalResourceSet { .create_transient( trans.physical.format, trans.physical.size, - trans.physical.usage, + trans.physical.access, ) .expect("unable to create transient"); // TODO: error } @@ -640,35 +647,21 @@ impl PhysicalResourceSet { } impl PhysicalResourceSet { - fn derive_framebuffer_sizes( - &mut self, - passes: &[PassDescription], - subpasses: &[SubPassDescription], - ) { + fn derive_framebuffer_sizes(&mut self, passes: &[PassDescription]) { for pass in passes { if pass.active { - self.derive_framebuffer_size_for_pass(pass, &subpasses[pass.sub_pass_range()]); + self.derive_framebuffer_size_for_pass(pass); } } } - fn derive_framebuffer_size_for_pass( - &mut self, - pass: &PassDescription, - subpasses: &[SubPassDescription], - ) { + fn derive_framebuffer_size_for_pass(&mut self, pass: &PassDescription) { let mut pass_size = None; let mut empty = true; - for p in subpasses { - for r_index in p - .color_attachments - .iter() - .copied() - .chain(p.depth_stencil_attachment.iter().copied()) - .chain(p.input_attachments.iter().copied()) - { + for r in pass.textures().iter() { + if r.access().is_graphics_attachment() { empty = false; - if let Some(s) = self.assignment_sizes[r_index as usize] { + if let Some(s) = self.assignment_sizes[r.index as usize] { if let Some(s2) = pass_size { assert_eq!(s2, s, "pass attachments have to be the same size"); } else { @@ -687,15 +680,9 @@ impl PhysicalResourceSet { ); } - for p in subpasses { - for r_index in p - .color_attachments - .iter() - .copied() - .chain(p.depth_stencil_attachment.iter().copied()) - .chain(p.input_attachments.iter().copied()) - { - let s = &mut self.assignment_sizes[r_index as usize]; + for r in pass.textures().iter() { + if r.access().is_graphics_attachment() { + let s = &mut self.assignment_sizes[r.index as usize]; if s.is_none() { *s = pass_size; } @@ -733,8 +720,7 @@ impl PhysicalResources { self.buffers .assign_externals(&graph.buffers, &graph.buffers_ext, backend); - self.textures - .derive_framebuffer_sizes(&graph.passes, &graph.subpasses); + self.textures.derive_framebuffer_sizes(&graph.passes); self.textures .assign_transient(&graph.textures, &graph.textures_ext, backend); @@ -852,16 +838,16 @@ where &mut self, format: TextureFormat, size: TextureDimensions, - usage: TextureUsage, + access: Access, ) -> Option { - self.create_transient_texture(format, size, usage) + self.create_transient_texture(format, size, access) } } impl CreateTransient for B where B: PhysicalResourceResolver, { - fn create_transient(&mut self, _format: (), size: usize, usage: BufferUsage) -> Option { - self.create_transient_buffer(size, usage) + fn create_transient(&mut self, _format: (), size: usize, access: Access) -> Option { + self.create_transient_buffer(size, access) } } diff --git a/crates/render/src/pipeline/graphics_pass.rs b/crates/render/src/pipeline/graphics_pass.rs index 93b70ae..bbd20a2 100644 --- a/crates/render/src/pipeline/graphics_pass.rs +++ b/crates/render/src/pipeline/graphics_pass.rs @@ -3,10 +3,10 @@ use serde::{Deserialize, Serialize}; use crate::{ graph::{ - pass::PipelineBindPoint, resources::PhysicalResources, PassDescription, RenderGraph, - ResourceIndex, + access::Access, pass::PipelineBindPoint, resources::PhysicalResources, PassDescription, + RenderGraph, ResourceIndex, }, - texture::{TextureFormat, TextureLayout, TextureUsage}, + texture::TextureFormat, }; crate::backend::define_gpu_resource!(GraphicsPass, GraphicsPassDescriptor); @@ -40,31 +40,31 @@ pub struct LoadStoreOps { #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] pub struct AttachmentDescriptor { pub format: TextureFormat, - pub usage: TextureUsage, - pub initial_layout: TextureLayout, - pub final_layout: TextureLayout, + pub access: Access, + pub initial_access: Access, + pub final_access: Access, pub samples: u8, } #[derive(Clone, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] pub struct SubpassDescriptor { - input_attachments: Vec, - color_attachments: Vec, - depth_stencil_attachment: Option, + input_attachments: Vec<(u16, Access)>, + color_attachments: Vec<(u16, Access)>, + depth_stencil_attachment: Option<(u16, Access)>, //resolve_attachments: Vec, } impl SubpassDescriptor { #[inline] - pub fn input_attachments(&self) -> &[u16] { + pub fn input_attachments(&self) -> &[(u16, Access)] { &self.input_attachments } #[inline] - pub fn color_attachments(&self) -> &[u16] { + pub fn color_attachments(&self) -> &[(u16, Access)] { &self.color_attachments } #[inline] - pub fn depth_stencil_attachment(&self) -> Option { + pub fn depth_stencil_attachment(&self) -> Option<(u16, Access)> { self.depth_stencil_attachment } } @@ -101,7 +101,7 @@ impl ExtendedGraphicsPassDescriptor { pub fn from_graph( graph: &RenderGraph, physical_resources: &PhysicalResources, - current_texture_layouts: &mut [TextureLayout], + current_texture_access: &mut [Access], pass: &PassDescription, ) -> Option { if pass.bind_point() != PipelineBindPoint::Graphics { @@ -109,7 +109,7 @@ impl ExtendedGraphicsPassDescriptor { } let mut attachment_indices = Vec::with_capacity(pass.textures().len()); for (i, tex) in pass.textures().deps().iter().enumerate() { - if tex.usage().is_attachment() { + if tex.access().is_graphics_attachment() { attachment_indices.push(i as ResourceIndex); } } @@ -137,20 +137,21 @@ impl ExtendedGraphicsPassDescriptor { // TODO: is resource used in later pass? then STORE, else DONT_CARE store_op: StoreOp::Store, }; - if a.is_read() { + let current_usage = if a.is_read() { load_store.load_op = LoadOp::Load; + current_texture_access[resource_index as usize] } else { // overide to undefined - current_texture_layouts[resource_index as usize] = TextureLayout::Undefined; - } - let current_texture_layout = current_texture_layouts[resource_index as usize]; + current_texture_access[resource_index as usize] = Access::NONE; + Access::NONE + }; attachments.push(AttachmentDescriptor { format, samples, - usage: a.usage(), - initial_layout: current_texture_layout, - final_layout: current_texture_layout, + access: a.access(), + initial_access: current_usage, + final_access: current_usage, }); load_store_ops.push(load_store); @@ -161,18 +162,20 @@ impl ExtendedGraphicsPassDescriptor { // pass.textures() is sorted by resource-index! *i = pass.textures()[*i as usize].resource_index(); } - let mut map_attachment_index = |resource_index: &u16, mut layout: TextureLayout| { - if layout == TextureLayout::Undefined { - layout = current_texture_layouts[*resource_index as usize]; - } else { - current_texture_layouts[*resource_index as usize] = layout; + + let mut map_attachment_index_and_update_usage = + |resource_index: u16, mut current_access: Access| { + if current_access.is_empty() { + current_access = current_texture_access[resource_index as usize]; + } else { + current_texture_access[resource_index as usize] = current_access; + }; + let a = attachment_indices + .binary_search(&resource_index) + .expect("unvalid resource index") as u16; + attachments[a as usize].final_access = current_access; + (a, current_access) }; - let a = attachment_indices - .binary_search(resource_index) - .expect("unvalid resource index") as u16; - attachments[a as usize].final_layout = layout; - a - }; let mut subpasses = Vec::with_capacity(pass.sub_pass_range().len()); for sp in pass.sub_pass_range() { @@ -180,22 +183,24 @@ impl ExtendedGraphicsPassDescriptor { let input_attachments = sp .input_attachments() .iter() - .map(|r| map_attachment_index(r, TextureLayout::InputAttachment)) + .copied() + .map(|(r, u)| map_attachment_index_and_update_usage(r, u)) .collect(); let color_attachments = sp .color_attachments() .iter() - .map(|r| map_attachment_index(r, TextureLayout::ColorAttachment)) + .copied() + .map(|(r, u)| map_attachment_index_and_update_usage(r, u)) .collect(); let depth_stencil_attachment = sp .depth_stencil_attachment() - .as_ref() - .map(|r| map_attachment_index(r, TextureLayout::DepthStencilAttachment)); + .map(|(r, u)| map_attachment_index_and_update_usage(r, u)); subpasses.push(SubpassDescriptor { input_attachments, color_attachments, depth_stencil_attachment, }) + // update } // TODO: if this pass is the last pass accessing this resource (and resource not extern), then STOREOP = DON'T CARE diff --git a/crates/render/src/texture/descriptor.rs b/crates/render/src/texture/descriptor.rs index d3797cd..dfb2787 100644 --- a/crates/render/src/texture/descriptor.rs +++ b/crates/render/src/texture/descriptor.rs @@ -2,7 +2,10 @@ use bitflags::bitflags; use pulz_transform::math::{usize2, usize3}; use serde::{Deserialize, Serialize}; -use crate::math::{USize2, USize3}; +use crate::{ + graph::access::Access, + math::{USize2, USize3}, +}; #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] pub struct TextureDescriptor { @@ -22,7 +25,7 @@ impl TextureDescriptor { sample_count: 1, format: TextureFormat::DEFAULT, aspects: TextureAspects::DEFAULT, - usage: TextureUsage::ALL_READ, + usage: TextureUsage::empty(), } } @@ -262,21 +265,20 @@ impl Default for TextureAspects { bitflags! { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] pub struct TextureUsage: u32 { - const TRANSFER_SRC = 1; - const TRANSFER_DST = 2; - const SAMPLED = 4; - const STORAGE = 8; - const COLOR_ATTACHMENT = 16; - const DEPTH_STENCIL_ATTACHMENT = 32; - const INPUT_ATTACHMENT = 64; - const PRESENT = 128; - + const TRANSFER_READ = 0x0001; + const TRANSFER_WRITE = 0x0002; + const HOST_READ = 0x0004; + const HOST_WRITE = 0x0008; + const SAMPLED = 0x0010; + const STORAGE = 0x0020; + const COLOR_ATTACHMENT = 0x0040; + const DEPTH_STENCIL_ATTACHMENT = 0x0080; + const INPUT_ATTACHMENT = 0x0100; + const TRANSIENT = 0x0200; // modifiers - const BY_REGION = 256; - + const PRESENT = 0x1000; + const BY_REGION = 0x2000; const NONE = 0; - const ALL_READ = Self::TRANSFER_SRC.bits() | Self::SAMPLED.bits() | Self::INPUT_ATTACHMENT.bits() | Self::PRESENT.bits(); - const ALL_WRITE = Self::TRANSFER_DST.bits() | Self::STORAGE.bits() | Self::COLOR_ATTACHMENT.bits() | Self::DEPTH_STENCIL_ATTACHMENT.bits(); const ALL_ATTACHMENTS = Self::COLOR_ATTACHMENT.bits() | Self::DEPTH_STENCIL_ATTACHMENT.bits() | Self::INPUT_ATTACHMENT.bits(); } } @@ -286,29 +288,51 @@ impl TextureUsage { pub const fn is_attachment(self) -> bool { self.intersects(Self::ALL_ATTACHMENTS) } - #[inline] pub const fn is_non_attachment(self) -> bool { self.intersects(Self::ALL_ATTACHMENTS.complement()) } } -#[derive( - Copy, Clone, Debug, Default, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, -)] -#[non_exhaustive] -pub enum TextureLayout { - #[default] - Undefined, - General, - TransferSrc, - TransferDst, - Preinitialized, - ShaderReadOnly, - ColorAttachment, - DepthStencilAttachment, - InputAttachment, - Present, +impl Access { + pub fn as_texture_usage(self) -> TextureUsage { + let mut result = TextureUsage::NONE; + if self.intersects( + Self::COLOR_INPUT_ATTACHMENT_READ | Self::DEPTH_STENCIL_INPUT_ATTACHMENT_READ, + ) { + result |= TextureUsage::INPUT_ATTACHMENT; + } + + if self.intersects(Self::SHADER_READ | Self::SHADER_WRITE) { + result |= TextureUsage::STORAGE; + } + if self.intersects(Self::SAMPLED_READ) { + result |= TextureUsage::SAMPLED; + } + + if self.intersects(Self::COLOR_ATTACHMENT_READ | Self::COLOR_ATTACHMENT_WRITE) { + result |= TextureUsage::COLOR_ATTACHMENT; + } + if self + .intersects(Self::DEPTH_STENCIL_ATTACHMENT_READ | Self::DEPTH_STENCIL_ATTACHMENT_WRITE) + { + result |= TextureUsage::DEPTH_STENCIL_ATTACHMENT; + } + if self.intersects(Self::TRANSFER_READ) { + result |= TextureUsage::TRANSFER_READ; + } + if self.intersects(Self::TRANSFER_WRITE) { + result |= TextureUsage::TRANSFER_WRITE; + } + result + } +} + +impl From for TextureUsage { + #[inline] + fn from(access: Access) -> Self { + access.as_texture_usage() + } } #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default, Serialize, Deserialize)] From 885215abd7bb7d899335d276f902900842dec6dc Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sun, 28 Jan 2024 21:50:53 +0100 Subject: [PATCH 30/36] feat(render): PhysicalResourceAccessTracker (wip) --- crates/render-ash/src/convert.rs | 7 +- crates/render-ash/src/graph.rs | 28 ++--- crates/render/src/graph/builder.rs | 7 ++ crates/render/src/graph/mod.rs | 19 ++- crates/render/src/graph/resources.rs | 124 ++++++++++++++++++-- crates/render/src/pipeline/graphics_pass.rs | 54 +++++---- crates/render/src/texture/descriptor.rs | 3 + 7 files changed, 192 insertions(+), 50 deletions(-) diff --git a/crates/render-ash/src/convert.rs b/crates/render-ash/src/convert.rs index adbaa3b..e30955e 100644 --- a/crates/render-ash/src/convert.rs +++ b/crates/render-ash/src/convert.rs @@ -987,8 +987,11 @@ impl CreateInfoConverter6 { .store_op(load_store_ops.store_op.vk_into()) .stencil_load_op(load_store_ops.load_op.vk_into()) .stencil_store_op(load_store_ops.store_op.vk_into()) - // TODO: initial_layout if load-op == LOAD - .initial_layout(a.initial_access.vk_into()) + .initial_layout(if load_store_ops.load_op == LoadOp::Load { + a.initial_access.vk_into() + } else { + vk::ImageLayout::UNDEFINED + }) .final_layout(a.final_access.vk_into()) .build(), ); diff --git a/crates/render-ash/src/graph.rs b/crates/render-ash/src/graph.rs index 4a72c6c..19c05cb 100644 --- a/crates/render-ash/src/graph.rs +++ b/crates/render-ash/src/graph.rs @@ -8,7 +8,7 @@ use pulz_render::{ graph::{ access::Access, pass::PipelineBindPoint, - resources::{PhysicalResource, PhysicalResources}, + resources::{PhysicalResource, PhysicalResourceAccessTracker, PhysicalResources}, PassDescription, PassIndex, RenderGraph, }, math::USize2, @@ -28,6 +28,7 @@ use crate::{ pub struct AshRenderGraph { physical_resources: PhysicalResources, + physical_resource_access: PhysicalResourceAccessTracker, topo: Vec, barriers: Vec, hash: u64, @@ -84,7 +85,7 @@ impl PhysicalResourceResolver for AshPhysicalResourceResolver<'_> { .request_semaphore() .expect("request semaphore"); self.submission_group - .wait(sem, PipelineStageFlags::TRANSFER); + .wait(sem, PipelineStageFlags::TOP_OF_PIPE); // TODO: better sync let aquired_texture = surface .acquire_next_image(self.res, sem) .expect("aquire failed") @@ -137,12 +138,11 @@ impl TopoRenderPass { res: &mut AshResources, src: &RenderGraph, phys: &PhysicalResources, - current_texture_access: &mut [Access], + current_access: &mut PhysicalResourceAccessTracker, pass: &PassDescription, ) -> Result { let pass_descr = - ExtendedGraphicsPassDescriptor::from_graph(src, phys, current_texture_access, pass) - .unwrap(); + ExtendedGraphicsPassDescriptor::from_graph(src, phys, current_access, pass).unwrap(); let graphics_pass = res.create::(&pass_descr.graphics_pass)?; let render_pass = res[graphics_pass]; Ok(Self { @@ -180,14 +180,14 @@ impl TopoRenderPass { let mut image_views = Vec::with_capacity(self.attachment_resource_indices.len()); for &i in &self.attachment_resource_indices { - let (tex, _format, _samples, dim) = phys.get_texture(i).expect("unassigned resource"); - let dim = dim.subimage_extents(); + let physical_resource = phys.get_texture(i).expect("unassigned resource"); + let dim = physical_resource.size.subimage_extents(); if dim != self.size { // TODO: handle size changed! // TODO: error handling panic!("all framebuffer textures need to have the same dimensions"); } - image_views.push(res[tex].1); + image_views.push(res[physical_resource.resource].1); } /* @@ -219,6 +219,7 @@ impl AshRenderGraph { pub const fn new() -> Self { Self { physical_resources: PhysicalResources::new(), + physical_resource_access: PhysicalResourceAccessTracker::new(), topo: Vec::new(), barriers: Vec::new(), hash: 0, @@ -276,8 +277,8 @@ impl AshRenderGraph { self.topo .resize_with(num_topological_groups, Default::default); - let mut current_texture_access = Vec::new(); - current_texture_access.resize(src.get_num_textures(), Access::NONE); + self.physical_resource_access + .reset(&self.physical_resources); // TODO: get initial layout of external textures for topo_index in 0..num_topological_groups { @@ -289,7 +290,7 @@ impl AshRenderGraph { res, src, &self.physical_resources, - &mut current_texture_access, + &mut self.physical_resource_access, pass, )?); } @@ -338,9 +339,8 @@ impl AshRenderGraph { } clear_values.clear(); for &i in &topo_render_pass.attachment_resource_indices { - let (_tex, format, _samples, _dim) = - self.physical_resources.get_texture(i).unwrap(); - let format: vk::Format = format.vk_into(); + let physical_resource = self.physical_resources.get_texture(i).unwrap(); + let format: vk::Format = physical_resource.format.vk_into(); let clear_value = default_clear_value_for_format(format); clear_values.push(clear_value); } diff --git a/crates/render/src/graph/builder.rs b/crates/render/src/graph/builder.rs index 5fe4cd9..7ecd2bc 100644 --- a/crates/render/src/graph/builder.rs +++ b/crates/render/src/graph/builder.rs @@ -90,6 +90,7 @@ impl RenderGraph { self.subpasses.clear(); self.subpasses_exec.clear(); self.passes.clear(); + self.pass_topo_group.clear(); self.passes_topo_order.clear(); } @@ -128,6 +129,12 @@ impl RenderGraph { m.remove_self_references(); self.passes_topo_order = m.into_topological_order(); + self.pass_topo_group.resize(self.passes.len(), !0); + for (i, g) in self.passes_topo_order.iter().enumerate() { + for p in g { + self.pass_topo_group[*p] = i; + } + } debug!("Topological order: {:?}", self.passes_topo_order); diff --git a/crates/render/src/graph/mod.rs b/crates/render/src/graph/mod.rs index 7a7456b..91dc86f 100644 --- a/crates/render/src/graph/mod.rs +++ b/crates/render/src/graph/mod.rs @@ -57,6 +57,7 @@ pub struct RenderGraph { subpasses: Vec, subpasses_exec: Vec>, passes: Vec, + pass_topo_group: Vec, passes_topo_order: Vec>, } @@ -83,6 +84,7 @@ impl RenderGraph { subpasses: Vec::new(), subpasses_exec: Vec::new(), passes: Vec::new(), + pass_topo_group: Vec::new(), passes_topo_order: Vec::new(), } } @@ -113,13 +115,13 @@ impl RenderGraph { } #[inline] - pub(crate) fn get_texture_info(&self, index: usize) -> Option<&Resource> { - self.textures.get(index) + pub(crate) fn get_texture_info(&self, index: ResourceIndex) -> Option<&Resource> { + self.textures.get(index as usize) } #[inline] - pub(crate) fn get_buffer_info(&self, index: usize) -> Option<&Resource> { - self.buffers.get(index) + pub(crate) fn get_buffer_info(&self, index: ResourceIndex) -> Option<&Resource> { + self.buffers.get(index as usize) } pub fn get_topological_group( @@ -141,6 +143,15 @@ impl RenderGraph { self.passes.get(pass_index as usize) } + pub fn get_topo_group_for_pass(&self, pass_index: PassIndex) -> Option { + let g = *self.pass_topo_group.get(pass_index as usize)?; + if g == !0 { + None + } else { + Some(g) + } + } + pub fn execute_sub_pass( &self, sub_pass_index: usize, diff --git a/crates/render/src/graph/resources.rs b/crates/render/src/graph/resources.rs index c206ac9..4462300 100644 --- a/crates/render/src/graph/resources.rs +++ b/crates/render/src/graph/resources.rs @@ -128,6 +128,7 @@ pub enum ResourceVariant { pub(crate) struct Resource { first_written: SubPassIndex, last_written: SubPassIndex, + is_read_after_last_written: bool, access: Access, format: Option, size: Option, @@ -242,6 +243,7 @@ impl ResourceSet { self.resources.push(Resource { first_written: SUBPASS_UNDEFINED, last_written: SUBPASS_UNDEFINED, + is_read_after_last_written: false, access: Access::empty(), format: None, size: None, @@ -283,6 +285,7 @@ impl ResourceSet { ); r.access |= access; if new_pass != last_written_by_pass { + r.is_read_after_last_written = false; r.last_written = new_pass; if r.first_written.0 == PASS_UNDEFINED { r.first_written = new_pass @@ -304,6 +307,7 @@ impl ResourceSet { last_written_by_pass, slot.last_written_by, "resource also written by an other pass (slot out of sync)" ); + r.is_read_after_last_written = true; r.access |= access; } @@ -738,17 +742,121 @@ impl PhysicalResources { changed } - pub fn get_texture( + pub fn get_texture(&self, idx: ResourceIndex) -> Option<&PhysicalResource> { + self.textures.get_physical(idx) + } + + pub fn get_buffer(&self, idx: ResourceIndex) -> Option<&PhysicalResource> { + self.buffers.get_physical(idx) + } +} + +struct PhysicalResourceMap { + external: Vec, + transient: Vec, + _phanton: PhantomData R>, +} + +impl PhysicalResourceMap { + pub const fn new() -> Self { + Self { + external: Vec::new(), + transient: Vec::new(), + _phanton: PhantomData, + } + } + fn clear(&mut self) { + self.external.clear(); + self.transient.clear(); + } + pub fn reset(&mut self, p: &PhysicalResourceSet) { + self.clear(); + self.external.resize(p.externals.len(), T::default()); + self.transient.resize(p.externals.len(), T::default()); + } + pub fn get(&self, p: &PhysicalResourceSet, i: ResourceIndex) -> Option<&T> { + match p.assignments.get(i as usize)? { + ExternalOrTransient::None => None, + ExternalOrTransient::External(i) => self.external.get(*i as usize), + ExternalOrTransient::Transient(i) => self.transient.get(*i as usize), + } + } + pub fn get_mut(&mut self, p: &PhysicalResourceSet, i: ResourceIndex) -> Option<&mut T> { + match p.assignments.get(i as usize)? { + ExternalOrTransient::None => None, + ExternalOrTransient::External(i) => self.external.get_mut(*i as usize), + ExternalOrTransient::Transient(i) => self.transient.get_mut(*i as usize), + } + } +} + +pub struct PhysicalResourceAccessTracker { + textures: PhysicalResourceMap, + buffers: PhysicalResourceMap, + total: Access, +} + +impl PhysicalResourceAccessTracker { + pub const fn new() -> Self { + Self { + textures: PhysicalResourceMap::new(), + buffers: PhysicalResourceMap::new(), + total: Access::empty(), + } + } + + pub fn reset(&mut self, r: &PhysicalResources) { + self.textures.reset(&r.textures); + self.buffers.reset(&r.buffers); + self.total = Access::empty(); + } + + pub(crate) fn get_current_texture_access( &self, - idx: ResourceIndex, - ) -> Option<(Texture, TextureFormat, u8, TextureDimensions)> { - let r = self.textures.get_physical(idx)?; - Some((r.resource, r.format, 1, r.size)) + p: &PhysicalResources, + resource_index: ResourceIndex, + ) -> Access { + *self + .textures + .get(&p.textures, resource_index) + .expect("no valid physical buffer resource") + } + pub(crate) fn get_current_buffer_access( + &self, + p: &PhysicalResources, + resource_index: ResourceIndex, + ) -> Access { + *self + .buffers + .get(&p.buffers, resource_index) + .expect("no valid physical buffer resource") } - pub fn get_buffer(&self, idx: ResourceIndex) -> Option<(Buffer, usize)> { - let r = self.buffers.get_physical(idx)?; - Some((r.resource, r.size)) + pub(crate) fn update_texture_access( + &mut self, + p: &PhysicalResources, + resource_index: ResourceIndex, + new_access: Access, + ) -> Access { + let dest = self + .textures + .get_mut(&p.textures, resource_index) + .expect("no valid physical texture resource"); + self.total |= new_access; + std::mem::replace(dest, new_access) + } + pub(crate) fn update_buffer_access( + &mut self, + p: &PhysicalResources, + resource_index: ResourceIndex, + new_access: Access, + ) -> Access { + let dest = self + .buffers + .get_mut(&p.buffers, resource_index) + .expect("no valid physical buffer resource"); + self.total |= new_access; + std::mem::replace(dest, new_access) } } diff --git a/crates/render/src/pipeline/graphics_pass.rs b/crates/render/src/pipeline/graphics_pass.rs index bbd20a2..303d1f7 100644 --- a/crates/render/src/pipeline/graphics_pass.rs +++ b/crates/render/src/pipeline/graphics_pass.rs @@ -3,8 +3,10 @@ use serde::{Deserialize, Serialize}; use crate::{ graph::{ - access::Access, pass::PipelineBindPoint, resources::PhysicalResources, PassDescription, - RenderGraph, ResourceIndex, + access::Access, + pass::PipelineBindPoint, + resources::{PhysicalResourceAccessTracker, PhysicalResources}, + PassDescription, RenderGraph, ResourceIndex, }, texture::TextureFormat, }; @@ -101,7 +103,7 @@ impl ExtendedGraphicsPassDescriptor { pub fn from_graph( graph: &RenderGraph, physical_resources: &PhysicalResources, - current_texture_access: &mut [Access], + resource_access: &mut PhysicalResourceAccessTracker, pass: &PassDescription, ) -> Option { if pass.bind_point() != PipelineBindPoint::Graphics { @@ -120,10 +122,10 @@ impl ExtendedGraphicsPassDescriptor { for i in attachment_indices.iter().copied() { let a = &pass.textures()[i as usize]; let resource_index = a.resource_index(); - let (_tex, format, samples, dim) = physical_resources + let physical_resource = physical_resources .get_texture(resource_index) .expect("unassigned resource"); - let dim = dim.subimage_extents(); + let dim = physical_resource.size.subimage_extents(); if size == USize2::ZERO { size = dim; } else if size != dim { @@ -137,21 +139,19 @@ impl ExtendedGraphicsPassDescriptor { // TODO: is resource used in later pass? then STORE, else DONT_CARE store_op: StoreOp::Store, }; - let current_usage = if a.is_read() { + if a.is_read() { load_store.load_op = LoadOp::Load; - current_texture_access[resource_index as usize] - } else { - // overide to undefined - current_texture_access[resource_index as usize] = Access::NONE; - Access::NONE - }; + } + + let current_access = + resource_access.get_current_texture_access(physical_resources, resource_index); attachments.push(AttachmentDescriptor { - format, - samples, + format: physical_resource.format, + samples: 1, // TODO: multi-sample access: a.access(), - initial_access: current_usage, - final_access: current_usage, + initial_access: current_access, + final_access: current_access, }); load_store_ops.push(load_store); @@ -165,15 +165,14 @@ impl ExtendedGraphicsPassDescriptor { let mut map_attachment_index_and_update_usage = |resource_index: u16, mut current_access: Access| { - if current_access.is_empty() { - current_access = current_texture_access[resource_index as usize]; - } else { - current_texture_access[resource_index as usize] = current_access; - }; let a = attachment_indices .binary_search(&resource_index) .expect("unvalid resource index") as u16; - attachments[a as usize].final_access = current_access; + if current_access.is_empty() { + current_access = attachments[a as usize].final_access; + } else { + attachments[a as usize].final_access = current_access; + } (a, current_access) }; @@ -206,6 +205,17 @@ impl ExtendedGraphicsPassDescriptor { // TODO: if this pass is the last pass accessing this resource (and resource not extern), then STOREOP = DON'T CARE // TODO: if this pass is the last pass accessing this resource, and usage us PRESENT, then finalLayout=PRESENT + for (a, r) in attachment_indices.iter().copied().enumerate() { + let attachment = &mut attachments[a]; + let physical_resource = physical_resources.get_texture(r).unwrap(); + // TODO: only present, if this is the last pass accessing this resource! + if physical_resource.access.intersects(Access::PRESENT) { + attachment.final_access = Access::PRESENT; + } + + resource_access.update_texture_access(physical_resources, r, attachment.final_access); + } + let graphics_pass = GraphicsPassDescriptor { attachments, load_store_ops, diff --git a/crates/render/src/texture/descriptor.rs b/crates/render/src/texture/descriptor.rs index dfb2787..66825d6 100644 --- a/crates/render/src/texture/descriptor.rs +++ b/crates/render/src/texture/descriptor.rs @@ -324,6 +324,9 @@ impl Access { if self.intersects(Self::TRANSFER_WRITE) { result |= TextureUsage::TRANSFER_WRITE; } + if self.intersects(Self::PRESENT) { + result |= TextureUsage::PRESENT; + } result } } From 4db9983db5a3146202babc5b521698a930c74803 Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sun, 14 Apr 2024 13:57:44 +0200 Subject: [PATCH 31/36] fix: updates --- Cargo.toml | 5 +- crates/assets-loader/src/lib.rs | 6 + crates/assets-loader/src/platform/fs.rs | 6 + crates/render-ash/src/convert.rs | 139 ++++++++++++++---------- crates/render-ash/src/debug_utils.rs | 2 +- crates/render-ash/src/instance.rs | 2 +- crates/render-ash/src/lib.rs | 4 +- crates/render/Cargo.toml | 1 - crates/render/src/camera.rs | 6 + crates/render/src/draw.rs | 6 + crates/render/src/graph/resources.rs | 12 ++ crates/window-winit/Cargo.toml | 2 +- 12 files changed, 125 insertions(+), 66 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b359562..b600b42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,14 +26,13 @@ slotmap = "1.0" anyhow = "1.0" thiserror = "1.0" tracing = "0.1" -bitflags = "2.4" +bitflags = "2.5" fnv = "1.0" radsort = "0.1" -bytemuck = "1.14" +bytemuck = "1.15" blink-alloc = "0.3" downcast-rs = "1.2" dynsequence = { version = "0.1.0-alpha.4" } -ambassador = "0.3" blocking = "1.5" threadpool = "1.8" backtrace = "0.3" diff --git a/crates/assets-loader/src/lib.rs b/crates/assets-loader/src/lib.rs index a4826ec..6c57fe1 100644 --- a/crates/assets-loader/src/lib.rs +++ b/crates/assets-loader/src/lib.rs @@ -108,6 +108,12 @@ pub struct AssetServer { io: Box, } +impl Default for AssetServer { + fn default() -> Self { + Self::new() + } +} + impl AssetServer { #[cfg(not(target_os = "android"))] pub fn new() -> Self { diff --git a/crates/assets-loader/src/platform/fs.rs b/crates/assets-loader/src/platform/fs.rs index b8f9333..7b2759e 100644 --- a/crates/assets-loader/src/platform/fs.rs +++ b/crates/assets-loader/src/platform/fs.rs @@ -32,6 +32,12 @@ fn join_path(base_path: &mut PathBuf, asset_path: &AssetPath) { } } +impl Default for FileSystemAssetLoaderIo { + fn default() -> Self { + Self::new() + } +} + impl FileSystemAssetLoaderIo { pub const fn new() -> Self { Self { dirs: Vec::new() } diff --git a/crates/render-ash/src/convert.rs b/crates/render-ash/src/convert.rs index e30955e..ce9a0e0 100644 --- a/crates/render-ash/src/convert.rs +++ b/crates/render-ash/src/convert.rs @@ -929,33 +929,53 @@ pub struct CreateInfoConverter6( pub struct CreateInfoConverter2(ScratchBuffer, ScratchBuffer); -fn get_or_create_subpass_dep( - buf: &mut ScratchBuffer, - src: u32, - dst: u32, - src_access_mask: vk::AccessFlags, -) -> &mut vk::SubpassDependency { - buf.binary_search_insert_by_key_with( - &(src, dst), - |d| (d.src_subpass, d.dst_subpass), - || { - vk::SubpassDependency::builder() - .src_subpass(src) - .dst_subpass(dst) - .src_stage_mask( - if src_access_mask.contains(vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE) { - vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS - | vk::PipelineStageFlags::LATE_FRAGMENT_TESTS - } else { - vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT - }, - ) - .src_access_mask(src_access_mask) - // use BY-REGION by default - .dependency_flags(vk::DependencyFlags::BY_REGION) - .build() - }, - ) +struct SubpassDepTracker<'a> { + subpass_deps: &'a mut ScratchBuffer, + attachment_dep_data: Vec<(u32, vk::PipelineStageFlags, vk::AccessFlags)>, + attachment_usage: &'a mut BitSet, +} + +impl SubpassDepTracker<'_> { + fn get_or_create_subpass_dep(&mut self, src: u32, dst: u32) -> &mut vk::SubpassDependency { + self.subpass_deps.binary_search_insert_by_key_with( + &(src, dst), + |d| (d.src_subpass, d.dst_subpass), + || { + vk::SubpassDependency::builder() + .src_subpass(src) + .dst_subpass(dst) + // use BY-REGION by default + .dependency_flags(vk::DependencyFlags::BY_REGION) + .build() + }, + ) + } + + fn mark_subpass_attachment( + &mut self, + attachment: usize, + dst: usize, + dst_stage: vk::PipelineStageFlags, + dst_access: vk::AccessFlags, + next_access: vk::AccessFlags, + ) { + let (src, src_stage, src_access) = self.attachment_dep_data[attachment]; + if src != vk::SUBPASS_EXTERNAL { + let dep = self.get_or_create_subpass_dep(src, dst as u32); + dep.src_stage_mask |= src_stage; + dep.dst_access_mask |= src_access; + dep.dst_stage_mask |= dst_stage; + dep.dst_access_mask |= dst_access; + } + + if next_access != vk::AccessFlags::NONE { + self.attachment_dep_data[attachment] = (dst as u32, dst_stage, next_access); + } + + let num_attachments = self.attachment_dep_data.len(); + self.attachment_usage + .insert(dst * num_attachments + attachment); + } } impl CreateInfoConverter6 { @@ -995,7 +1015,11 @@ impl CreateInfoConverter6 { .final_layout(a.final_access.vk_into()) .build(), ); - attachment_dep_data.push((vk::SUBPASS_EXTERNAL, vk::AccessFlags::NONE)); + attachment_dep_data.push(( + vk::SUBPASS_EXTERNAL, + vk::PipelineStageFlags::NONE, + vk::AccessFlags::NONE, + )); } // calculate subpass deps @@ -1003,20 +1027,25 @@ impl CreateInfoConverter6 { let a_refs = self.2.clear_and_use_as::(); let num_subpasses = desc.subpasses().len(); let mut attachment_usage = BitSet::with_capacity_for(num_attachments * num_subpasses); + let mut subpass_tracker = SubpassDepTracker { + attachment_dep_data, + subpass_deps: sp_deps, + attachment_usage: &mut attachment_usage, + }; for (i, sp) in desc.subpasses().iter().enumerate() { // TODO: handle Write>Read>Write! // TODO: also non-attachment dubpass-deps let dst = i as u32; for &(a, _u) in sp.input_attachments() { - let a = a as usize; - let (src, src_access) = attachment_dep_data[a]; - if src != vk::SUBPASS_EXTERNAL { - let dep = get_or_create_subpass_dep(sp_deps, src, dst, src_access); - dep.dst_stage_mask |= vk::PipelineStageFlags::FRAGMENT_SHADER; - dep.dst_access_mask |= vk::AccessFlags::INPUT_ATTACHMENT_READ; - } - attachment_usage.insert(i * num_attachments + a); - + subpass_tracker.mark_subpass_attachment( + a as usize, + i, + vk::PipelineStageFlags::FRAGMENT_SHADER, + vk::AccessFlags::INPUT_ATTACHMENT_READ, + vk::AccessFlags::NONE, + ); + + //attachments[a].final_layout = vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL; a_refs.push(vk::AttachmentReference { attachment: a as u32, // if a==!0 => vk::ATTACHMENT_UNUSED //layout: u.vk_into(), @@ -1024,15 +1053,13 @@ impl CreateInfoConverter6 { }); } for &(a, _u) in sp.color_attachments() { - let a = a as usize; - let (src, src_access) = attachment_dep_data[a]; - if src != vk::SUBPASS_EXTERNAL { - let dep = get_or_create_subpass_dep(sp_deps, src, dst, src_access); - dep.dst_stage_mask |= vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT; - dep.dst_access_mask |= vk::AccessFlags::COLOR_ATTACHMENT_READ; - } - attachment_dep_data[a] = (dst, vk::AccessFlags::COLOR_ATTACHMENT_WRITE); - attachment_usage.insert(i * num_attachments + a); + subpass_tracker.mark_subpass_attachment( + a as usize, + i, + vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT, + vk::AccessFlags::COLOR_ATTACHMENT_READ, + vk::AccessFlags::COLOR_ATTACHMENT_WRITE, + ); //attachments[a].final_layout = vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL; a_refs.push(vk::AttachmentReference { @@ -1042,16 +1069,14 @@ impl CreateInfoConverter6 { }); } if let Some((a, _u)) = sp.depth_stencil_attachment() { - let a = a as usize; - let (src, src_access) = attachment_dep_data[a]; - if src != vk::SUBPASS_EXTERNAL { - let dep = get_or_create_subpass_dep(sp_deps, src, dst, src_access); - dep.dst_stage_mask |= vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS - | vk::PipelineStageFlags::LATE_FRAGMENT_TESTS; - dep.dst_access_mask |= vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ; - } - attachment_dep_data[a] = (dst, vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE); - attachment_usage.insert(i * num_attachments + a); + subpass_tracker.mark_subpass_attachment( + a as usize, + i, + vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS + | vk::PipelineStageFlags::LATE_FRAGMENT_TESTS, + vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ, + vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE, + ); //attachments[a].final_layout = vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL; a_refs.push(vk::AttachmentReference { @@ -1061,7 +1086,7 @@ impl CreateInfoConverter6 { }); } } - drop(attachment_dep_data); + drop(subpass_tracker); // preserve attachment let mut a_preserve_tmp: Vec> = Vec::new(); diff --git a/crates/render-ash/src/debug_utils.rs b/crates/render-ash/src/debug_utils.rs index 358bf98..bd982b2 100644 --- a/crates/render-ash/src/debug_utils.rs +++ b/crates/render-ash/src/debug_utils.rs @@ -145,7 +145,7 @@ impl DebugUtils { if message_severities.is_empty() { Self { functions, - utils_messenger: ash::vk::DebugUtilsMessengerEXT::null(), + utils_messenger: vk::DebugUtilsMessengerEXT::null(), } } else { let utils_messenger = unsafe { diff --git a/crates/render-ash/src/instance.rs b/crates/render-ash/src/instance.rs index 9be1b4a..35d642a 100644 --- a/crates/render-ash/src/instance.rs +++ b/crates/render-ash/src/instance.rs @@ -107,7 +107,7 @@ fn get_instance_extensions( let available_extensions = entry.enumerate_instance_extension_properties(None)?; let mut extensions = Vec::with_capacity(5); - extensions.push(ash::extensions::khr::Surface::name()); + extensions.push(khr::Surface::name()); if cfg!(target_os = "windows") { extensions.push(khr::Win32Surface::name()); diff --git a/crates/render-ash/src/lib.rs b/crates/render-ash/src/lib.rs index 636ea3b..efdf653 100644 --- a/crates/render-ash/src/lib.rs +++ b/crates/render-ash/src/lib.rs @@ -27,7 +27,7 @@ use std::{backtrace::Backtrace, ffi::CStr, rc::Rc, sync::Arc}; -use ash::vk::{self, PipelineStageFlags}; +use ash::vk; use bitflags::bitflags; use convert::default_clear_color_value_for_format; use device::AshDevice; @@ -321,7 +321,7 @@ impl AshRendererFull { let sem = self.frames[self.current_frame] .command_pool .request_semaphore()?; - submission_group.wait(sem, PipelineStageFlags::TRANSFER); + submission_group.wait(sem, vk::PipelineStageFlags::TRANSFER); let surface = &mut self.surfaces[window_id]; if let Some(acquired) = surface.acquire_next_image(&mut self.res, sem)? { images.push(( diff --git a/crates/render/Cargo.toml b/crates/render/Cargo.toml index 949907f..7459ccc 100644 --- a/crates/render/Cargo.toml +++ b/crates/render/Cargo.toml @@ -20,7 +20,6 @@ pulz-functional-utils = { version = "0.1.0-alpha", path = "../functional-utils" atomic_refcell = { workspace = true } serde = { workspace = true } -ambassador = { workspace = true } dynsequence = { workspace = true } palette = { workspace = true, features = ["std"], default-features = false } image = { workspace = true, default-features = false } diff --git a/crates/render/src/camera.rs b/crates/render/src/camera.rs index 079001d..dc3ee4e 100644 --- a/crates/render/src/camera.rs +++ b/crates/render/src/camera.rs @@ -216,6 +216,12 @@ pub struct Camera { pub projection_matrix: Mat4, } +impl Default for Camera { + fn default() -> Self { + Self::new() + } +} + impl Camera { pub const fn new() -> Self { Self { diff --git a/crates/render/src/draw.rs b/crates/render/src/draw.rs index 02d7da2..967057e 100644 --- a/crates/render/src/draw.rs +++ b/crates/render/src/draw.rs @@ -311,6 +311,12 @@ impl SystemData for Draw<'_, I> { pub struct PhaseModule(PhantomData); +impl Default for PhaseModule { + fn default() -> Self { + Self::new() + } +} + impl PhaseModule { #[inline] pub const fn new() -> Self { diff --git a/crates/render/src/graph/resources.rs b/crates/render/src/graph/resources.rs index 4462300..2f252df 100644 --- a/crates/render/src/graph/resources.rs +++ b/crates/render/src/graph/resources.rs @@ -702,6 +702,12 @@ pub struct PhysicalResources { hash: u64, } +impl Default for PhysicalResources { + fn default() -> Self { + Self::new() + } +} + impl PhysicalResources { pub const fn new() -> Self { Self { @@ -796,6 +802,12 @@ pub struct PhysicalResourceAccessTracker { total: Access, } +impl Default for PhysicalResourceAccessTracker { + fn default() -> Self { + Self::new() + } +} + impl PhysicalResourceAccessTracker { pub const fn new() -> Self { Self { diff --git a/crates/window-winit/Cargo.toml b/crates/window-winit/Cargo.toml index 03197a2..594143e 100644 --- a/crates/window-winit/Cargo.toml +++ b/crates/window-winit/Cargo.toml @@ -21,7 +21,7 @@ pulz-input = { path = "../input" } fnv = { workspace = true } tracing = { workspace = true } -winit = { version = "0.29.10", default-features = false, features = ["rwh_06"] } +winit = { version = "0.29.15", default-features = false, features = ["rwh_06"] } raw-window-handle = { workspace = true, features = ["std"] } [target.'cfg(not(target_os = "unknown"))'.dev-dependencies] From beb7ef00e9f6d9760b586ff528304470258527c4 Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sun, 14 Apr 2024 16:29:55 +0200 Subject: [PATCH 32/36] fix: update ash --- crates/render-ash/Cargo.toml | 6 +- crates/render-ash/src/convert.rs | 108 ++++++++---------- crates/render-ash/src/debug_utils.rs | 85 ++++++++------ crates/render-ash/src/device.rs | 63 +++++----- crates/render-ash/src/drop_guard.rs | 39 ++++--- crates/render-ash/src/encoder.rs | 30 ++--- crates/render-ash/src/graph.rs | 16 ++- crates/render-ash/src/instance.rs | 55 +++++---- crates/render-ash/src/lib.rs | 16 +-- crates/render-ash/src/resources/mod.rs | 5 +- .../render-ash/src/resources/resource_impl.rs | 10 +- crates/render-ash/src/swapchain.rs | 53 ++++----- 12 files changed, 239 insertions(+), 247 deletions(-) diff --git a/crates/render-ash/Cargo.toml b/crates/render-ash/Cargo.toml index d17594f..56beedb 100644 --- a/crates/render-ash/Cargo.toml +++ b/crates/render-ash/Cargo.toml @@ -21,11 +21,11 @@ slotmap = { workspace = true } serde = { workspace = true } fnv = { workspace = true } raw-window-handle = { workspace = true } -ash = "0.37" +ash = "0.38" scratchbuffer = "0.1.0-alpha.1" gpu-alloc = "0.6" -gpu-alloc-ash = "0.6" -gpu-descriptor = "0.2" +gpu-alloc-ash = "0.7" +gpu-descriptor = "0.3" crossbeam-queue = { workspace = true } [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] diff --git a/crates/render-ash/src/convert.rs b/crates/render-ash/src/convert.rs index ce9a0e0..170a8d0 100644 --- a/crates/render-ash/src/convert.rs +++ b/crates/render-ash/src/convert.rs @@ -45,14 +45,13 @@ impl, V> VkFrom> for Option { } } -impl VkFrom for vk::BufferCreateInfo { +impl VkFrom for vk::BufferCreateInfo<'static> { #[inline] fn from(val: &BufferDescriptor) -> Self { - Self::builder() + Self::default() .size(val.size as u64) .usage(val.usage.vk_into()) .sharing_mode(vk::SharingMode::EXCLUSIVE) - .build() } } @@ -109,9 +108,9 @@ fn get_array_layers(dimensions: &TextureDimensions) -> u32 { } } -impl VkFrom for vk::ImageCreateInfo { +impl VkFrom for vk::ImageCreateInfo<'static> { fn from(val: &TextureDescriptor) -> Self { - Self::builder() + Self::default() .image_type(val.dimensions.vk_into()) .format(val.format.vk_into()) .extent(val.dimensions.vk_into()) @@ -122,25 +121,22 @@ impl VkFrom for vk::ImageCreateInfo { .tiling(vk::ImageTiling::OPTIMAL) .sharing_mode(vk::SharingMode::EXCLUSIVE) .initial_layout(vk::ImageLayout::UNDEFINED) - .build() } } -impl VkFrom for vk::ImageViewCreateInfo { +impl VkFrom for vk::ImageViewCreateInfo<'static> { fn from(val: &TextureDescriptor) -> Self { - Self::builder() + Self::default() .view_type(val.dimensions.vk_into()) .format(val.format.vk_into()) .subresource_range( - vk::ImageSubresourceRange::builder() + vk::ImageSubresourceRange::default() .aspect_mask(val.aspects().vk_into()) .base_mip_level(0) .level_count(1) .base_array_layer(0) .layer_count(get_array_layers(&val.dimensions)) - .build(), ) - .build() } } @@ -732,44 +728,44 @@ impl VkFrom for vk::PipelineStageFlags { } } -impl VkFrom for vk::PipelineInputAssemblyStateCreateInfo { +impl VkFrom for vk::PipelineInputAssemblyStateCreateInfo<'static> { #[inline] fn from(val: &PrimitiveState) -> Self { - Self::builder().topology(val.topology.vk_into()).build() + Self::default().topology(val.topology.vk_into()) } } -impl VkFrom for vk::PipelineRasterizationStateCreateInfo { +impl VkFrom for vk::PipelineRasterizationStateCreateInfo<'static> { #[inline] fn from(val: &PrimitiveState) -> Self { - let mut builder = Self::builder() + let mut r = Self::default() .polygon_mode(vk::PolygonMode::FILL) .front_face(val.front_face.vk_into()) .line_width(1.0); if let Some(cull_mode) = val.cull_mode { - builder = builder.cull_mode(cull_mode.vk_into()) + r = r.cull_mode(cull_mode.vk_into()) } - builder.build() + r } } -impl VkFrom for vk::PipelineDepthStencilStateCreateInfo { +impl VkFrom for vk::PipelineDepthStencilStateCreateInfo<'static> { #[inline] fn from(val: &DepthStencilState) -> Self { - let mut builder = Self::builder(); + let mut r = Self::default(); if val.is_depth_enabled() { - builder = builder + r = r .depth_test_enable(true) .depth_write_enable(val.depth.write_enabled) .depth_compare_op(val.depth.compare.vk_into()); } if val.stencil.is_enabled() { - builder = builder + r = r .stencil_test_enable(true) .front(val.stencil.front.vk_into()) .back(val.stencil.back.vk_into()); } - builder.build() + r } } @@ -941,12 +937,11 @@ impl SubpassDepTracker<'_> { &(src, dst), |d| (d.src_subpass, d.dst_subpass), || { - vk::SubpassDependency::builder() + vk::SubpassDependency::default() .src_subpass(src) .dst_subpass(dst) // use BY-REGION by default .dependency_flags(vk::DependencyFlags::BY_REGION) - .build() }, ) } @@ -991,7 +986,7 @@ impl CreateInfoConverter6 { ) } - pub fn graphics_pass(&mut self, desc: &GraphicsPassDescriptor) -> &vk::RenderPassCreateInfo { + pub fn graphics_pass(&mut self, desc: &GraphicsPassDescriptor) -> &vk::RenderPassCreateInfo<'_> { // collect attachments let attachments = self.0.clear_and_use_as::(); let num_attachments = desc.attachments().len(); @@ -1000,7 +995,7 @@ impl CreateInfoConverter6 { for (i, a) in desc.attachments().iter().enumerate() { let load_store_ops = desc.load_store_ops().get(i).copied().unwrap_or_default(); attachments.push( - vk::AttachmentDescription::builder() + vk::AttachmentDescription::default() .format(a.format.vk_into()) .samples(vk::SampleCountFlags::from_raw(a.samples as u32)) .load_op(load_store_ops.load_op.vk_into()) @@ -1013,7 +1008,6 @@ impl CreateInfoConverter6 { vk::ImageLayout::UNDEFINED }) .final_layout(a.final_access.vk_into()) - .build(), ); attachment_dep_data.push(( vk::SUBPASS_EXTERNAL, @@ -1032,14 +1026,13 @@ impl CreateInfoConverter6 { subpass_deps: sp_deps, attachment_usage: &mut attachment_usage, }; - for (i, sp) in desc.subpasses().iter().enumerate() { + for (dst, sp) in desc.subpasses().iter().enumerate() { // TODO: handle Write>Read>Write! // TODO: also non-attachment dubpass-deps - let dst = i as u32; for &(a, _u) in sp.input_attachments() { subpass_tracker.mark_subpass_attachment( a as usize, - i, + dst, vk::PipelineStageFlags::FRAGMENT_SHADER, vk::AccessFlags::INPUT_ATTACHMENT_READ, vk::AccessFlags::NONE, @@ -1055,7 +1048,7 @@ impl CreateInfoConverter6 { for &(a, _u) in sp.color_attachments() { subpass_tracker.mark_subpass_attachment( a as usize, - i, + dst, vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT, vk::AccessFlags::COLOR_ATTACHMENT_READ, vk::AccessFlags::COLOR_ATTACHMENT_WRITE, @@ -1071,7 +1064,7 @@ impl CreateInfoConverter6 { if let Some((a, _u)) = sp.depth_stencil_attachment() { subpass_tracker.mark_subpass_attachment( a as usize, - i, + dst, vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS | vk::PipelineStageFlags::LATE_FRAGMENT_TESTS, vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ, @@ -1128,14 +1121,14 @@ impl CreateInfoConverter6 { let a_refs = a_refs.as_slice(); let a_preserves = a_preserves.as_slice(); - let subpasses = self.4.clear_and_use_as::(); + let subpasses = self.4.clear_and_use_as::>(); let mut a_refs_offset = 0; let mut a_preserves_offset = 0; for (i, s) in desc.subpasses().iter().enumerate() { let end_input_offset = a_refs_offset + s.input_attachments().len(); let end_color_offset = end_input_offset + s.color_attachments().len(); let end_preserves_offset = a_preserves_offset + a_preserve_tmp[i].len(); - let mut b = vk::SubpassDescription::builder() + let mut b = vk::SubpassDescription::default() .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS) .preserve_attachments(&a_preserves[a_preserves_offset..end_preserves_offset]) .input_attachments(&a_refs[a_refs_offset..end_input_offset]) @@ -1147,17 +1140,16 @@ impl CreateInfoConverter6 { a_refs_offset += 1; b = b.depth_stencil_attachment(&a_refs[end_color_offset]); } - subpasses.push(b.build()); + subpasses.push(b); } - let buf = self.5.clear_and_use_as::(); + let buf = self.5.clear_and_use_as::>(); buf.reserve(1); buf.push( - vk::RenderPassCreateInfo::builder() + vk::RenderPassCreateInfo::default() .attachments(attachments.as_slice()) .subpasses(subpasses.as_slice()) .dependencies(sp_deps.as_slice()) - .build(), ); &buf.as_slice()[0] } @@ -1172,15 +1164,14 @@ impl CreateInfoConverter2 { pub fn bind_group_layout( &mut self, desc: &BindGroupLayoutDescriptor<'_>, - ) -> &vk::DescriptorSetLayoutCreateInfo { - let buf0 = self.0.clear_and_use_as::(); + ) -> &vk::DescriptorSetLayoutCreateInfo<'_> { + let buf0 = self.0.clear_and_use_as::>(); buf0.reserve(desc.entries.len()); for e in desc.entries.as_ref() { buf0.push( - vk::DescriptorSetLayoutBinding::builder() + vk::DescriptorSetLayoutBinding::default() .binding(e.binding) .descriptor_count(e.count) - .build(), ); // TODO: descriptor_type, stage_flags, immutable_samplers todo!(); @@ -1188,12 +1179,11 @@ impl CreateInfoConverter2 { let buf = self .1 - .clear_and_use_as::(); + .clear_and_use_as::>(); buf.reserve(1); buf.push( - vk::DescriptorSetLayoutCreateInfo::builder() + vk::DescriptorSetLayoutCreateInfo::default() .bindings(buf0.as_slice()) - .build(), ); &buf.as_slice()[0] } @@ -1202,19 +1192,18 @@ impl CreateInfoConverter2 { &mut self, res: &AshResources, desc: &PipelineLayoutDescriptor<'_>, - ) -> &vk::PipelineLayoutCreateInfo { + ) -> &vk::PipelineLayoutCreateInfo<'_> { let buf0 = self.0.clear_and_use_as::(); buf0.reserve(desc.bind_group_layouts.len()); for bgl in desc.bind_group_layouts.as_ref() { buf0.push(res.bind_group_layouts[*bgl]); } - let buf = self.1.clear_and_use_as::(); + let buf = self.1.clear_and_use_as::>(); buf.reserve(1); buf.push( - vk::PipelineLayoutCreateInfo::builder() + vk::PipelineLayoutCreateInfo::default() .set_layouts(buf0.as_slice()) - .build(), ); &buf.as_slice()[0] } @@ -1223,17 +1212,16 @@ impl CreateInfoConverter2 { &mut self, res: &AshResources, descs: &[GraphicsPipelineDescriptor<'_>], - ) -> &[vk::GraphicsPipelineCreateInfo] { - let buf = self.0.clear_and_use_as::(); + ) -> &[vk::GraphicsPipelineCreateInfo<'_>] { + let buf = self.0.clear_and_use_as::>(); buf.reserve(descs.len()); for desc in descs { let layout = desc .layout .map_or(vk::PipelineLayout::null(), |l| res.pipeline_layouts[l]); buf.push( - vk::GraphicsPipelineCreateInfo::builder() + vk::GraphicsPipelineCreateInfo::default() .layout(layout) - .build(), ); // TODO: vertex, primitive, depth_stencil, fragment, samples, specialization todo!(" implement graphics_pipeline_descriptor"); @@ -1245,8 +1233,8 @@ impl CreateInfoConverter2 { &mut self, res: &AshResources, descs: &[ComputePipelineDescriptor<'_>], - ) -> &[vk::ComputePipelineCreateInfo] { - let buf = self.0.clear_and_use_as::(); + ) -> &[vk::ComputePipelineCreateInfo<'_>] { + let buf = self.0.clear_and_use_as::>(); buf.reserve(descs.len()); for desc in descs { let layout = desc @@ -1254,9 +1242,8 @@ impl CreateInfoConverter2 { .map_or(vk::PipelineLayout::null(), |l| res.pipeline_layouts[l]); // TODO: module, entry_point, specialization buf.push( - vk::ComputePipelineCreateInfo::builder() + vk::ComputePipelineCreateInfo::default() .layout(layout) - .build(), ); todo!(" implement compute_pipeline_descriptor"); } @@ -1267,10 +1254,10 @@ impl CreateInfoConverter2 { &mut self, res: &AshResources, descs: &[RayTracingPipelineDescriptor<'_>], - ) -> &[vk::RayTracingPipelineCreateInfoKHR] { + ) -> &[vk::RayTracingPipelineCreateInfoKHR<'_>] { let buf = self .0 - .clear_and_use_as::(); + .clear_and_use_as::>(); buf.reserve(descs.len()); for desc in descs { let layout = desc @@ -1278,10 +1265,9 @@ impl CreateInfoConverter2 { .map_or(vk::PipelineLayout::null(), |l| res.pipeline_layouts[l]); // TODO: modules, groups, specialization buf.push( - vk::RayTracingPipelineCreateInfoKHR::builder() + vk::RayTracingPipelineCreateInfoKHR::default() .layout(layout) .max_pipeline_ray_recursion_depth(desc.max_recursion_depth) - .build(), ); todo!(" implement ray_tracing_pipeline_descriptor"); } diff --git a/crates/render-ash/src/debug_utils.rs b/crates/render-ash/src/debug_utils.rs index bd982b2..9cff1c3 100644 --- a/crates/render-ash/src/debug_utils.rs +++ b/crates/render-ash/src/debug_utils.rs @@ -3,12 +3,14 @@ use std::{ os::raw::c_char, }; -use ash::vk; +use ash::vk::{self, Handle}; use tracing::{debug, error, info, warn}; +pub const EXT_NAME: &'static CStr = ash::ext::debug_utils::NAME; + unsafe fn c_str_from_ptr<'a>(str_ptr: *const c_char) -> &'a CStr { if str_ptr.is_null() { - CStr::from_bytes_with_nul_unchecked(b"\0") + c"" } else { CStr::from_ptr(str_ptr) } @@ -17,7 +19,7 @@ unsafe fn c_str_from_ptr<'a>(str_ptr: *const c_char) -> &'a CStr { unsafe extern "system" fn debug_callback( message_severity: vk::DebugUtilsMessageSeverityFlagsEXT, message_type: vk::DebugUtilsMessageTypeFlagsEXT, - p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT, + p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT<'_>, _p_user_data: *mut c_void, ) -> vk::Bool32 { use vk::DebugUtilsMessageSeverityFlagsEXT; @@ -126,22 +128,21 @@ impl CStrBuf { } } -pub struct DebugUtils { - functions: ash::extensions::ext::DebugUtils, +pub struct InstanceDebugUtils { + functions: ash::ext::debug_utils::Instance, utils_messenger: vk::DebugUtilsMessengerEXT, } +#[repr(transparent)] +pub struct DeviceDebugUtils(ash::ext::debug_utils::Device); -impl DebugUtils { - pub const fn name() -> &'static CStr { - ash::extensions::ext::DebugUtils::name() - } +impl InstanceDebugUtils { pub fn new( entry: &ash::Entry, instance: &ash::Instance, message_severities: vk::DebugUtilsMessageSeverityFlagsEXT, ) -> Self { - let functions = ash::extensions::ext::DebugUtils::new(entry, instance); + let functions = ash::ext::debug_utils::Instance::new(entry, instance); if message_severities.is_empty() { Self { functions, @@ -151,7 +152,7 @@ impl DebugUtils { let utils_messenger = unsafe { functions .create_debug_utils_messenger( - &vk::DebugUtilsMessengerCreateInfoEXT::builder() + &vk::DebugUtilsMessengerCreateInfoEXT::default() .message_severity(message_severities) .message_type( vk::DebugUtilsMessageTypeFlagsEXT::GENERAL @@ -173,32 +174,39 @@ impl DebugUtils { #[inline(always)] pub fn is_messenger_enabled(&self) -> bool { - self.utils_messenger != vk::DebugUtilsMessengerEXT::null() + !self.utils_messenger.is_null() + } +} + +impl DeviceDebugUtils { + pub fn new( + instance: &ash::Instance, + device: &ash::Device, + ) -> Self { + let functions = ash::ext::debug_utils::Device::new(instance, device); + Self(functions) } #[inline(always)] - pub unsafe fn object_name( + pub unsafe fn object_name( &self, - device: vk::Device, handle: H, object_name: &str, ) { - self._object_name(device, H::TYPE, handle.as_raw(), object_name) + self._object_name(H::TYPE, handle.as_raw(), object_name) } #[inline(always)] - pub unsafe fn object_name_cstr( + pub unsafe fn object_name_cstr( &self, - device: vk::Device, handle: H, object_name: &CStr, ) { - self._object_name_cstr(device, H::TYPE, handle.as_raw(), object_name) + self._object_name_cstr(H::TYPE, handle.as_raw(), object_name) } #[inline] unsafe fn _object_name( &self, - device: vk::Device, object_type: vk::ObjectType, object_handle: u64, object_name: &str, @@ -209,7 +217,6 @@ impl DebugUtils { let mut cstr_buf = CStrBuf::new(); self._object_name_cstr( - device, object_type, object_handle, cstr_buf.get_cstr(object_name), @@ -218,7 +225,6 @@ impl DebugUtils { unsafe fn _object_name_cstr( &self, - device: vk::Device, object_type: vk::ObjectType, object_handle: u64, object_name: &CStr, @@ -226,12 +232,13 @@ impl DebugUtils { if object_handle == 0 { return; } - let _result = self.functions.set_debug_utils_object_name( - device, - &vk::DebugUtilsObjectNameInfoEXT::builder() - .object_type(object_type) - .object_handle(object_handle) - .object_name(object_name), + let _result = self.0.set_debug_utils_object_name( + &vk::DebugUtilsObjectNameInfoEXT { + object_handle, + object_type, + p_object_name: object_name.as_ptr(), + ..Default::default() + }, ); } @@ -245,9 +252,9 @@ impl DebugUtils { command_buffer: vk::CommandBuffer, label: &CStr, ) { - self.functions.cmd_insert_debug_utils_label( + self.0.cmd_insert_debug_utils_label( command_buffer, - &vk::DebugUtilsLabelEXT::builder().label_name(label), + &vk::DebugUtilsLabelEXT::default().label_name(label), ); } @@ -261,15 +268,15 @@ impl DebugUtils { command_buffer: vk::CommandBuffer, label: &CStr, ) { - self.functions.cmd_begin_debug_utils_label( + self.0.cmd_begin_debug_utils_label( command_buffer, - &vk::DebugUtilsLabelEXT::builder().label_name(label), + &vk::DebugUtilsLabelEXT::default().label_name(label), ); } #[inline] pub unsafe fn cmd_end_debug_label(&self, command_buffer: vk::CommandBuffer) { - self.functions.cmd_end_debug_utils_label(command_buffer); + self.0.cmd_end_debug_utils_label(command_buffer); } #[inline] @@ -279,9 +286,9 @@ impl DebugUtils { } pub unsafe fn queue_insert_debug_label_cstr(&self, queue: vk::Queue, label: &CStr) { - self.functions.queue_insert_debug_utils_label( + self.0.queue_insert_debug_utils_label( queue, - &vk::DebugUtilsLabelEXT::builder().label_name(label), + &vk::DebugUtilsLabelEXT::default().label_name(label), ); } @@ -292,19 +299,19 @@ impl DebugUtils { } pub unsafe fn queue_begin_debug_label_cstr(&self, queue: vk::Queue, label: &CStr) { - self.functions.queue_begin_debug_utils_label( + self.0.queue_begin_debug_utils_label( queue, - &vk::DebugUtilsLabelEXT::builder().label_name(label), + &vk::DebugUtilsLabelEXT::default().label_name(label), ); } #[inline] pub unsafe fn queue_end_debug_label(&self, queue: vk::Queue) { - self.functions.queue_end_debug_utils_label(queue); + self.0.queue_end_debug_utils_label(queue); } } -impl Drop for DebugUtils { +impl Drop for InstanceDebugUtils { fn drop(&mut self) { if self.utils_messenger != vk::DebugUtilsMessengerEXT::null() { let utils_messenger = std::mem::take(&mut self.utils_messenger); @@ -315,3 +322,5 @@ impl Drop for DebugUtils { } } } + + diff --git a/crates/render-ash/src/device.rs b/crates/render-ash/src/device.rs index 9f13bcf..8822f77 100644 --- a/crates/render-ash/src/device.rs +++ b/crates/render-ash/src/device.rs @@ -1,19 +1,20 @@ use std::{ffi::CStr, ops::Deref, os::raw::c_char, sync::Arc}; -use ash::{extensions::khr, vk}; +use ash::vk; use pulz_render::graph::pass::PipelineBindPoint; use tracing::{debug, info, warn}; -use crate::{instance::AshInstance, Error, ErrorNoExtension, Result}; +use crate::{debug_utils, instance::AshInstance, AshRendererFlags, Error, ErrorNoExtension, Result}; pub struct AshDevice { device_raw: ash::Device, instance: Arc, physical_device: vk::PhysicalDevice, device_extensions: Vec<&'static CStr>, - ext_swapchain: Option, - ext_sync2: Option, - ext_raytracing_pipeline: Option, + debug_utils: Option, + ext_swapchain: Option, + ext_sync2: Option, + ext_raytracing_pipeline: Option, queues: Queues, } @@ -54,20 +55,24 @@ impl AshDevice { physical_device, device_raw, device_extensions, + debug_utils: None, ext_swapchain: None, ext_sync2: None, ext_raytracing_pipeline: None, queues, }; - if device.has_device_extension(khr::Swapchain::name()) { - device.ext_swapchain = Some(khr::Swapchain::new(instance, &device)); + if instance.debug_utils().is_ok() { + device.debug_utils = Some(debug_utils::DeviceDebugUtils::new(instance, &device)); } - if device.has_device_extension(khr::Synchronization2::name()) { - device.ext_sync2 = Some(khr::Synchronization2::new(instance, &device)) + if device.has_device_extension(ash::khr::swapchain::NAME) { + device.ext_swapchain = Some(ash::khr::swapchain::Device::new(instance, &device)); } - if device.has_device_extension(khr::RayTracingPipeline::name()) { - device.ext_raytracing_pipeline = Some(khr::RayTracingPipeline::new(instance, &device)) + if device.has_device_extension(ash::khr::synchronization2::NAME) { + device.ext_sync2 = Some(ash::khr::synchronization2::Device::new(instance, &device)) + } + if device.has_device_extension(ash::khr::ray_tracing_pipeline::NAME) { + device.ext_raytracing_pipeline = Some(ash::khr::ray_tracing_pipeline::Device::new(instance, &device)) } Ok(Arc::new(device)) @@ -99,32 +104,39 @@ impl AshDevice { } #[inline] - pub(crate) fn ext_swapchain(&self) -> Result<&khr::Swapchain, ErrorNoExtension> { + pub(crate) fn debug_utils(&self) -> Result<&debug_utils::DeviceDebugUtils, ErrorNoExtension> { + self.debug_utils + .as_ref() + .ok_or(ErrorNoExtension(ash::ext::debug_utils::NAME)) + } + + #[inline] + pub(crate) fn ext_swapchain(&self) -> Result<&ash::khr::swapchain::Device, ErrorNoExtension> { self.ext_swapchain .as_ref() - .ok_or(ErrorNoExtension(khr::Swapchain::name())) + .ok_or(ErrorNoExtension(ash::khr::swapchain::NAME)) } #[inline] - pub(crate) fn ext_sync2(&self) -> Result<&khr::Synchronization2, ErrorNoExtension> { + pub(crate) fn ext_sync2(&self) -> Result<&ash::khr::synchronization2::Device, ErrorNoExtension> { self.ext_sync2 .as_ref() - .ok_or(ErrorNoExtension(khr::Synchronization2::name())) + .ok_or(ErrorNoExtension(ash::khr::synchronization2::NAME)) } #[inline] pub(crate) fn ext_raytracing_pipeline( &self, - ) -> Result<&khr::RayTracingPipeline, ErrorNoExtension> { + ) -> Result<&ash::khr::ray_tracing_pipeline::Device, ErrorNoExtension> { self.ext_raytracing_pipeline .as_ref() - .ok_or(ErrorNoExtension(khr::RayTracingPipeline::name())) + .ok_or(ErrorNoExtension(ash::khr::ray_tracing_pipeline::NAME)) } #[inline] pub unsafe fn object_name(&self, handle: H, name: &str) { - if let Ok(debug_utils) = self.instance.ext_debug_utils() { - debug_utils.object_name(self.handle(), handle, name) + if let Some(debug_utils) = &self.debug_utils { + debug_utils.object_name(handle, name) } } } @@ -147,8 +159,8 @@ fn get_device_extensions( unsafe { instance.enumerate_device_extension_properties(physical_device)? }; let mut extensions = Vec::with_capacity(4); - extensions.push(khr::Swapchain::name()); - extensions.push(khr::Synchronization2::name()); + extensions.push(ash::khr::swapchain::NAME); + extensions.push(ash::khr::synchronization2::NAME); // Only keep available extensions. extensions.retain(|&ext| { @@ -236,7 +248,7 @@ impl AshInstance { let extensions = get_device_extensions(self, physical_device).ok()?; if for_surface_opt != vk::SurfaceKHR::null() - && (!extensions.contains(&khr::Swapchain::name()) + && (!extensions.contains(&ash::khr::swapchain::NAME) || self .query_swapchain_support(for_surface_opt, physical_device) .is_none()) @@ -267,11 +279,10 @@ impl AshInstance { let device = unsafe { self.create_device( physical_device, - &vk::DeviceCreateInfo::builder() - .queue_create_infos(&[vk::DeviceQueueCreateInfo::builder() + &vk::DeviceCreateInfo::default() + .queue_create_infos(&[vk::DeviceQueueCreateInfo::default() .queue_family_index(indices.graphics_family) - .queue_priorities(&[1.0_f32]) - .build()]) + .queue_priorities(&[1.0_f32])]) .enabled_extension_names(extensions_ptr), // .enabled_features(&vk::PhysicalDeviceFeatures { // ..Default::default() // default just enable no feature. diff --git a/crates/render-ash/src/drop_guard.rs b/crates/render-ash/src/drop_guard.rs index 5c34fb9..4200109 100644 --- a/crates/render-ash/src/drop_guard.rs +++ b/crates/render-ash/src/drop_guard.rs @@ -10,13 +10,13 @@ pub trait Destroy { } pub trait CreateWithInfo: Destroy + Sized { - type CreateInfo; - unsafe fn create(context: &Self::Context, create_info: &Self::CreateInfo) -> Result; + type CreateInfo<'a>; + unsafe fn create(context: &Self::Context, create_info: &Self::CreateInfo<'_>) -> Result; } impl AshDevice { #[inline] - pub unsafe fn create(&self, create_info: &C::CreateInfo) -> Result> + pub unsafe fn create(&self, create_info: &C::CreateInfo<'_>) -> Result> where C: CreateWithInfo, { @@ -42,7 +42,7 @@ impl AshDevice { impl AshInstance { #[inline] - pub unsafe fn create(&self, create_info: &C::CreateInfo) -> Result> + pub unsafe fn create(&self, create_info: &C::CreateInfo<'_>) -> Result> where C: CreateWithInfo, { @@ -156,6 +156,7 @@ impl Destroy for std::collections::vec_deque::Drain<'_, D> { macro_rules! impl_create_destroy { ($ctx:ty { + <$lt:tt> $( $vktype:ty : ($destroy:ident $(, $create:ident $createinfo:ty)?) ),* $(,)? @@ -172,9 +173,9 @@ macro_rules! impl_create_destroy { $( impl CreateWithInfo for $vktype { - type CreateInfo = $createinfo; + type CreateInfo<$lt> = $createinfo; #[inline] - unsafe fn create(ctx: &Self::Context, create_info: &Self::CreateInfo) -> Result { + unsafe fn create(ctx: &Self::Context, create_info: &Self::CreateInfo<'_>) -> Result { Ok(ctx.$create(create_info, None)?) } } @@ -184,19 +185,19 @@ macro_rules! impl_create_destroy { } impl_create_destroy! { - AshDevice { - vk::Fence : (destroy_fence, create_fence vk::FenceCreateInfo), - vk::Semaphore : (destroy_semaphore, create_semaphore vk::SemaphoreCreateInfo), - vk::Event : (destroy_event, create_event vk::EventCreateInfo), - vk::CommandPool : (destroy_command_pool, create_command_pool vk::CommandPoolCreateInfo), - vk::Buffer : (destroy_buffer, create_buffer vk::BufferCreateInfo), - vk::Image : (destroy_image, create_image vk::ImageCreateInfo), - vk::ImageView : (destroy_image_view, create_image_view vk::ImageViewCreateInfo), - vk::Framebuffer : (destroy_framebuffer, create_framebuffer vk::FramebufferCreateInfo), - vk::RenderPass : (destroy_render_pass, create_render_pass vk::RenderPassCreateInfo), - vk::ShaderModule : (destroy_shader_module, create_shader_module vk::ShaderModuleCreateInfo), - vk::DescriptorSetLayout : (destroy_descriptor_set_layout, create_descriptor_set_layout vk::DescriptorSetLayoutCreateInfo), - vk::PipelineLayout : (destroy_pipeline_layout, create_pipeline_layout vk::PipelineLayoutCreateInfo), + AshDevice { <'a> + vk::Fence : (destroy_fence, create_fence vk::FenceCreateInfo<'a>), + vk::Semaphore : (destroy_semaphore, create_semaphore vk::SemaphoreCreateInfo<'a>), + vk::Event : (destroy_event, create_event vk::EventCreateInfo<'a>), + vk::CommandPool : (destroy_command_pool, create_command_pool vk::CommandPoolCreateInfo<'a>), + vk::Buffer : (destroy_buffer, create_buffer vk::BufferCreateInfo<'a>), + vk::Image : (destroy_image, create_image vk::ImageCreateInfo<'a>), + vk::ImageView : (destroy_image_view, create_image_view vk::ImageViewCreateInfo<'a>), + vk::Framebuffer : (destroy_framebuffer, create_framebuffer vk::FramebufferCreateInfo<'a>), + vk::RenderPass : (destroy_render_pass, create_render_pass vk::RenderPassCreateInfo<'a>), + vk::ShaderModule : (destroy_shader_module, create_shader_module vk::ShaderModuleCreateInfo<'a>), + vk::DescriptorSetLayout : (destroy_descriptor_set_layout, create_descriptor_set_layout vk::DescriptorSetLayoutCreateInfo<'a>), + vk::PipelineLayout : (destroy_pipeline_layout, create_pipeline_layout vk::PipelineLayoutCreateInfo<'a>), vk::Pipeline : (destroy_pipeline), } } diff --git a/crates/render-ash/src/encoder.rs b/crates/render-ash/src/encoder.rs index f341db7..b84bfa7 100644 --- a/crates/render-ash/src/encoder.rs +++ b/crates/render-ash/src/encoder.rs @@ -23,7 +23,7 @@ impl AshDevice { ) -> VkResult { let pool = unsafe { self.create_command_pool( - &vk::CommandPoolCreateInfo::builder() + &vk::CommandPoolCreateInfo::default() .queue_family_index(queue_family_index) .flags(vk::CommandPoolCreateFlags::TRANSIENT), None, @@ -66,7 +66,7 @@ impl AshCommandPool { } else { unsafe { self.device - .create_semaphore(&vk::SemaphoreCreateInfo::builder().build(), None)? + .create_semaphore(&vk::SemaphoreCreateInfo::default(), None)? } }; self.used_semaphores.push(s); @@ -77,7 +77,7 @@ impl AshCommandPool { if self.fresh_buffers.is_empty() { let new_buffers = unsafe { self.device.allocate_command_buffers( - &vk::CommandBufferAllocateInfo::builder() + &vk::CommandBufferAllocateInfo::default() .command_pool(self.pool) .level(vk::CommandBufferLevel::PRIMARY) .command_buffer_count(self.new_allocation_count), @@ -89,7 +89,7 @@ impl AshCommandPool { unsafe { self.device.begin_command_buffer( buffer, - &vk::CommandBufferBeginInfo::builder() + &vk::CommandBufferBeginInfo::default() .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), )?; } @@ -154,7 +154,7 @@ impl AshCommandEncoder<'_> { } pub fn insert_debug_label(&self, label: &str) { - if let Ok(debug_utils) = self.pool.device.instance().ext_debug_utils() { + if let Ok(debug_utils) = self.pool.device.debug_utils() { unsafe { debug_utils.cmd_begin_debug_label(self.buffer, label); } @@ -162,7 +162,7 @@ impl AshCommandEncoder<'_> { } pub fn begin_debug_label(&self, label: &str) { - if let Ok(debug_utils) = self.pool.device.instance().ext_debug_utils() { + if let Ok(debug_utils) = self.pool.device.debug_utils() { unsafe { debug_utils.cmd_begin_debug_label(self.buffer, label); } @@ -172,7 +172,7 @@ impl AshCommandEncoder<'_> { } pub fn end_debug_label(&self) { - if let Ok(debug_utils) = self.pool.device.instance().ext_debug_utils() { + if let Ok(debug_utils) = self.pool.device.debug_utils() { unsafe { debug_utils.cmd_end_debug_label(self.buffer); } @@ -187,7 +187,7 @@ impl AshCommandEncoder<'_> { let debug_levels = self.debug_levels.get(); if debug_levels > 0 { self.debug_levels.set(0); - if let Ok(debug_utils) = self.pool.device.instance().ext_debug_utils() { + if let Ok(debug_utils) = self.pool.device.debug_utils() { for _i in 0..debug_levels { unsafe { debug_utils.cmd_end_debug_label(self.buffer); @@ -233,9 +233,9 @@ impl AshCommandEncoder<'_> { &self, src_stage_mask: vk::PipelineStageFlags, dst_stage_mask: vk::PipelineStageFlags, - memory_barriers: &[vk::MemoryBarrier], - buffer_memory_barriers: &[vk::BufferMemoryBarrier], - image_memory_barriers: &[vk::ImageMemoryBarrier], + memory_barriers: &[vk::MemoryBarrier<'_>], + buffer_memory_barriers: &[vk::BufferMemoryBarrier<'_>], + image_memory_barriers: &[vk::ImageMemoryBarrier<'_>], ) { self.pool.device().cmd_pipeline_barrier( self.buffer, @@ -250,7 +250,7 @@ impl AshCommandEncoder<'_> { pub unsafe fn begin_render_pass( &self, - create_info: &vk::RenderPassBeginInfo, + create_info: &vk::RenderPassBeginInfo<'_>, contents: vk::SubpassContents, ) { self.pool @@ -348,8 +348,8 @@ impl SubmissionGroup { self.command_buffers.is_empty() } - pub fn submit_info(&self) -> vk::SubmitInfoBuilder<'_> { - vk::SubmitInfo::builder() + pub fn submit_info(&self) -> vk::SubmitInfo<'_> { + vk::SubmitInfo::default() .wait_semaphores(&self.wait_semaphores) .wait_dst_stage_mask(&self.wait_semaphores_dst_stages) .command_buffers(&self.command_buffers) @@ -360,7 +360,7 @@ impl SubmissionGroup { unsafe { device.queue_submit( device.queues().graphics, - &[self.submit_info().build()], + &[self.submit_info()], fence, )?; } diff --git a/crates/render-ash/src/graph.rs b/crates/render-ash/src/graph.rs index 19c05cb..bee76f9 100644 --- a/crates/render-ash/src/graph.rs +++ b/crates/render-ash/src/graph.rs @@ -53,8 +53,8 @@ struct TopoRenderPass { #[derive(Debug)] pub struct Barrier { - image: Vec, - buffer: Vec, + image: Vec>, + buffer: Vec>, } // implement Send+Sync manually, because vk::*MemoryBarrier have unused p_next pointers @@ -200,14 +200,14 @@ impl TopoRenderPass { } */ - let create_info = vk::FramebufferCreateInfo::builder() + let create_info = vk::FramebufferCreateInfo::default() .render_pass(self.render_pass) // TODO .attachments(&image_views) .width(self.size.x) .height(self.size.y) .layers(1); - let fb = unsafe { res.device().create(&create_info.build())? }; + let fb = unsafe { res.device().create(&create_info)? }; self.framebuffer = fb.take(); //self.framebuffers_cache.insert(key, self.framebuffer); Ok(()) @@ -348,17 +348,15 @@ impl AshRenderGraph { // TODO: caching of framebuffer // TODO: clear-values, render-area, ... encoder.begin_render_pass( - &vk::RenderPassBeginInfo::builder() + &vk::RenderPassBeginInfo::default() .render_pass(topo_render_pass.render_pass) .framebuffer(topo_render_pass.framebuffer) .clear_values(&clear_values) .render_area( - vk::Rect2D::builder() + vk::Rect2D::default() .offset(vk::Offset2D { x: 0, y: 0 }) .extent(topo_render_pass.size.vk_into()) - .build(), - ) - .build(), + ), vk::SubpassContents::INLINE, ); let mut first = true; diff --git a/crates/render-ash/src/instance.rs b/crates/render-ash/src/instance.rs index 35d642a..a10f196 100644 --- a/crates/render-ash/src/instance.rs +++ b/crates/render-ash/src/instance.rs @@ -1,12 +1,9 @@ use std::{ffi::CStr, ops::Deref, os::raw::c_char, sync::Arc}; -use ash::{ - extensions::{ext, khr}, - vk, -}; +use ash::vk; use tracing::{debug, warn}; -use crate::{debug_utils::DebugUtils, AshRendererFlags, ErrorNoExtension, Result}; +use crate::{debug_utils, AshRendererFlags, ErrorNoExtension, Result}; pub const ENGINE_NAME: &[u8] = concat!(env!("CARGO_PKG_NAME"), "\0").as_bytes(); pub const ENGINE_VERSION: u32 = parse_version(env!("CARGO_PKG_VERSION")); @@ -16,8 +13,8 @@ pub struct AshInstance { instance_raw: ash::Instance, entry: ash::Entry, instance_extensions: Vec<&'static CStr>, - ext_debug_utils: Option, - ext_surface: Option, + debug_utils: Option, + ext_surface: Option, flags: AshRendererFlags, } @@ -39,13 +36,13 @@ impl AshInstance { entry, instance_raw, instance_extensions, - ext_debug_utils: None, + debug_utils: None, ext_surface: None, flags, }; - if instance.has_instance_extension(DebugUtils::name()) { - instance.ext_debug_utils = Some(DebugUtils::new( + if instance.has_instance_extension(debug_utils::EXT_NAME) { + instance.debug_utils = Some(debug_utils::InstanceDebugUtils::new( instance.entry(), &instance, // TODO: filter activated severities @@ -56,8 +53,8 @@ impl AshInstance { )); } - if instance.has_instance_extension(khr::Surface::name()) { - instance.ext_surface = Some(khr::Surface::new(instance.entry(), &instance)); + if instance.has_instance_extension(ash::khr::surface::NAME) { + instance.ext_surface = Some(ash::khr::surface::Instance::new(instance.entry(), &instance)); } Ok(Arc::new(instance)) @@ -74,17 +71,17 @@ impl AshInstance { } #[inline] - pub(crate) fn ext_surface(&self) -> Result<&khr::Surface, ErrorNoExtension> { + pub(crate) fn ext_surface(&self) -> Result<&ash::khr::surface::Instance, ErrorNoExtension> { self.ext_surface .as_ref() - .ok_or(ErrorNoExtension(khr::Surface::name())) + .ok_or(ErrorNoExtension(ash::khr::surface::NAME)) } #[inline] - pub(crate) fn ext_debug_utils(&self) -> Result<&DebugUtils, ErrorNoExtension> { - self.ext_debug_utils + pub(crate) fn debug_utils(&self) -> Result<&debug_utils::InstanceDebugUtils, ErrorNoExtension> { + self.debug_utils .as_ref() - .ok_or(ErrorNoExtension(ext::DebugUtils::name())) + .ok_or(ErrorNoExtension(debug_utils::EXT_NAME)) } #[inline] @@ -95,7 +92,7 @@ impl AshInstance { impl Drop for AshInstance { fn drop(&mut self) { - self.ext_debug_utils = None; + self.debug_utils = None; unsafe { self.instance_raw.destroy_instance(None) } } } @@ -104,25 +101,25 @@ fn get_instance_extensions( entry: &ash::Entry, flags: AshRendererFlags, ) -> Result> { - let available_extensions = entry.enumerate_instance_extension_properties(None)?; + let available_extensions = unsafe { entry.enumerate_instance_extension_properties(None)? }; let mut extensions = Vec::with_capacity(5); - extensions.push(khr::Surface::name()); + extensions.push(ash::khr::surface::NAME); if cfg!(target_os = "windows") { - extensions.push(khr::Win32Surface::name()); + extensions.push(ash::khr::win32_surface::NAME); } else if cfg!(target_os = "android") { - extensions.push(khr::AndroidSurface::name()); + extensions.push(ash::khr::android_surface::NAME); } else if cfg!(any(target_os = "macos", target_os = "ios")) { - extensions.push(ext::MetalSurface::name()); + extensions.push(ash::ext::metal_surface::NAME); } else if cfg!(unix) { - extensions.push(khr::XlibSurface::name()); - extensions.push(khr::XcbSurface::name()); - extensions.push(khr::WaylandSurface::name()); + extensions.push(ash::khr::xlib_surface::NAME); + extensions.push(ash::khr::xcb_surface::NAME); + extensions.push(ash::khr::wayland_surface::NAME); } if flags.contains(AshRendererFlags::DEBUG) { - extensions.push(DebugUtils::name()); + extensions.push(debug_utils::EXT_NAME); } // Only keep available extensions. @@ -156,9 +153,9 @@ fn _create_instance(entry: &ash::Entry, extensions_ptr: &[*const c_char]) -> Res let instance = unsafe { entry.create_instance( - &vk::InstanceCreateInfo::builder() + &vk::InstanceCreateInfo::default() .application_info( - &vk::ApplicationInfo::builder() + &vk::ApplicationInfo::default() .application_name(engine_name) .application_version(ENGINE_VERSION) .engine_name(engine_name) diff --git a/crates/render-ash/src/lib.rs b/crates/render-ash/src/lib.rs index efdf653..3ce1003 100644 --- a/crates/render-ash/src/lib.rs +++ b/crates/render-ash/src/lib.rs @@ -202,11 +202,10 @@ impl Frame { unsafe fn create(device: &Arc) -> Result { let command_pool = device.new_command_pool(device.queues().graphics_family)?; let finished_fence = device.create( - &vk::FenceCreateInfo::builder() + &vk::FenceCreateInfo::default() .flags(vk::FenceCreateFlags::SIGNALED) - .build(), )?; - let finished_semaphore = device.create(&vk::SemaphoreCreateInfo::builder().build())?; + let finished_semaphore = device.create(&vk::SemaphoreCreateInfo::default())?; Ok(Self { command_pool, finished_fence: finished_fence.take(), @@ -331,23 +330,21 @@ impl AshRendererFull { } } - let subrange = vk::ImageSubresourceRange::builder() + let subrange = vk::ImageSubresourceRange::default() .aspect_mask(vk::ImageAspectFlags::COLOR) .layer_count(vk::REMAINING_ARRAY_LAYERS) - .level_count(vk::REMAINING_MIP_LEVELS) - .build(); + .level_count(vk::REMAINING_MIP_LEVELS); let barriers = images .iter() .map(|(image, _)| { - vk::ImageMemoryBarrier::builder() + vk::ImageMemoryBarrier::default() .src_access_mask(vk::AccessFlags::empty()) .dst_access_mask(vk::AccessFlags::TRANSFER_WRITE) .old_layout(vk::ImageLayout::UNDEFINED) .new_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL) .subresource_range(subrange) .image(*image) - .build() }) .collect::>(); @@ -379,14 +376,13 @@ impl AshRendererFull { let barriers = images .iter() .map(|(image, _)| { - vk::ImageMemoryBarrier::builder() + vk::ImageMemoryBarrier::default() .src_access_mask(vk::AccessFlags::TRANSFER_WRITE) .dst_access_mask(vk::AccessFlags::empty()) .old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL) .new_layout(vk::ImageLayout::PRESENT_SRC_KHR) .subresource_range(subrange) .image(*image) - .build() }) .collect::>(); unsafe { diff --git a/crates/render-ash/src/resources/mod.rs b/crates/render-ash/src/resources/mod.rs index edc1bef..a3db53e 100644 --- a/crates/render-ash/src/resources/mod.rs +++ b/crates/render-ash/src/resources/mod.rs @@ -120,9 +120,8 @@ impl AshResources { self.pipeline_cache = vk::PipelineCache::null(); } self.pipeline_cache = self.alloc.device().create_pipeline_cache( - &vk::PipelineCacheCreateInfo::builder() - .initial_data(initial_data) - .build(), + &vk::PipelineCacheCreateInfo::default() + .initial_data(initial_data), None, )?; } diff --git a/crates/render-ash/src/resources/resource_impl.rs b/crates/render-ash/src/resources/resource_impl.rs index 87ae2b6..d054248 100644 --- a/crates/render-ash/src/resources/resource_impl.rs +++ b/crates/render-ash/src/resources/resource_impl.rs @@ -36,7 +36,7 @@ impl AshGpuResource for Buffer { ) -> Result { let alloc = &mut res.alloc; let device = alloc.device_arc(); - let create_info: vk::BufferCreateInfo = descr.vk_into(); + let create_info: vk::BufferCreateInfo<'static> = descr.vk_into(); let buf = device.create(&create_info)?; let mreq = device.get_buffer_memory_requirements(buf.raw()); let mem = alloc.alloc(gpu_alloc::Request { @@ -84,7 +84,7 @@ impl AshGpuResource for Texture { ) -> Result { let alloc = &mut res.alloc; let device = alloc.device_arc(); - let img_create_info: vk::ImageCreateInfo = descr.vk_into(); + let img_create_info: vk::ImageCreateInfo<'static> = descr.vk_into(); let img = device.create(&img_create_info)?; let mreq = device.get_image_memory_requirements(img.raw()); let mem = alloc.alloc(gpu_alloc::Request { @@ -94,7 +94,7 @@ impl AshGpuResource for Texture { memory_types: mreq.memory_type_bits, })?; device.bind_image_memory(img.raw(), *mem.memory(), mem.offset())?; - let mut view_create_info: vk::ImageViewCreateInfo = descr.vk_into(); + let mut view_create_info: vk::ImageViewCreateInfo<'static> = descr.vk_into(); view_create_info.image = img.raw(); let view = device.create(&view_create_info)?; Ok((img.take(), view.take(), Some(mem.take()))) @@ -169,7 +169,7 @@ impl AshGpuResource for ShaderModule { descr: &Self::Descriptor<'_>, ) -> Result { let code = compie_into_spv(&descr.source)?; - let create_info = vk::ShaderModuleCreateInfo::builder().code(&code).build(); + let create_info = vk::ShaderModuleCreateInfo::default().code(&code); let raw = res.device().create(&create_info)?; if let Some(label) = descr.label { res.device().object_name(raw.raw(), label); @@ -366,7 +366,7 @@ impl AshGpuResource for RayTracingPipeline { res.pipeline_cache, create_infos, None, - )?; + ).map_err(|(_,e)|e)?; let raw = res.device().hold(raw[0]); if let Some(label) = descr.label { res.device().object_name(raw.raw(), label); diff --git a/crates/render-ash/src/swapchain.rs b/crates/render-ash/src/swapchain.rs index a83ea9c..14e5426 100644 --- a/crates/render-ash/src/swapchain.rs +++ b/crates/render-ash/src/swapchain.rs @@ -1,6 +1,6 @@ use std::rc::Rc; -use ash::{extensions::khr, vk}; +use ash::vk; use pulz_render::{ math::{uvec2, USize2}, texture::{Texture, TextureFormat}, @@ -40,12 +40,12 @@ impl Destroy for vk::SwapchainKHR { } } -macro_rules! check_and_get_extension { - ($self:ident => $ext:ty) => {{ - if !$self.has_instance_extension(<$ext>::name()) { - return Err(Error::ExtensionNotSupported(<$ext>::name())); +macro_rules! check_and_get_instance_extension { + ($self:ident => $ext:ident :: $extname:ident) => {{ + if !$self.has_instance_extension(::ash::$ext::$extname::NAME) { + return Err(Error::ExtensionNotSupported(::ash::$ext::$extname::NAME)); } - <$ext>::new($self.entry(), $self) + ::ash::$ext::$extname::Instance::new($self.entry(), $self) }}; } @@ -61,9 +61,9 @@ impl AshInstance { dpy: *mut vk::Display, window: vk::Window, ) -> Result { - let functions = check_and_get_extension!(self => khr::XlibSurface); + let functions = check_and_get_instance_extension!(self => khr::xlib_surface); let surface = functions.create_xlib_surface( - &vk::XlibSurfaceCreateInfoKHR::builder() + &vk::XlibSurfaceCreateInfoKHR::default() .dpy(dpy) .window(window), None, @@ -82,9 +82,9 @@ impl AshInstance { connection: *mut vk::xcb_connection_t, window: vk::xcb_window_t, ) -> Result { - let functions = check_and_get_extension!(self => khr::XcbSurface); + let functions = check_and_get_instance_extension!(self => khr::xcb_surface); let surface = functions.create_xcb_surface( - &vk::XcbSurfaceCreateInfoKHR::builder() + &vk::XcbSurfaceCreateInfoKHR::default() .connection(connection) .window(window), None, @@ -103,9 +103,9 @@ impl AshInstance { display: *mut vk::wl_display, surface: *mut vk::wl_surface, ) -> Result { - let functions = check_and_get_extension!(self => khr::WaylandSurface); + let functions = check_and_get_instance_extension!(self => khr::wayland_surface); let surface = functions.create_wayland_surface( - &vk::WaylandSurfaceCreateInfoKHR::builder() + &vk::WaylandSurfaceCreateInfoKHR::default() .display(display) .surface(surface), None, @@ -118,9 +118,9 @@ impl AshInstance { &self, window: *mut vk::ANativeWindow, ) -> Result { - let functions = check_and_get_extension!(self => khr::AndroidSurface); + let functions = check_and_get_instance_extension!(self => khr::android_surface); let surface = functions.create_android_surface( - &vk::AndroidSurfaceCreateInfoKHR::builder() + &vk::AndroidSurfaceCreateInfoKHR::default() .window(window) .build(), None, @@ -134,9 +134,9 @@ impl AshInstance { hinstance: vk::HINSTANCE, hwnd: vk::HWND, ) -> Result { - let functions = check_and_get_extension!(self => khr::Win32Surface); + let functions = check_and_get_instance_extension!(self => khr::win32_surface); let surface = functions.create_win32_surface( - &vk::Win32SurfaceCreateInfoKHR::builder() + &vk::Win32SurfaceCreateInfoKHR::default() .hinstance(hinstance) .hwnd(hwnd) .build(), @@ -150,10 +150,9 @@ impl AshInstance { &self, layer: *const vk::CAMetalLayer, ) -> Result { - use ash::extensions::ext; - let functions = check_and_get_extension!(self => ext::MetalSurface); + let functions = check_and_get_extension!(self => ext::metal_surface); let surface = functions.create_metal_surface( - &vk::MetalSurfaceCreateInfoEXT::builder() + &vk::MetalSurfaceCreateInfoEXT::default() .layer(layer) .build(), None, @@ -558,7 +557,7 @@ impl AshSurfaceSwapchain { let old_swapchain = self.put_to_garbage(res); self.swapchain_raw = unsafe { res.device().ext_swapchain()?.create_swapchain( - &vk::SwapchainCreateInfoKHR::builder() + &vk::SwapchainCreateInfoKHR::default() .surface(self.surface_raw) .min_image_count(self.image_count) .image_format(self.surface_format.format) @@ -582,8 +581,7 @@ impl AshSurfaceSwapchain { .present_mode(self.present_mode) .clipped(true) .old_swapchain(old_swapchain) - .image_array_layers(1) - .build(), + .image_array_layers(1), None, )? }; @@ -601,20 +599,18 @@ impl AshSurfaceSwapchain { for image in self.images.iter().copied() { unsafe { let image_view = res.device().create_image_view( - &vk::ImageViewCreateInfo::builder() + &vk::ImageViewCreateInfo::default() .image(image) .view_type(vk::ImageViewType::TYPE_2D) .format(self.surface_format.format) .subresource_range( - vk::ImageSubresourceRange::builder() + vk::ImageSubresourceRange::default() .aspect_mask(vk::ImageAspectFlags::COLOR) .base_mip_level(0) .level_count(1) .base_array_layer(0) .layer_count(1) - .build(), - ) - .build(), + ), None, )?; self.image_views.push(image_view); @@ -786,12 +782,11 @@ impl AshRendererFull { let result = unsafe { ext_swapchain.queue_present( self.device.queues().present, - &vk::PresentInfoKHR::builder() + &vk::PresentInfoKHR::default() .wait_semaphores(wait_semaphores) .swapchains(&swapchains) .image_indices(&image_indices) .results(&mut results) - .build(), ) }; From 7418d1a1cdf085877d4ab04c35ff600c3ea67040 Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sun, 14 Apr 2024 16:31:13 +0200 Subject: [PATCH 33/36] style: fmt --- crates/render-ash/src/convert.rs | 51 +++++++++---------- crates/render-ash/src/debug_utils.rs | 36 ++++--------- crates/render-ash/src/device.rs | 12 +++-- crates/render-ash/src/encoder.rs | 6 +-- crates/render-ash/src/graph.rs | 2 +- crates/render-ash/src/instance.rs | 5 +- crates/render-ash/src/lib.rs | 6 +-- crates/render-ash/src/resources/mod.rs | 3 +- .../render-ash/src/resources/resource_impl.rs | 14 ++--- crates/render-ash/src/swapchain.rs | 4 +- 10 files changed, 62 insertions(+), 77 deletions(-) diff --git a/crates/render-ash/src/convert.rs b/crates/render-ash/src/convert.rs index 170a8d0..f31a10c 100644 --- a/crates/render-ash/src/convert.rs +++ b/crates/render-ash/src/convert.rs @@ -135,7 +135,7 @@ impl VkFrom for vk::ImageViewCreateInfo<'static> { .base_mip_level(0) .level_count(1) .base_array_layer(0) - .layer_count(get_array_layers(&val.dimensions)) + .layer_count(get_array_layers(&val.dimensions)), ) } } @@ -986,7 +986,10 @@ impl CreateInfoConverter6 { ) } - pub fn graphics_pass(&mut self, desc: &GraphicsPassDescriptor) -> &vk::RenderPassCreateInfo<'_> { + pub fn graphics_pass( + &mut self, + desc: &GraphicsPassDescriptor, + ) -> &vk::RenderPassCreateInfo<'_> { // collect attachments let attachments = self.0.clear_and_use_as::(); let num_attachments = desc.attachments().len(); @@ -1007,7 +1010,7 @@ impl CreateInfoConverter6 { } else { vk::ImageLayout::UNDEFINED }) - .final_layout(a.final_access.vk_into()) + .final_layout(a.final_access.vk_into()), ); attachment_dep_data.push(( vk::SUBPASS_EXTERNAL, @@ -1149,7 +1152,7 @@ impl CreateInfoConverter6 { vk::RenderPassCreateInfo::default() .attachments(attachments.as_slice()) .subpasses(subpasses.as_slice()) - .dependencies(sp_deps.as_slice()) + .dependencies(sp_deps.as_slice()), ); &buf.as_slice()[0] } @@ -1165,13 +1168,15 @@ impl CreateInfoConverter2 { &mut self, desc: &BindGroupLayoutDescriptor<'_>, ) -> &vk::DescriptorSetLayoutCreateInfo<'_> { - let buf0 = self.0.clear_and_use_as::>(); + let buf0 = self + .0 + .clear_and_use_as::>(); buf0.reserve(desc.entries.len()); for e in desc.entries.as_ref() { buf0.push( vk::DescriptorSetLayoutBinding::default() .binding(e.binding) - .descriptor_count(e.count) + .descriptor_count(e.count), ); // TODO: descriptor_type, stage_flags, immutable_samplers todo!(); @@ -1181,10 +1186,7 @@ impl CreateInfoConverter2 { .1 .clear_and_use_as::>(); buf.reserve(1); - buf.push( - vk::DescriptorSetLayoutCreateInfo::default() - .bindings(buf0.as_slice()) - ); + buf.push(vk::DescriptorSetLayoutCreateInfo::default().bindings(buf0.as_slice())); &buf.as_slice()[0] } @@ -1199,12 +1201,11 @@ impl CreateInfoConverter2 { buf0.push(res.bind_group_layouts[*bgl]); } - let buf = self.1.clear_and_use_as::>(); + let buf = self + .1 + .clear_and_use_as::>(); buf.reserve(1); - buf.push( - vk::PipelineLayoutCreateInfo::default() - .set_layouts(buf0.as_slice()) - ); + buf.push(vk::PipelineLayoutCreateInfo::default().set_layouts(buf0.as_slice())); &buf.as_slice()[0] } @@ -1213,16 +1214,15 @@ impl CreateInfoConverter2 { res: &AshResources, descs: &[GraphicsPipelineDescriptor<'_>], ) -> &[vk::GraphicsPipelineCreateInfo<'_>] { - let buf = self.0.clear_and_use_as::>(); + let buf = self + .0 + .clear_and_use_as::>(); buf.reserve(descs.len()); for desc in descs { let layout = desc .layout .map_or(vk::PipelineLayout::null(), |l| res.pipeline_layouts[l]); - buf.push( - vk::GraphicsPipelineCreateInfo::default() - .layout(layout) - ); + buf.push(vk::GraphicsPipelineCreateInfo::default().layout(layout)); // TODO: vertex, primitive, depth_stencil, fragment, samples, specialization todo!(" implement graphics_pipeline_descriptor"); } @@ -1234,17 +1234,16 @@ impl CreateInfoConverter2 { res: &AshResources, descs: &[ComputePipelineDescriptor<'_>], ) -> &[vk::ComputePipelineCreateInfo<'_>] { - let buf = self.0.clear_and_use_as::>(); + let buf = self + .0 + .clear_and_use_as::>(); buf.reserve(descs.len()); for desc in descs { let layout = desc .layout .map_or(vk::PipelineLayout::null(), |l| res.pipeline_layouts[l]); // TODO: module, entry_point, specialization - buf.push( - vk::ComputePipelineCreateInfo::default() - .layout(layout) - ); + buf.push(vk::ComputePipelineCreateInfo::default().layout(layout)); todo!(" implement compute_pipeline_descriptor"); } buf.as_slice() @@ -1267,7 +1266,7 @@ impl CreateInfoConverter2 { buf.push( vk::RayTracingPipelineCreateInfoKHR::default() .layout(layout) - .max_pipeline_ray_recursion_depth(desc.max_recursion_depth) + .max_pipeline_ray_recursion_depth(desc.max_recursion_depth), ); todo!(" implement ray_tracing_pipeline_descriptor"); } diff --git a/crates/render-ash/src/debug_utils.rs b/crates/render-ash/src/debug_utils.rs index 9cff1c3..d80a905 100644 --- a/crates/render-ash/src/debug_utils.rs +++ b/crates/render-ash/src/debug_utils.rs @@ -136,7 +136,6 @@ pub struct InstanceDebugUtils { pub struct DeviceDebugUtils(ash::ext::debug_utils::Device); impl InstanceDebugUtils { - pub fn new( entry: &ash::Entry, instance: &ash::Instance, @@ -179,28 +178,17 @@ impl InstanceDebugUtils { } impl DeviceDebugUtils { - pub fn new( - instance: &ash::Instance, - device: &ash::Device, - ) -> Self { + pub fn new(instance: &ash::Instance, device: &ash::Device) -> Self { let functions = ash::ext::debug_utils::Device::new(instance, device); Self(functions) } #[inline(always)] - pub unsafe fn object_name( - &self, - handle: H, - object_name: &str, - ) { + pub unsafe fn object_name(&self, handle: H, object_name: &str) { self._object_name(H::TYPE, handle.as_raw(), object_name) } #[inline(always)] - pub unsafe fn object_name_cstr( - &self, - handle: H, - object_name: &CStr, - ) { + pub unsafe fn object_name_cstr(&self, handle: H, object_name: &CStr) { self._object_name_cstr(H::TYPE, handle.as_raw(), object_name) } @@ -216,11 +204,7 @@ impl DeviceDebugUtils { } let mut cstr_buf = CStrBuf::new(); - self._object_name_cstr( - object_type, - object_handle, - cstr_buf.get_cstr(object_name), - ) + self._object_name_cstr(object_type, object_handle, cstr_buf.get_cstr(object_name)) } unsafe fn _object_name_cstr( @@ -232,14 +216,14 @@ impl DeviceDebugUtils { if object_handle == 0 { return; } - let _result = self.0.set_debug_utils_object_name( - &vk::DebugUtilsObjectNameInfoEXT { + let _result = self + .0 + .set_debug_utils_object_name(&vk::DebugUtilsObjectNameInfoEXT { object_handle, - object_type, + object_type, p_object_name: object_name.as_ptr(), ..Default::default() - }, - ); + }); } #[inline] @@ -322,5 +306,3 @@ impl Drop for InstanceDebugUtils { } } } - - diff --git a/crates/render-ash/src/device.rs b/crates/render-ash/src/device.rs index 8822f77..4ebd578 100644 --- a/crates/render-ash/src/device.rs +++ b/crates/render-ash/src/device.rs @@ -4,7 +4,9 @@ use ash::vk; use pulz_render::graph::pass::PipelineBindPoint; use tracing::{debug, info, warn}; -use crate::{debug_utils, instance::AshInstance, AshRendererFlags, Error, ErrorNoExtension, Result}; +use crate::{ + debug_utils, instance::AshInstance, AshRendererFlags, Error, ErrorNoExtension, Result, +}; pub struct AshDevice { device_raw: ash::Device, @@ -72,7 +74,9 @@ impl AshDevice { device.ext_sync2 = Some(ash::khr::synchronization2::Device::new(instance, &device)) } if device.has_device_extension(ash::khr::ray_tracing_pipeline::NAME) { - device.ext_raytracing_pipeline = Some(ash::khr::ray_tracing_pipeline::Device::new(instance, &device)) + device.ext_raytracing_pipeline = Some(ash::khr::ray_tracing_pipeline::Device::new( + instance, &device, + )) } Ok(Arc::new(device)) @@ -118,7 +122,9 @@ impl AshDevice { } #[inline] - pub(crate) fn ext_sync2(&self) -> Result<&ash::khr::synchronization2::Device, ErrorNoExtension> { + pub(crate) fn ext_sync2( + &self, + ) -> Result<&ash::khr::synchronization2::Device, ErrorNoExtension> { self.ext_sync2 .as_ref() .ok_or(ErrorNoExtension(ash::khr::synchronization2::NAME)) diff --git a/crates/render-ash/src/encoder.rs b/crates/render-ash/src/encoder.rs index b84bfa7..590f0ab 100644 --- a/crates/render-ash/src/encoder.rs +++ b/crates/render-ash/src/encoder.rs @@ -358,11 +358,7 @@ impl SubmissionGroup { pub fn submit(&mut self, device: &AshDevice, fence: vk::Fence) -> VkResult<&mut Self> { unsafe { - device.queue_submit( - device.queues().graphics, - &[self.submit_info()], - fence, - )?; + device.queue_submit(device.queues().graphics, &[self.submit_info()], fence)?; } self.reset(); Ok(self) diff --git a/crates/render-ash/src/graph.rs b/crates/render-ash/src/graph.rs index bee76f9..65d3f77 100644 --- a/crates/render-ash/src/graph.rs +++ b/crates/render-ash/src/graph.rs @@ -355,7 +355,7 @@ impl AshRenderGraph { .render_area( vk::Rect2D::default() .offset(vk::Offset2D { x: 0, y: 0 }) - .extent(topo_render_pass.size.vk_into()) + .extent(topo_render_pass.size.vk_into()), ), vk::SubpassContents::INLINE, ); diff --git a/crates/render-ash/src/instance.rs b/crates/render-ash/src/instance.rs index a10f196..850bfdb 100644 --- a/crates/render-ash/src/instance.rs +++ b/crates/render-ash/src/instance.rs @@ -54,7 +54,10 @@ impl AshInstance { } if instance.has_instance_extension(ash::khr::surface::NAME) { - instance.ext_surface = Some(ash::khr::surface::Instance::new(instance.entry(), &instance)); + instance.ext_surface = Some(ash::khr::surface::Instance::new( + instance.entry(), + &instance, + )); } Ok(Arc::new(instance)) diff --git a/crates/render-ash/src/lib.rs b/crates/render-ash/src/lib.rs index 3ce1003..ce7b767 100644 --- a/crates/render-ash/src/lib.rs +++ b/crates/render-ash/src/lib.rs @@ -201,10 +201,8 @@ impl Frame { impl Frame { unsafe fn create(device: &Arc) -> Result { let command_pool = device.new_command_pool(device.queues().graphics_family)?; - let finished_fence = device.create( - &vk::FenceCreateInfo::default() - .flags(vk::FenceCreateFlags::SIGNALED) - )?; + let finished_fence = + device.create(&vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED))?; let finished_semaphore = device.create(&vk::SemaphoreCreateInfo::default())?; Ok(Self { command_pool, diff --git a/crates/render-ash/src/resources/mod.rs b/crates/render-ash/src/resources/mod.rs index a3db53e..ec38976 100644 --- a/crates/render-ash/src/resources/mod.rs +++ b/crates/render-ash/src/resources/mod.rs @@ -120,8 +120,7 @@ impl AshResources { self.pipeline_cache = vk::PipelineCache::null(); } self.pipeline_cache = self.alloc.device().create_pipeline_cache( - &vk::PipelineCacheCreateInfo::default() - .initial_data(initial_data), + &vk::PipelineCacheCreateInfo::default().initial_data(initial_data), None, )?; } diff --git a/crates/render-ash/src/resources/resource_impl.rs b/crates/render-ash/src/resources/resource_impl.rs index d054248..dbc692b 100644 --- a/crates/render-ash/src/resources/resource_impl.rs +++ b/crates/render-ash/src/resources/resource_impl.rs @@ -361,12 +361,14 @@ impl AshGpuResource for RayTracingPipeline { let ext = res.device().ext_raytracing_pipeline()?; let mut conv = CreateInfoConverter2::new(); let create_infos = conv.ray_tracing_pipeline_descriptor(res, std::slice::from_ref(descr)); - let raw = ext.create_ray_tracing_pipelines( - vk::DeferredOperationKHR::null(), - res.pipeline_cache, - create_infos, - None, - ).map_err(|(_,e)|e)?; + let raw = ext + .create_ray_tracing_pipelines( + vk::DeferredOperationKHR::null(), + res.pipeline_cache, + create_infos, + None, + ) + .map_err(|(_, e)| e)?; let raw = res.device().hold(raw[0]); if let Some(label) = descr.label { res.device().object_name(raw.raw(), label); diff --git a/crates/render-ash/src/swapchain.rs b/crates/render-ash/src/swapchain.rs index 14e5426..646fe85 100644 --- a/crates/render-ash/src/swapchain.rs +++ b/crates/render-ash/src/swapchain.rs @@ -609,7 +609,7 @@ impl AshSurfaceSwapchain { .base_mip_level(0) .level_count(1) .base_array_layer(0) - .layer_count(1) + .layer_count(1), ), None, )?; @@ -786,7 +786,7 @@ impl AshRendererFull { .wait_semaphores(wait_semaphores) .swapchains(&swapchains) .image_indices(&image_indices) - .results(&mut results) + .results(&mut results), ) }; From 9e5653d358bb0d97d3de7b3dd2c237f73d138c82 Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sun, 1 Sep 2024 23:32:44 +0200 Subject: [PATCH 34/36] fix(deps): update deps, mifrate new winit version, refactored windowing --- Cargo.toml | 18 +- crates/render-ash/examples/render-ash-demo.rs | 37 +- crates/render-ash/src/debug_utils.rs | 2 +- crates/render-ash/src/device.rs | 4 +- crates/render-ash/src/instance.rs | 12 +- crates/render-ash/src/lib.rs | 38 +- crates/render-ash/src/swapchain.rs | 29 +- crates/render-wgpu/Cargo.toml | 6 +- .../render-wgpu/examples/render-wgpu-demo.rs | 40 +- crates/render-wgpu/src/convert.rs | 5 + crates/render-wgpu/src/lib.rs | 31 +- crates/render-wgpu/src/surface.rs | 25 +- crates/render/src/texture/image.rs | 2 +- crates/window-winit/Cargo.toml | 3 +- .../examples/window-winit-demo.rs | 36 +- crates/window-winit/src/icon.png | Bin 0 -> 942 bytes crates/window-winit/src/lib.rs | 624 +++++++++--------- crates/window/src/lib.rs | 6 +- crates/window/src/listener.rs | 9 +- crates/window/src/window.rs | 121 ++-- examples/android/Cargo.toml | 2 +- examples/android/src/lib.rs | 22 +- run-wasm/Cargo.toml | 2 +- 23 files changed, 558 insertions(+), 516 deletions(-) create mode 100644 crates/window-winit/src/icon.png diff --git a/Cargo.toml b/Cargo.toml index b600b42..ff16504 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,29 +19,29 @@ edition = "2024" rust-version = "1.86" [workspace.dependencies] -glam = "0.25" +glam = "0.28" serde = "1.0" raw-window-handle = "0.6" slotmap = "1.0" anyhow = "1.0" thiserror = "1.0" tracing = "0.1" -bitflags = "2.5" +bitflags = "2.6" fnv = "1.0" radsort = "0.1" -bytemuck = "1.15" +bytemuck = "1.17" blink-alloc = "0.3" downcast-rs = "1.2" dynsequence = { version = "0.1.0-alpha.4" } -blocking = "1.5" +blocking = "1.6" threadpool = "1.8" backtrace = "0.3" -gametime = "0.3" +gametime = "0.5" atomic_refcell = "0.1" palette = { version = "0.7", default-features = false } -image = { version = "0.24", default-features = false } -encase = { version = "0.7", features = ["glam"], default-features = false } -encase_derive_impl = { version = "0.7" } +image = { version = "0.25", default-features = false } +encase = { version = "0.9", features = ["glam"], default-features = false } +encase_derive_impl = { version = "0.9" } crossbeam-utils = "0.8" crossbeam-queue = "0.3" tracing-subscriber = { version = "0.3", features = ["env-filter"] } @@ -52,7 +52,7 @@ wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" js-sys = "0.3" web-sys = "0.3" -ndk = "0.8" +ndk = "0.9" ndk-glue = "0.7" darling = "0.20" proc-macro2 = "1.0" diff --git a/crates/render-ash/examples/render-ash-demo.rs b/crates/render-ash/examples/render-ash-demo.rs index 573f502..bd2adcd 100644 --- a/crates/render-ash/examples/render-ash-demo.rs +++ b/crates/render-ash/examples/render-ash-demo.rs @@ -1,26 +1,24 @@ -use std::rc::Rc; +use std::error::Error; use pulz_ecs::prelude::*; use pulz_render::camera::{Camera, RenderTarget}; use pulz_render_ash::AshRenderer; use pulz_render_pipeline_core::core_3d::CoreShadingModule; -use pulz_window::{WindowDescriptor, WindowId}; -use pulz_window_winit::{ - winit::{event_loop::EventLoop, window::Window}, - WinitWindowModule, WinitWindowSystem, -}; +use pulz_window::{WindowAttributes, WindowId, WindowModule}; +use pulz_window_winit::{winit::event_loop::EventLoop, Application}; use tracing::*; -fn init() -> (Resources, EventLoop<()>, Rc, WinitWindowSystem) { +fn init() -> Resources { info!("Initializing..."); let mut resources = Resources::new(); resources.install(CoreShadingModule); - let event_loop = EventLoop::new().unwrap(); + /* let (window_system, window_id, window) = WinitWindowModule::new(WindowDescriptor::default(), &event_loop) .unwrap() .install(&mut resources); + */ resources.install(AshRenderer::new().unwrap()); @@ -29,9 +27,12 @@ fn init() -> (Resources, EventLoop<()>, Rc, WinitWindowSystem) { // schedule.debug_dump_if_env(None).unwrap(); // resources.insert_again(schedule); + let windows = resources.install(WindowModule); + let window_id = windows.create(WindowAttributes::new()); + setup_demo_scene(&mut resources, window_id); - (resources, event_loop, window, window_system) + resources } fn setup_demo_scene(resources: &mut Resources, window: WindowId) { @@ -44,7 +45,7 @@ fn setup_demo_scene(resources: &mut Resources, window: WindowId) { } #[cfg(not(target_arch = "wasm32"))] -fn main() { +fn main() -> Result<(), Box> { use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter}; let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); tracing_subscriber::fmt() @@ -52,13 +53,15 @@ fn main() { .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE) .init(); - let (mut resources, event_loop, _window, window_system) = init(); - - window_system.run(&mut resources, event_loop).unwrap(); + let event_loop = EventLoop::new().unwrap(); + let resources = init(); + let mut app = Application::new(resources); + event_loop.run_app(&mut app).map_err(Into::into) } #[cfg(target_arch = "wasm32")] fn main() { + use pulz_window_winit::winit::event_loop; use wasm_bindgen::prelude::*; use winit::platform::web::WindowExtWebSys; @@ -66,8 +69,11 @@ fn main() { tracing_log::LogTracer::init().expect("unable to create log-tracer"); tracing_wasm::set_as_global_default(); - let (resources, event_loop, window, window_system) = init(); + let event_loop = EventLoop::new().unwrap(); + let resources = init(); + let app = Application::new(resources); + /* let canvas = window.canvas(); canvas.style().set_css_text("background-color: teal;"); web_sys::window() @@ -75,6 +81,7 @@ fn main() { .and_then(|doc| doc.body()) .and_then(|body| body.append_child(&web_sys::Element::from(canvas)).ok()) .expect("couldn't append canvas to document body"); + */ - window_system.spawn(resources, event_loop); + event_loop.spawn_app(app); } diff --git a/crates/render-ash/src/debug_utils.rs b/crates/render-ash/src/debug_utils.rs index d80a905..07d9cbf 100644 --- a/crates/render-ash/src/debug_utils.rs +++ b/crates/render-ash/src/debug_utils.rs @@ -6,7 +6,7 @@ use std::{ use ash::vk::{self, Handle}; use tracing::{debug, error, info, warn}; -pub const EXT_NAME: &'static CStr = ash::ext::debug_utils::NAME; +pub const EXT_NAME: &CStr = ash::ext::debug_utils::NAME; unsafe fn c_str_from_ptr<'a>(str_ptr: *const c_char) -> &'a CStr { if str_ptr.is_null() { diff --git a/crates/render-ash/src/device.rs b/crates/render-ash/src/device.rs index 4ebd578..89ccdc5 100644 --- a/crates/render-ash/src/device.rs +++ b/crates/render-ash/src/device.rs @@ -4,9 +4,7 @@ use ash::vk; use pulz_render::graph::pass::PipelineBindPoint; use tracing::{debug, info, warn}; -use crate::{ - debug_utils, instance::AshInstance, AshRendererFlags, Error, ErrorNoExtension, Result, -}; +use crate::{debug_utils, instance::AshInstance, Error, ErrorNoExtension, Result}; pub struct AshDevice { device_raw: ash::Device, diff --git a/crates/render-ash/src/instance.rs b/crates/render-ash/src/instance.rs index 850bfdb..67d6585 100644 --- a/crates/render-ash/src/instance.rs +++ b/crates/render-ash/src/instance.rs @@ -5,7 +5,11 @@ use tracing::{debug, warn}; use crate::{debug_utils, AshRendererFlags, ErrorNoExtension, Result}; -pub const ENGINE_NAME: &[u8] = concat!(env!("CARGO_PKG_NAME"), "\0").as_bytes(); +pub const ENGINE_NAME: &CStr = + match CStr::from_bytes_with_nul(concat!(env!("CARGO_PKG_NAME"), "\0").as_bytes()) { + Ok(s) => s, + Err(_) => panic!("invalid CStr"), + }; pub const ENGINE_VERSION: u32 = parse_version(env!("CARGO_PKG_VERSION")); pub const VK_API_VERSION: u32 = vk::API_VERSION_1_1; @@ -152,16 +156,14 @@ fn create_instance<'a>( } fn _create_instance(entry: &ash::Entry, extensions_ptr: &[*const c_char]) -> Result { - let engine_name = unsafe { CStr::from_bytes_with_nul_unchecked(ENGINE_NAME) }; - let instance = unsafe { entry.create_instance( &vk::InstanceCreateInfo::default() .application_info( &vk::ApplicationInfo::default() - .application_name(engine_name) + .application_name(ENGINE_NAME) .application_version(ENGINE_VERSION) - .engine_name(engine_name) + .engine_name(ENGINE_NAME) .engine_version(ENGINE_VERSION) .api_version(VK_API_VERSION), ) diff --git a/crates/render-ash/src/lib.rs b/crates/render-ash/src/lib.rs index ce7b767..d82bcfc 100644 --- a/crates/render-ash/src/lib.rs +++ b/crates/render-ash/src/lib.rs @@ -25,7 +25,7 @@ #![doc(html_no_source)] #![doc = include_str!("../README.md")] -use std::{backtrace::Backtrace, ffi::CStr, rc::Rc, sync::Arc}; +use std::{backtrace::Backtrace, ffi::CStr, sync::Arc}; use ash::vk; use bitflags::bitflags; @@ -52,7 +52,7 @@ mod shader; mod swapchain; use pulz_window::{ - listener::WindowSystemListener, HasWindowAndDisplayHandle, Window, WindowId, Windows, + listener::WindowSystemListener, DisplayHandle, Window, WindowHandle, WindowId, Windows, WindowsMirror, }; @@ -474,26 +474,29 @@ impl AshRenderer { Ok(renderer) } - fn init_window( + /// UNSAFE: needs to ensure, window is kept alive while surface/swapchain is alive + unsafe fn init_window( &mut self, window_id: WindowId, - window_descriptor: &Window, - window: Rc, + window: &Window, + display_handle: DisplayHandle<'_>, + window_handle: WindowHandle<'_>, ) -> Result<&mut AshRendererFull> { if let AshRendererInner::Full(renderer) = &mut self.0 { let device = renderer.device.clone(); // SAVETY: window is kept alive - let surface = unsafe { device.instance().new_surface(&*window)? }; - renderer.init_swapchain(window_id, window_descriptor, window, surface)?; - } else { - let AshRendererInner::Early(instance) = &self.0 else { - unreachable!() + let surface = unsafe { + device + .instance() + .new_surface(display_handle, window_handle)? }; + renderer.init_swapchain(window_id, window, surface)?; + } else if let AshRendererInner::Early(instance) = &self.0 { // SAVETY: window is kept alive - let surface = unsafe { instance.new_surface(&*window)? }; + let surface = unsafe { instance.new_surface(display_handle, window_handle)? }; let device = instance.new_device(surface.raw())?; let mut renderer = AshRendererFull::from_device(device)?; - renderer.init_swapchain(window_id, window_descriptor, window, surface)?; + renderer.init_swapchain(window_id, window, surface)?; self.0 = AshRendererInner::Full(renderer); } let AshRendererInner::Full(renderer) = &mut self.0 else { @@ -515,10 +518,15 @@ impl WindowSystemListener for AshRenderer { fn on_created( &mut self, window_id: WindowId, - window_desc: &Window, - window: Rc, + window: &Window, + display_handle: DisplayHandle<'_>, + window_handle: WindowHandle<'_>, ) { - self.init_window(window_id, window_desc, window).unwrap(); + // SAVETY: surface/swapchain is desproyed on_suspenden/on_close + unsafe { + self.init_window(window_id, window, display_handle, window_handle) + .unwrap(); + } } fn on_resumed(&mut self) { self.init().unwrap(); diff --git a/crates/render-ash/src/swapchain.rs b/crates/render-ash/src/swapchain.rs index 646fe85..78f2cc3 100644 --- a/crates/render-ash/src/swapchain.rs +++ b/crates/render-ash/src/swapchain.rs @@ -1,11 +1,9 @@ -use std::rc::Rc; - use ash::vk; use pulz_render::{ math::{uvec2, USize2}, texture::{Texture, TextureFormat}, }; -use pulz_window::{HasWindowAndDisplayHandle, Size2, Window, WindowDescriptor, WindowId, Windows}; +use pulz_window::{DisplayHandle, Size2, Window, WindowHandle, WindowId, Windows}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; use tracing::{debug, info}; @@ -233,7 +231,8 @@ impl AshInstance { /// SAFETY: display and window handles must be valid for the complete lifetime of surface pub(crate) unsafe fn new_surface( &self, - window: &dyn HasWindowAndDisplayHandle, + display_handle: DisplayHandle<'_>, + window_handle: WindowHandle<'_>, ) -> Result> { fn map_handle_error(e: raw_window_handle::HandleError) -> Error { use raw_window_handle::HandleError; @@ -242,8 +241,8 @@ impl AshInstance { _ => Error::UnsupportedWindowSystem, } } - let raw_display_handle = window.display_handle().map_err(map_handle_error)?.as_raw(); - let raw_window_handle = window.window_handle().map_err(map_handle_error)?.as_raw(); + let raw_display_handle = display_handle.as_raw(); + let raw_window_handle = window_handle.as_raw(); let surface_raw = self.create_surface_raw(raw_display_handle, raw_window_handle)?; Ok(Guard::new(self, surface_raw)) } @@ -373,11 +372,10 @@ pub struct AshSurfaceSwapchain { image_views: Vec, textures: Vec, acquired_image: u32, - window: Rc, // for keeping ownership } impl AshSurfaceSwapchain { - fn window_swapchain_config(window: &WindowDescriptor) -> (u32, vk::PresentModeKHR, USize2) { + fn window_swapchain_config(window: &Window) -> (u32, vk::PresentModeKHR, USize2) { let (image_count, present_mode) = if window.vsync { (3, vk::PresentModeKHR::MAILBOX) } else { @@ -386,10 +384,7 @@ impl AshSurfaceSwapchain { (image_count, present_mode, window.size) } - fn new_unconfigured( - window: Rc, - surface_raw: vk::SurfaceKHR, - ) -> Self { + fn new_unconfigured(surface_raw: vk::SurfaceKHR) -> Self { Self { surface_raw, swapchain_raw: vk::SwapchainKHR::null(), @@ -403,7 +398,6 @@ impl AshSurfaceSwapchain { image_views: Vec::new(), textures: Vec::new(), acquired_image: !0, - window, } } @@ -483,7 +477,7 @@ impl AshSurfaceSwapchain { Ok(()) } - fn configure_with(&mut self, res: &mut AshResources, window: &WindowDescriptor) -> Result<()> { + fn configure_with(&mut self, res: &mut AshResources, window: &Window) -> Result<()> { let (suggested_image_count, suggested_present_mode, suggessted_size) = Self::window_swapchain_config(window); self.image_count = suggested_image_count; @@ -690,19 +684,18 @@ impl AshRendererFull { pub(crate) fn init_swapchain( &mut self, window_id: WindowId, - window_descriptor: &Window, - window: Rc, + window: &Window, surface: Guard<'_, vk::SurfaceKHR>, ) -> Result<&mut AshSurfaceSwapchain> { assert!(self .surfaces .insert( window_id, - AshSurfaceSwapchain::new_unconfigured(window, surface.take()) + AshSurfaceSwapchain::new_unconfigured(surface.take()) ) .is_none()); let swapchain = self.surfaces.get_mut(window_id).unwrap(); - swapchain.configure_with(&mut self.res, window_descriptor)?; + swapchain.configure_with(&mut self.res, window)?; Ok(swapchain) } diff --git a/crates/render-wgpu/Cargo.toml b/crates/render-wgpu/Cargo.toml index e6324d3..fe86cb2 100644 --- a/crates/render-wgpu/Cargo.toml +++ b/crates/render-wgpu/Cargo.toml @@ -15,7 +15,7 @@ pulz-render = { path = "../render" } thiserror = { workspace = true } tracing = { workspace = true } slotmap = { workspace = true } -wgpu = "0.19" +wgpu = "22.1" raw-window-handle = { workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] @@ -23,7 +23,7 @@ pollster = "0.3" [dev-dependencies] anyhow = { workspace = true } -naga = "0.19" +naga = "22.1" pulz-window-winit = { path = "../window-winit" } pulz-render-pipeline-core = { path = "../render-pipeline-core" } @@ -31,7 +31,7 @@ pulz-render-pipeline-core = { path = "../render-pipeline-core" } tracing-subscriber = { workspace = true } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wgpu = { version = "0.19" , features = ["webgl"] } +wgpu = { version = "22.1" , features = ["webgl"] } tracing-wasm = { workspace = true } tracing-log = { workspace = true } console_error_panic_hook = { workspace = true } diff --git a/crates/render-wgpu/examples/render-wgpu-demo.rs b/crates/render-wgpu/examples/render-wgpu-demo.rs index e480c52..b5193c5 100644 --- a/crates/render-wgpu/examples/render-wgpu-demo.rs +++ b/crates/render-wgpu/examples/render-wgpu-demo.rs @@ -1,27 +1,18 @@ -use std::rc::Rc; +use std::error::Error; use pulz_ecs::prelude::*; use pulz_render::camera::{Camera, RenderTarget}; use pulz_render_pipeline_core::core_3d::CoreShadingModule; use pulz_render_wgpu::WgpuRenderer; -use pulz_window::{WindowDescriptor, WindowId}; -use pulz_window_winit::{ - winit::{event_loop::EventLoop, window::Window}, - WinitWindowModule, WinitWindowSystem, -}; +use pulz_window::{WindowAttributes, WindowId, WindowModule}; +use pulz_window_winit::{winit::event_loop::EventLoop, Application}; use tracing::*; -async fn init() -> (Resources, EventLoop<()>, Rc, WinitWindowSystem) { +async fn init() -> Resources { info!("Initializing..."); let mut resources = Resources::new(); resources.install(CoreShadingModule); - let event_loop = EventLoop::new().unwrap(); - let (window_system, window_id, window) = - WinitWindowModule::new(WindowDescriptor::default(), &event_loop) - .unwrap() - .install(&mut resources); - WgpuRenderer::new().await.unwrap().install(&mut resources); // let mut schedule = resources.remove::().unwrap(); @@ -29,9 +20,12 @@ async fn init() -> (Resources, EventLoop<()>, Rc, WinitWindowSystem) { // schedule.debug_dump_if_env(None).unwrap(); // resources.insert_again(schedule); + let windows = resources.install(WindowModule); + let window_id = windows.create(WindowAttributes::new()); + setup_demo_scene(&mut resources, window_id); - (resources, event_loop, window, window_system) + resources } fn setup_demo_scene(resources: &mut Resources, window: WindowId) { @@ -44,7 +38,7 @@ fn setup_demo_scene(resources: &mut Resources, window: WindowId) { } #[cfg(not(target_arch = "wasm32"))] -fn main() { +fn main() -> Result<(), Box> { // todo: run blocking! use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter}; let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); @@ -54,9 +48,10 @@ fn main() { .init(); pollster::block_on(async move { - let (mut resources, event_loop, _window, window_system) = init().await; - - window_system.run(&mut resources, event_loop).unwrap(); + let event_loop = EventLoop::new().unwrap(); + let resources = init().await; + let mut app = Application::new(resources); + event_loop.run_app(&mut app).map_err(Into::into) }) } @@ -70,8 +65,11 @@ fn main() { tracing_wasm::set_as_global_default(); wasm_bindgen_futures::spawn_local(async move { - let (resources, event_loop, window, window_system) = init().await; + let event_loop = EventLoop::new().unwrap(); + let resources = init().await; + let app = Application::new(resources); + /* let canvas = window.canvas(); canvas.style().set_css_text("background-color: teal;"); web_sys::window() @@ -79,7 +77,7 @@ fn main() { .and_then(|doc| doc.body()) .and_then(|body| body.append_child(&web_sys::Element::from(canvas)).ok()) .expect("couldn't append canvas to document body"); - - window_system.spawn(resources, event_loop); + */ + event_loop.spawn_app(app); }) } diff --git a/crates/render-wgpu/src/convert.rs b/crates/render-wgpu/src/convert.rs index a39eb58..0add9a8 100644 --- a/crates/render-wgpu/src/convert.rs +++ b/crates/render-wgpu/src/convert.rs @@ -354,6 +354,8 @@ pub fn convert_compute_pipeline_descriptor<'l, 'r: 'l>( layout, module, entry_point: desc.entry_point, + compilation_options: Default::default(), + cache: None, }) } @@ -401,6 +403,7 @@ pub fn convert_graphics_pipeline_descriptor<'l, 'r: 'l>( }, fragment, multiview: None, + cache: None, }) } @@ -442,6 +445,7 @@ fn convert_vertex_state<'l, 'r: 'l>( module, entry_point: state.entry_point, buffers: buffers_tmp, + compilation_options: Default::default(), }) } @@ -464,6 +468,7 @@ fn convert_fragment_state<'l, 'r: 'l>( module, entry_point: state.entry_point, targets: targets_tmp, + compilation_options: Default::default(), }) } diff --git a/crates/render-wgpu/src/lib.rs b/crates/render-wgpu/src/lib.rs index c22a84d..6287cf3 100644 --- a/crates/render-wgpu/src/lib.rs +++ b/crates/render-wgpu/src/lib.rs @@ -25,20 +25,19 @@ #![doc(html_no_source)] #![doc = include_str!("../README.md")] -use std::rc::Rc; - use convert::ConversionError; use graph::WgpuRenderGraph; use pulz_ecs::prelude::*; use pulz_render::{draw::DrawPhases, graph::RenderGraph, RenderModule, RenderSystemPhase}; use pulz_window::{ - listener::WindowSystemListener, HasWindowAndDisplayHandle, Window, WindowId, Windows, + listener::WindowSystemListener, DisplayHandle, Window, WindowHandle, WindowId, Windows, WindowsMirror, }; use resources::WgpuResources; use surface::Surface; use thiserror::Error; use tracing::info; +use wgpu::MemoryHints; mod backend; mod convert; @@ -101,6 +100,7 @@ impl WgpuRendererFull { // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain. required_limits: wgpu::Limits::downlevel_defaults() .using_resolution(adapter.limits()), + memory_hints: MemoryHints::Performance, }, trace_dir.ok().as_ref().map(std::path::Path::new), ) @@ -218,15 +218,22 @@ impl WgpuRenderer { Ok(Self(WgpuRendererInner::Early { instance })) } - fn init_window( + /// UNSAFE: needs to ensure window is alive while surface is alive + unsafe fn init_window( &mut self, window_id: WindowId, window_descriptor: &Window, - window: Rc, + display_handle: DisplayHandle<'_>, + window_handle: WindowHandle<'_>, ) -> Result<&mut WgpuRendererFull> { if let WgpuRendererInner::Full(renderer) = &mut self.0 { renderer.surfaces.remove(window_id); // replaces old surface - let surface = Surface::create(&renderer.instance, window_descriptor, window)?; + let surface = Surface::create( + &renderer.instance, + window_descriptor, + display_handle, + window_handle, + )?; renderer.surfaces.insert(window_id, surface); } else { // Delayed initialization @@ -237,7 +244,8 @@ impl WgpuRenderer { else { panic!("unexpected state"); }; - let surface = Surface::create(&instance, window_descriptor, window)?; + let surface = + Surface::create(&instance, window_descriptor, display_handle, window_handle)?; let mut renderer = pollster::block_on(async { let adapter = wgpu::util::initialize_adapter_from_env_or_default( &instance, @@ -293,9 +301,14 @@ impl WindowSystemListener for WgpuRenderer { &mut self, window_id: WindowId, window_desc: &Window, - window: Rc, + display_handle: DisplayHandle<'_>, + window_handle: WindowHandle<'_>, ) { - self.init_window(window_id, window_desc, window).unwrap(); + // SAFETY: surface destroyed on_closed/on_suspended + unsafe { + self.init_window(window_id, window_desc, display_handle, window_handle) + .unwrap(); + } } fn on_resumed(&mut self) { self.init().unwrap(); diff --git a/crates/render-wgpu/src/surface.rs b/crates/render-wgpu/src/surface.rs index 5e5af3e..697dae5 100644 --- a/crates/render-wgpu/src/surface.rs +++ b/crates/render-wgpu/src/surface.rs @@ -1,9 +1,6 @@ -use std::{ - ops::{Deref, DerefMut}, - rc::Rc, -}; +use std::ops::{Deref, DerefMut}; -use pulz_window::{HasWindowAndDisplayHandle, Size2, Window}; +use pulz_window::{DisplayHandle, Size2, Window, WindowHandle}; use tracing::info; use crate::{Error, Result}; @@ -13,14 +10,15 @@ pub struct Surface { size: Size2, vsync: bool, format: wgpu::TextureFormat, - window: Rc, } impl Surface { - pub fn create( + /// UNSAFE: needs to ensure, whndow is alive while surface is alive + pub unsafe fn create( instance: &wgpu::Instance, - window_descriptor: &Window, - window: Rc, + window: &Window, + display_handle: DisplayHandle<'_>, + window_handle: WindowHandle<'_>, ) -> Result { fn map_handle_error(e: raw_window_handle::HandleError) -> Error { use raw_window_handle::HandleError; @@ -29,8 +27,8 @@ impl Surface { _ => Error::UnsupportedWindowSystem, } } - let raw_display_handle = window.display_handle().map_err(map_handle_error)?.as_raw(); - let raw_window_handle = window.window_handle().map_err(map_handle_error)?.as_raw(); + let raw_display_handle = display_handle.as_raw(); + let raw_window_handle = window_handle.as_raw(); let surface = unsafe { instance.create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle { raw_display_handle, @@ -39,10 +37,9 @@ impl Surface { }; Ok(Self { surface, - size: window_descriptor.size, - vsync: window_descriptor.vsync, + size: window.size, + vsync: window.vsync, format: wgpu::TextureFormat::Rgba8UnormSrgb, - window, }) } diff --git a/crates/render/src/texture/image.rs b/crates/render/src/texture/image.rs index bb7fec0..4f19662 100644 --- a/crates/render/src/texture/image.rs +++ b/crates/render/src/texture/image.rs @@ -43,7 +43,7 @@ impl Image { P::Subpixel: bytemuck::Pod, { let (width, height) = image.dimensions(); - let bytes_per_pixel = std::mem::size_of::

(); + let bytes_per_pixel = size_of::

(); let bytes_per_row = bytes_per_pixel as u32 * width; let data = image.into_raw(); let bytes: Vec = bytemuck::cast_vec(data); diff --git a/crates/window-winit/Cargo.toml b/crates/window-winit/Cargo.toml index 594143e..c26be03 100644 --- a/crates/window-winit/Cargo.toml +++ b/crates/window-winit/Cargo.toml @@ -21,7 +21,8 @@ pulz-input = { path = "../input" } fnv = { workspace = true } tracing = { workspace = true } -winit = { version = "0.29.15", default-features = false, features = ["rwh_06"] } +image = { workspace = true, features = ["png"] } +winit = { version = "0.30", default-features = false, features = ["rwh_06"] } raw-window-handle = { workspace = true, features = ["std"] } [target.'cfg(not(target_os = "unknown"))'.dev-dependencies] diff --git a/crates/window-winit/examples/window-winit-demo.rs b/crates/window-winit/examples/window-winit-demo.rs index d6fe5ba..57eed49 100644 --- a/crates/window-winit/examples/window-winit-demo.rs +++ b/crates/window-winit/examples/window-winit-demo.rs @@ -1,27 +1,26 @@ -use std::rc::Rc; +use std::error::Error; use pulz_ecs::prelude::*; -use pulz_window::WindowDescriptor; -use pulz_window_winit::{WinitWindowModule, WinitWindowSystem}; +use pulz_window_winit::Application; use tracing::*; -use winit::{event_loop::EventLoop, window::Window}; +use winit::event_loop::EventLoop; -fn init() -> (Resources, EventLoop<()>, Rc, WinitWindowSystem) { +fn init() -> Resources { info!("Initializing..."); - let mut resources = Resources::new(); - - let event_loop = EventLoop::new().unwrap(); - + Resources::new() + /* + let window_attributes = pulz_window_winit::default_window_attributes(&event_loop); let (window_system, _window_id, window) = WinitWindowModule::new(WindowDescriptor::default(), &event_loop) .unwrap() .install(&mut resources); - (resources, event_loop, window, window_system) + resources + */ } #[cfg(not(target_arch = "wasm32"))] -fn main() { +fn main() -> Result<(), Box> { use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter}; let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); tracing_subscriber::fmt() @@ -29,9 +28,10 @@ fn main() { .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE) .init(); - let (mut resources, event_loop, _window, window_system) = init(); - - window_system.run(&mut resources, event_loop).unwrap(); + let event_loop = EventLoop::new().unwrap(); + let resources = init(); + let mut app = Application::new(resources); + event_loop.run_app(&mut app).map_err(Into::into) } #[cfg(target_arch = "wasm32")] @@ -43,8 +43,11 @@ fn main() { tracing_log::LogTracer::init().expect("unable to create log-tracer"); tracing_wasm::set_as_global_default(); - let (resources, event_loop, window, window_system) = init(); + let event_loop = EventLoop::new().unwrap(); + let resources = init(); + let app = Application::new(resources); + /* let canvas = window.canvas(); canvas.style().set_css_text("background-color: teal;"); web_sys::window() @@ -52,6 +55,7 @@ fn main() { .and_then(|doc| doc.body()) .and_then(|body| body.append_child(&web_sys::Element::from(canvas)).ok()) .expect("couldn't append canvas to document body"); + */ - window_system.spawn(resources, event_loop); + event_loop.spawn_app(app); } diff --git a/crates/window-winit/src/icon.png b/crates/window-winit/src/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c45f9bdce0b6579763655de9fa4695bdbcb1399d GIT binary patch literal 942 zcmV;f15x~mP)A4FCWD8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H113O7X zK~z|Uy_U~wTvZguKi~I;W_~2vrl1I-;=+|;#hsM43ZnR9q0nr!NeV?#5DFcWsjdcW zo6&%R;166hGd4n-MM)`EXhB5W_y>p-YN^Q9pk0dM|@0s8D9>fWZN_0nZ8hbj97cR+y-~r?G|je*q*)`T=k$x*TvM zpa1Qg#>!g;1`ts%fMwuSU^R7@@oYYq{d{l$X*A8PqyvrW^dfMf2PnY)k@2IC4g_Gv z-QnnVXlZV09zbTN`T&qq^yWYSVBQi)eA1jNef;ltCSeg+>4RbCKmfLWqPthmG@1_= zB~+NGe5Uc)!u9!59XQ_`fCqhxnb2-I{bUizf8V&}W^`l5PVG$!lQSP+dMn4;_rQYC zf?+S-+{;CWPIvgj5Mw4`|IVqmgDT7Ko`a8wlg>Yi$435YZ&eWodmp~vGVYG@XBN6+y%OBklXls=E z)O+Qs#E)jwn$_v=`W?-@0Fhj<1P0$o_JbTNCe@v&vzocm(fO+VQ+oj>, - window_id_map: FnvHashMap, +struct WindowState { + window: WinitWindow, + id: WindowId, } -impl WinitWindows { - fn insert(&mut self, window_id: WindowId, winit_window: Rc) { - self.windows.insert(window_id, winit_window.clone()); - self.window_id_map.insert(winit_window.id(), window_id); - } - - #[inline] - pub fn len(&self) -> usize { - self.windows.len() - } - - #[inline] - pub fn is_empty(&self) -> bool { - self.windows.is_empty() - } - - #[inline] - pub fn get(&self, id: WindowId) -> Option<&WinitWindow> { - self.windows.get(id).map(Deref::deref) - } - - fn close(&mut self, window_id: WindowId) -> bool { - let Some(window) = self.windows.remove(window_id) else { - return false; - }; - self.window_id_map.remove(&window.id()); - window.set_visible(false); - true - } +struct WinitWindowFactory { + icon: Icon, } - -impl std::ops::Index for WinitWindows { - type Output = WinitWindow; - #[inline] - fn index(&self, id: WindowId) -> &Self::Output { - &self.windows[id] - } +struct WinitWindowMap { + ids: WindowsMirror, + state: HashMap, } -fn update_window_descriptor( - window_descriptor: &mut WindowDescriptor, - winit_window: &winit::window::Window, -) { - window_descriptor.scale_factor = winit_window.scale_factor(); - let phys_size: [u32; 2] = winit_window.inner_size().into(); - window_descriptor.size = phys_size.into(); +pub struct Application { + resources: Resources, + window_factory: WinitWindowFactory, + window_map: WinitWindowMap, + active: bool, + schedule: RemovedResource, + windows_resource_id: ResourceId, } -fn descriptor_from_window(winit_window: &WinitWindow) -> WindowDescriptor { - let mut descriptor = WindowDescriptor::new(); - descriptor.title = winit_window.title().into(); - update_window_descriptor(&mut descriptor, winit_window); - descriptor -} +impl WinitWindowFactory { + pub const DEFAULT_TITLE: &'static str = + concat!(env!("CARGO_PKG_NAME"), ": ", env!("CARGO_PKG_VERSION")); + fn create_winit_window( + &mut self, + event_loop: &ActiveEventLoop, + mut attributes: WinitWindowAttributes, + ) -> Result { + if attributes.title.is_empty() { + attributes.title = Self::DEFAULT_TITLE.to_owned(); + } + if attributes.window_icon.is_none() { + attributes.window_icon = Some(self.icon.clone()); + } -fn builder_for_descriptor(descriptor: &WindowDescriptor) -> winit::window::WindowBuilder { - let mut builder = winit::window::WindowBuilder::new().with_title(descriptor.title.to_owned()); + #[cfg(all( + any(feature = "x11", feature = "wayland"), + unix, + not(target_vendor = "apple"), + not(target_os = "android"), + not(target_os = "emscripten"), + not(target_os = "redox"), + ))] + { + use winit::platform::startup_notify::{ + EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, + }; + if let Some(token) = event_loop.read_token_from_env() { + winit::platform::startup_notify::reset_activation_token_env(); + info!({ ?token }, "Using token to activate a window"); + attributes = attributes.with_activation_token(token); + } + } - #[cfg(target_os = "windows")] - { - use winit::platform::windows::WindowBuilderExtWindows; - builder = builder.with_drag_and_drop(false); + event_loop.create_window(attributes) } +} - if descriptor.size != Size2::ZERO { - builder = builder.with_inner_size(PhysicalSize::new(descriptor.size.x, descriptor.size.y)); +impl WinitWindowMap { + fn insert_winit_window(&mut self, id: WindowId, winit_window: WinitWindow) { + let winit_window_id = winit_window.id(); + self.ids.insert(id, winit_window_id); + self.state.insert( + winit_window_id, + WindowState { + window: winit_window, + id, + }, + ); + info!({ ?id, ?winit_window_id }, "new window"); } - builder -} - -pub struct WinitWindowSystem { - windows_id: ResourceId, - winit_windows_id: ResourceId, - schedule_id: ResourceId, - active: bool, -} + fn contains_id(&self, id: WindowId) -> bool { + self.ids.contains_key(id) + } + fn get_mut_by_winit_id(&mut self, id: WinitWindowId) -> Option<&mut WindowState> { + self.state.get_mut(&id) + } -pub struct WinitWindowSystemMut<'l> { - windows: ResMut<'l, Windows>, - winit_windows: ResMut<'l, WinitWindows>, -} + fn is_empty(&self) -> bool { + self.state.is_empty() + } -pub struct WinitWindowModule { - descriptor: WindowDescriptor, - window: Rc, + fn remove_by_winit_id(&mut self, winit_window_id: WinitWindowId) -> Option { + if let Some(window_state) = self.state.remove(&winit_window_id) { + self.ids.remove(window_state.id); + Some(window_state) + } else { + None + } + } } -impl WinitWindowModule { - pub fn new( - mut descriptor: WindowDescriptor, - event_loop: &EventLoopWindowTarget, - ) -> Result { - let builder = builder_for_descriptor(&descriptor); - let window = Rc::new(builder.build(event_loop)?); - update_window_descriptor(&mut descriptor, &window); - Ok(Self { descriptor, window }) +impl Application { + pub fn new(mut resources: Resources) -> Self { + let windows_resource_id = resources.init::(); + let schedule = resources.remove::().expect("schedule"); + let icon = load_icon(include_bytes!("icon.png")); + Self { + resources, + window_factory: WinitWindowFactory { icon }, + window_map: WinitWindowMap { + ids: WindowsMirror::new(), + state: HashMap::new(), + }, + active: false, + schedule, + windows_resource_id, + } } - pub fn from_window(window: impl Into>) -> Self { - let window: Rc = window.into(); - let descriptor = descriptor_from_window(&window); - Self { descriptor, window } + pub fn into_resources(self) -> Resources { + let Self { + mut resources, + schedule, + .. + } = self; + resources.insert_again(schedule); + resources } -} -impl ModuleWithOutput for WinitWindowModule { - type Output<'l> = (WinitWindowSystem, WindowId, Rc); - fn install_resources(self, resources: &mut Resources) -> Self::Output<'_> { - let sys = WinitWindowSystem::install(resources); - let mut sys_mut = sys.as_mut(resources); - let Self { descriptor, window } = self; - let window_id = sys_mut.add_winit_window_with_descriptor(descriptor, window.clone()); - (sys, window_id, window) + #[inline] + pub fn resources(&self) -> &Resources { + &self.resources } -} -impl WinitWindowSystemMut<'_> { - pub fn add_window( - &mut self, - mut descriptor: WindowDescriptor, - event_loop: &EventLoopWindowTarget, - ) -> Result<(WindowId, Rc), OsError> { - let builder = builder_for_descriptor(&descriptor); - let window = Rc::new(builder.build(event_loop)?); - update_window_descriptor(&mut descriptor, &window); - let window_id = self.add_winit_window_with_descriptor(descriptor, window.clone()); - Ok((window_id, window)) + #[inline] + pub fn resources_mut(&mut self) -> &mut Resources { + &mut self.resources } - pub fn add_winit_window(&mut self, window: impl Into>) -> WindowId { - let window: Rc = window.into(); - let descriptor = descriptor_from_window(&window); - self.add_winit_window_with_descriptor(descriptor, window) - } + pub fn default_window_attributes() -> WinitWindowAttributes { + let attributes = WinitWindow::default_attributes().with_transparent(true); - fn add_winit_window_with_descriptor( - &mut self, - descriptor: WindowDescriptor, - window: Rc, - ) -> WindowId { - let window_id = self.windows.create(descriptor); - debug!( - "Added window {:?} with {:?}, {:?}", - window_id, - window.id(), - window.inner_size() - ); - self.winit_windows.insert(window_id, window); - window_id + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + { + use winit::platform::web::WindowAttributesExtWebSys; + attributes = attributes.with_append(true); + } + + #[cfg(target_os = "windows")] + { + use winit::platform::windows::WindowAttributesExtWindows; + attributes = attributes.with_drag_and_drop(false); + } + + attributes } - fn handle_window_event( - &mut self, - res: &Resources, - window_id: WinitWindowId, - event: WindowEvent, - ) { - if let Some(window_id) = self.winit_windows.window_id_map.get(&window_id).copied() { - if matches!(event, WindowEvent::Destroyed) { - debug!("Window {:?} destroyed", window_id); - self.close(res, window_id); - } else if let Some(window) = self.windows.get_mut(window_id) { - match event { - WindowEvent::CloseRequested => { - debug!("Window {:?} close requested", window_id); - window.close_requested = true; - } - WindowEvent::Resized(size) => { - let phys_size: [u32; 2] = size.into(); - window.size = phys_size.into(); - } - WindowEvent::ScaleFactorChanged { scale_factor, .. } => { - window.scale_factor = scale_factor; - } - _ => {} - } - } + fn winit_window_attributes_from_attributes( + attributes: WindowAttributes, + ) -> WinitWindowAttributes { + let mut winit_attributes = Self::default_window_attributes(); + if let Some(size) = attributes.size { + winit_attributes.inner_size = + Some(winit::dpi::PhysicalSize::new(size.x, size.y).into()); } + if !attributes.title.is_empty() { + winit_attributes.title = attributes.title.into_owned(); + } + winit_attributes } - fn handle_created(&mut self, res: &Resources, event_loop: &EventLoopWindowTarget) { - while let Some((window_id, window_descr)) = self.windows.pop_next_created_window() { - let winit_window = if let Some(w) = self.winit_windows.windows.get(window_id) { - Rc::clone(w) - } else { - let builder = builder_for_descriptor(window_descr); - let winit_window = - Rc::new(builder.build(event_loop).expect("unable to create window")); - update_window_descriptor(window_descr, &winit_window); - self.winit_windows.insert(window_id, winit_window.clone()); - winit_window - }; - res.foreach_meta_mut(move |l: &mut dyn WindowSystemListener| { - l.on_created(window_id, window_descr, winit_window.clone()); + pub fn create_window( + &mut self, + event_loop: &ActiveEventLoop, + winit_window_attributes: WinitWindowAttributes, + ) -> Result { + let winit_window = self + .window_factory + .create_winit_window(event_loop, winit_window_attributes)?; + let mut windows = self + .resources + .borrow_res_mut_id(self.windows_resource_id) + .expect("Windows"); + let (id, window) = windows.create_new(); + update_window_from_winit(window, &winit_window); + let display_handle = winit_window.display_handle().unwrap(); + let window_handle = winit_window.window_handle().unwrap(); + self.resources + .foreach_meta_mut(|l: &mut dyn WindowSystemListener| { + l.on_created(id, window, display_handle, window_handle) }); + self.window_map.insert_winit_window(id, winit_window); + Ok(id) + } + + fn sync_create_windows(&mut self, event_loop: &ActiveEventLoop) -> Result<(), OsError> { + let mut windows = self + .resources + .borrow_res_mut_id(self.windows_resource_id) + .expect("Windows"); + while let Some((id, window, window_attributes)) = windows.pop_next_window_to_create() { + if window.is_pending && !window.is_close_requested & &!self.window_map.contains_id(id) { + let winit_window_attributes = + Self::winit_window_attributes_from_attributes(window_attributes); + let winit_window = self + .window_factory + .create_winit_window(event_loop, winit_window_attributes)?; + update_window_from_winit(window, &winit_window); + let display_handle = winit_window.display_handle().unwrap(); + let window_handle = winit_window.window_handle().unwrap(); + self.resources + .foreach_meta_mut(|l: &mut dyn WindowSystemListener| { + l.on_created(id, window, display_handle, window_handle) + }); + self.window_map.insert_winit_window(id, winit_window); + } } + Ok(()) } - // close all windows where the close_requested flag is not cleared - fn handle_close(&mut self, res: &Resources) -> bool { + fn sync_close_windows(&mut self) -> bool { + let windows = self + .resources + .get_mut_id(self.windows_resource_id) + .expect("Windows"); let mut to_close = Vec::new(); - for (window_id, _) in self.winit_windows.windows.iter() { - match self.windows.get(window_id) { - None => to_close.push(window_id), - Some(w) if w.close_requested => to_close.push(window_id), - _ => {} + for (window_id, window_state) in self.window_map.state.iter() { + match windows.get(window_state.id) { + Some(w) if !w.is_close_requested => {} + _ => to_close.push(*window_id), } } if !to_close.is_empty() { debug!("Closing {} windows", to_close.len()); - for window_id in to_close { - self.close(res, window_id); + for winit_window_id in to_close { + self.close_window_by_winit_id(winit_window_id); } } - self.winit_windows.windows.is_empty() // all windows closed + self.window_map.is_empty() // all windows closed } - fn close(&mut self, res: &Resources, window_id: WindowId) -> bool { - if self.winit_windows.get(window_id).is_some() { - debug!("Window {:?} closing", window_id); - res.foreach_meta_mut(|l: &mut dyn WindowSystemListener| l.on_closed(window_id)); - self.windows.close(window_id); - self.winit_windows.close(window_id); + fn close_window_by_winit_id(&mut self, winit_window_id: WinitWindowId) -> bool { + if let Some(window_state) = self.window_map.remove_by_winit_id(winit_window_id) { + info!({id=?window_state.id, ?winit_window_id}, "Window closing"); + self.resources + .foreach_meta_mut(|l: &mut dyn WindowSystemListener| l.on_closed(window_state.id)); + let windows = self + .resources + .get_mut_id(self.windows_resource_id) + .expect("Windows"); + windows.close(window_state.id); true } else { false } } + + fn get_window_with_state_by_winit_id_mut( + &mut self, + winit_window_id: WinitWindowId, + ) -> Option<(&mut Window, &mut WindowState)> { + let window_state = self.window_map.get_mut_by_winit_id(winit_window_id)?; + let windows = self.resources.get_mut_id(self.windows_resource_id)?; + let window = windows.get_mut(window_state.id)?; + Some((window, window_state)) + } + + fn run_schedule(&mut self) { + self.schedule.run(&mut self.resources); + } } -impl WinitWindowSystem { - fn install(res: &mut Resources) -> Self { - let windows_id = res.init::(); - let winit_windows_id = res.init_unsend::(); - let schedule_id = res.init_unsend::(); - Self { - windows_id, - winit_windows_id, - schedule_id, - active: false, +fn update_window_from_winit(window: &mut Window, winit_window: &WinitWindow) { + window.scale_factor = winit_window.scale_factor(); + let phys_size: [u32; 2] = winit_window.inner_size().into(); + window.size = phys_size.into(); + window.is_pending = false; +} + +impl ApplicationHandler for Application { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + info!("resumed"); + if !self.active { + self.active = true; + self.sync_create_windows(event_loop).unwrap(); + self.resources + .foreach_meta_mut(|l: &mut dyn WindowSystemListener| l.on_resumed()); + } + event_loop.set_control_flow(ControlFlow::Poll); + } + + fn suspended(&mut self, event_loop: &ActiveEventLoop) { + info!("suspended"); + if self.active { + self.active = false; + if self.sync_close_windows() { + // all windows closed + event_loop.exit(); + } + self.resources + .foreach_meta_mut(|l: &mut dyn WindowSystemListener| l.on_suspended()); } + event_loop.set_control_flow(ControlFlow::Wait); } - fn as_mut<'l>(&self, res: &'l Resources) -> WinitWindowSystemMut<'l> { - WinitWindowSystemMut { - windows: res.borrow_res_mut_id(self.windows_id).unwrap(), - winit_windows: res.borrow_res_mut_id(self.winit_windows_id).unwrap(), + fn exiting(&mut self, _event_loop: &ActiveEventLoop) { + info!("event loop ended"); + if self.active { + self.active = false; + self.sync_close_windows(); + self.resources + .foreach_meta_mut(|l: &mut dyn WindowSystemListener| l.on_suspended()); } + self.resources.clear(); } - pub fn handle_event( + fn window_event( &mut self, - res: &mut Resources, - event: Event, - event_loop: &EventLoopWindowTarget, + _event_loop: &ActiveEventLoop, + winit_window_id: WinitWindowId, + event: WindowEvent, ) { match event { - Event::NewEvents(StartCause::Init) => { - info!("event loop started..."); + WindowEvent::Destroyed => { + self.close_window_by_winit_id(winit_window_id); } - Event::Resumed => { - info!("resumed"); - if !self.active { - self.active = true; - let mut s = self.as_mut(res); - s.handle_created(res, event_loop); - res.foreach_meta_mut(|l: &mut dyn WindowSystemListener| l.on_resumed()); - } - event_loop.set_control_flow(ControlFlow::Poll); + WindowEvent::CloseRequested => { + let Some((window, window_state)) = + self.get_window_with_state_by_winit_id_mut(winit_window_id) + else { + return; + }; + debug!({ id=?window_state.id, ?winit_window_id}, "close requested"); + window.is_close_requested = true; } - Event::WindowEvent { window_id, event } => { - self.as_mut(res).handle_window_event(res, window_id, event); + WindowEvent::Resized(size) => { + let Some((window, _window_state)) = + self.get_window_with_state_by_winit_id_mut(winit_window_id) + else { + return; + }; + let phys_size: [u32; 2] = size.into(); + window.size = phys_size.into(); } - Event::Suspended => { - info!("suspended"); - if self.active { - self.active = false; - res.foreach_meta_mut(|l: &mut dyn WindowSystemListener| l.on_suspended()); - } - event_loop.set_control_flow(ControlFlow::Wait); - } - Event::AboutToWait => { - if self.active { - self.as_mut(res).handle_created(res, event_loop); - let mut schedule = res.remove_id(self.schedule_id).unwrap(); - schedule.run(res); - res.insert_again(schedule); - if self.as_mut(res).handle_close(res) { - // all windows closed - event_loop.exit(); - } - } - } - Event::LoopExiting => { - info!("event loop ended"); - if self.active { - self.active = false; - res.foreach_meta_mut(|l: &mut dyn WindowSystemListener| l.on_suspended()); - } + WindowEvent::ScaleFactorChanged { scale_factor, .. } => { + let Some((window, _window_state)) = + self.get_window_with_state_by_winit_id_mut(winit_window_id) + else { + return; + }; + window.scale_factor = scale_factor; } _ => {} } } - pub fn event_handler<'a, T>( - &'a mut self, - res: &'a mut Resources, - ) -> impl FnMut(Event, &EventLoopWindowTarget) + 'a { - let event_loop_span = tracing::trace_span!("EventLoop"); - move |event, event_loop| { - let span = event_loop_span.enter(); - self.handle_event(res, event, event_loop); - drop(span); - } - } - - #[cfg(any(not(target_arch = "wasm32"), doc))] - pub fn run( - mut self, - resources: &mut Resources, - event_loop: EventLoop, - ) -> Result<(), EventLoopError> { - #[cfg(not(target_arch = "wasm32"))] - { - event_loop.run(self.event_handler(resources)) - } - #[cfg(target_arch = "wasm32")] - { - Ok(()) - } - } - - #[cfg(any( - target_os = "windows", - target_os = "macos", - target_os = "android", - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - doc - ))] - pub fn pump_events( - &mut self, - resources: &mut Resources, - event_loop: &mut EventLoop, - timeout: Option, - ) -> winit::platform::pump_events::PumpStatus { - use winit::platform::pump_events::EventLoopExtPumpEvents; - let result = event_loop.pump_events(timeout, self.event_handler(resources)); - result - } - - #[cfg(any(target_arch = "wasm32", doc))] - pub fn spawn(mut self, mut resources: Resources, event_loop: EventLoop) { - #[cfg(target_arch = "wasm32")] - { - use winit::platform::web::EventLoopExtWebSys; - event_loop.spawn(self.handler(&mut resources)) + fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { + if self.active { + self.run_schedule(); + self.sync_create_windows(event_loop).unwrap(); + if self.sync_close_windows() { + // all windows closed + event_loop.exit(); + } } } } -impl ModuleWithOutput for WinitWindowSystem { - type Output<'l> = Self; - - fn install_resources(self, resources: &mut Resources) -> Self { - Self::install(resources) - } +fn load_icon(bytes: &[u8]) -> Icon { + let (icon_rgba, icon_width, icon_height) = { + let image = image::load_from_memory(bytes).unwrap().into_rgba8(); + let (width, height) = image.dimensions(); + let rgba = image.into_raw(); + (rgba, width, height) + }; + Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon") } diff --git a/crates/window/src/lib.rs b/crates/window/src/lib.rs index f560105..3d6a292 100644 --- a/crates/window/src/lib.rs +++ b/crates/window/src/lib.rs @@ -32,10 +32,6 @@ mod window; pub type Point2 = glam::IVec2; pub type Size2 = glam::UVec2; -use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; +pub use raw_window_handle::{DisplayHandle, WindowHandle}; pub use crate::window::*; - -pub trait HasWindowAndDisplayHandle: HasWindowHandle + HasDisplayHandle {} - -impl HasWindowAndDisplayHandle for T where T: HasWindowHandle + HasDisplayHandle {} diff --git a/crates/window/src/listener.rs b/crates/window/src/listener.rs index 20f7e0a..bf69f2a 100644 --- a/crates/window/src/listener.rs +++ b/crates/window/src/listener.rs @@ -1,15 +1,14 @@ -use std::rc::Rc; - use pulz_ecs::impl_any_cast; -use crate::{HasWindowAndDisplayHandle, Window, WindowId}; +use crate::{DisplayHandle, Window, WindowHandle, WindowId}; pub trait WindowSystemListener: 'static { fn on_created( &mut self, _window_id: WindowId, - _window_desc: &Window, - _window: Rc, + _window_props: &Window, + _display_handle: DisplayHandle<'_>, + _window_handle: WindowHandle<'_>, ) { } fn on_closed(&mut self, _window_id: WindowId) {} diff --git a/crates/window/src/window.rs b/crates/window/src/window.rs index 98fc495..ce45dae 100644 --- a/crates/window/src/window.rs +++ b/crates/window/src/window.rs @@ -1,10 +1,6 @@ -use std::{ - borrow::Cow, - collections::VecDeque, - ops::{Deref, DerefMut}, -}; +use std::{borrow::Cow, collections::VecDeque}; -use pulz_ecs::Component; +use pulz_ecs::{module::ModuleWithOutput, Component}; use slotmap::{new_key_type, SlotMap}; use crate::Size2; @@ -19,55 +15,69 @@ pub type IterMut<'a, T = Window> = slotmap::basic::IterMut<'a, WindowId, T>; pub type WindowsMirror = slotmap::SecondaryMap; #[derive(Debug)] -pub struct WindowDescriptor { - pub size: Size2, - pub scale_factor: f64, +pub struct WindowAttributes { + pub size: Option, pub title: Cow<'static, str>, pub vsync: bool, } pub struct Window { - descriptor: WindowDescriptor, - pub close_requested: bool, + pub size: Size2, + pub scale_factor: f64, + pub vsync: bool, + pub is_pending: bool, + pub is_close_requested: bool, command_queue: VecDeque, } pub struct Windows { windows: SlotMap, - created: VecDeque, + created: VecDeque<(WindowId, WindowAttributes)>, } -impl WindowDescriptor { - pub const DEFAULT_TITLE: &'static str = - concat!(env!("CARGO_PKG_NAME"), ": ", env!("CARGO_PKG_VERSION")); - #[inline] - pub fn new() -> Self { +impl WindowAttributes { + pub const fn new() -> Self { Self { - size: Size2::ZERO, - scale_factor: 1.0, - title: Cow::Borrowed(Self::DEFAULT_TITLE), + size: None, + title: Cow::Borrowed(""), vsync: true, } } } -impl Default for WindowDescriptor { +impl Default for WindowAttributes { #[inline] fn default() -> Self { Self::new() } } -impl Deref for Window { - type Target = WindowDescriptor; - fn deref(&self) -> &Self::Target { - &self.descriptor +impl Window { + #[inline] + pub const fn new() -> Self { + Self { + size: Size2::ZERO, + scale_factor: 1.0, + vsync: true, + is_pending: true, + is_close_requested: false, + command_queue: VecDeque::new(), + } + } + + pub fn from_attributes(attributes: &WindowAttributes) -> Self { + Self { + size: attributes.size.unwrap_or(Size2::ZERO), + vsync: attributes.vsync, + ..Self::new() + } } } -impl DerefMut for Window { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.descriptor +impl Default for Window { + #[inline] + fn default() -> Self { + Self::new() } } @@ -80,18 +90,34 @@ impl Windows { } #[inline] - pub fn create(&mut self, descriptor: WindowDescriptor) -> WindowId { - let window = Window { - descriptor, - close_requested: false, - command_queue: VecDeque::new(), - }; - + pub fn create(&mut self, attributes: WindowAttributes) -> WindowId { + let window = Window::from_attributes(&attributes); let id = self.windows.insert(window); - self.created.push_back(id); + self.created.push_back((id, attributes)); id } + #[doc(hidden)] + pub fn create_new(&mut self) -> (WindowId, &mut Window) { + let id = self.windows.insert(Window::new()); + let window = self.get_mut(id).unwrap(); + (id, window) + } + + #[doc(hidden)] + pub fn pop_next_window_to_create( + &mut self, + ) -> Option<(WindowId, &mut Window, WindowAttributes)> { + let (id, attributes) = loop { + let (id, attributes) = self.created.pop_front()?; + if self.windows.contains_key(id) { + break (id, attributes); + } + }; + let window = self.windows.get_mut(id).unwrap(); + Some((id, window, attributes)) + } + #[inline] pub fn len(&self) -> usize { self.windows.len() @@ -117,16 +143,6 @@ impl Windows { self.windows.remove(id).is_some() } - pub fn pop_next_created_window(&mut self) -> Option<(WindowId, &mut Window)> { - let id = loop { - let id = self.created.pop_front()?; - if self.windows.contains_key(id) { - break id; - } - }; - Some((id, &mut self.windows[id])) - } - #[inline] pub fn iter(&self) -> Iter<'_> { self.windows.iter() @@ -164,5 +180,18 @@ impl std::ops::IndexMut for Windows { #[non_exhaustive] pub enum WindowCommand { SetTitle(Cow<'static, String>), + SetVisible(bool), + SetFullscreen(bool), Close, } + +pub struct WindowModule; + +impl ModuleWithOutput for WindowModule { + type Output<'l> = &'l mut Windows; + + fn install_resources(self, resources: &mut pulz_ecs::prelude::Resources) -> Self::Output<'_> { + let id = resources.init::(); + resources.get_mut_id(id).unwrap() + } +} diff --git a/examples/android/Cargo.toml b/examples/android/Cargo.toml index dc3cfba..db40dc1 100644 --- a/examples/android/Cargo.toml +++ b/examples/android/Cargo.toml @@ -20,5 +20,5 @@ pulz-window = { path = "../../crates/window" } pulz-window-winit = { path = "../../crates/window-winit", features = ["android-game-activity"]} log = "0.4" -android_logger = "0.13" +android_logger = "0.14" tracing = { workspace = true, features = ["log"] } diff --git a/examples/android/src/lib.rs b/examples/android/src/lib.rs index 615cd4d..b5ee7e9 100644 --- a/examples/android/src/lib.rs +++ b/examples/android/src/lib.rs @@ -1,5 +1,3 @@ -use std::rc::Rc; - use log::info; #[cfg(target_os = "android")] use platform::android::activity::AndroidApp; @@ -7,24 +5,20 @@ use pulz_ecs::prelude::*; use pulz_render::camera::{Camera, RenderTarget}; use pulz_render_ash::AshRenderer; use pulz_render_pipeline_core::core_3d::CoreShadingModule; -use pulz_window::{WindowDescriptor, WindowId}; -use pulz_window_winit::{winit, WinitWindowModule, WinitWindowSystem}; -use winit::{event_loop::EventLoopWindowTarget, window::Window}; +use pulz_window::{WindowAttributes, WindowId, WindowModule}; -fn init(event_loop: &EventLoopWindowTarget<()>) -> (Resources, Rc, WinitWindowSystem) { +fn init() -> Resources { info!("Initializing..."); let mut resources = Resources::new(); resources.install(CoreShadingModule); resources.install(AshRenderer::new().unwrap()); - let (window_system, window_id, window) = - WinitWindowModule::new(WindowDescriptor::default(), event_loop) - .unwrap() - .install(&mut resources); + let windows = resources.install(WindowModule); + let window_id = windows.create(WindowAttributes::new()); setup_demo_scene(&mut resources, window_id); - (resources, window, window_system) + resources } fn setup_demo_scene(resources: &mut Resources, window: WindowId) { @@ -39,6 +33,7 @@ fn setup_demo_scene(resources: &mut Resources, window: WindowId) { #[cfg(target_os = "android")] #[no_mangle] pub fn android_main(app: AndroidApp) { + use pulz_window_winit::Application; use winit::platform::android::EventLoopBuilderExtAndroid; // #[cfg(debug_assertions)] // std::env::set_var("RUST_BACKTRACE", "1"); @@ -47,6 +42,7 @@ pub fn android_main(app: AndroidApp) { ); let event_loop = EventLoopBuilder::new().with_android_app(app).build(); - let (resources, _window, window_system) = init(&event_loop); - window_system.run(resources, event_loop); + let resources = init(); + let mut app = Application::new(resources); + event_loop.run_app(&mut app).unwrap(); } diff --git a/run-wasm/Cargo.toml b/run-wasm/Cargo.toml index e8fc52f..433e857 100644 --- a/run-wasm/Cargo.toml +++ b/run-wasm/Cargo.toml @@ -8,4 +8,4 @@ edition.workspace = true repository.workspace = true [dependencies] -cargo-run-wasm = "0.3.2" +cargo-run-wasm = "0.4.0" From cad313a9cb3ff27c526b9187b0be5864dd6a46ab Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sun, 24 Nov 2024 13:54:38 +0100 Subject: [PATCH 35/36] fix(deps): updates --- Cargo.toml | 8 ++--- crates/render-ash/Cargo.toml | 2 +- crates/render-wgpu/Cargo.toml | 8 ++--- crates/render-wgpu/src/convert.rs | 8 ++--- justfile | 55 +++++++++++++++++++++++++++++-- 5 files changed, 65 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ff16504..024bcef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ edition = "2024" rust-version = "1.86" [workspace.dependencies] -glam = "0.28" +glam = "0.29" serde = "1.0" raw-window-handle = "0.6" slotmap = "1.0" @@ -29,7 +29,7 @@ tracing = "0.1" bitflags = "2.6" fnv = "1.0" radsort = "0.1" -bytemuck = "1.17" +bytemuck = "1.20" blink-alloc = "0.3" downcast-rs = "1.2" dynsequence = { version = "0.1.0-alpha.4" } @@ -40,8 +40,8 @@ gametime = "0.5" atomic_refcell = "0.1" palette = { version = "0.7", default-features = false } image = { version = "0.25", default-features = false } -encase = { version = "0.9", features = ["glam"], default-features = false } -encase_derive_impl = { version = "0.9" } +encase = { version = "0.10", features = ["glam"], default-features = false } +encase_derive_impl = { version = "0.10" } crossbeam-utils = "0.8" crossbeam-queue = "0.3" tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/crates/render-ash/Cargo.toml b/crates/render-ash/Cargo.toml index 56beedb..84b6331 100644 --- a/crates/render-ash/Cargo.toml +++ b/crates/render-ash/Cargo.toml @@ -29,7 +29,7 @@ gpu-descriptor = "0.3" crossbeam-queue = { workspace = true } [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] -raw-window-metal = "0.4" +raw-window-metal = "1.0" [dev-dependencies] anyhow = { workspace = true } diff --git a/crates/render-wgpu/Cargo.toml b/crates/render-wgpu/Cargo.toml index fe86cb2..34a4216 100644 --- a/crates/render-wgpu/Cargo.toml +++ b/crates/render-wgpu/Cargo.toml @@ -15,15 +15,15 @@ pulz-render = { path = "../render" } thiserror = { workspace = true } tracing = { workspace = true } slotmap = { workspace = true } -wgpu = "22.1" +wgpu = "23.0" raw-window-handle = { workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -pollster = "0.3" +pollster = "0.4" [dev-dependencies] anyhow = { workspace = true } -naga = "22.1" +naga = "23.0" pulz-window-winit = { path = "../window-winit" } pulz-render-pipeline-core = { path = "../render-pipeline-core" } @@ -31,7 +31,7 @@ pulz-render-pipeline-core = { path = "../render-pipeline-core" } tracing-subscriber = { workspace = true } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wgpu = { version = "22.1" , features = ["webgl"] } +wgpu = { version = "23.0" , features = ["webgl"] } tracing-wasm = { workspace = true } tracing-log = { workspace = true } console_error_panic_hook = { workspace = true } diff --git a/crates/render-wgpu/src/convert.rs b/crates/render-wgpu/src/convert.rs index 0add9a8..762c26d 100644 --- a/crates/render-wgpu/src/convert.rs +++ b/crates/render-wgpu/src/convert.rs @@ -182,7 +182,7 @@ fn convert_texture_format(val: TextureFormat) -> Result { // Packed 32-bit formats TextureFormat::Rgb9E5Ufloat => wgpu::TextureFormat::Rgb9e5Ufloat, TextureFormat::Rgb10A2Unorm => wgpu::TextureFormat::Rgb10a2Unorm, - TextureFormat::Rg11B10Float => wgpu::TextureFormat::Rg11b10Float, + TextureFormat::Rg11B10Float => wgpu::TextureFormat::Rg11b10Ufloat, // 64-bit formats TextureFormat::Rg32Uint => wgpu::TextureFormat::Rg32Uint, @@ -353,7 +353,7 @@ pub fn convert_compute_pipeline_descriptor<'l, 'r: 'l>( label: desc.label, layout, module, - entry_point: desc.entry_point, + entry_point: Some(desc.entry_point), compilation_options: Default::default(), cache: None, }) @@ -443,7 +443,7 @@ fn convert_vertex_state<'l, 'r: 'l>( Ok(wgpu::VertexState::<'l> { module, - entry_point: state.entry_point, + entry_point: Some(state.entry_point), buffers: buffers_tmp, compilation_options: Default::default(), }) @@ -466,7 +466,7 @@ fn convert_fragment_state<'l, 'r: 'l>( Ok(wgpu::FragmentState { module, - entry_point: state.entry_point, + entry_point: Some(state.entry_point), targets: targets_tmp, compilation_options: Default::default(), }) diff --git a/justfile b/justfile index 8b19b33..dd958a6 100644 --- a/justfile +++ b/justfile @@ -1,6 +1,7 @@ alias b := build alias t := test alias c := check +alias re := run-example default: @just --list @@ -12,13 +13,61 @@ clippy: cargo clippy --workspace --all-targets --all-features clippy-fix: - cargo clippy --fix --workspace --all-targets --all-features + cargo clippy --fix --workspace --all-targets --all-features --allow-dirty --allow-staged test *testname: cargo test --workspace --all-targets --all-features {{testname}} -run: - cargo run +_run_defaults_sh := ' +export RUST_LOG="${RUST_LOG:-debug}" +export PULZ_DUMP_SCHEDULE="dump.dot" +export RUST_BACKTRACE="${RUST_BACKTRACE:-1}" +' + +_vk_layers_sh := ' +export VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation +export VK_DEVICE_LAYERS=VK_LAYER_KHRONOS_validation +export VK_LAYER_DISABLES= +export VK_LAYER_ENABLES="VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT:VALIDATION_CHECK_ENABLE_SYNCHRONIZATION_VALIDATION_QUEUE_SUBMIT:VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT" +# :VALIDATION_CHECK_ENABLE_VENDOR_SPECIFIC_NVIDIA:VALIDATION_CHECK_ENABLE_VENDOR_SPECIFIC_AMD +' + +_default_example := 'render-ash-demo' + +list-examples: + cargo run --example + +run-example example=_default_example: + #!/usr/bin/bash + set -e + {{ _run_defaults_sh }} + set -x + exec cargo run --example {{example}} + +validate-example example=_default_example: + #!/usr/bin/bash + set -ex + {{ _run_defaults_sh }} + {{ _vk_layers_sh }} + set -x + exec cargo run --example {{example}} + +capture-example example=_default_example: + #!/usr/bin/bash + set -e + {{ _run_defaults_sh }} + # force usage of x11 + unset WAYLAND_DISPLAY + export CAPTURE_OPTS="\ + --capture-file renderdoc-capture.rdc + --opt-capture-callstacks \ + --opt-hook-children \ + --opt-api-validation + --opt-api-validation-unmute + --wait-for-exit \ + " + set -x + exec renderdoccmd capture $CAPTURE_OPTS cargo run --example {{example}} fmt: cargo +nightly fmt --all From 11e30865aa86e7d0d0515ad5884aa000748411a2 Mon Sep 17 00:00:00 2001 From: Christoph Hommelsheim Date: Sat, 26 Apr 2025 15:07:42 +0200 Subject: [PATCH 36/36] fix(deps): update deps, edition, format, clippy --- Cargo.toml | 14 +- crates/assets-loader/src/path.rs | 6 +- crates/assets-loader/src/platform/fs.rs | 5 +- crates/render-ash/Cargo.toml | 3 +- crates/render-ash/examples/render-ash-demo.rs | 4 +- crates/render-ash/src/alloc.rs | 44 +-- crates/render-ash/src/convert.rs | 2 +- crates/render-ash/src/debug_utils.rs | 236 +++++++------- crates/render-ash/src/device.rs | 8 +- crates/render-ash/src/drop_guard.rs | 22 +- crates/render-ash/src/encoder.rs | 90 +++--- crates/render-ash/src/graph.rs | 6 +- crates/render-ash/src/instance.rs | 6 +- crates/render-ash/src/lib.rs | 32 +- crates/render-ash/src/resources/mod.rs | 72 +++-- .../render-ash/src/resources/resource_impl.rs | 287 ++++++++++-------- crates/render-ash/src/resources/traits.rs | 16 +- crates/render-ash/src/swapchain.rs | 273 +++++++++-------- crates/render-pipeline-core/src/common.rs | 4 +- .../render-pipeline-core/src/core_3d/mod.rs | 6 +- .../src/deferred_3d/mod.rs | 6 +- crates/render-wgpu/Cargo.toml | 11 +- .../render-wgpu/examples/render-wgpu-demo.rs | 4 +- crates/render-wgpu/src/convert.rs | 2 +- crates/render-wgpu/src/lib.rs | 139 +++++---- crates/render-wgpu/src/resources.rs | 2 +- crates/render/Cargo.toml | 1 - crates/render/macros/src/binding_layout.rs | 4 +- crates/render/macros/src/lib.rs | 2 +- crates/render/macros/src/utils.rs | 2 +- crates/render/src/camera.rs | 2 +- crates/render/src/draw.rs | 4 +- crates/render/src/graph/builder.rs | 4 +- crates/render/src/graph/mod.rs | 8 +- crates/render/src/graph/pass/builder.rs | 2 +- crates/render/src/graph/resources.rs | 4 +- crates/render/src/pipeline/graphics_pass.rs | 2 +- crates/render/src/shader/mod.rs | 2 +- crates/render/src/texture/image.rs | 2 +- .../examples/window-winit-demo.rs | 2 +- crates/window-winit/src/lib.rs | 4 +- crates/window/src/window.rs | 4 +- examples/android/Cargo.toml | 4 +- 43 files changed, 737 insertions(+), 616 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 024bcef..6f51291 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,19 +19,17 @@ edition = "2024" rust-version = "1.86" [workspace.dependencies] -glam = "0.29" +glam = "0.30" serde = "1.0" raw-window-handle = "0.6" slotmap = "1.0" -anyhow = "1.0" -thiserror = "1.0" +thiserror = "2.0" tracing = "0.1" -bitflags = "2.6" +bitflags = "2.9" fnv = "1.0" radsort = "0.1" -bytemuck = "1.20" +bytemuck = "1.22" blink-alloc = "0.3" -downcast-rs = "1.2" dynsequence = { version = "0.1.0-alpha.4" } blocking = "1.6" threadpool = "1.8" @@ -40,8 +38,8 @@ gametime = "0.5" atomic_refcell = "0.1" palette = { version = "0.7", default-features = false } image = { version = "0.25", default-features = false } -encase = { version = "0.10", features = ["glam"], default-features = false } -encase_derive_impl = { version = "0.10" } +encase = { version = "0.11", features = ["glam"], default-features = false } +encase_derive_impl = { version = "0.11" } crossbeam-utils = "0.8" crossbeam-queue = "0.3" tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/crates/assets-loader/src/path.rs b/crates/assets-loader/src/path.rs index 19e1c4d..0211a93 100644 --- a/crates/assets-loader/src/path.rs +++ b/crates/assets-loader/src/path.rs @@ -277,11 +277,7 @@ impl AssetPath { pub fn file_name(&self) -> Option<&str> { let (_parent, file, _frag) = self.split_parent_fragment(); let file = file.trim_end_matches(Self::is_seperator); - if file.is_empty() { - None - } else { - Some(file) - } + if file.is_empty() { None } else { Some(file) } } #[inline] diff --git a/crates/assets-loader/src/platform/fs.rs b/crates/assets-loader/src/platform/fs.rs index 7b2759e..0af240d 100644 --- a/crates/assets-loader/src/platform/fs.rs +++ b/crates/assets-loader/src/platform/fs.rs @@ -85,10 +85,7 @@ impl FileSystemAssetLoaderIo { // TODO: smaller max file size? // TODO: use this when stabelized // return Err(std::io::ErrorKind::FileTooLarge.into()); - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "file to large", - )); + return Err(std::io::Error::other("file to large")); } blocking::unblock(move || { let mut file = File::open(full_path)?; diff --git a/crates/render-ash/Cargo.toml b/crates/render-ash/Cargo.toml index 84b6331..3d1a4c9 100644 --- a/crates/render-ash/Cargo.toml +++ b/crates/render-ash/Cargo.toml @@ -29,10 +29,9 @@ gpu-descriptor = "0.3" crossbeam-queue = { workspace = true } [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] -raw-window-metal = "1.0" +raw-window-metal = "1.1" [dev-dependencies] -anyhow = { workspace = true } pulz-window-winit = { path = "../window-winit" } pulz-render-pipeline-core = { path = "../render-pipeline-core" } tracing-subscriber = { workspace = true } diff --git a/crates/render-ash/examples/render-ash-demo.rs b/crates/render-ash/examples/render-ash-demo.rs index bd2adcd..f51528d 100644 --- a/crates/render-ash/examples/render-ash-demo.rs +++ b/crates/render-ash/examples/render-ash-demo.rs @@ -5,7 +5,7 @@ use pulz_render::camera::{Camera, RenderTarget}; use pulz_render_ash::AshRenderer; use pulz_render_pipeline_core::core_3d::CoreShadingModule; use pulz_window::{WindowAttributes, WindowId, WindowModule}; -use pulz_window_winit::{winit::event_loop::EventLoop, Application}; +use pulz_window_winit::{Application, winit::event_loop::EventLoop}; use tracing::*; fn init() -> Resources { @@ -46,7 +46,7 @@ fn setup_demo_scene(resources: &mut Resources, window: WindowId) { #[cfg(not(target_arch = "wasm32"))] fn main() -> Result<(), Box> { - use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter}; + use tracing_subscriber::{EnvFilter, fmt::format::FmtSpan}; let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); tracing_subscriber::fmt() .with_env_filter(env_filter) diff --git a/crates/render-ash/src/alloc.rs b/crates/render-ash/src/alloc.rs index 60f01cd..e0c148e 100644 --- a/crates/render-ash/src/alloc.rs +++ b/crates/render-ash/src/alloc.rs @@ -3,9 +3,9 @@ use std::{mem::ManuallyDrop, sync::Arc}; use ash::vk; use crate::{ + Result, device::AshDevice, instance::{AshInstance, VK_API_VERSION}, - Result, }; type GpuAllocator = gpu_alloc::GpuAllocator; @@ -61,13 +61,15 @@ impl AshAllocator { &mut self, request: gpu_alloc::Request, ) -> Result, AllocationError> { - let block = self - .gpu_allocator - .alloc(gpu_alloc_ash::AshMemoryDevice::wrap(&self.device), request)?; - Ok(AshMemoryBlockGuard { - allocator: self, - block: ManuallyDrop::new(block), - }) + unsafe { + let block = self + .gpu_allocator + .alloc(gpu_alloc_ash::AshMemoryDevice::wrap(&self.device), request)?; + Ok(AshMemoryBlockGuard { + allocator: self, + block: ManuallyDrop::new(block), + }) + } } #[inline] @@ -76,21 +78,25 @@ impl AshAllocator { request: gpu_alloc::Request, dedicated: gpu_alloc::Dedicated, ) -> Result, AllocationError> { - let block = self.gpu_allocator.alloc_with_dedicated( - gpu_alloc_ash::AshMemoryDevice::wrap(&self.device), - request, - dedicated, - )?; - Ok(AshMemoryBlockGuard { - allocator: self, - block: ManuallyDrop::new(block), - }) + unsafe { + let block = self.gpu_allocator.alloc_with_dedicated( + gpu_alloc_ash::AshMemoryDevice::wrap(&self.device), + request, + dedicated, + )?; + Ok(AshMemoryBlockGuard { + allocator: self, + block: ManuallyDrop::new(block), + }) + } } #[inline] pub unsafe fn dealloc(&mut self, block: GpuMemoryBlock) { - self.gpu_allocator - .dealloc(gpu_alloc_ash::AshMemoryDevice::wrap(&self.device), block) + unsafe { + self.gpu_allocator + .dealloc(gpu_alloc_ash::AshMemoryDevice::wrap(&self.device), block) + } } } diff --git a/crates/render-ash/src/convert.rs b/crates/render-ash/src/convert.rs index f31a10c..780af2f 100644 --- a/crates/render-ash/src/convert.rs +++ b/crates/render-ash/src/convert.rs @@ -625,7 +625,7 @@ impl VkFrom for vk::AccessFlags { if val.intersects(Access::UNIFORM_READ) { result |= Self::UNIFORM_READ; } - if val.intersects(Access::SAMPLED_READ | Access::SAMPLED_READ) { + if val.intersects(Access::SHADER_READ | Access::SAMPLED_READ) { result |= Self::SHADER_READ; } if val.intersects(Access::COLOR_ATTACHMENT_READ) { diff --git a/crates/render-ash/src/debug_utils.rs b/crates/render-ash/src/debug_utils.rs index 07d9cbf..981e024 100644 --- a/crates/render-ash/src/debug_utils.rs +++ b/crates/render-ash/src/debug_utils.rs @@ -1,5 +1,5 @@ use std::{ - ffi::{c_void, CStr}, + ffi::{CStr, c_void}, os::raw::c_char, }; @@ -9,10 +9,12 @@ use tracing::{debug, error, info, warn}; pub const EXT_NAME: &CStr = ash::ext::debug_utils::NAME; unsafe fn c_str_from_ptr<'a>(str_ptr: *const c_char) -> &'a CStr { - if str_ptr.is_null() { - c"" - } else { - CStr::from_ptr(str_ptr) + unsafe { + if str_ptr.is_null() { + c"" + } else { + CStr::from_ptr(str_ptr) + } } } @@ -22,67 +24,69 @@ unsafe extern "system" fn debug_callback( p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT<'_>, _p_user_data: *mut c_void, ) -> vk::Bool32 { - use vk::DebugUtilsMessageSeverityFlagsEXT; + unsafe { + use vk::DebugUtilsMessageSeverityFlagsEXT; - if std::thread::panicking() { - return vk::FALSE; - } + if std::thread::panicking() { + return vk::FALSE; + } - let message = c_str_from_ptr((*p_callback_data).p_message); - let message_id_name = c_str_from_ptr((*p_callback_data).p_message_id_name); - let message_id_number = (*p_callback_data).message_id_number; + let message = c_str_from_ptr((*p_callback_data).p_message); + let message_id_name = c_str_from_ptr((*p_callback_data).p_message_id_name); + let message_id_number = (*p_callback_data).message_id_number; - // TODO: queues, labels, objects, ... + // TODO: queues, labels, objects, ... - match message_severity { - DebugUtilsMessageSeverityFlagsEXT::VERBOSE => { - debug!( - "Vk[{:?},#{},{:?}]: {}", - message_type, - message_id_number, - message_id_name, - message.to_string_lossy() - ) - } - DebugUtilsMessageSeverityFlagsEXT::INFO => { - info!( - "Vk[{:?},#{},{:?}]: {}", - message_type, - message_id_number, - message_id_name, - message.to_string_lossy() - ) - } - DebugUtilsMessageSeverityFlagsEXT::WARNING => { - warn!( - "Vk[{:?},#{},{:?}]: {}", - message_type, - message_id_number, - message_id_name, - message.to_string_lossy() - ) - } - DebugUtilsMessageSeverityFlagsEXT::ERROR => { - error!( - "Vk[{:?},#{},{:?}]: {}", - message_type, - message_id_number, - message_id_name, - message.to_string_lossy() - ) - } - _ => { - warn!( - "Vk[{:?},#{},{:?}]: {}", - message_type, - message_id_number, - message_id_name, - message.to_string_lossy() - ) - } - }; + match message_severity { + DebugUtilsMessageSeverityFlagsEXT::VERBOSE => { + debug!( + "Vk[{:?},#{},{:?}]: {}", + message_type, + message_id_number, + message_id_name, + message.to_string_lossy() + ) + } + DebugUtilsMessageSeverityFlagsEXT::INFO => { + info!( + "Vk[{:?},#{},{:?}]: {}", + message_type, + message_id_number, + message_id_name, + message.to_string_lossy() + ) + } + DebugUtilsMessageSeverityFlagsEXT::WARNING => { + warn!( + "Vk[{:?},#{},{:?}]: {}", + message_type, + message_id_number, + message_id_name, + message.to_string_lossy() + ) + } + DebugUtilsMessageSeverityFlagsEXT::ERROR => { + error!( + "Vk[{:?},#{},{:?}]: {}", + message_type, + message_id_number, + message_id_name, + message.to_string_lossy() + ) + } + _ => { + warn!( + "Vk[{:?},#{},{:?}]: {}", + message_type, + message_id_number, + message_id_name, + message.to_string_lossy() + ) + } + }; - vk::FALSE + vk::FALSE + } } // stack-allocated buffer for keeping a copy of the object_name (for appending \0-byte) @@ -185,11 +189,11 @@ impl DeviceDebugUtils { #[inline(always)] pub unsafe fn object_name(&self, handle: H, object_name: &str) { - self._object_name(H::TYPE, handle.as_raw(), object_name) + unsafe { self._object_name(H::TYPE, handle.as_raw(), object_name) } } #[inline(always)] pub unsafe fn object_name_cstr(&self, handle: H, object_name: &CStr) { - self._object_name_cstr(H::TYPE, handle.as_raw(), object_name) + unsafe { self._object_name_cstr(H::TYPE, handle.as_raw(), object_name) } } #[inline] @@ -199,12 +203,14 @@ impl DeviceDebugUtils { object_handle: u64, object_name: &str, ) { - if object_handle == 0 { - return; - } + unsafe { + if object_handle == 0 { + return; + } - let mut cstr_buf = CStrBuf::new(); - self._object_name_cstr(object_type, object_handle, cstr_buf.get_cstr(object_name)) + let mut cstr_buf = CStrBuf::new(); + self._object_name_cstr(object_type, object_handle, cstr_buf.get_cstr(object_name)) + } } unsafe fn _object_name_cstr( @@ -213,85 +219,107 @@ impl DeviceDebugUtils { object_handle: u64, object_name: &CStr, ) { - if object_handle == 0 { - return; + unsafe { + if object_handle == 0 { + return; + } + let _result = self + .0 + .set_debug_utils_object_name(&vk::DebugUtilsObjectNameInfoEXT { + object_handle, + object_type, + p_object_name: object_name.as_ptr(), + ..Default::default() + }); } - let _result = self - .0 - .set_debug_utils_object_name(&vk::DebugUtilsObjectNameInfoEXT { - object_handle, - object_type, - p_object_name: object_name.as_ptr(), - ..Default::default() - }); } #[inline] pub unsafe fn cmd_insert_debug_label(&self, command_buffer: vk::CommandBuffer, label: &str) { - let mut cstr_buf = CStrBuf::new(); - self.cmd_insert_debug_label_cstr(command_buffer, cstr_buf.get_cstr(label)) + unsafe { + let mut cstr_buf = CStrBuf::new(); + self.cmd_insert_debug_label_cstr(command_buffer, cstr_buf.get_cstr(label)) + } } pub unsafe fn cmd_insert_debug_label_cstr( &self, command_buffer: vk::CommandBuffer, label: &CStr, ) { - self.0.cmd_insert_debug_utils_label( - command_buffer, - &vk::DebugUtilsLabelEXT::default().label_name(label), - ); + unsafe { + self.0.cmd_insert_debug_utils_label( + command_buffer, + &vk::DebugUtilsLabelEXT::default().label_name(label), + ); + } } #[inline] pub unsafe fn cmd_begin_debug_label(&self, command_buffer: vk::CommandBuffer, label: &str) { - let mut cstr_buf = CStrBuf::new(); - self.cmd_begin_debug_label_cstr(command_buffer, cstr_buf.get_cstr(label)) + unsafe { + let mut cstr_buf = CStrBuf::new(); + self.cmd_begin_debug_label_cstr(command_buffer, cstr_buf.get_cstr(label)) + } } pub unsafe fn cmd_begin_debug_label_cstr( &self, command_buffer: vk::CommandBuffer, label: &CStr, ) { - self.0.cmd_begin_debug_utils_label( - command_buffer, - &vk::DebugUtilsLabelEXT::default().label_name(label), - ); + unsafe { + self.0.cmd_begin_debug_utils_label( + command_buffer, + &vk::DebugUtilsLabelEXT::default().label_name(label), + ); + } } #[inline] pub unsafe fn cmd_end_debug_label(&self, command_buffer: vk::CommandBuffer) { - self.0.cmd_end_debug_utils_label(command_buffer); + unsafe { + self.0.cmd_end_debug_utils_label(command_buffer); + } } #[inline] pub unsafe fn queue_insert_debug_label(&self, queue: vk::Queue, label: &str) { - let mut cstr_buf = CStrBuf::new(); - self.queue_insert_debug_label_cstr(queue, cstr_buf.get_cstr(label)) + unsafe { + let mut cstr_buf = CStrBuf::new(); + self.queue_insert_debug_label_cstr(queue, cstr_buf.get_cstr(label)) + } } pub unsafe fn queue_insert_debug_label_cstr(&self, queue: vk::Queue, label: &CStr) { - self.0.queue_insert_debug_utils_label( - queue, - &vk::DebugUtilsLabelEXT::default().label_name(label), - ); + unsafe { + self.0.queue_insert_debug_utils_label( + queue, + &vk::DebugUtilsLabelEXT::default().label_name(label), + ); + } } #[inline] pub unsafe fn queue_begin_debug_label(&self, queue: vk::Queue, label: &str) { - let mut cstr_buf = CStrBuf::new(); - self.queue_begin_debug_label_cstr(queue, cstr_buf.get_cstr(label)) + unsafe { + let mut cstr_buf = CStrBuf::new(); + self.queue_begin_debug_label_cstr(queue, cstr_buf.get_cstr(label)) + } } pub unsafe fn queue_begin_debug_label_cstr(&self, queue: vk::Queue, label: &CStr) { - self.0.queue_begin_debug_utils_label( - queue, - &vk::DebugUtilsLabelEXT::default().label_name(label), - ); + unsafe { + self.0.queue_begin_debug_utils_label( + queue, + &vk::DebugUtilsLabelEXT::default().label_name(label), + ); + } } #[inline] pub unsafe fn queue_end_debug_label(&self, queue: vk::Queue) { - self.0.queue_end_debug_utils_label(queue); + unsafe { + self.0.queue_end_debug_utils_label(queue); + } } } diff --git a/crates/render-ash/src/device.rs b/crates/render-ash/src/device.rs index 89ccdc5..1844600 100644 --- a/crates/render-ash/src/device.rs +++ b/crates/render-ash/src/device.rs @@ -4,7 +4,7 @@ use ash::vk; use pulz_render::graph::pass::PipelineBindPoint; use tracing::{debug, info, warn}; -use crate::{debug_utils, instance::AshInstance, Error, ErrorNoExtension, Result}; +use crate::{Error, ErrorNoExtension, Result, debug_utils, instance::AshInstance}; pub struct AshDevice { device_raw: ash::Device, @@ -139,8 +139,10 @@ impl AshDevice { #[inline] pub unsafe fn object_name(&self, handle: H, name: &str) { - if let Some(debug_utils) = &self.debug_utils { - debug_utils.object_name(handle, name) + unsafe { + if let Some(debug_utils) = &self.debug_utils { + debug_utils.object_name(handle, name) + } } } } diff --git a/crates/render-ash/src/drop_guard.rs b/crates/render-ash/src/drop_guard.rs index 4200109..e32bc2b 100644 --- a/crates/render-ash/src/drop_guard.rs +++ b/crates/render-ash/src/drop_guard.rs @@ -2,7 +2,7 @@ use std::{collections::VecDeque, mem::ManuallyDrop}; use ash::vk; -use crate::{instance::AshInstance, AshDevice, Result}; +use crate::{AshDevice, Result, instance::AshInstance}; pub trait Destroy { type Context; @@ -28,7 +28,7 @@ impl AshDevice { where D: Destroy, { - handle.destroy(self) + unsafe { handle.destroy(self) } } #[inline] @@ -54,7 +54,7 @@ impl AshInstance { where D: Destroy, { - handle.destroy(self) + unsafe { handle.destroy(self) } } #[inline] @@ -126,7 +126,7 @@ impl Destroy for Vec { type Context = D::Context; #[inline] unsafe fn destroy(self, device: &D::Context) { - self.into_iter().for_each(|d| d.destroy(device)) + unsafe { self.into_iter().for_each(|d| d.destroy(device)) } } } @@ -134,7 +134,7 @@ impl Destroy for VecDeque { type Context = D::Context; #[inline] unsafe fn destroy(self, device: &D::Context) { - self.into_iter().for_each(|d| d.destroy(device)) + unsafe { self.into_iter().for_each(|d| d.destroy(device)) } } } @@ -142,7 +142,7 @@ impl Destroy for std::vec::Drain<'_, D> { type Context = D::Context; #[inline] unsafe fn destroy(self, device: &D::Context) { - self.for_each(|d| d.destroy(device)) + unsafe { self.for_each(|d| d.destroy(device)) } } } @@ -150,7 +150,7 @@ impl Destroy for std::collections::vec_deque::Drain<'_, D> { type Context = D::Context; #[inline] unsafe fn destroy(self, device: &D::Context) { - self.for_each(|d| d.destroy(device)) + unsafe { self.for_each(|d| d.destroy(device)) } } } @@ -166,18 +166,18 @@ macro_rules! impl_create_destroy { impl Destroy for $vktype { type Context = $ctx; #[inline] - unsafe fn destroy(self, ctx: &Self::Context) { + unsafe fn destroy(self, ctx: &Self::Context) { unsafe { ctx.$destroy(self, None); - } + }} } $( impl CreateWithInfo for $vktype { type CreateInfo<$lt> = $createinfo; #[inline] - unsafe fn create(ctx: &Self::Context, create_info: &Self::CreateInfo<'_>) -> Result { + unsafe fn create(ctx: &Self::Context, create_info: &Self::CreateInfo<'_>) -> Result { unsafe { Ok(ctx.$create(create_info, None)?) - } + }} } )? )* diff --git a/crates/render-ash/src/encoder.rs b/crates/render-ash/src/encoder.rs index 590f0ab..6e4389e 100644 --- a/crates/render-ash/src/encoder.rs +++ b/crates/render-ash/src/encoder.rs @@ -3,7 +3,7 @@ use std::{cell::Cell, collections::VecDeque, sync::Arc}; use ash::{prelude::VkResult, vk}; use pulz_render::backend::CommandEncoder; -use crate::{device::AshDevice, Result}; +use crate::{Result, device::AshDevice}; pub struct AshCommandPool { device: Arc, @@ -49,14 +49,16 @@ impl AshCommandPool { } pub unsafe fn reset(&mut self) -> VkResult<()> { - self.device - .reset_command_pool(self.pool, vk::CommandPoolResetFlags::empty())?; - self.fresh_buffers.extend(self.done_buffers.drain(..)); + unsafe { + self.device + .reset_command_pool(self.pool, vk::CommandPoolResetFlags::empty())?; + self.fresh_buffers.extend(self.done_buffers.drain(..)); - // return all semaphores to pool - self.semaphores_pool.extend(self.used_semaphores.drain(..)); + // return all semaphores to pool + self.semaphores_pool.extend(self.used_semaphores.drain(..)); - Ok(()) + Ok(()) + } } pub fn request_semaphore(&mut self) -> Result { @@ -101,8 +103,10 @@ impl AshCommandPool { } unsafe fn free_command_buffers(&self, buffers: &[vk::CommandBuffer]) { - if !buffers.is_empty() { - self.device.free_command_buffers(self.pool, buffers); + unsafe { + if !buffers.is_empty() { + self.device.free_command_buffers(self.pool, buffers); + } } } } @@ -204,13 +208,15 @@ impl AshCommandEncoder<'_> { clear_value: &vk::ClearColorValue, ranges: &[vk::ImageSubresourceRange], ) { - self.pool.device().cmd_clear_color_image( - self.buffer, - image, - image_layout, - clear_value, - ranges, - ) + unsafe { + self.pool.device().cmd_clear_color_image( + self.buffer, + image, + image_layout, + clear_value, + ranges, + ) + } } pub unsafe fn clear_depth_stencil_image( @@ -220,13 +226,15 @@ impl AshCommandEncoder<'_> { clear_value: &vk::ClearDepthStencilValue, ranges: &[vk::ImageSubresourceRange], ) { - self.pool.device().cmd_clear_depth_stencil_image( - self.buffer, - image, - image_layout, - clear_value, - ranges, - ) + unsafe { + self.pool.device().cmd_clear_depth_stencil_image( + self.buffer, + image, + image_layout, + clear_value, + ranges, + ) + } } pub unsafe fn pipeline_barrier( @@ -237,15 +245,17 @@ impl AshCommandEncoder<'_> { buffer_memory_barriers: &[vk::BufferMemoryBarrier<'_>], image_memory_barriers: &[vk::ImageMemoryBarrier<'_>], ) { - self.pool.device().cmd_pipeline_barrier( - self.buffer, - src_stage_mask, - dst_stage_mask, - vk::DependencyFlags::empty(), - memory_barriers, - buffer_memory_barriers, - image_memory_barriers, - ) + unsafe { + self.pool.device().cmd_pipeline_barrier( + self.buffer, + src_stage_mask, + dst_stage_mask, + vk::DependencyFlags::empty(), + memory_barriers, + buffer_memory_barriers, + image_memory_barriers, + ) + } } pub unsafe fn begin_render_pass( @@ -253,17 +263,23 @@ impl AshCommandEncoder<'_> { create_info: &vk::RenderPassBeginInfo<'_>, contents: vk::SubpassContents, ) { - self.pool - .device() - .cmd_begin_render_pass(self.buffer, create_info, contents); + unsafe { + self.pool + .device() + .cmd_begin_render_pass(self.buffer, create_info, contents); + } } pub unsafe fn next_subpass(&self, contents: vk::SubpassContents) { - self.pool.device().cmd_next_subpass(self.buffer, contents); + unsafe { + self.pool.device().cmd_next_subpass(self.buffer, contents); + } } pub unsafe fn end_render_pass(&self) { - self.pool.device().cmd_end_render_pass(self.buffer); + unsafe { + self.pool.device().cmd_end_render_pass(self.buffer); + } } } diff --git a/crates/render-ash/src/graph.rs b/crates/render-ash/src/graph.rs index 65d3f77..cdb655e 100644 --- a/crates/render-ash/src/graph.rs +++ b/crates/render-ash/src/graph.rs @@ -6,10 +6,10 @@ use pulz_render::{ camera::RenderTarget, draw::DrawPhases, graph::{ + PassDescription, PassIndex, RenderGraph, access::Access, pass::PipelineBindPoint, resources::{PhysicalResource, PhysicalResourceAccessTracker, PhysicalResources}, - PassDescription, PassIndex, RenderGraph, }, math::USize2, pipeline::{ExtendedGraphicsPassDescriptor, GraphicsPass}, @@ -19,11 +19,11 @@ use pulz_window::WindowsMirror; use tracing::debug; use crate::{ - convert::{default_clear_value_for_format, VkInto}, + Result, + convert::{VkInto, default_clear_value_for_format}, encoder::{AshCommandPool, SubmissionGroup}, resources::AshResources, swapchain::AshSurfaceSwapchain, - Result, }; pub struct AshRenderGraph { diff --git a/crates/render-ash/src/instance.rs b/crates/render-ash/src/instance.rs index 67d6585..5231a62 100644 --- a/crates/render-ash/src/instance.rs +++ b/crates/render-ash/src/instance.rs @@ -3,7 +3,7 @@ use std::{ffi::CStr, ops::Deref, os::raw::c_char, sync::Arc}; use ash::vk; use tracing::{debug, warn}; -use crate::{debug_utils, AshRendererFlags, ErrorNoExtension, Result}; +use crate::{AshRendererFlags, ErrorNoExtension, Result, debug_utils}; pub const ENGINE_NAME: &CStr = match CStr::from_bytes_with_nul(concat!(env!("CARGO_PKG_NAME"), "\0").as_bytes()) { @@ -181,8 +181,8 @@ macro_rules! parse_int_iteration { } $value *= 10; let c = $input[$pos]; - if c < '0' as u8 || c > '9' as u8 { - if c != '.' as u8 && c != '-' as u8 { + if c < b'0' || c > b'9' { + if c != b'.' && c != b'-' { panic!("invalid character in version"); } return ($value, $pos + 1); diff --git a/crates/render-ash/src/lib.rs b/crates/render-ash/src/lib.rs index d82bcfc..84c17f6 100644 --- a/crates/render-ash/src/lib.rs +++ b/crates/render-ash/src/lib.rs @@ -35,7 +35,7 @@ use encoder::{AshCommandPool, SubmissionGroup}; use graph::AshRenderGraph; use instance::AshInstance; use pulz_ecs::prelude::*; -use pulz_render::{draw::DrawPhases, graph::RenderGraph, RenderModule, RenderSystemPhase}; +use pulz_render::{RenderModule, RenderSystemPhase, draw::DrawPhases, graph::RenderGraph}; use resources::AshResources; use thiserror::Error; @@ -52,8 +52,8 @@ mod shader; mod swapchain; use pulz_window::{ - listener::WindowSystemListener, DisplayHandle, Window, WindowHandle, WindowId, Windows, - WindowsMirror, + DisplayHandle, Window, WindowHandle, WindowId, Windows, WindowsMirror, + listener::WindowSystemListener, }; // wrapper object for printing backtrace, until provide() is stable @@ -200,20 +200,24 @@ impl Frame { impl Frame { unsafe fn create(device: &Arc) -> Result { - let command_pool = device.new_command_pool(device.queues().graphics_family)?; - let finished_fence = - device.create(&vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED))?; - let finished_semaphore = device.create(&vk::SemaphoreCreateInfo::default())?; - Ok(Self { - command_pool, - finished_fence: finished_fence.take(), - finished_semaphore: finished_semaphore.take(), - }) + unsafe { + let command_pool = device.new_command_pool(device.queues().graphics_family)?; + let finished_fence = device + .create(&vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED))?; + let finished_semaphore = device.create(&vk::SemaphoreCreateInfo::default())?; + Ok(Self { + command_pool, + finished_fence: finished_fence.take(), + finished_semaphore: finished_semaphore.take(), + }) + } } unsafe fn reset(&mut self, _device: &AshDevice) -> Result<(), vk::Result> { - self.command_pool.reset()?; - Ok(()) + unsafe { + self.command_pool.reset()?; + Ok(()) + } } } diff --git a/crates/render-ash/src/resources/mod.rs b/crates/render-ash/src/resources/mod.rs index ec38976..118c66b 100644 --- a/crates/render-ash/src/resources/mod.rs +++ b/crates/render-ash/src/resources/mod.rs @@ -14,10 +14,10 @@ use pulz_render::{ use slotmap::SlotMap; use crate::{ + Result, alloc::{AshAllocator, GpuMemoryBlock}, device::AshDevice, instance::AshInstance, - Result, }; mod replay; @@ -204,53 +204,59 @@ impl AshResources { } pub(crate) unsafe fn clear_garbage(&mut self, frame: usize) { - let garbage = &mut self.frame_garbage[frame]; - let mut textures = std::mem::take(&mut garbage.texture_handles); - for texture in textures.drain(..) { - if let Some(raw) = self.textures.remove(texture) { - Texture::put_to_garbage(garbage, raw) + unsafe { + let garbage = &mut self.frame_garbage[frame]; + let mut textures = std::mem::take(&mut garbage.texture_handles); + for texture in textures.drain(..) { + if let Some(raw) = self.textures.remove(texture) { + Texture::put_to_garbage(garbage, raw) + } } - } - garbage.texture_handles = textures; - let mut buffers = std::mem::take(&mut garbage.buffer_handles); - for buffer in buffers.drain(..) { - if let Some(raw) = self.buffers.remove(buffer) { - Buffer::put_to_garbage(garbage, raw) + garbage.texture_handles = textures; + let mut buffers = std::mem::take(&mut garbage.buffer_handles); + for buffer in buffers.drain(..) { + if let Some(raw) = self.buffers.remove(buffer) { + Buffer::put_to_garbage(garbage, raw) + } } + garbage.buffer_handles = buffers; + garbage.clear_frame(&mut self.alloc); } - garbage.buffer_handles = buffers; - garbage.clear_frame(&mut self.alloc); } /// # SAFETY /// caller must ensure, that the next frame has finished pub(crate) unsafe fn next_frame_and_clear_garbage(&mut self) { - self.current_frame = (self.current_frame + 1) % self.frame_garbage.len(); - self.clear_garbage(self.current_frame); + unsafe { + self.current_frame = (self.current_frame + 1) % self.frame_garbage.len(); + self.clear_garbage(self.current_frame); + } } } impl AshFrameGarbage { unsafe fn clear_frame(&mut self, alloc: &mut AshAllocator) { - let device = alloc.device(); - self.framebuffers - .drain(..) - .for_each(|r| device.destroy_framebuffer(r, None)); - self.image_views - .drain(..) - .for_each(|r| device.destroy_image_view(r, None)); - self.images - .drain(..) - .for_each(|r| device.destroy_image(r, None)); - self.buffers - .drain(..) - .for_each(|r| device.destroy_buffer(r, None)); - if let Ok(ext_swapchain) = device.ext_swapchain() { - self.swapchains + unsafe { + let device = alloc.device(); + self.framebuffers + .drain(..) + .for_each(|r| device.destroy_framebuffer(r, None)); + self.image_views + .drain(..) + .for_each(|r| device.destroy_image_view(r, None)); + self.images .drain(..) - .for_each(|r| ext_swapchain.destroy_swapchain(r, None)); + .for_each(|r| device.destroy_image(r, None)); + self.buffers + .drain(..) + .for_each(|r| device.destroy_buffer(r, None)); + if let Ok(ext_swapchain) = device.ext_swapchain() { + self.swapchains + .drain(..) + .for_each(|r| ext_swapchain.destroy_swapchain(r, None)); + } + self.memory.drain(..).for_each(|r| alloc.dealloc(r)); } - self.memory.drain(..).for_each(|r| alloc.dealloc(r)); } } diff --git a/crates/render-ash/src/resources/resource_impl.rs b/crates/render-ash/src/resources/resource_impl.rs index dbc692b..08bdaaf 100644 --- a/crates/render-ash/src/resources/resource_impl.rs +++ b/crates/render-ash/src/resources/resource_impl.rs @@ -11,14 +11,14 @@ use pulz_render::{ use slotmap::SlotMap; use super::{ - traits::{AshGpuResource, AshGpuResourceCached, AshGpuResourceCreate, AshGpuResourceRemove}, AshResources, U64HashMap, + traits::{AshGpuResource, AshGpuResourceCached, AshGpuResourceCreate, AshGpuResourceRemove}, }; use crate::{ + Result, alloc::{AshAllocator, GpuMemoryBlock}, convert::{CreateInfoConverter2, CreateInfoConverter6, VkInto}, shader::compie_into_spv, - Result, }; impl AshGpuResource for Buffer { @@ -34,26 +34,30 @@ impl AshGpuResource for Buffer { res: &mut AshResources, descr: &Self::Descriptor<'_>, ) -> Result { - let alloc = &mut res.alloc; - let device = alloc.device_arc(); - let create_info: vk::BufferCreateInfo<'static> = descr.vk_into(); - let buf = device.create(&create_info)?; - let mreq = device.get_buffer_memory_requirements(buf.raw()); - let mem = alloc.alloc(gpu_alloc::Request { - size: mreq.size, - align_mask: mreq.alignment, - usage: gpu_alloc::UsageFlags::FAST_DEVICE_ACCESS, - memory_types: mreq.memory_type_bits, - })?; - device.bind_buffer_memory(buf.raw(), *mem.memory(), mem.offset())?; - Ok((buf.take(), Some(mem.take()))) + unsafe { + let alloc = &mut res.alloc; + let device = alloc.device_arc(); + let create_info: vk::BufferCreateInfo<'static> = descr.vk_into(); + let buf = device.create(&create_info)?; + let mreq = device.get_buffer_memory_requirements(buf.raw()); + let mem = alloc.alloc(gpu_alloc::Request { + size: mreq.size, + align_mask: mreq.alignment, + usage: gpu_alloc::UsageFlags::FAST_DEVICE_ACCESS, + memory_types: mreq.memory_type_bits, + })?; + device.bind_buffer_memory(buf.raw(), *mem.memory(), mem.offset())?; + Ok((buf.take(), Some(mem.take()))) + } } unsafe fn destroy_raw(alloc: &mut AshAllocator, (buf, mem): Self::Raw) { - if buf != vk::Buffer::null() { - alloc.device().destroy_buffer(buf, None); - } - if let Some(mem) = mem { - alloc.dealloc(mem); + unsafe { + if buf != vk::Buffer::null() { + alloc.device().destroy_buffer(buf, None); + } + if let Some(mem) = mem { + alloc.dealloc(mem); + } } } } @@ -82,33 +86,37 @@ impl AshGpuResource for Texture { res: &mut AshResources, descr: &Self::Descriptor<'_>, ) -> Result { - let alloc = &mut res.alloc; - let device = alloc.device_arc(); - let img_create_info: vk::ImageCreateInfo<'static> = descr.vk_into(); - let img = device.create(&img_create_info)?; - let mreq = device.get_image_memory_requirements(img.raw()); - let mem = alloc.alloc(gpu_alloc::Request { - size: mreq.size, - align_mask: mreq.alignment, - usage: gpu_alloc::UsageFlags::FAST_DEVICE_ACCESS, - memory_types: mreq.memory_type_bits, - })?; - device.bind_image_memory(img.raw(), *mem.memory(), mem.offset())?; - let mut view_create_info: vk::ImageViewCreateInfo<'static> = descr.vk_into(); - view_create_info.image = img.raw(); - let view = device.create(&view_create_info)?; - Ok((img.take(), view.take(), Some(mem.take()))) + unsafe { + let alloc = &mut res.alloc; + let device = alloc.device_arc(); + let img_create_info: vk::ImageCreateInfo<'static> = descr.vk_into(); + let img = device.create(&img_create_info)?; + let mreq = device.get_image_memory_requirements(img.raw()); + let mem = alloc.alloc(gpu_alloc::Request { + size: mreq.size, + align_mask: mreq.alignment, + usage: gpu_alloc::UsageFlags::FAST_DEVICE_ACCESS, + memory_types: mreq.memory_type_bits, + })?; + device.bind_image_memory(img.raw(), *mem.memory(), mem.offset())?; + let mut view_create_info: vk::ImageViewCreateInfo<'static> = descr.vk_into(); + view_create_info.image = img.raw(); + let view = device.create(&view_create_info)?; + Ok((img.take(), view.take(), Some(mem.take()))) + } } unsafe fn destroy_raw(alloc: &mut AshAllocator, (img, view, mem): Self::Raw) { - if view != vk::ImageView::null() { - alloc.device().destroy(view); - } - if img != vk::Image::null() { - alloc.device().destroy(img); - } - if let Some(mem) = mem { - alloc.dealloc(mem); + unsafe { + if view != vk::ImageView::null() { + alloc.device().destroy(view); + } + if img != vk::Image::null() { + alloc.device().destroy(img); + } + if let Some(mem) = mem { + alloc.dealloc(mem); + } } } } @@ -139,14 +147,18 @@ impl AshGpuResource for GraphicsPass { res: &mut AshResources, descr: &Self::Descriptor<'_>, ) -> Result { - let mut conv = CreateInfoConverter6::new(); - let create_info = conv.graphics_pass(descr); - let raw = res.device().create(create_info)?; - Ok(raw.take()) + unsafe { + let mut conv = CreateInfoConverter6::new(); + let create_info = conv.graphics_pass(descr); + let raw = res.device().create(create_info)?; + Ok(raw.take()) + } } unsafe fn destroy_raw(alloc: &mut AshAllocator, raw: Self::Raw) { - if raw != vk::RenderPass::null() { - alloc.device().destroy(raw); + unsafe { + if raw != vk::RenderPass::null() { + alloc.device().destroy(raw); + } } } } @@ -168,17 +180,21 @@ impl AshGpuResource for ShaderModule { res: &mut AshResources, descr: &Self::Descriptor<'_>, ) -> Result { - let code = compie_into_spv(&descr.source)?; - let create_info = vk::ShaderModuleCreateInfo::default().code(&code); - let raw = res.device().create(&create_info)?; - if let Some(label) = descr.label { - res.device().object_name(raw.raw(), label); + unsafe { + let code = compie_into_spv(&descr.source)?; + let create_info = vk::ShaderModuleCreateInfo::default().code(&code); + let raw = res.device().create(&create_info)?; + if let Some(label) = descr.label { + res.device().object_name(raw.raw(), label); + } + Ok(raw.take()) } - Ok(raw.take()) } unsafe fn destroy_raw(alloc: &mut AshAllocator, raw: Self::Raw) { - if raw != vk::ShaderModule::null() { - alloc.device().destroy(raw); + unsafe { + if raw != vk::ShaderModule::null() { + alloc.device().destroy(raw); + } } } } @@ -201,17 +217,21 @@ impl AshGpuResource for BindGroupLayout { res: &mut AshResources, descr: &Self::Descriptor<'_>, ) -> Result { - let mut conv = CreateInfoConverter2::new(); - let create_info = conv.bind_group_layout(descr); - let raw = res.device().create(create_info)?; - if let Some(label) = descr.label { - res.device().object_name(raw.raw(), label); + unsafe { + let mut conv = CreateInfoConverter2::new(); + let create_info = conv.bind_group_layout(descr); + let raw = res.device().create(create_info)?; + if let Some(label) = descr.label { + res.device().object_name(raw.raw(), label); + } + Ok(raw.take()) } - Ok(raw.take()) } unsafe fn destroy_raw(alloc: &mut AshAllocator, raw: Self::Raw) { - if raw != vk::DescriptorSetLayout::null() { - alloc.device().destroy(raw); + unsafe { + if raw != vk::DescriptorSetLayout::null() { + alloc.device().destroy(raw); + } } } } @@ -234,17 +254,21 @@ impl AshGpuResource for PipelineLayout { res: &mut AshResources, descr: &Self::Descriptor<'_>, ) -> Result { - let mut conv = CreateInfoConverter2::new(); - let create_info = conv.pipeline_layout(res, descr); - let raw = res.device().create(create_info)?; - if let Some(label) = descr.label { - res.device().object_name(raw.raw(), label); + unsafe { + let mut conv = CreateInfoConverter2::new(); + let create_info = conv.pipeline_layout(res, descr); + let raw = res.device().create(create_info)?; + if let Some(label) = descr.label { + res.device().object_name(raw.raw(), label); + } + Ok(raw.take()) } - Ok(raw.take()) } unsafe fn destroy_raw(alloc: &mut AshAllocator, raw: Self::Raw) { - if raw != vk::PipelineLayout::null() { - alloc.device().destroy(raw); + unsafe { + if raw != vk::PipelineLayout::null() { + alloc.device().destroy(raw); + } } } } @@ -267,29 +291,33 @@ impl AshGpuResource for GraphicsPipeline { res: &mut AshResources, descr: &Self::Descriptor<'_>, ) -> Result { - let mut conv = CreateInfoConverter2::new(); - let create_infos = conv.graphics_pipeline_descriptor(res, std::slice::from_ref(descr)); - match res - .device() - .create_graphics_pipelines(res.pipeline_cache, create_infos, None) - { - Ok(raw) => { - let raw = res.device().hold(raw[0]); - if let Some(label) = descr.label { - res.device().object_name(raw.raw(), label); + unsafe { + let mut conv = CreateInfoConverter2::new(); + let create_infos = conv.graphics_pipeline_descriptor(res, std::slice::from_ref(descr)); + match res + .device() + .create_graphics_pipelines(res.pipeline_cache, create_infos, None) + { + Ok(raw) => { + let raw = res.device().hold(raw[0]); + if let Some(label) = descr.label { + res.device().object_name(raw.raw(), label); + } + Ok(raw.take()) + } + Err((pipelines, e)) => { + res.device().destroy(pipelines); + Err(e.into()) } - Ok(raw.take()) - } - Err((pipelines, e)) => { - res.device().destroy(pipelines); - Err(e.into()) } } } unsafe fn destroy_raw(alloc: &mut AshAllocator, raw: Self::Raw) { - if raw != vk::Pipeline::null() { - alloc.device().destroy_pipeline(raw, None); + unsafe { + if raw != vk::Pipeline::null() { + alloc.device().destroy_pipeline(raw, None); + } } } } @@ -312,29 +340,33 @@ impl AshGpuResource for ComputePipeline { res: &mut AshResources, descr: &Self::Descriptor<'_>, ) -> Result { - let mut conv = CreateInfoConverter2::new(); - let create_infos = conv.compute_pipeline_descriptor(res, std::slice::from_ref(descr)); - match res - .device() - .create_compute_pipelines(res.pipeline_cache, create_infos, None) - { - Ok(raw) => { - let raw = res.device().hold(raw[0]); - if let Some(label) = descr.label { - res.device().object_name(raw.raw(), label); + unsafe { + let mut conv = CreateInfoConverter2::new(); + let create_infos = conv.compute_pipeline_descriptor(res, std::slice::from_ref(descr)); + match res + .device() + .create_compute_pipelines(res.pipeline_cache, create_infos, None) + { + Ok(raw) => { + let raw = res.device().hold(raw[0]); + if let Some(label) = descr.label { + res.device().object_name(raw.raw(), label); + } + Ok(raw.take()) + } + Err((pipelines, e)) => { + res.device().destroy(pipelines); + Err(e.into()) } - Ok(raw.take()) - } - Err((pipelines, e)) => { - res.device().destroy(pipelines); - Err(e.into()) } } } unsafe fn destroy_raw(alloc: &mut AshAllocator, raw: Self::Raw) { - if raw != vk::Pipeline::null() { - alloc.device().destroy_pipeline(raw, None); + unsafe { + if raw != vk::Pipeline::null() { + alloc.device().destroy_pipeline(raw, None); + } } } } @@ -358,27 +390,32 @@ impl AshGpuResource for RayTracingPipeline { res: &mut AshResources, descr: &Self::Descriptor<'_>, ) -> Result { - let ext = res.device().ext_raytracing_pipeline()?; - let mut conv = CreateInfoConverter2::new(); - let create_infos = conv.ray_tracing_pipeline_descriptor(res, std::slice::from_ref(descr)); - let raw = ext - .create_ray_tracing_pipelines( - vk::DeferredOperationKHR::null(), - res.pipeline_cache, - create_infos, - None, - ) - .map_err(|(_, e)| e)?; - let raw = res.device().hold(raw[0]); - if let Some(label) = descr.label { - res.device().object_name(raw.raw(), label); + unsafe { + let ext = res.device().ext_raytracing_pipeline()?; + let mut conv = CreateInfoConverter2::new(); + let create_infos = + conv.ray_tracing_pipeline_descriptor(res, std::slice::from_ref(descr)); + let raw = ext + .create_ray_tracing_pipelines( + vk::DeferredOperationKHR::null(), + res.pipeline_cache, + create_infos, + None, + ) + .map_err(|(_, e)| e)?; + let raw = res.device().hold(raw[0]); + if let Some(label) = descr.label { + res.device().object_name(raw.raw(), label); + } + Ok(raw.take()) } - Ok(raw.take()) } unsafe fn destroy_raw(res: &mut AshAllocator, raw: Self::Raw) { - if raw != vk::Pipeline::null() { - res.device().destroy_pipeline(raw, None); + unsafe { + if raw != vk::Pipeline::null() { + res.device().destroy_pipeline(raw, None); + } } } } diff --git a/crates/render-ash/src/resources/traits.rs b/crates/render-ash/src/resources/traits.rs index b009b22..ec73217 100644 --- a/crates/render-ash/src/resources/traits.rs +++ b/crates/render-ash/src/resources/traits.rs @@ -3,8 +3,8 @@ use std::hash::{Hash, Hasher}; use pulz_render::backend::GpuResource; use slotmap::SlotMap; -use super::{replay::AsResourceRecord, AshFrameGarbage, AshResources, U64HashMap}; -use crate::{alloc::AshAllocator, Result}; +use super::{AshFrameGarbage, AshResources, U64HashMap, replay::AsResourceRecord}; +use crate::{Result, alloc::AshAllocator}; pub trait AshGpuResource: GpuResource + 'static { type Raw; @@ -61,11 +61,13 @@ impl AshGpuResourceCollection for SlotMap { } } unsafe fn destroy(&mut self, key: Self::Resource, alloc: &mut AshAllocator) -> bool { - if let Some(raw) = self.remove(key) { - R::destroy_raw(alloc, raw); - true - } else { - false + unsafe { + if let Some(raw) = self.remove(key) { + R::destroy_raw(alloc, raw); + true + } else { + false + } } } } diff --git a/crates/render-ash/src/swapchain.rs b/crates/render-ash/src/swapchain.rs index 78f2cc3..f3622c0 100644 --- a/crates/render-ash/src/swapchain.rs +++ b/crates/render-ash/src/swapchain.rs @@ -1,6 +1,6 @@ use ash::vk; use pulz_render::{ - math::{uvec2, USize2}, + math::{USize2, uvec2}, texture::{Texture, TextureFormat}, }; use pulz_window::{DisplayHandle, Size2, Window, WindowHandle, WindowId, Windows}; @@ -8,21 +8,23 @@ use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; use tracing::{debug, info}; use crate::{ + AshRendererFull, Error, Result, convert::VkInto, device::AshDevice, drop_guard::{Destroy, Guard}, instance::AshInstance, resources::AshResources, - AshRendererFull, Error, Result, }; impl Destroy for vk::SurfaceKHR { type Context = AshInstance; #[inline] unsafe fn destroy(self, instance: &AshInstance) { - if self != Self::null() { - let ext_surface = instance.ext_surface().unwrap(); - ext_surface.destroy_surface(self, None); + unsafe { + if self != Self::null() { + let ext_surface = instance.ext_surface().unwrap(); + ext_surface.destroy_surface(self, None); + } } } } @@ -31,9 +33,11 @@ impl Destroy for vk::SwapchainKHR { type Context = AshDevice; #[inline] unsafe fn destroy(self, device: &AshDevice) { - if self != Self::null() { - let ext_swapchain = device.ext_swapchain().unwrap(); - ext_swapchain.destroy_swapchain(self, None); + unsafe { + if self != Self::null() { + let ext_swapchain = device.ext_swapchain().unwrap(); + ext_swapchain.destroy_swapchain(self, None); + } } } } @@ -59,14 +63,16 @@ impl AshInstance { dpy: *mut vk::Display, window: vk::Window, ) -> Result { - let functions = check_and_get_instance_extension!(self => khr::xlib_surface); - let surface = functions.create_xlib_surface( - &vk::XlibSurfaceCreateInfoKHR::default() - .dpy(dpy) - .window(window), - None, - )?; - Ok(surface) + unsafe { + let functions = check_and_get_instance_extension!(self => khr::xlib_surface); + let surface = functions.create_xlib_surface( + &vk::XlibSurfaceCreateInfoKHR::default() + .dpy(dpy) + .window(window), + None, + )?; + Ok(surface) + } } #[cfg(all( @@ -80,14 +86,16 @@ impl AshInstance { connection: *mut vk::xcb_connection_t, window: vk::xcb_window_t, ) -> Result { - let functions = check_and_get_instance_extension!(self => khr::xcb_surface); - let surface = functions.create_xcb_surface( - &vk::XcbSurfaceCreateInfoKHR::default() - .connection(connection) - .window(window), - None, - )?; - Ok(surface) + unsafe { + let functions = check_and_get_instance_extension!(self => khr::xcb_surface); + let surface = functions.create_xcb_surface( + &vk::XcbSurfaceCreateInfoKHR::default() + .connection(connection) + .window(window), + None, + )?; + Ok(surface) + } } #[cfg(all( @@ -101,14 +109,16 @@ impl AshInstance { display: *mut vk::wl_display, surface: *mut vk::wl_surface, ) -> Result { - let functions = check_and_get_instance_extension!(self => khr::wayland_surface); - let surface = functions.create_wayland_surface( - &vk::WaylandSurfaceCreateInfoKHR::default() - .display(display) - .surface(surface), - None, - )?; - Ok(surface) + unsafe { + let functions = check_and_get_instance_extension!(self => khr::wayland_surface); + let surface = functions.create_wayland_surface( + &vk::WaylandSurfaceCreateInfoKHR::default() + .display(display) + .surface(surface), + None, + )?; + Ok(surface) + } } #[cfg(target_os = "android")] @@ -164,67 +174,69 @@ impl AshInstance { raw_display_handle: RawDisplayHandle, raw_window_handle: RawWindowHandle, ) -> Result { - // check for surface-extension - self.ext_surface()?; - - match (raw_display_handle, raw_window_handle) { - #[cfg(all( - unix, - not(target_os = "android"), - not(target_os = "macos"), - not(target_os = "ios") - ))] - (RawDisplayHandle::Xlib(d), RawWindowHandle::Xlib(w)) => self.create_surface_xlib( - d.display.ok_or(Error::WindowNotAvailable)?.as_ptr().cast(), - w.window, - ), - #[cfg(all( - unix, - not(target_os = "android"), - not(target_os = "macos"), - not(target_os = "ios") - ))] - (RawDisplayHandle::Xcb(d), RawWindowHandle::Xcb(w)) => self.create_surface_xcb( - d.connection.ok_or(Error::WindowNotAvailable)?.as_ptr(), - w.window.get(), - ), - #[cfg(all( - unix, - not(target_os = "android"), - not(target_os = "macos"), - not(target_os = "ios") - ))] - (RawDisplayHandle::Wayland(d), RawWindowHandle::Wayland(w)) => { - self.create_surface_wayland(d.display.as_ptr(), w.surface.as_ptr()) - } - #[cfg(target_os = "android")] - (RawDisplayHandle::Android(_), RawWindowHandle::AndroidNdk(w)) => { - self.create_surface_android(w.a_native_window) - } - #[cfg(target_os = "windows")] - (RawDisplayHandle::Windows(_), RawWindowHandle::Windows(w)) => { - self.create_surface_win32(w.hinstance, w.hwnd)? - } - #[cfg(target_os = "macos")] - (RawDisplayHandle::AppKit(_), RawWindowHandle::AppKit(h)) => { - use raw_window_metal::{appkit, Layer}; - let layer = match appkit::metal_layer_from_handle(h) { - Layer::Existing(layer) | Layer::Allocated(layer) => layer.cast(), - Layer::None => return Err(vk::Result::ERROR_INITIALIZATION_FAILED), - }; - self.create_surface_metal(layer)? - } - #[cfg(target_os = "ios")] - (RawDisplayHandle::UiKit(_), RawWindowHandle::UiKit(w)) => { - use raw_window_metal::{uikit, Layer}; - let layer = match uikit::metal_layer_from_handle(h) { - Layer::Existing(layer) | Layer::Allocated(layer) => layer.cast(), - Layer::None => return Err(vk::Result::ERROR_INITIALIZATION_FAILED), - }; - self.create_surface_metal(layer)? - } + unsafe { + // check for surface-extension + self.ext_surface()?; + + match (raw_display_handle, raw_window_handle) { + #[cfg(all( + unix, + not(target_os = "android"), + not(target_os = "macos"), + not(target_os = "ios") + ))] + (RawDisplayHandle::Xlib(d), RawWindowHandle::Xlib(w)) => self.create_surface_xlib( + d.display.ok_or(Error::WindowNotAvailable)?.as_ptr().cast(), + w.window, + ), + #[cfg(all( + unix, + not(target_os = "android"), + not(target_os = "macos"), + not(target_os = "ios") + ))] + (RawDisplayHandle::Xcb(d), RawWindowHandle::Xcb(w)) => self.create_surface_xcb( + d.connection.ok_or(Error::WindowNotAvailable)?.as_ptr(), + w.window.get(), + ), + #[cfg(all( + unix, + not(target_os = "android"), + not(target_os = "macos"), + not(target_os = "ios") + ))] + (RawDisplayHandle::Wayland(d), RawWindowHandle::Wayland(w)) => { + self.create_surface_wayland(d.display.as_ptr(), w.surface.as_ptr()) + } + #[cfg(target_os = "android")] + (RawDisplayHandle::Android(_), RawWindowHandle::AndroidNdk(w)) => { + self.create_surface_android(w.a_native_window) + } + #[cfg(target_os = "windows")] + (RawDisplayHandle::Windows(_), RawWindowHandle::Windows(w)) => { + self.create_surface_win32(w.hinstance, w.hwnd)? + } + #[cfg(target_os = "macos")] + (RawDisplayHandle::AppKit(_), RawWindowHandle::AppKit(h)) => { + use raw_window_metal::{Layer, appkit}; + let layer = match appkit::metal_layer_from_handle(h) { + Layer::Existing(layer) | Layer::Allocated(layer) => layer.cast(), + Layer::None => return Err(vk::Result::ERROR_INITIALIZATION_FAILED), + }; + self.create_surface_metal(layer)? + } + #[cfg(target_os = "ios")] + (RawDisplayHandle::UiKit(_), RawWindowHandle::UiKit(w)) => { + use raw_window_metal::{Layer, uikit}; + let layer = match uikit::metal_layer_from_handle(h) { + Layer::Existing(layer) | Layer::Allocated(layer) => layer.cast(), + Layer::None => return Err(vk::Result::ERROR_INITIALIZATION_FAILED), + }; + self.create_surface_metal(layer)? + } - _ => Err(Error::UnsupportedWindowSystem), + _ => Err(Error::UnsupportedWindowSystem), + } } } @@ -234,17 +246,19 @@ impl AshInstance { display_handle: DisplayHandle<'_>, window_handle: WindowHandle<'_>, ) -> Result> { - fn map_handle_error(e: raw_window_handle::HandleError) -> Error { - use raw_window_handle::HandleError; - match e { - HandleError::Unavailable => Error::WindowNotAvailable, - _ => Error::UnsupportedWindowSystem, + unsafe { + fn map_handle_error(e: raw_window_handle::HandleError) -> Error { + use raw_window_handle::HandleError; + match e { + HandleError::Unavailable => Error::WindowNotAvailable, + _ => Error::UnsupportedWindowSystem, + } } + let raw_display_handle = display_handle.as_raw(); + let raw_window_handle = window_handle.as_raw(); + let surface_raw = self.create_surface_raw(raw_display_handle, raw_window_handle)?; + Ok(Guard::new(self, surface_raw)) } - let raw_display_handle = display_handle.as_raw(); - let raw_window_handle = window_handle.as_raw(); - let surface_raw = self.create_surface_raw(raw_display_handle, raw_window_handle)?; - Ok(Guard::new(self, surface_raw)) } } @@ -453,28 +467,30 @@ impl AshSurfaceSwapchain { /// #SAFETY: there must not be any outstanding operations pub unsafe fn destroy_with_surface(mut self, res: &mut AshResources) -> Result<()> { - let swapchain = self.swapchain_raw; - for texture_id in self.textures.drain(..) { - res.textures.remove(texture_id); // forget texture without destroy! - } - for image_view in self.image_views.drain(..) { - res.device().destroy_image_view(image_view, None); - } - self.images.clear(); // images owned by swapchain! - if swapchain != vk::SwapchainKHR::null() { - self.swapchain_raw = vk::SwapchainKHR::null(); - res.device() - .ext_swapchain()? - .destroy_swapchain(swapchain, None); - } - let surface = self.surface_raw; - if surface != vk::SurfaceKHR::null() { - self.surface_raw = vk::SurfaceKHR::null(); - if let Ok(ext_surface) = res.device().instance().ext_surface() { - ext_surface.destroy_surface(surface, None); + unsafe { + let swapchain = self.swapchain_raw; + for texture_id in self.textures.drain(..) { + res.textures.remove(texture_id); // forget texture without destroy! + } + for image_view in self.image_views.drain(..) { + res.device().destroy_image_view(image_view, None); + } + self.images.clear(); // images owned by swapchain! + if swapchain != vk::SwapchainKHR::null() { + self.swapchain_raw = vk::SwapchainKHR::null(); + res.device() + .ext_swapchain()? + .destroy_swapchain(swapchain, None); + } + let surface = self.surface_raw; + if surface != vk::SurfaceKHR::null() { + self.surface_raw = vk::SurfaceKHR::null(); + if let Ok(ext_surface) = res.device().instance().ext_surface() { + ext_surface.destroy_surface(surface, None); + } } + Ok(()) } - Ok(()) } fn configure_with(&mut self, res: &mut AshResources, window: &Window) -> Result<()> { @@ -687,13 +703,14 @@ impl AshRendererFull { window: &Window, surface: Guard<'_, vk::SurfaceKHR>, ) -> Result<&mut AshSurfaceSwapchain> { - assert!(self - .surfaces - .insert( - window_id, - AshSurfaceSwapchain::new_unconfigured(surface.take()) - ) - .is_none()); + assert!( + self.surfaces + .insert( + window_id, + AshSurfaceSwapchain::new_unconfigured(surface.take()) + ) + .is_none() + ); let swapchain = self.surfaces.get_mut(window_id).unwrap(); swapchain.configure_with(&mut self.res, window)?; Ok(swapchain) diff --git a/crates/render-pipeline-core/src/common.rs b/crates/render-pipeline-core/src/common.rs index 4bacf98..5a01261 100644 --- a/crates/render-pipeline-core/src/common.rs +++ b/crates/render-pipeline-core/src/common.rs @@ -1,7 +1,7 @@ -use pulz_ecs::{prelude::Module, Entity}; +use pulz_ecs::{Entity, prelude::Module}; use pulz_render::{ - draw::{PhaseItem, PhaseModule}, RenderModule, + draw::{PhaseItem, PhaseModule}, }; pub struct Opaque { diff --git a/crates/render-pipeline-core/src/core_3d/mod.rs b/crates/render-pipeline-core/src/core_3d/mod.rs index 8469d78..53dc70f 100644 --- a/crates/render-pipeline-core/src/core_3d/mod.rs +++ b/crates/render-pipeline-core/src/core_3d/mod.rs @@ -1,14 +1,14 @@ use pulz_ecs::prelude::*; use pulz_render::{ + RenderSystemPhase, camera::{Camera, RenderTarget}, graph::{ - pass::{builder::PassBuilder, run::PassExec, Graphics, Pass}, - resources::WriteSlot, RenderGraphBuilder, + pass::{Graphics, Pass, builder::PassBuilder, run::PassExec}, + resources::WriteSlot, }, math::Mat4, texture::Texture, - RenderSystemPhase, }; pub use crate::common::*; diff --git a/crates/render-pipeline-core/src/deferred_3d/mod.rs b/crates/render-pipeline-core/src/deferred_3d/mod.rs index ad70a0f..c5462bb 100644 --- a/crates/render-pipeline-core/src/deferred_3d/mod.rs +++ b/crates/render-pipeline-core/src/deferred_3d/mod.rs @@ -1,18 +1,18 @@ use pulz_ecs::prelude::*; use pulz_render::{ + RenderSystemPhase, camera::{Camera, RenderTarget}, graph::{ + RenderGraphBuilder, pass::{ + Graphics, Pass, PassGroup, builder::{PassBuilder, PassGroupBuilder}, run::PassExec, - Graphics, Pass, PassGroup, }, resources::{Slot, WriteSlot}, - RenderGraphBuilder, }, math::Mat4, texture::Texture, - RenderSystemPhase, }; pub use crate::common::*; diff --git a/crates/render-wgpu/Cargo.toml b/crates/render-wgpu/Cargo.toml index 34a4216..62e937d 100644 --- a/crates/render-wgpu/Cargo.toml +++ b/crates/render-wgpu/Cargo.toml @@ -7,6 +7,10 @@ edition.workspace = true repository.workspace = true readme = "README.md" +[features] +# uncomment if Uncomment once we get to https://github.com/gfx-rs/wgpu/issues/5974 is resolved +# trace = ["wgpu/trace"] + [dependencies] pulz-ecs = { path = "../ecs" } pulz-window = { path = "../window" } @@ -15,15 +19,14 @@ pulz-render = { path = "../render" } thiserror = { workspace = true } tracing = { workspace = true } slotmap = { workspace = true } -wgpu = "23.0" +wgpu = "25.0" raw-window-handle = { workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] pollster = "0.4" [dev-dependencies] -anyhow = { workspace = true } -naga = "23.0" +naga = "25.0" pulz-window-winit = { path = "../window-winit" } pulz-render-pipeline-core = { path = "../render-pipeline-core" } @@ -31,7 +34,7 @@ pulz-render-pipeline-core = { path = "../render-pipeline-core" } tracing-subscriber = { workspace = true } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wgpu = { version = "23.0" , features = ["webgl"] } +wgpu = { version = "25.0" , features = ["webgl"] } tracing-wasm = { workspace = true } tracing-log = { workspace = true } console_error_panic_hook = { workspace = true } diff --git a/crates/render-wgpu/examples/render-wgpu-demo.rs b/crates/render-wgpu/examples/render-wgpu-demo.rs index b5193c5..f0abee2 100644 --- a/crates/render-wgpu/examples/render-wgpu-demo.rs +++ b/crates/render-wgpu/examples/render-wgpu-demo.rs @@ -5,7 +5,7 @@ use pulz_render::camera::{Camera, RenderTarget}; use pulz_render_pipeline_core::core_3d::CoreShadingModule; use pulz_render_wgpu::WgpuRenderer; use pulz_window::{WindowAttributes, WindowId, WindowModule}; -use pulz_window_winit::{winit::event_loop::EventLoop, Application}; +use pulz_window_winit::{Application, winit::event_loop::EventLoop}; use tracing::*; async fn init() -> Resources { @@ -40,7 +40,7 @@ fn setup_demo_scene(resources: &mut Resources, window: WindowId) { #[cfg(not(target_arch = "wasm32"))] fn main() -> Result<(), Box> { // todo: run blocking! - use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter}; + use tracing_subscriber::{EnvFilter, fmt::format::FmtSpan}; let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); tracing_subscriber::fmt() .with_env_filter(env_filter) diff --git a/crates/render-wgpu/src/convert.rs b/crates/render-wgpu/src/convert.rs index 762c26d..332c2b1 100644 --- a/crates/render-wgpu/src/convert.rs +++ b/crates/render-wgpu/src/convert.rs @@ -247,7 +247,7 @@ fn convert_vertex_format(val: VertexFormat) -> Result { VertexFormat::Float64x3 => wgpu::VertexFormat::Float64x3, VertexFormat::Float64x4 => wgpu::VertexFormat::Float64x4, - _ => panic!("unsupported vertex format: {:?}", val), + _ => panic!("unsupported vertex format: {val:?}"), }) } diff --git a/crates/render-wgpu/src/lib.rs b/crates/render-wgpu/src/lib.rs index 6287cf3..ba4bcf8 100644 --- a/crates/render-wgpu/src/lib.rs +++ b/crates/render-wgpu/src/lib.rs @@ -28,10 +28,10 @@ use convert::ConversionError; use graph::WgpuRenderGraph; use pulz_ecs::prelude::*; -use pulz_render::{draw::DrawPhases, graph::RenderGraph, RenderModule, RenderSystemPhase}; +use pulz_render::{RenderModule, RenderSystemPhase, draw::DrawPhases, graph::RenderGraph}; use pulz_window::{ - listener::WindowSystemListener, DisplayHandle, Window, WindowHandle, WindowId, Windows, - WindowsMirror, + DisplayHandle, Window, WindowHandle, WindowId, Windows, WindowsMirror, + listener::WindowSystemListener, }; use resources::WgpuResources; use surface::Surface; @@ -48,9 +48,6 @@ mod surface; #[derive(Error, Debug)] #[non_exhaustive] pub enum Error { - #[error("No suitable GPU adapters found on the system!")] - NoAdapter, - #[error("Unable to request a suitable device!")] NoDevice, @@ -60,7 +57,10 @@ pub enum Error { #[error("The used Window-System is not supported")] UnsupportedWindowSystem, - #[error("Unable to create surface")] + #[error("Unable to request an Adapter: {0}")] + RequestAdapterError(#[from] wgpu::RequestAdapterError), + + #[error("Unable to create surface: {0}")] CreateSurfaceError(#[from] wgpu::CreateSurfaceError), #[error("Unable to convert objects")] @@ -91,19 +91,28 @@ struct WgpuRendererFull { impl WgpuRendererFull { async fn for_adapter(instance: wgpu::Instance, adapter: wgpu::Adapter) -> Result { - let trace_dir = std::env::var("WGPU_TRACE"); + #[cfg(feature = "trace")] + let trace = if let Some(path) = std::env::var_os("WGPU_TRACE").map(std::path::PathBuf::from) + { + wgpu::Trace::Dictionary(path) + } else { + wgpu::Trace::Off + }; + + #[cfg(not(feature = "trace"))] + let trace = wgpu::Trace::Off; + let (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - label: None, - required_features: wgpu::Features::empty(), - // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain. - required_limits: wgpu::Limits::downlevel_defaults() - .using_resolution(adapter.limits()), - memory_hints: MemoryHints::Performance, - }, - trace_dir.ok().as_ref().map(std::path::Path::new), - ) + .request_device(&wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain. + required_limits: wgpu::Limits::downlevel_defaults() + .using_resolution(adapter.limits()) + .using_alignment(adapter.limits()), + memory_hints: MemoryHints::Performance, + trace, + }) .await?; Ok(Self { @@ -146,7 +155,7 @@ impl WgpuRendererFull { .get_current_texture() .expect("Failed to acquire next surface texture!") } - Err(e) => panic!("unable to aquire next frame: {}", e), // TODO: better error handling + Err(e) => panic!("unable to aquire next frame: {e}"), // TODO: better error handling }; self.tmp_surface_textures.push(tex); } @@ -198,11 +207,13 @@ pub struct WgpuRenderer(WgpuRendererInner); impl WgpuRenderer { pub async fn new() -> Result { - let backends = wgpu::util::backend_bits_from_env().unwrap_or(wgpu::Backends::PRIMARY); - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends, - ..Default::default() - }); + let instance = wgpu::Instance::new( + &wgpu::InstanceDescriptor { + backends: wgpu::Backends::PRIMARY, + ..Default::default() + } + .with_env(), + ); #[cfg(target_arch = "wasm32")] { // full-initialization on wasm32: @@ -226,43 +237,48 @@ impl WgpuRenderer { display_handle: DisplayHandle<'_>, window_handle: WindowHandle<'_>, ) -> Result<&mut WgpuRendererFull> { - if let WgpuRendererInner::Full(renderer) = &mut self.0 { - renderer.surfaces.remove(window_id); // replaces old surface - let surface = Surface::create( - &renderer.instance, - window_descriptor, - display_handle, - window_handle, - )?; - renderer.surfaces.insert(window_id, surface); - } else { - // Delayed initialization - #[cfg(not(target_arch = "wasm32"))] - { - let WgpuRendererInner::Early { instance } = - std::mem::replace(&mut self.0, WgpuRendererInner::Tmp) - else { - panic!("unexpected state"); - }; - let surface = - Surface::create(&instance, window_descriptor, display_handle, window_handle)?; - let mut renderer = pollster::block_on(async { - let adapter = wgpu::util::initialize_adapter_from_env_or_default( - &instance, - Some(&surface), - ) - .await - .ok_or(Error::NoAdapter)?; - WgpuRendererFull::for_adapter(instance, adapter).await - })?; + unsafe { + if let WgpuRendererInner::Full(renderer) = &mut self.0 { + renderer.surfaces.remove(window_id); // replaces old surface + let surface = Surface::create( + &renderer.instance, + window_descriptor, + display_handle, + window_handle, + )?; renderer.surfaces.insert(window_id, surface); - self.0 = WgpuRendererInner::Full(renderer); + } else { + // Delayed initialization + #[cfg(not(target_arch = "wasm32"))] + { + let WgpuRendererInner::Early { instance } = + std::mem::replace(&mut self.0, WgpuRendererInner::Tmp) + else { + panic!("unexpected state"); + }; + let surface = Surface::create( + &instance, + window_descriptor, + display_handle, + window_handle, + )?; + let mut renderer = pollster::block_on(async { + let adapter = wgpu::util::initialize_adapter_from_env_or_default( + &instance, + Some(&surface), + ) + .await?; + WgpuRendererFull::for_adapter(instance, adapter).await + })?; + renderer.surfaces.insert(window_id, surface); + self.0 = WgpuRendererInner::Full(renderer); + } } + let WgpuRendererInner::Full(renderer) = &mut self.0 else { + unreachable!() + }; + Ok(renderer) } - let WgpuRendererInner::Full(renderer) = &mut self.0 else { - unreachable!() - }; - Ok(renderer) } fn init(&mut self) -> Result<&mut WgpuRendererFull> { @@ -274,9 +290,8 @@ impl WgpuRenderer { panic!("unexpected state"); }; let renderer = pollster::block_on(async { - let adapter = wgpu::util::initialize_adapter_from_env_or_default(&instance, None) - .await - .ok_or(Error::NoAdapter)?; + let adapter = + wgpu::util::initialize_adapter_from_env_or_default(&instance, None).await?; WgpuRendererFull::for_adapter(instance, adapter).await })?; self.0 = WgpuRendererInner::Full(renderer); diff --git a/crates/render-wgpu/src/resources.rs b/crates/render-wgpu/src/resources.rs index 1d4f75a..7407383 100644 --- a/crates/render-wgpu/src/resources.rs +++ b/crates/render-wgpu/src/resources.rs @@ -7,7 +7,7 @@ use pulz_render::{ }; use slotmap::SlotMap; -use crate::{convert as c, Result}; +use crate::{Result, convert as c}; pub trait WgpuResource: GpuResource + 'static { type Wgpu: 'static; diff --git a/crates/render/Cargo.toml b/crates/render/Cargo.toml index 7459ccc..069ad6c 100644 --- a/crates/render/Cargo.toml +++ b/crates/render/Cargo.toml @@ -29,7 +29,6 @@ fnv = { workspace = true } bitflags = { workspace = true, features = ["serde"] } thiserror = { workspace = true } tracing = { workspace = true } -downcast-rs = { workspace = true } crossbeam-queue = { workspace = true } encase = { workspace = true } diff --git a/crates/render/macros/src/binding_layout.rs b/crates/render/macros/src/binding_layout.rs index abaacea..73118a8 100644 --- a/crates/render/macros/src/binding_layout.rs +++ b/crates/render/macros/src/binding_layout.rs @@ -1,6 +1,6 @@ -use darling::{export::NestedMeta, Error, FromMeta, Result, ToTokens}; +use darling::{Error, FromMeta, Result, ToTokens, export::NestedMeta}; use proc_macro2::{Ident, Span, TokenStream}; -use quote::{quote, TokenStreamExt}; +use quote::{TokenStreamExt, quote}; use syn::{Attribute, Data, DeriveInput, Expr, Fields, Lit, LitInt, Meta}; use crate::utils::resolve_render_crate; diff --git a/crates/render/macros/src/lib.rs b/crates/render/macros/src/lib.rs index 20f816f..1e18890 100644 --- a/crates/render/macros/src/lib.rs +++ b/crates/render/macros/src/lib.rs @@ -1,7 +1,7 @@ #![cfg_attr(feature = "unstable", feature(proc_macro_span))] use proc_macro::TokenStream; -use syn::{parse_macro_input, DeriveInput}; +use syn::{DeriveInput, parse_macro_input}; use utils::resolve_render_crate; #[macro_use] diff --git a/crates/render/macros/src/utils.rs b/crates/render/macros/src/utils.rs index 4f2059b..9a5e927 100644 --- a/crates/render/macros/src/utils.rs +++ b/crates/render/macros/src/utils.rs @@ -1,5 +1,5 @@ -use proc_macro2::{Ident, Span}; use proc_macro_crate::FoundCrate; +use proc_macro2::{Ident, Span}; use syn::{Error, Path, Result, Token}; #[cfg(test)] diff --git a/crates/render/src/camera.rs b/crates/render/src/camera.rs index dc3ee4e..eeaa776 100644 --- a/crates/render/src/camera.rs +++ b/crates/render/src/camera.rs @@ -1,6 +1,6 @@ use pulz_assets::{Assets, Handle}; use pulz_ecs::prelude::*; -use pulz_transform::math::{size2, Mat4, Size2}; +use pulz_transform::math::{Mat4, Size2, size2}; use pulz_window::WindowId; use crate::{ diff --git a/crates/render/src/draw.rs b/crates/render/src/draw.rs index 967057e..2d83175 100644 --- a/crates/render/src/draw.rs +++ b/crates/render/src/draw.rs @@ -6,11 +6,11 @@ use std::{ }; use atomic_refcell::AtomicRefCell; -use dynsequence::{dyn_sequence, DynSequence}; +use dynsequence::{DynSequence, dyn_sequence}; use fnv::FnvHashMap as HashMap; use pulz_ecs::{prelude::*, resource::ResState, system::data::SystemData}; -use crate::{backend::CommandEncoder, utils::hash::TypeIdHashMap, RenderSystemPhase}; +use crate::{RenderSystemPhase, backend::CommandEncoder, utils::hash::TypeIdHashMap}; pub type DrawContext<'a> = &'a mut (dyn CommandEncoder + 'a); diff --git a/crates/render/src/graph/builder.rs b/crates/render/src/graph/builder.rs index 7ecd2bc..f1f4266 100644 --- a/crates/render/src/graph/builder.rs +++ b/crates/render/src/graph/builder.rs @@ -1,5 +1,5 @@ use std::{ - collections::{hash_map::DefaultHasher, VecDeque}, + collections::{VecDeque, hash_map::DefaultHasher}, hash::{Hash, Hasher}, }; @@ -7,10 +7,10 @@ use pulz_bitset::BitSet; use tracing::{debug, trace}; use super::{ + RenderGraph, RenderGraphBuilder, access::ResourceAccess, deps::DependencyMatrix, resources::{ExtendedResourceData, Slot}, - RenderGraph, RenderGraphBuilder, }; use crate::{buffer::Buffer, texture::Texture}; diff --git a/crates/render/src/graph/mod.rs b/crates/render/src/graph/mod.rs index 91dc86f..8fac172 100644 --- a/crates/render/src/graph/mod.rs +++ b/crates/render/src/graph/mod.rs @@ -2,7 +2,7 @@ use core::fmt; use self::{ access::Access, - pass::{run::PassExec, PipelineBindPoint}, + pass::{PipelineBindPoint, run::PassExec}, resources::{ExtendedResourceData, Resource, ResourceDeps, ResourceSet}, }; use crate::{ @@ -145,11 +145,7 @@ impl RenderGraph { pub fn get_topo_group_for_pass(&self, pass_index: PassIndex) -> Option { let g = *self.pass_topo_group.get(pass_index as usize)?; - if g == !0 { - None - } else { - Some(g) - } + if g == !0 { None } else { Some(g) } } pub fn execute_sub_pass( diff --git a/crates/render/src/graph/pass/builder.rs b/crates/render/src/graph/pass/builder.rs index c014894..e63906f 100644 --- a/crates/render/src/graph/pass/builder.rs +++ b/crates/render/src/graph/pass/builder.rs @@ -4,9 +4,9 @@ use super::{Graphics, Pass, PassGroup, PipelineType, SubPassDescription}; use crate::{ buffer::Buffer, graph::{ + PassDescription, PassIndex, RenderGraphBuilder, SubPassIndex, access::{Access, ShaderStage}, resources::{ResourceDeps, Slot, SlotAccess, WriteSlot}, - PassDescription, PassIndex, RenderGraphBuilder, SubPassIndex, }, texture::{Texture, TextureDimensions, TextureFormat}, }; diff --git a/crates/render/src/graph/resources.rs b/crates/render/src/graph/resources.rs index 2f252df..bebea1f 100644 --- a/crates/render/src/graph/resources.rs +++ b/crates/render/src/graph/resources.rs @@ -11,11 +11,11 @@ use pulz_bitset::BitSet; use pulz_window::WindowId; use super::{ + PASS_UNDEFINED, PassDescription, PassIndex, RenderGraph, ResourceIndex, SUBPASS_UNDEFINED, + SubPassIndex, access::{Access, ResourceAccess, Stage}, builder::{GraphExport, GraphImport}, deps::DependencyMatrix, - PassDescription, PassIndex, RenderGraph, ResourceIndex, SubPassIndex, PASS_UNDEFINED, - SUBPASS_UNDEFINED, }; use crate::{ backend::PhysicalResourceResolver, diff --git a/crates/render/src/pipeline/graphics_pass.rs b/crates/render/src/pipeline/graphics_pass.rs index 303d1f7..211f370 100644 --- a/crates/render/src/pipeline/graphics_pass.rs +++ b/crates/render/src/pipeline/graphics_pass.rs @@ -3,10 +3,10 @@ use serde::{Deserialize, Serialize}; use crate::{ graph::{ + PassDescription, RenderGraph, ResourceIndex, access::Access, pass::PipelineBindPoint, resources::{PhysicalResourceAccessTracker, PhysicalResources}, - PassDescription, RenderGraph, ResourceIndex, }, texture::TextureFormat, }; diff --git a/crates/render/src/shader/mod.rs b/crates/render/src/shader/mod.rs index 8016485..7cf38e4 100644 --- a/crates/render/src/shader/mod.rs +++ b/crates/render/src/shader/mod.rs @@ -5,7 +5,7 @@ mod preprocessor; use serde::{Deserialize, Serialize}; mod encase { - pub use ::encase::{private, ShaderSize, ShaderType}; + pub use ::encase::{ShaderSize, ShaderType, private}; } pub use ::pulz_render_macros::ShaderType; diff --git a/crates/render/src/texture/image.rs b/crates/render/src/texture/image.rs index 4f19662..def7aa3 100644 --- a/crates/render/src/texture/image.rs +++ b/crates/render/src/texture/image.rs @@ -1,4 +1,4 @@ -use image::{buffer::ConvertBuffer, ImageFormat}; +use image::{ImageFormat, buffer::ConvertBuffer}; use thiserror::Error; use super::{ImageDataLayout, TextureDescriptor, TextureDimensions, TextureFormat}; diff --git a/crates/window-winit/examples/window-winit-demo.rs b/crates/window-winit/examples/window-winit-demo.rs index 57eed49..0afbb0a 100644 --- a/crates/window-winit/examples/window-winit-demo.rs +++ b/crates/window-winit/examples/window-winit-demo.rs @@ -21,7 +21,7 @@ fn init() -> Resources { #[cfg(not(target_arch = "wasm32"))] fn main() -> Result<(), Box> { - use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter}; + use tracing_subscriber::{EnvFilter, fmt::format::FmtSpan}; let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); tracing_subscriber::fmt() .with_env_filter(env_filter) diff --git a/crates/window-winit/src/lib.rs b/crates/window-winit/src/lib.rs index 752fb33..00271de 100644 --- a/crates/window-winit/src/lib.rs +++ b/crates/window-winit/src/lib.rs @@ -30,7 +30,7 @@ use std::collections::HashMap; use pulz_ecs::{prelude::*, resource::RemovedResource}; use pulz_window::{ - listener::WindowSystemListener, Window, WindowAttributes, WindowId, Windows, WindowsMirror, + Window, WindowAttributes, WindowId, Windows, WindowsMirror, listener::WindowSystemListener, }; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use tracing::{debug, info, warn}; @@ -241,7 +241,7 @@ impl Application { .borrow_res_mut_id(self.windows_resource_id) .expect("Windows"); while let Some((id, window, window_attributes)) = windows.pop_next_window_to_create() { - if window.is_pending && !window.is_close_requested & &!self.window_map.contains_id(id) { + if window.is_pending && !window.is_close_requested && !self.window_map.contains_id(id) { let winit_window_attributes = Self::winit_window_attributes_from_attributes(window_attributes); let winit_window = self diff --git a/crates/window/src/window.rs b/crates/window/src/window.rs index ce45dae..defad23 100644 --- a/crates/window/src/window.rs +++ b/crates/window/src/window.rs @@ -1,7 +1,7 @@ use std::{borrow::Cow, collections::VecDeque}; -use pulz_ecs::{module::ModuleWithOutput, Component}; -use slotmap::{new_key_type, SlotMap}; +use pulz_ecs::{Component, module::ModuleWithOutput}; +use slotmap::{SlotMap, new_key_type}; use crate::Size2; diff --git a/examples/android/Cargo.toml b/examples/android/Cargo.toml index db40dc1..df91ced 100644 --- a/examples/android/Cargo.toml +++ b/examples/android/Cargo.toml @@ -9,7 +9,7 @@ repository.workspace = true [lib] name="pulzdemo_android" -crate_type=["cdylib"] +crate-type=["cdylib"] [dependencies] pulz-ecs = { path = "../../crates/ecs" } @@ -20,5 +20,5 @@ pulz-window = { path = "../../crates/window" } pulz-window-winit = { path = "../../crates/window-winit", features = ["android-game-activity"]} log = "0.4" -android_logger = "0.14" +android_logger = "0.15" tracing = { workspace = true, features = ["log"] }