diff --git a/Cargo.lock b/Cargo.lock index 3c7c813b..f33756f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,6 +120,15 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "ash" +version = "0.37.3+1.3.251" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" +dependencies = [ + "libloading 0.7.4", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -279,6 +288,15 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +[[package]] +name = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.0" @@ -340,15 +358,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", @@ -357,15 +375,15 @@ dependencies = [ [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-core", "futures-macro", @@ -392,6 +410,36 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.4.1", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-ash" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2424bc9be88170e1a56e57c25d3d0e2dfdd22e8f328e892786aeb4da1415732" +dependencies = [ + "ash", + "gpu-alloc-types", + "tinyvec", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.4.1", +] + [[package]] name = "hashbrown" version = "0.14.2" @@ -457,6 +505,7 @@ dependencies = [ "algorithms", "anyhow", "arrayvec", + "ash", "backtrace", "bincode", "bitflags 2.4.1", @@ -468,11 +517,13 @@ dependencies = [ "default-config", "dirs", "futures-util", + "gpu-alloc", + "gpu-alloc-ash", "humantime", "indexmap", "isnt", "jay-config", - "libloading", + "libloading 0.8.1", "log", "num-derive", "num-traits", @@ -481,6 +532,7 @@ dependencies = [ "pin-project", "rand", "repc", + "shaderc", "smallvec", "thiserror", "uapi", @@ -515,6 +567,16 @@ version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + [[package]] name = "libloading" version = "0.8.1" @@ -785,6 +847,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e110b7d5a1335c2e801176c42a626a905c23eecdee104d9bdfbd6ea5f0b8368" +[[package]] +name = "roxmltree" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b" +dependencies = [ + "xmlparser", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -830,6 +901,27 @@ dependencies = [ "syn 2.0.38", ] +[[package]] +name = "shaderc" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31cef52787a0db5108788ea20bed13d6bf4b96287c5c5201e55725f7070f3443" +dependencies = [ + "libc", + "shaderc-sys", +] + +[[package]] +name = "shaderc-sys" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e8f8439fffcffd6efcd74197204addf935dbab5752696bd990a6cd36d54cf64" +dependencies = [ + "cmake", + "libc", + "roxmltree", +] + [[package]] name = "slab" version = "0.4.9" @@ -903,6 +995,21 @@ dependencies = [ "syn 2.0.38", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "uapi" version = "0.2.10" @@ -1013,6 +1120,28 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.51.1" @@ -1087,3 +1216,9 @@ name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" diff --git a/Cargo.toml b/Cargo.toml index c2f2e8d2..404f2ec1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,11 +43,15 @@ chrono = "0.4.19" parking_lot = "0.12.1" arrayvec = "0.7.4" indexmap = "2.1.0" +ash = "0.37.3" +gpu-alloc = "0.6.0" +gpu-alloc-ash = "0.6.0" [build-dependencies] repc = "0.1.1" anyhow = "1.0.52" bstr = { version = "1.1.0", default-features = false, features = ["std"] } +shaderc = "0.8.2" #[profile.dev.build-override] #opt-level = 3 diff --git a/build/build.rs b/build/build.rs index 254ca1fc..85580ae8 100644 --- a/build/build.rs +++ b/build/build.rs @@ -25,6 +25,7 @@ use std::{ mod egl; mod enums; mod tokens; +mod vulkan; mod wire; mod wire_dbus; mod wire_xcon; @@ -47,6 +48,7 @@ fn main() -> anyhow::Result<()> { wire_xcon::main()?; enums::main()?; egl::main()?; + vulkan::main()?; println!("cargo:rerun-if-changed=build/build.rs"); Ok(()) diff --git a/build/vulkan.rs b/build/vulkan.rs new file mode 100644 index 00000000..69f41aab --- /dev/null +++ b/build/vulkan.rs @@ -0,0 +1,38 @@ +use { + crate::open, + anyhow::{bail, Context}, + std::{io::Write, path::Path}, +}; + +const ROOT: &str = "src/gfx_apis/vulkan/shaders"; + +pub fn main() -> anyhow::Result<()> { + println!("cargo:rerun-if-changed={}", ROOT); + for shader in std::fs::read_dir(ROOT)? { + let shader = shader?; + let name = shader.file_name().to_string_lossy().into_owned(); + compile_shader(&name).context(name)?; + } + Ok(()) +} + +fn compile_shader(name: &str) -> anyhow::Result<()> { + let stage = match Path::new(name) + .extension() + .and_then(|e| e.to_str()) + .unwrap_or("") + { + "frag" => shaderc::ShaderKind::Fragment, + "vert" => shaderc::ShaderKind::Vertex, + n => bail!("Unknown shader stage {}", n), + }; + let src = std::fs::read_to_string(format!("{}/{}", ROOT, name))?; + let compiler = shaderc::Compiler::new().unwrap(); + let binary = compiler + .compile_into_spirv(&src, stage, name, "main", None) + .unwrap(); + let mut file = open(&format!("{}.spv", name))?; + file.write_all(binary.as_binary_u8())?; + file.flush()?; + Ok(()) +} diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index e303d759..306cbd65 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -15,7 +15,7 @@ use { timer::Timer, video::{ connector_type::{ConnectorType, CON_UNKNOWN}, - Connector, DrmDevice, Mode, + Connector, DrmDevice, GfxApi, Mode, }, Axis, Direction, ModifiedKeySym, PciId, Workspace, }, @@ -506,6 +506,10 @@ impl Client { self.send(&ClientMessage::MakeRenderDevice { device }); } + pub fn set_gfx_api(&self, device: Option, api: GfxApi) { + self.send(&ClientMessage::SetGfxApi { device, api }); + } + pub fn connector_connected(&self, connector: Connector) -> bool { let res = self.send_with_response(&ClientMessage::ConnectorConnected { connector }); get_response!(res, false, ConnectorConnected { connected }); diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index e5f2def8..e88b88aa 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -5,7 +5,7 @@ use { logging::LogLevel, theme::{colors::Colorable, sized::Resizable, Color}, timer::Timer, - video::{connector_type::ConnectorType, Connector, DrmDevice}, + video::{connector_type::ConnectorType, Connector, DrmDevice, GfxApi}, Axis, Direction, PciId, Workspace, }, bincode::{BorrowDecode, Decode, Encode}, @@ -330,6 +330,10 @@ pub enum ClientMessage<'a> { GetWorkspaceCapture { workspace: Workspace, }, + SetGfxApi { + device: Option, + api: GfxApi, + }, } #[derive(Encode, Decode, Debug)] diff --git a/jay-config/src/video.rs b/jay-config/src/video.rs index 5f9d494b..6d89c33a 100644 --- a/jay-config/src/video.rs +++ b/jay-config/src/video.rs @@ -362,4 +362,30 @@ impl DrmDevice { pub fn make_render_device(self) { get!().make_render_device(self); } + + /// Sets the preferred graphics API for this device. + /// + /// If the API cannot be used, the compositor will try other APIs. + pub fn set_gfx_api(self, gfx_api: GfxApi) { + get!().set_gfx_api(Some(self), gfx_api); + } +} + +/// A graphics API. +#[non_exhaustive] +#[derive(Encode, Decode, Copy, Clone, Debug, Hash, Eq, PartialEq)] +pub enum GfxApi { + OpenGl, + Vulkan, +} + +/// Sets the default graphics API. +/// +/// If the API cannot be used, the compositor will try other APIs. +/// +/// This setting can be overwritten per-device with [DrmDevice::set_gfx_api]. +/// +/// This call has no effect on devices that have already been initialized. +pub fn set_gfx_api(gfx_api: GfxApi) { + get!().set_gfx_api(None, gfx_api); } diff --git a/src/backend.rs b/src/backend.rs index 6666cedf..c076f424 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -6,6 +6,7 @@ use { ifs::wl_seat::wl_pointer::{CONTINUOUS, FINGER, HORIZONTAL_SCROLL, VERTICAL_SCROLL, WHEEL}, video::drm::{ConnectorType, DrmError, DrmVersion}, }, + jay_config::video::GfxApi, std::{ any::Any, error::Error, @@ -214,6 +215,7 @@ pub enum InputEvent { pub enum DrmEvent { #[allow(dead_code)] Removed, + GfxApiChanged, } pub trait BackendDrmDevice { @@ -221,6 +223,8 @@ pub trait BackendDrmDevice { fn event(&self) -> Option; fn on_change(&self, cb: Rc); fn dev_t(&self) -> c::dev_t; - fn make_render_device(self: Rc); + fn make_render_device(&self); + fn set_gfx_api(&self, api: GfxApi); + fn gtx_api(&self) -> GfxApi; fn version(&self) -> Result; } diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 9415172c..d56174af 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -9,7 +9,6 @@ use { edid::Descriptor, format::{Format, ARGB8888, XRGB8888}, gfx_api::{GfxContext, GfxFramebuffer, GfxTexture}, - gfx_apis::create_gfx_context, ifs::wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC}, renderer::RenderResult, state::State, @@ -34,6 +33,7 @@ use { ahash::{AHashMap, AHashSet}, bstr::{BString, ByteSlice}, indexmap::{indexset, IndexSet}, + jay_config::video::GfxApi, std::{ cell::{Cell, RefCell}, ffi::CString, @@ -75,7 +75,8 @@ pub struct MetalDrmDevice { pub cursor_height: u64, pub gbm: GbmDevice, pub handle_events: HandleEvents, - pub ctx: Rc, + pub ctx: CloneCell>, + pub on_change: OnChange, } impl BackendDrmDevice for MetalDrmDevice { @@ -84,19 +85,27 @@ impl BackendDrmDevice for MetalDrmDevice { } fn event(&self) -> Option { - None + self.on_change.events.pop() } - fn on_change(&self, _cb: Rc) { - // nothing + fn on_change(&self, cb: Rc) { + self.on_change.on_change.set(Some(cb)); } fn dev_t(&self) -> dev_t { self.devnum } - fn make_render_device(self: Rc) { - self.backend.make_render_device(&self, true); + fn make_render_device(&self) { + self.backend.make_render_device(&self, false); + } + + fn set_gfx_api(&self, api: GfxApi) { + self.backend.set_gfx_api(self, api) + } + + fn gtx_api(&self) -> GfxApi { + self.ctx.get().gfx.gfx_api() } fn version(&self) -> Result { @@ -162,8 +171,6 @@ pub struct MetalConnector { pub connector_id: ConnectorId, - pub events: SyncQueue, - pub buffers: CloneCell>>, pub next_buffer: NumCell, @@ -182,7 +189,7 @@ pub struct MetalConnector { pub crtc: CloneCell>>, - pub on_change: OnChange, + pub on_change: OnChange, pub present_trigger: AsyncEvent, @@ -271,12 +278,30 @@ impl Debug for ConnectorFutures { } } -#[derive(Default)] -pub struct OnChange { +pub struct OnChange { pub on_change: CloneCell>>, + pub events: SyncQueue, +} + +impl OnChange { + pub fn send_event(&self, event: T) { + self.events.push(event); + if let Some(cb) = self.on_change.get() { + cb(); + } + } } -impl Debug for OnChange { +impl Default for OnChange { + fn default() -> Self { + Self { + on_change: Default::default(), + events: Default::default(), + } + } +} + +impl Debug for OnChange { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self.on_change.get() { None => f.write_str("None"), @@ -311,7 +336,8 @@ impl MetalConnector { }) as _), _ => None, }; - self.send_event(ConnectorEvent::HardwareCursor(hc)); + self.on_change + .send_event(ConnectorEvent::HardwareCursor(hc)); } fn connected(&self) -> bool { @@ -321,13 +347,6 @@ impl MetalConnector { && self.primary_plane.get().is_some() } - fn send_event(&self, event: ConnectorEvent) { - self.events.push(event); - if let Some(oc) = self.on_change.on_change.get() { - oc(); - } - } - pub fn schedule_present(&self) { self.present_trigger.trigger(); } @@ -445,7 +464,7 @@ impl Connector for MetalConnector { } fn event(&self) -> Option { - self.events.pop() + self.on_change.events.pop() } fn on_change(&self, cb: Rc) { @@ -577,7 +596,6 @@ fn create_connector( dev: dev.clone(), backend: backend.clone(), connector_id: backend.state.connector_ids.next(), - events: Default::default(), buffers: Default::default(), next_buffer: Default::default(), enabled: Cell::new(true), @@ -910,7 +928,7 @@ impl MetalBackend { if let Some(r) = ctx .gfx .reset_status() - .or_else(|| dev.ctx.gfx.reset_status()) + .or_else(|| dev.ctx.get().gfx.reset_status()) { fatal!("EGL context has been reset: {:?}", r); } @@ -988,9 +1006,9 @@ impl MetalBackend { dev.futures.remove(&c); if let Some(c) = dev.connectors.remove(&c) { if c.connect_sent.get() { - c.send_event(ConnectorEvent::Disconnected); + c.on_change.send_event(ConnectorEvent::Disconnected); } - c.send_event(ConnectorEvent::Removed); + c.on_change.send_event(ConnectorEvent::Removed); } } let mut preserve = Preserve::default(); @@ -1012,7 +1030,7 @@ impl MetalBackend { || old.connection != ConnectorStatus::Connected || !old.is_same_monitor(&dd) { - c.send_event(ConnectorEvent::Disconnected); + c.on_change.send_event(ConnectorEvent::Disconnected); c.connect_sent.set(false); } else if preserve_any { preserve.connectors.insert(c.id); @@ -1054,15 +1072,17 @@ impl MetalBackend { modes.push(mode); } } - connector.send_event(ConnectorEvent::Connected(MonitorInfo { - modes, - manufacturer: dd.monitor_manufacturer.clone(), - product: dd.monitor_name.clone(), - serial_number: dd.monitor_serial_number.clone(), - initial_mode: dd.mode.clone().unwrap().to_backend(), - width_mm: dd.mm_width as _, - height_mm: dd.mm_height as _, - })); + connector + .on_change + .send_event(ConnectorEvent::Connected(MonitorInfo { + modes, + manufacturer: dd.monitor_manufacturer.clone(), + product: dd.monitor_name.clone(), + serial_number: dd.monitor_serial_number.clone(), + initial_mode: dd.mode.clone().unwrap().to_backend(), + width_mm: dd.mm_width as _, + height_mm: dd.mm_height as _, + })); connector.connect_sent.set(true); connector.send_hardware_cursor(); } @@ -1115,7 +1135,7 @@ impl MetalBackend { } } - let gfx = match create_gfx_context(master) { + let gfx = match self.state.create_gfx_context(master, None) { Ok(r) => r, Err(e) => return Err(MetalError::CreateRenderContex(e)), }; @@ -1148,7 +1168,8 @@ impl MetalBackend { handle_events: HandleEvents { handle_events: Cell::new(None), }, - ctx, + ctx: CloneCell::new(ctx), + on_change: Default::default(), }); let (connectors, futures) = get_connectors(self, &dev, &resources.connectors)?; @@ -1440,28 +1461,68 @@ impl MetalBackend { } } - fn make_render_device(&self, dev: &Rc, log: bool) -> bool { - if let Some(ctx) = self.ctx.get() { - if ctx.dev_id == dev.id { - return true; + fn make_render_device(&self, dev: &MetalDrmDevice, force: bool) { + if !force { + if let Some(ctx) = self.ctx.get() { + if ctx.dev_id == dev.id { + return; + } } } - self.state.set_render_ctx(Some(dev.ctx.gfx.clone())); - self.ctx.set(Some(dev.ctx.clone())); - let mut preserve = Preserve::default(); + let ctx = dev.ctx.get(); + self.state.set_render_ctx(Some(ctx.gfx.clone())); + self.ctx.set(Some(ctx)); for dev in self.device_holder.drm_devices.lock().values() { - if let Err(e) = self.init_drm_device(dev, &mut preserve) { - if log { - log::error!("Could not initialize device: {}", ErrorFmt(e)); - } + self.re_init_drm_device(&dev); + } + } + + fn set_gfx_api(&self, dev: &MetalDrmDevice, api: GfxApi) { + if dev.ctx.get().gfx.gfx_api() == api { + return; + } + let gfx = match self.state.create_gfx_context(&dev.master, Some(api)) { + Ok(r) => r, + Err(e) => { + log::error!( + "Could not create a new graphics context for device {:?}: {}", + dev.devnode, + ErrorFmt(e) + ); + return; } - for connector in dev.connectors.lock().values() { - if connector.connected() { - self.start_connector(connector, false); - } + }; + dev.on_change + .send_event(crate::backend::DrmEvent::GfxApiChanged); + dev.ctx.set(Rc::new(MetalRenderContext { + dev_id: dev.id, + gfx, + })); + let mut is_render_ctx = false; + if let Some(render_ctx) = self.ctx.get() { + if render_ctx.dev_id == dev.id { + is_render_ctx = true; + } + } + if is_render_ctx { + self.make_render_device(dev, true); + } else { + if let Some(dev) = self.device_holder.drm_devices.get(&dev.devnum) { + self.re_init_drm_device(&dev); + } + } + } + + fn re_init_drm_device(&self, dev: &Rc) { + let mut preserve = Preserve::default(); + if let Err(e) = self.init_drm_device(dev, &mut preserve) { + log::error!("Could not initialize device: {}", ErrorFmt(e)); + } + for connector in dev.connectors.lock().values() { + if connector.connected() { + self.start_connector(connector, false); } } - true } fn init_drm_device( @@ -1616,7 +1677,8 @@ impl MetalBackend { render_ctx: &MetalRenderContext, cursor: bool, ) -> Result { - let dev_gfx_formats = dev.ctx.gfx.formats(); + let ctx = dev.ctx.get(); + let dev_gfx_formats = ctx.gfx.formats(); let dev_gfx_format = match dev_gfx_formats.get(&format.drm) { None => return Err(MetalError::MissingDevFormat(format.name)), Some(f) => f, @@ -1647,7 +1709,7 @@ impl MetalBackend { Ok(fb) => Rc::new(fb), Err(e) => return Err(MetalError::Framebuffer(e)), }; - let dev_img = match dev.ctx.gfx.clone().dmabuf_img(dev_bo.dmabuf()) { + let dev_img = match ctx.gfx.clone().dmabuf_img(dev_bo.dmabuf()) { Ok(img) => img, Err(e) => return Err(MetalError::ImportImage(e)), }; @@ -1708,7 +1770,7 @@ impl MetalBackend { }; // Import the bridge BO into the current device - let dev_img = match dev.ctx.gfx.clone().dmabuf_img(render_bo.dmabuf()) { + let dev_img = match ctx.gfx.clone().dmabuf_img(render_bo.dmabuf()) { Ok(img) => img, Err(e) => return Err(MetalError::ImportImage(e)), }; diff --git a/src/backends/x.rs b/src/backends/x.rs index 9a915270..640d6e07 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -10,7 +10,6 @@ use { fixed::Fixed, format::XRGB8888, gfx_api::{GfxContext, GfxError, GfxFramebuffer, GfxTexture}, - gfx_apis::create_gfx_context, renderer::RenderResult, state::State, time::now_usec, @@ -50,6 +49,7 @@ use { Event, XEvent, Xcon, XconError, }, }, + jay_config::video::GfxApi, std::{ any::Any, borrow::Cow, @@ -181,7 +181,7 @@ pub async fn create(state: &Rc) -> Result, XBackendError> { Err(e) => return Err(XBackendError::DrmDeviceFstat(e)), }; let gbm = GbmDevice::new(&drm)?; - let ctx = match create_gfx_context(&drm) { + let ctx = match state.create_gfx_context(&drm, None) { Ok(r) => r, Err(e) => return Err(XBackendError::CreateEgl(e)), }; @@ -975,11 +975,20 @@ impl BackendDrmDevice for XDrmDevice { self.dev } - fn make_render_device(self: Rc) { + fn make_render_device(&self) { log::warn!("make_render_device is not supported by the X backend"); // nothing } + fn set_gfx_api(&self, _api: GfxApi) { + log::warn!("set_gfx_api is not supported by the X backend"); + // nothing + } + + fn gtx_api(&self) -> GfxApi { + self.backend.ctx.gfx_api() + } + fn version(&self) -> Result { self.backend.gbm.drm.version() } diff --git a/src/compositor.rs b/src/compositor.rs index a7ccae67..daf8b5e3 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -39,6 +39,7 @@ use { }, ahash::AHashSet, forker::ForkerProxy, + jay_config::video::GfxApi, std::{cell::Cell, env, future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration}, thiserror::Error, uapi::c, @@ -195,6 +196,7 @@ fn start_compositor2( render_ctx_watchers: Default::default(), workspace_watchers: Default::default(), default_workspace_capture: Cell::new(true), + default_gfx_api: Cell::new(GfxApi::OpenGl), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/config/handler.rs b/src/config/handler.rs index e6e7372b..ee0fa932 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -40,7 +40,7 @@ use { logging::LogLevel, theme::{colors::Colorable, sized::Resizable}, timer::Timer as JayTimer, - video::{Connector, DrmDevice}, + video::{Connector, DrmDevice, GfxApi}, Axis, Direction, Workspace, }, libloading::Library, @@ -572,6 +572,14 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_set_gfx_api(&self, device: Option, api: GfxApi) -> Result<(), CphError> { + match device { + Some(dev) => self.get_drm_device(dev)?.dev.set_gfx_api(api), + _ => self.state.default_gfx_api.set(api), + } + Ok(()) + } + fn handle_get_default_workspace_capture(&self) { self.respond(Response::GetDefaultWorkspaceCapture { capture: self.state.default_workspace_capture.get(), @@ -1296,6 +1304,9 @@ impl ConfigProxyHandler { ClientMessage::GetWorkspaceCapture { workspace } => self .handle_get_workspace_capture(workspace) .wrn("get_workspace_capture")?, + ClientMessage::SetGfxApi { device, api } => { + self.handle_set_gfx_api(device, api).wrn("set_gfx_api")? + } } Ok(()) } diff --git a/src/cursor.rs b/src/cursor.rs index 9c3dd279..fa9d38dd 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -241,7 +241,7 @@ impl CursorImageScaled { extents: Rect::new_sized(-xhot, -yhot, width, height).unwrap(), tex: ctx .clone() - .shmem_texture(data, ARGB8888, width, height, width * 4)?, + .shmem_texture(None, data, ARGB8888, width, height, width * 4)?, })) } } diff --git a/src/edid.rs b/src/edid.rs index 7d976cb6..d5623118 100644 --- a/src/edid.rs +++ b/src/edid.rs @@ -1,5 +1,7 @@ use { - crate::utils::{bitflags::BitflagsExt, ptr_ext::PtrExt, stack::Stack}, + crate::utils::{ + bitflags::BitflagsExt, clonecell::UnsafeCellCloneSafe, ptr_ext::PtrExt, stack::Stack, + }, bstr::{BString, ByteSlice}, std::{ fmt::{Debug, Formatter}, @@ -393,6 +395,8 @@ pub enum EdidParseContext { VideoInputDefinition, } +unsafe impl UnsafeCellCloneSafe for EdidParseContext {} + struct EdidPushedContext { stack: Rc>, } diff --git a/src/format.rs b/src/format.rs index 80523b8e..90a78212 100644 --- a/src/format.rs +++ b/src/format.rs @@ -8,6 +8,7 @@ use { utils::debug_fn::debug_fn, }, ahash::AHashMap, + ash::vk, once_cell::sync::Lazy, std::fmt::{Debug, Write}, }; @@ -18,6 +19,7 @@ pub struct Format { pub bpp: u32, pub gl_format: GLint, pub gl_type: GLint, + pub vk_format: vk::Format, pub drm: u32, pub wl_id: Option, pub external_only_guess: bool, @@ -80,39 +82,44 @@ pub fn map_wayland_format_id(id: u32) -> u32 { } #[allow(dead_code)] -pub static ARGB8888: &Format = &FORMATS[0]; -pub static XRGB8888: &Format = &FORMATS[1]; +pub static ARGB8888: &Format = &Format { + name: "argb8888", + bpp: 4, + gl_format: GL_BGRA_EXT, + gl_type: GL_UNSIGNED_BYTE, + vk_format: vk::Format::B8G8R8A8_SRGB, + drm: ARGB8888_DRM, + wl_id: Some(ARGB8888_ID), + external_only_guess: false, + has_alpha: true, + shm_supported: true, + pipewire: SPA_VIDEO_FORMAT_BGRA, +}; + +pub static XRGB8888: &Format = &Format { + name: "xrgb8888", + bpp: 4, + gl_format: GL_BGRA_EXT, + gl_type: GL_UNSIGNED_BYTE, + vk_format: vk::Format::B8G8R8A8_SRGB, + drm: XRGB8888_DRM, + wl_id: Some(XRGB8888_ID), + external_only_guess: false, + has_alpha: false, + shm_supported: true, + pipewire: SPA_VIDEO_FORMAT_BGRx, +}; pub static FORMATS: &[Format] = &[ - Format { - name: "argb8888", - bpp: 4, - gl_format: GL_BGRA_EXT, - gl_type: GL_UNSIGNED_BYTE, - drm: ARGB8888_DRM, - wl_id: Some(ARGB8888_ID), - external_only_guess: false, - has_alpha: true, - shm_supported: true, - pipewire: SPA_VIDEO_FORMAT_BGRA, - }, - Format { - name: "xrgb8888", - bpp: 4, - gl_format: GL_BGRA_EXT, - gl_type: GL_UNSIGNED_BYTE, - drm: XRGB8888_DRM, - wl_id: Some(XRGB8888_ID), - external_only_guess: false, - has_alpha: false, - shm_supported: true, - pipewire: SPA_VIDEO_FORMAT_BGRx, - }, + *ARGB8888, + *XRGB8888, + // *NV12, Format { name: "abgr8888", bpp: 4, gl_format: GL_RGBA, gl_type: GL_UNSIGNED_BYTE, + vk_format: vk::Format::R8G8B8A8_SRGB, drm: fourcc_code('A', 'B', '2', '4'), wl_id: None, external_only_guess: false, @@ -125,6 +132,7 @@ pub static FORMATS: &[Format] = &[ bpp: 4, gl_format: GL_RGBA, gl_type: GL_UNSIGNED_BYTE, + vk_format: vk::Format::R8G8B8A8_SRGB, drm: fourcc_code('X', 'B', '2', '4'), wl_id: None, external_only_guess: false, diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 23393211..cc8fc018 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -13,6 +13,7 @@ use { }, ahash::AHashMap, indexmap::IndexSet, + jay_config::video::GfxApi, std::{ any::Any, cell::Cell, @@ -80,6 +81,7 @@ impl BufferPoints { } } +#[derive(Debug)] pub struct AbsoluteRect { pub x1: f32, pub x2: f32, @@ -87,6 +89,7 @@ pub struct AbsoluteRect { pub y2: f32, } +#[derive(Debug)] pub struct FillRect { pub rect: AbsoluteRect, pub color: Color, @@ -263,6 +266,7 @@ pub trait GfxImage { pub trait GfxTexture: Debug { fn size(&self) -> (i32, i32); fn as_any(&self) -> &dyn Any; + fn into_any(self: Rc) -> Rc; } pub trait GfxContext: Debug { @@ -272,12 +276,15 @@ pub trait GfxContext: Debug { fn formats(&self) -> Rc>; - fn dmabuf_fb(self: Rc, buf: &DmaBuf) -> Result, GfxError>; + fn dmabuf_fb(self: Rc, buf: &DmaBuf) -> Result, GfxError> { + self.dmabuf_img(buf)?.to_framebuffer() + } fn dmabuf_img(self: Rc, buf: &DmaBuf) -> Result, GfxError>; fn shmem_texture( self: Rc, + old: Option>, data: &[Cell], format: &'static Format, width: i32, @@ -286,6 +293,8 @@ pub trait GfxContext: Debug { ) -> Result, GfxError>; fn gbm(&self) -> &GbmDevice; + + fn gfx_api(&self) -> GfxApi; } #[derive(Debug)] diff --git a/src/gfx_apis.rs b/src/gfx_apis.rs index d6450911..5cc1dd3a 100644 --- a/src/gfx_apis.rs +++ b/src/gfx_apis.rs @@ -1,13 +1,49 @@ use { crate::{ + async_engine::AsyncEngine, gfx_api::{GfxContext, GfxError}, + io_uring::IoUring, + utils::errorfmt::ErrorFmt, video::drm::Drm, }, + jay_config::video::GfxApi, std::rc::Rc, }; pub mod gl; +mod vulkan; -pub fn create_gfx_context(drm: &Drm) -> Result, GfxError> { - gl::create_gfx_context(drm) +pub fn create_gfx_context( + eng: &Rc, + ring: &Rc, + drm: &Drm, + api: GfxApi, +) -> Result, GfxError> { + let mut apis = [GfxApi::OpenGl, GfxApi::Vulkan]; + apis.sort_by_key(|&a| if a == api { -1 } else { a as i32 }); + let mut last_err = None; + for api in apis { + let res = create_gfx_context_(eng, ring, drm, api); + match res { + Ok(_) => return res, + Err(e) => { + log::warn!("Could not create {:?} API: {}", api, ErrorFmt(&e)); + last_err = Some(e); + } + } + } + Err(last_err.unwrap()) +} + +fn create_gfx_context_( + eng: &Rc, + ring: &Rc, + drm: &Drm, + api: GfxApi, +) -> Result, GfxError> { + match api { + GfxApi::OpenGl => gl::create_gfx_context(drm), + GfxApi::Vulkan => vulkan::create_graphics_context(eng, ring, drm), + _ => unreachable!(), + } } diff --git a/src/gfx_apis/gl/renderer/context.rs b/src/gfx_apis/gl/renderer/context.rs index 103ffc82..b9680151 100644 --- a/src/gfx_apis/gl/renderer/context.rs +++ b/src/gfx_apis/gl/renderer/context.rs @@ -14,13 +14,10 @@ use { renderer::{framebuffer::Framebuffer, image::Image}, GfxGlState, RenderError, Texture, }, - video::{ - dmabuf::DmaBuf, - drm::{Drm, NodeType}, - gbm::GbmDevice, - }, + video::{dmabuf::DmaBuf, drm::Drm, gbm::GbmDevice}, }, ahash::AHashMap, + jay_config::video::GfxApi, std::{ cell::{Cell, RefCell}, ffi::CString, @@ -82,14 +79,10 @@ impl GlRenderContext { } pub(in crate::gfx_apis::gl) fn from_drm_device(drm: &Drm) -> Result { - let nodes = drm.get_nodes()?; - let node = match nodes - .get(&NodeType::Render) - .or_else(|| nodes.get(&NodeType::Primary)) - { - None => return Err(RenderError::NoRenderNode), - Some(path) => Rc::new(path.to_owned()), - }; + let node = drm + .get_render_node()? + .ok_or(RenderError::NoRenderNode) + .map(Rc::new)?; let dpy = EglDisplay::create(drm)?; if !dpy.formats.contains_key(&XRGB8888.drm) { return Err(RenderError::XRGB888); @@ -226,6 +219,7 @@ impl GfxContext for GlRenderContext { fn shmem_texture( self: Rc, + _old: Option>, data: &[Cell], format: &'static Format, width: i32, @@ -241,4 +235,8 @@ impl GfxContext for GlRenderContext { fn gbm(&self) -> &GbmDevice { &self.gbm } + + fn gfx_api(&self) -> GfxApi { + GfxApi::OpenGl + } } diff --git a/src/gfx_apis/gl/renderer/texture.rs b/src/gfx_apis/gl/renderer/texture.rs index 6f8c1a96..237286a3 100644 --- a/src/gfx_apis/gl/renderer/texture.rs +++ b/src/gfx_apis/gl/renderer/texture.rs @@ -39,4 +39,8 @@ impl GfxTexture for Texture { fn as_any(&self) -> &dyn Any { self } + + fn into_any(self: Rc) -> Rc { + self + } } diff --git a/src/gfx_apis/vulkan.rs b/src/gfx_apis/vulkan.rs new file mode 100644 index 00000000..5dc2ed8d --- /dev/null +++ b/src/gfx_apis/vulkan.rs @@ -0,0 +1,252 @@ +mod allocator; +mod command; +mod descriptor; +mod device; +mod fence; +mod format; +mod image; +mod instance; +mod pipeline; +mod renderer; +mod sampler; +mod semaphore; +mod shaders; +mod staging; +mod util; + +use { + crate::{ + async_engine::AsyncEngine, + format::Format, + gfx_api::{GfxContext, GfxError, GfxFormat, GfxImage, GfxTexture, ResetStatus}, + gfx_apis::vulkan::{ + image::VulkanImageMemory, instance::VulkanInstance, renderer::VulkanRenderer, + }, + io_uring::IoUring, + utils::oserror::OsError, + video::{ + dmabuf::DmaBuf, + drm::{Drm, DrmError}, + gbm::{GbmDevice, GbmError}, + }, + }, + ahash::AHashMap, + ash::{vk, LoadingError}, + gpu_alloc::{AllocationError, MapError}, + jay_config::video::GfxApi, + once_cell::sync::Lazy, + std::{ + cell::Cell, + ffi::{CStr, CString}, + rc::Rc, + sync::Arc, + }, + thiserror::Error, + uapi::c::dev_t, +}; + +#[derive(Debug, Error)] +pub enum VulkanError { + #[error("Could not create a GBM device")] + Gbm(#[source] GbmError), + #[error("Could not load libvulkan.so")] + Load(#[source] Arc), + #[error("Could not list instance extensions")] + InstanceExtensions(#[source] vk::Result), + #[error("Could not list instance layers")] + InstanceLayers(#[source] vk::Result), + #[error("Could not list device extensions")] + DeviceExtensions(#[source] vk::Result), + #[error("Could not create the device")] + CreateDevice(#[source] vk::Result), + #[error("Could not create a semaphore")] + CreateSemaphore(#[source] vk::Result), + #[error("Could not create a fence")] + CreateFence(#[source] vk::Result), + #[error("Could not create the buffer")] + CreateBuffer(#[source] vk::Result), + #[error("Could not create a shader module")] + CreateShaderModule(#[source] vk::Result), + #[error("Missing required instance extension {0:?}")] + MissingInstanceExtension(&'static CStr), + #[error("Could not allocate a command pool")] + AllocateCommandPool(#[source] vk::Result), + #[error("Could not allocate a command buffer")] + AllocateCommandBuffer(#[source] vk::Result), + #[error("Device does not have a graphics queue")] + NoGraphicsQueue, + #[error("Missing required device extension {0:?}")] + MissingDeviceExtension(&'static CStr), + #[error("Could not create an instance")] + CreateInstance(#[source] vk::Result), + #[error("Could not create a debug-utils messenger")] + Messenger(#[source] vk::Result), + #[error("Could not fstat the DRM FD")] + Fstat(#[source] OsError), + #[error("Could not enumerate the physical devices")] + EnumeratePhysicalDevices(#[source] vk::Result), + #[error("Could not find a vulkan device that matches dev_t {0}")] + NoDeviceFound(dev_t), + #[error("Could not load image properties")] + LoadImageProperties(#[source] vk::Result), + #[error("Device does not support rending and texturing from the XRGB8888 format")] + XRGB8888, + #[error("Device does not support syncobj import")] + SyncobjImport, + #[error("Could not start a command buffer")] + BeginCommandBuffer(vk::Result), + #[error("Could not end a command buffer")] + EndCommandBuffer(vk::Result), + #[error("Could not submit a command buffer")] + Submit(vk::Result), + #[error("Could not create a sampler")] + CreateSampler(#[source] vk::Result), + #[error("Could not create a pipeline layout")] + CreatePipelineLayout(#[source] vk::Result), + #[error("Could not create a descriptor set layout")] + CreateDescriptorSetLayout(#[source] vk::Result), + #[error("Could not create a pipeline")] + CreatePipeline(#[source] vk::Result), + #[error("The format is not supported")] + FormatNotSupported, + #[error("The modifier is not supported")] + ModifierNotSupported, + #[error("The modifier does not support this use-case")] + ModifierUseNotSupported, + #[error("The image has a non-positive size")] + NonPositiveImageSize, + #[error("The image is too large")] + ImageTooLarge, + #[error("Could not retrieve device properties")] + GetDeviceProperties(#[source] vk::Result), + #[error("The dmabuf has an incorrect number of planes")] + BadPlaneCount, + #[error("The dmabuf is disjoint but the modifier does not support disjoint buffers")] + DisjointNotSupported, + #[error("Could not create the image")] + CreateImage(#[source] vk::Result), + #[error("Could not create an image view")] + CreateImageView(#[source] vk::Result), + #[error("Could not query the memory fd properties")] + MemoryFdProperties(#[source] vk::Result), + #[error("There is no matching memory type")] + MemoryType, + #[error("Could not duplicate the DRM fd")] + Dupfd(#[source] OsError), + #[error("Could not allocate memory")] + AllocateMemory(#[source] vk::Result), + #[error("Could not allocate memory")] + AllocateMemory2(#[source] AllocationError), + #[error("Could not bind memory to the image")] + BindImageMemory(#[source] vk::Result), + #[error("The format does not support shared memory images")] + ShmNotSupported, + #[error("Could not bind memory to the buffer")] + BindBufferMemory(#[source] vk::Result), + #[error("Could not map the memory")] + MapMemory(#[source] MapError), + #[error("Could not flush modified memory")] + FlushMemory(#[source] vk::Result), + #[error("Could not export a sync file from a dma-buf")] + IoctlExportSyncFile(#[source] OsError), + #[error("Could not import a sync file into a semaphore")] + ImportSyncFile(#[source] vk::Result), + #[error("Could not import a sync file into a dma-buf")] + IoctlImportSyncFile(#[source] OsError), + #[error("Could not export a sync file from a semaphore")] + ExportSyncFile(#[source] vk::Result), + #[error("Could not fetch the render node of the device")] + FetchRenderNode(#[source] DrmError), + #[error("Device has no render node")] + NoRenderNode, + #[error("Overflow while calculating shm buffer size")] + ShmOverflow, +} + +impl From for GfxError { + fn from(value: VulkanError) -> Self { + Self(Box::new(value)) + } +} + +pub static VULKAN_VALIDATION: Lazy = + Lazy::new(|| std::env::var("JAY_VULKAN_VALIDATION").ok().as_deref() == Some("1")); + +pub fn create_graphics_context( + eng: &Rc, + ring: &Rc, + drm: &Drm, +) -> Result, GfxError> { + let instance = VulkanInstance::new(eng, ring, *VULKAN_VALIDATION)?; + let device = instance.create_device(drm)?; + let renderer = device.create_renderer()?; + Ok(Rc::new(Context(renderer))) +} + +#[derive(Debug)] +struct Context(Rc); + +impl GfxContext for Context { + fn reset_status(&self) -> Option { + None + } + + fn render_node(&self) -> Rc { + self.0.device.render_node.clone() + } + + fn formats(&self) -> Rc> { + self.0.formats.clone() + } + + fn dmabuf_img(self: Rc, buf: &DmaBuf) -> Result, GfxError> { + self.0 + .import_dmabuf(buf) + .map(|v| v as _) + .map_err(|e| e.into()) + } + + fn shmem_texture( + self: Rc, + old: Option>, + data: &[Cell], + format: &'static Format, + width: i32, + height: i32, + stride: i32, + ) -> Result, GfxError> { + if let Some(old) = old { + let old = old.into_vk(&self.0.device.device); + let shm = match &old.ty { + VulkanImageMemory::DmaBuf(_) => unreachable!(), + VulkanImageMemory::Internal(shm) => shm, + }; + if old.width as i32 == width + && old.height as i32 == height + && shm.stride as i32 == stride + && old.format.vk_format == format.vk_format + { + shm.upload(data)?; + return Ok(old); + } + } + let tex = self + .0 + .create_shm_texture(format, width, height, stride, data)?; + Ok(tex as _) + } + + fn gbm(&self) -> &GbmDevice { + &self.0.device.gbm + } + + fn gfx_api(&self) -> GfxApi { + GfxApi::Vulkan + } +} + +impl Drop for Context { + fn drop(&mut self) { + self.0.on_drop(); + } +} diff --git a/src/gfx_apis/vulkan/allocator.rs b/src/gfx_apis/vulkan/allocator.rs new file mode 100644 index 00000000..83f661ba --- /dev/null +++ b/src/gfx_apis/vulkan/allocator.rs @@ -0,0 +1,128 @@ +use { + crate::{ + gfx_apis::vulkan::{device::VulkanDevice, instance::API_VERSION, VulkanError}, + utils::{numcell::NumCell, ptr_ext::MutPtrExt}, + }, + ash::vk::{DeviceMemory, DeviceSize, MemoryRequirements}, + gpu_alloc::{Config, GpuAllocator, MemoryBlock, Request, UsageFlags}, + gpu_alloc_ash::AshMemoryDevice, + std::{ + cell::{Cell, UnsafeCell}, + rc::Rc, + }, +}; + +pub struct VulkanAllocator { + pub(super) device: Rc, + pub(super) non_coherent_atom_mask: u64, + allocator: UnsafeCell>, + total: NumCell, +} + +pub struct VulkanAllocation { + pub(super) allocator: Rc, + pub(super) memory: DeviceMemory, + pub(super) offset: DeviceSize, + pub(super) mem: Option<*mut u8>, + pub(super) size: DeviceSize, + block: Cell>>, +} + +impl Drop for VulkanAllocation { + fn drop(&mut self) { + unsafe { + self.allocator.total.fetch_sub(self.size); + let mut block = self.block.take().unwrap(); + if let Some(_ptr) = self.mem { + // log::info!("free = {:?} - {:?} ({})", ptr, ptr.add(block.size() as usize), block.size()); + block.unmap(AshMemoryDevice::wrap(&self.allocator.device.device)); + } + self.allocator + .allocator + .get() + .deref_mut() + .dealloc(AshMemoryDevice::wrap(&self.allocator.device.device), block); + } + } +} + +impl VulkanDevice { + pub fn create_allocator(self: &Rc) -> Result, VulkanError> { + let config = Config::i_am_prototyping(); + let props = unsafe { + gpu_alloc_ash::device_properties( + &self.instance.instance, + API_VERSION, + self.physical_device, + ) + }; + let mut props = props.map_err(VulkanError::GetDeviceProperties)?; + props.buffer_device_address = false; + let non_coherent_atom_size = props.non_coherent_atom_size; + let allocator = GpuAllocator::new(config, props); + Ok(Rc::new(VulkanAllocator { + device: self.clone(), + non_coherent_atom_mask: non_coherent_atom_size - 1, + allocator: UnsafeCell::new(allocator), + total: Default::default(), + })) + } +} + +impl VulkanAllocator { + fn allocator(&self) -> &mut GpuAllocator { + unsafe { self.allocator.get().deref_mut() } + } + + pub fn alloc( + self: &Rc, + req: &MemoryRequirements, + usage: UsageFlags, + map: bool, + ) -> Result { + let request = Request { + size: req.size, + align_mask: req.alignment - 1, + usage, + memory_types: req.memory_type_bits, + }; + let block = unsafe { + self.allocator() + .alloc(AshMemoryDevice::wrap(&self.device.device), request) + }; + let mut block = block.map_err(VulkanError::AllocateMemory2)?; + let ptr = match map { + true => { + let ptr = unsafe { + block.map( + AshMemoryDevice::wrap(&self.device.device), + 0, + block.size() as usize, + ) + }; + Some(ptr.map_err(VulkanError::MapMemory)?.as_ptr()) + } + false => None, + }; + self.total.fetch_add(block.size()); + Ok(VulkanAllocation { + allocator: self.clone(), + memory: *block.memory(), + offset: block.offset(), + mem: ptr, + size: block.size(), + block: Cell::new(Some(block)), + }) + } +} + +impl Drop for VulkanAllocator { + fn drop(&mut self) { + unsafe { + self.allocator + .get() + .deref_mut() + .cleanup(AshMemoryDevice::wrap(&self.device.device)); + } + } +} diff --git a/src/gfx_apis/vulkan/command.rs b/src/gfx_apis/vulkan/command.rs new file mode 100644 index 00000000..e45a2374 --- /dev/null +++ b/src/gfx_apis/vulkan/command.rs @@ -0,0 +1,69 @@ +use { + crate::gfx_apis::vulkan::{device::VulkanDevice, VulkanError}, + ash::vk::{ + CommandBuffer, CommandBufferAllocateInfo, CommandBufferLevel, CommandPool, + CommandPoolCreateFlags, CommandPoolCreateInfo, + }, + std::rc::Rc, +}; + +pub struct VulkanCommandPool { + pub(super) device: Rc, + pub(super) pool: CommandPool, +} + +pub struct VulkanCommandBuffer { + pub(super) pool: Rc, + pub(super) buffer: CommandBuffer, +} + +impl Drop for VulkanCommandPool { + fn drop(&mut self) { + unsafe { + self.device.device.destroy_command_pool(self.pool, None); + } + } +} + +impl Drop for VulkanCommandBuffer { + fn drop(&mut self) { + unsafe { + self.pool + .device + .device + .free_command_buffers(self.pool.pool, &[self.buffer]); + } + } +} + +impl VulkanCommandPool { + pub fn allocate_buffer(self: &Rc) -> Result, VulkanError> { + let create_info = CommandBufferAllocateInfo::builder() + .command_pool(self.pool) + .command_buffer_count(1) + .level(CommandBufferLevel::PRIMARY); + let buffer = unsafe { self.device.device.allocate_command_buffers(&create_info) }; + let mut buffer = buffer.map_err(VulkanError::AllocateCommandBuffer)?; + assert_eq!(buffer.len(), 1); + Ok(Rc::new(VulkanCommandBuffer { + pool: self.clone(), + buffer: buffer.pop().unwrap(), + })) + } +} + +impl VulkanDevice { + pub fn create_command_pool(self: &Rc) -> Result, VulkanError> { + let info = CommandPoolCreateInfo::builder() + .queue_family_index(self.graphics_queue_idx) + .flags( + CommandPoolCreateFlags::TRANSIENT | CommandPoolCreateFlags::RESET_COMMAND_BUFFER, + ); + let pool = unsafe { self.device.create_command_pool(&info, None) }; + let pool = pool.map_err(VulkanError::AllocateCommandPool)?; + Ok(Rc::new(VulkanCommandPool { + device: self.clone(), + pool, + })) + } +} diff --git a/src/gfx_apis/vulkan/descriptor.rs b/src/gfx_apis/vulkan/descriptor.rs new file mode 100644 index 00000000..006efa7c --- /dev/null +++ b/src/gfx_apis/vulkan/descriptor.rs @@ -0,0 +1,49 @@ +use { + crate::gfx_apis::vulkan::{device::VulkanDevice, sampler::VulkanSampler, VulkanError}, + ash::vk::{ + DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorSetLayoutCreateFlags, + DescriptorSetLayoutCreateInfo, DescriptorType, ShaderStageFlags, + }, + std::{rc::Rc, slice}, +}; + +pub(super) struct VulkanDescriptorSetLayout { + pub(super) device: Rc, + pub(super) layout: DescriptorSetLayout, + pub(super) _sampler: Rc, +} + +impl Drop for VulkanDescriptorSetLayout { + fn drop(&mut self) { + unsafe { + self.device + .device + .destroy_descriptor_set_layout(self.layout, None); + } + } +} + +impl VulkanDevice { + pub(super) fn create_descriptor_set_layout( + &self, + sampler: &Rc, + ) -> Result, VulkanError> { + let immutable_sampler = [sampler.sampler]; + let binding = DescriptorSetLayoutBinding::builder() + .stage_flags(ShaderStageFlags::FRAGMENT) + .immutable_samplers(&immutable_sampler) + .descriptor_count(1) + .descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER) + .build(); + let create_info = DescriptorSetLayoutCreateInfo::builder() + .bindings(slice::from_ref(&binding)) + .flags(DescriptorSetLayoutCreateFlags::PUSH_DESCRIPTOR_KHR); + let layout = unsafe { self.device.create_descriptor_set_layout(&create_info, None) }; + let layout = layout.map_err(VulkanError::CreateDescriptorSetLayout)?; + Ok(Rc::new(VulkanDescriptorSetLayout { + device: sampler.device.clone(), + layout, + _sampler: sampler.clone(), + })) + } +} diff --git a/src/gfx_apis/vulkan/device.rs b/src/gfx_apis/vulkan/device.rs new file mode 100644 index 00000000..0d0a6886 --- /dev/null +++ b/src/gfx_apis/vulkan/device.rs @@ -0,0 +1,343 @@ +use { + crate::{ + format::XRGB8888, + gfx_apis::vulkan::{ + format::VulkanFormat, + instance::{ + map_extension_properties, ApiVersionDisplay, Extensions, VulkanInstance, + API_VERSION, + }, + util::OnDrop, + VulkanError, + }, + video::{drm::Drm, gbm::GbmDevice}, + }, + ahash::AHashMap, + arrayvec::ArrayVec, + ash::{ + extensions::khr::{ExternalFenceFd, ExternalMemoryFd, ExternalSemaphoreFd, PushDescriptor}, + vk::{ + DeviceCreateInfo, DeviceMemory, DeviceQueueCreateInfo, ExtExternalMemoryDmaBufFn, + ExtImageDrmFormatModifierFn, ExtPhysicalDeviceDrmFn, ExtQueueFamilyForeignFn, + ExternalSemaphoreFeatureFlags, ExternalSemaphoreHandleTypeFlags, + ExternalSemaphoreProperties, KhrDriverPropertiesFn, KhrExternalFenceFdFn, + KhrExternalMemoryFdFn, KhrExternalSemaphoreFdFn, KhrPushDescriptorFn, + MemoryPropertyFlags, MemoryType, PhysicalDevice, PhysicalDeviceDriverProperties, + PhysicalDeviceDriverPropertiesKHR, PhysicalDeviceDrmPropertiesEXT, + PhysicalDeviceDynamicRenderingFeatures, PhysicalDeviceExternalSemaphoreInfo, + PhysicalDeviceProperties, PhysicalDeviceProperties2, + PhysicalDeviceSynchronization2Features, PhysicalDeviceTimelineSemaphoreFeatures, Queue, + QueueFlags, MAX_MEMORY_TYPES, + }, + Device, + }, + isnt::std_1::collections::IsntHashMap2Ext, + std::{ + ffi::{CStr, CString}, + rc::Rc, + }, + uapi::Ustr, +}; + +pub struct VulkanDevice { + pub(super) physical_device: PhysicalDevice, + pub(super) render_node: Rc, + pub(super) gbm: GbmDevice, + pub(super) instance: Rc, + pub(super) device: Device, + pub(super) external_memory_fd: ExternalMemoryFd, + pub(super) external_semaphore_fd: ExternalSemaphoreFd, + pub(super) external_fence_fd: ExternalFenceFd, + pub(super) push_descriptor: PushDescriptor, + pub(super) formats: AHashMap, + pub(super) memory_types: ArrayVec, + pub(super) graphics_queue: Queue, + pub(super) graphics_queue_idx: u32, +} + +impl Drop for VulkanDevice { + fn drop(&mut self) { + unsafe { + self.device.destroy_device(None); + } + } +} + +impl VulkanDevice { + pub(super) fn find_memory_type( + &self, + flags: MemoryPropertyFlags, + memory_type_bits: u32, + ) -> Option { + for (idx, ty) in self.memory_types.iter().enumerate() { + if memory_type_bits & (1 << idx as u32) != 0 { + if ty.property_flags.contains(flags) { + return Some(idx as _); + } + } + } + None + } +} + +struct FreeMem<'a>(&'a Device, DeviceMemory); + +impl<'a> Drop for FreeMem<'a> { + fn drop(&mut self) { + unsafe { + self.0.free_memory(self.1, None); + } + } +} + +impl VulkanInstance { + fn get_device_extensions(&self, phy_dev: PhysicalDevice) -> Result { + unsafe { + self.instance + .enumerate_device_extension_properties(phy_dev) + .map(map_extension_properties) + .map_err(VulkanError::DeviceExtensions) + } + } + + fn find_dev(&self, drm: &Drm) -> Result { + let stat = match uapi::fstat(drm.raw()) { + Ok(s) => s, + Err(e) => return Err(VulkanError::Fstat(e.into())), + }; + let dev = stat.st_rdev; + log::info!( + "Searching for vulkan device with devnum {}:{}", + uapi::major(dev), + uapi::minor(dev) + ); + let phy_devs = unsafe { self.instance.enumerate_physical_devices() }; + let phy_devs = match phy_devs { + Ok(d) => d, + Err(e) => return Err(VulkanError::EnumeratePhysicalDevices(e)), + }; + let mut devices = vec![]; + for phy_dev in phy_devs { + let props = unsafe { self.instance.get_physical_device_properties(phy_dev) }; + if props.api_version < API_VERSION { + devices.push((props, None, None)); + continue; + } + let extensions = match self.get_device_extensions(phy_dev) { + Ok(e) => e, + Err(e) => { + log::error!( + "Could not enumerate extensions of device with id {}: {:#}", + props.device_id, + e + ); + devices.push((props, None, None)); + continue; + } + }; + if !extensions.contains_key(ExtPhysicalDeviceDrmFn::name()) { + devices.push((props, Some(extensions), None)); + continue; + } + let has_driver_props = extensions.contains_key(KhrDriverPropertiesFn::name()); + let mut drm_props = PhysicalDeviceDrmPropertiesEXT::builder().build(); + let mut driver_props = PhysicalDeviceDriverPropertiesKHR::builder().build(); + let mut props2 = PhysicalDeviceProperties2::builder().push_next(&mut drm_props); + if has_driver_props { + props2 = props2.push_next(&mut driver_props); + } + unsafe { + self.instance + .get_physical_device_properties2(phy_dev, &mut props2); + } + let primary_dev = + uapi::makedev(drm_props.primary_major as _, drm_props.primary_minor as _); + let render_dev = + uapi::makedev(drm_props.render_major as _, drm_props.render_minor as _); + if primary_dev == dev || render_dev == dev { + log::info!("Device with id {} matches", props.device_id); + log_device(&props, Some(&extensions), Some(&driver_props)); + return Ok(phy_dev); + } + devices.push((props, Some(extensions), Some(driver_props))); + } + if devices.is_empty() { + log::warn!("Found no devices"); + } else { + log::warn!("Found the following devices but none matches:"); + for (props, extensions, driver_props) in devices.iter() { + log::warn!("Found the following devices but none matches:"); + log::warn!("-----"); + log_device(props, extensions.as_ref(), driver_props.as_ref()); + } + } + Err(VulkanError::NoDeviceFound(dev)) + } + + fn find_graphics_queue(&self, phy_dev: PhysicalDevice) -> Result { + let props = unsafe { + self.instance + .get_physical_device_queue_family_properties(phy_dev) + }; + props + .iter() + .position(|p| p.queue_flags.contains(QueueFlags::GRAPHICS)) + .map(|v| v as _) + .ok_or(VulkanError::NoGraphicsQueue) + } + + fn supports_semaphore_import(&self, phy_dev: PhysicalDevice) -> bool { + let mut props = ExternalSemaphoreProperties::builder().build(); + let info = PhysicalDeviceExternalSemaphoreInfo::builder() + .handle_type(ExternalSemaphoreHandleTypeFlags::OPAQUE_FD) + .build(); + unsafe { + self.instance + .get_physical_device_external_semaphore_properties(phy_dev, &info, &mut props); + } + props + .external_semaphore_features + .contains(ExternalSemaphoreFeatureFlags::IMPORTABLE) + } + + pub fn create_device(self: &Rc, drm: &Drm) -> Result, VulkanError> { + let render_node = drm + .get_render_node() + .map_err(VulkanError::FetchRenderNode)? + .ok_or(VulkanError::NoRenderNode) + .map(Rc::new)?; + let gbm = GbmDevice::new(drm).map_err(VulkanError::Gbm)?; + let phy_dev = self.find_dev(drm)?; + let extensions = self.get_device_extensions(phy_dev)?; + for &ext in REQUIRED_DEVICE_EXTENSIONS { + if extensions.not_contains_key(ext) { + return Err(VulkanError::MissingDeviceExtension(ext)); + } + } + let graphics_queue_idx = self.find_graphics_queue(phy_dev)?; + if !self.supports_semaphore_import(phy_dev) { + return Err(VulkanError::SyncobjImport); + } + let enabled_extensions: Vec<_> = REQUIRED_DEVICE_EXTENSIONS + .iter() + .map(|n| n.as_ptr()) + .collect(); + let mut semaphore_features = + PhysicalDeviceTimelineSemaphoreFeatures::builder().timeline_semaphore(true); + let mut synchronization2_features = + PhysicalDeviceSynchronization2Features::builder().synchronization2(true); + let mut dynamic_rendering_features = + PhysicalDeviceDynamicRenderingFeatures::builder().dynamic_rendering(true); + let queue_create_info = DeviceQueueCreateInfo::builder() + .queue_family_index(graphics_queue_idx) + .queue_priorities(&[1.0]) + .build(); + let device_create_info = DeviceCreateInfo::builder() + .push_next(&mut semaphore_features) + .push_next(&mut synchronization2_features) + .push_next(&mut dynamic_rendering_features) + .queue_create_infos(std::slice::from_ref(&queue_create_info)) + .enabled_extension_names(&enabled_extensions); + let device = unsafe { + self.instance + .create_device(phy_dev, &device_create_info, None) + }; + let device = match device { + Ok(d) => d, + Err(e) => return Err(VulkanError::CreateDevice(e)), + }; + let destroy_device = OnDrop(|| unsafe { device.destroy_device(None) }); + let formats = self.load_formats(phy_dev)?; + let supports_xrgb8888 = formats + .get(&XRGB8888.drm) + .map(|f| { + let mut supports_rendering = false; + let mut supports_texturing = false; + f.modifiers.values().for_each(|v| { + supports_rendering |= v.render_max_extents.is_some(); + supports_texturing |= v.texture_max_extents.is_some(); + }); + supports_rendering && supports_texturing + }) + .unwrap_or(false); + if !supports_xrgb8888 { + return Err(VulkanError::XRGB8888); + } + destroy_device.forget(); + let external_memory_fd = ExternalMemoryFd::new(&self.instance, &device); + let external_semaphore_fd = ExternalSemaphoreFd::new(&self.instance, &device); + let external_fence_fd = ExternalFenceFd::new(&self.instance, &device); + let push_descriptor = PushDescriptor::new(&self.instance, &device); + let memory_properties = + unsafe { self.instance.get_physical_device_memory_properties(phy_dev) }; + let memory_types = memory_properties.memory_types + [..memory_properties.memory_type_count as _] + .iter() + .copied() + .collect(); + let graphics_queue = unsafe { device.get_device_queue(graphics_queue_idx, 0) }; + Ok(Rc::new(VulkanDevice { + physical_device: phy_dev, + render_node, + gbm, + instance: self.clone(), + device, + external_memory_fd, + external_semaphore_fd, + external_fence_fd, + push_descriptor, + formats, + memory_types, + graphics_queue, + graphics_queue_idx, + })) + } +} + +const REQUIRED_DEVICE_EXTENSIONS: &[&CStr] = &[ + KhrExternalMemoryFdFn::name(), + KhrExternalSemaphoreFdFn::name(), + KhrExternalFenceFdFn::name(), + ExtExternalMemoryDmaBufFn::name(), + ExtQueueFamilyForeignFn::name(), + ExtImageDrmFormatModifierFn::name(), + KhrPushDescriptorFn::name(), +]; + +fn log_device( + props: &PhysicalDeviceProperties, + extensions: Option<&Extensions>, + driver_props: Option<&PhysicalDeviceDriverProperties>, +) { + log::info!(" api version: {}", ApiVersionDisplay(props.api_version)); + log::info!( + " driver version: {}", + ApiVersionDisplay(props.driver_version) + ); + log::info!(" vendor id: {}", props.vendor_id); + log::info!(" device id: {}", props.device_id); + log::info!(" device type: {:?}", props.device_type); + unsafe { + log::info!( + " device name: {}", + Ustr::from_ptr(props.device_name.as_ptr()).display() + ); + } + if props.api_version < API_VERSION { + log::warn!(" device does not support vulkan 1.3"); + } + if let Some(extensions) = extensions { + if !extensions.contains_key(ExtPhysicalDeviceDrmFn::name()) { + log::warn!(" device does support not the VK_EXT_physical_device_drm extension"); + } + } + if let Some(driver_props) = driver_props { + unsafe { + log::info!( + " driver: {} ({})", + Ustr::from_ptr(driver_props.driver_name.as_ptr()).display(), + Ustr::from_ptr(driver_props.driver_info.as_ptr()).display() + ); + } + } +} diff --git a/src/gfx_apis/vulkan/fence.rs b/src/gfx_apis/vulkan/fence.rs new file mode 100644 index 00000000..defa0923 --- /dev/null +++ b/src/gfx_apis/vulkan/fence.rs @@ -0,0 +1,49 @@ +use { + crate::gfx_apis::vulkan::{device::VulkanDevice, VulkanError}, + ash::vk::{ + ExportFenceCreateInfo, ExternalFenceHandleTypeFlags, Fence, FenceCreateInfo, + FenceGetFdInfoKHR, + }, + std::rc::Rc, + uapi::OwnedFd, +}; + +pub struct VulkanFence { + pub(super) device: Rc, + pub(super) fence: Fence, +} + +impl Drop for VulkanFence { + fn drop(&mut self) { + unsafe { + self.device.device.destroy_fence(self.fence, None); + } + } +} + +impl VulkanDevice { + pub fn create_fence(self: &Rc) -> Result, VulkanError> { + let fence = { + let mut export_info = ExportFenceCreateInfo::builder() + .handle_types(ExternalFenceHandleTypeFlags::SYNC_FD); + let create_info = FenceCreateInfo::builder().push_next(&mut export_info); + let fence = unsafe { self.device.create_fence(&create_info, None) }; + fence.map_err(VulkanError::CreateFence)? + }; + Ok(Rc::new(VulkanFence { + device: self.clone(), + fence, + })) + } +} + +impl VulkanFence { + pub fn export_syncfile(&self) -> Result, VulkanError> { + let info = FenceGetFdInfoKHR::builder() + .fence(self.fence) + .handle_type(ExternalFenceHandleTypeFlags::SYNC_FD); + let res = unsafe { self.device.external_fence_fd.get_fence_fd(&info) }; + res.map_err(VulkanError::ExportSyncFile) + .map(|fd| Rc::new(OwnedFd::new(fd))) + } +} diff --git a/src/gfx_apis/vulkan/format.rs b/src/gfx_apis/vulkan/format.rs new file mode 100644 index 00000000..581df7fe --- /dev/null +++ b/src/gfx_apis/vulkan/format.rs @@ -0,0 +1,271 @@ +use { + crate::{ + format::{Format, FORMATS}, + gfx_apis::vulkan::{instance::VulkanInstance, VulkanError}, + video::Modifier, + }, + ahash::AHashMap, + ash::{ + vk, + vk::{ + DrmFormatModifierPropertiesEXT, DrmFormatModifierPropertiesListEXT, + ExternalImageFormatProperties, ExternalMemoryFeatureFlags, + ExternalMemoryHandleTypeFlags, FormatFeatureFlags, FormatProperties2, + ImageFormatProperties2, ImageTiling, ImageType, ImageUsageFlags, PhysicalDevice, + PhysicalDeviceExternalImageFormatInfo, PhysicalDeviceImageDrmFormatModifierInfoEXT, + PhysicalDeviceImageFormatInfo2, SharingMode, + }, + }, + isnt::std_1::collections::IsntHashMapExt, +}; + +#[derive(Debug)] +pub struct VulkanFormat { + pub format: &'static Format, + pub modifiers: AHashMap, + pub shm: Option, + pub features: FormatFeatureFlags, +} + +#[derive(Debug)] +pub struct VulkanFormatFeatures { + pub linear_sampling: bool, +} + +#[derive(Debug)] +pub struct VulkanModifier { + pub modifier: Modifier, + pub planes: usize, + pub features: FormatFeatureFlags, + pub render_max_extents: Option, + pub texture_max_extents: Option, +} + +#[derive(Copy, Clone, Debug)] +pub struct VulkanMaxExtents { + pub width: u32, + pub height: u32, +} + +#[derive(Debug)] +pub struct VulkanShmFormat { + pub max_extents: VulkanMaxExtents, +} + +const FRAMEBUFFER_FEATURES: FormatFeatureFlags = FormatFeatureFlags::from_raw( + 0 | FormatFeatureFlags::COLOR_ATTACHMENT.as_raw() + | FormatFeatureFlags::COLOR_ATTACHMENT_BLEND.as_raw(), +); +const TEX_FEATURES: FormatFeatureFlags = FormatFeatureFlags::from_raw( + 0 | FormatFeatureFlags::SAMPLED_IMAGE.as_raw() + | FormatFeatureFlags::SAMPLED_IMAGE_FILTER_LINEAR.as_raw(), +); +const SHM_FEATURES: FormatFeatureFlags = FormatFeatureFlags::from_raw( + 0 | FormatFeatureFlags::TRANSFER_SRC.as_raw() + | FormatFeatureFlags::TRANSFER_DST.as_raw() + | TEX_FEATURES.as_raw(), +); + +const FRAMEBUFFER_USAGE: ImageUsageFlags = ImageUsageFlags::from_raw( + 0 | ImageUsageFlags::COLOR_ATTACHMENT.as_raw() | ImageUsageFlags::TRANSFER_SRC.as_raw(), +); +const TEX_USAGE: ImageUsageFlags = ImageUsageFlags::from_raw(0 | ImageUsageFlags::SAMPLED.as_raw()); +const SHM_USAGE: ImageUsageFlags = + ImageUsageFlags::from_raw(0 | ImageUsageFlags::TRANSFER_DST.as_raw() | TEX_USAGE.as_raw()); + +impl VulkanInstance { + pub(super) fn load_formats( + &self, + phy_dev: PhysicalDevice, + ) -> Result, VulkanError> { + let mut res = AHashMap::new(); + for format in FORMATS { + self.load_format(phy_dev, format, &mut res)?; + } + Ok(res) + } + + fn load_format( + &self, + phy_dev: PhysicalDevice, + format: &'static Format, + dst: &mut AHashMap, + ) -> Result<(), VulkanError> { + let mut modifier_props = DrmFormatModifierPropertiesListEXT::builder().build(); + let mut format_properties = FormatProperties2::builder() + .push_next(&mut modifier_props) + .build(); + unsafe { + self.instance.get_physical_device_format_properties2( + phy_dev, + format.vk_format, + &mut format_properties, + ); + } + let shm = self.load_shm_format(phy_dev, format, &format_properties)?; + let modifiers = self.load_drm_format(phy_dev, format, &modifier_props)?; + if shm.is_some() || modifiers.is_not_empty() { + dst.insert( + format.drm, + VulkanFormat { + format, + modifiers, + shm, + features: format_properties.format_properties.optimal_tiling_features, + }, + ); + } + Ok(()) + } + + fn load_shm_format( + &self, + phy_dev: PhysicalDevice, + format: &Format, + props: &FormatProperties2, + ) -> Result, VulkanError> { + if !props + .format_properties + .optimal_tiling_features + .contains(SHM_FEATURES) + { + return Ok(None); + } + let format_info = PhysicalDeviceImageFormatInfo2::builder() + .ty(ImageType::TYPE_2D) + .format(format.vk_format) + .tiling(ImageTiling::OPTIMAL) + .usage(SHM_USAGE); + let mut format_properties = ImageFormatProperties2::builder(); + let res = unsafe { + self.instance.get_physical_device_image_format_properties2( + phy_dev, + &format_info, + &mut format_properties, + ) + }; + if let Err(e) = res { + return match e { + vk::Result::ERROR_FORMAT_NOT_SUPPORTED => Ok(None), + _ => Err(VulkanError::LoadImageProperties(e)), + }; + } + Ok(Some(VulkanShmFormat { + max_extents: VulkanMaxExtents { + width: format_properties.image_format_properties.max_extent.width, + height: format_properties.image_format_properties.max_extent.height, + }, + })) + } + + fn load_drm_format( + &self, + phy_dev: PhysicalDevice, + format: &Format, + props: &DrmFormatModifierPropertiesListEXT, + ) -> Result, VulkanError> { + if props.drm_format_modifier_count == 0 { + return Ok(AHashMap::new()); + } + let mut drm_mods = vec![ + DrmFormatModifierPropertiesEXT::default(); + props.drm_format_modifier_count as usize + ]; + let mut modifier_props = DrmFormatModifierPropertiesListEXT::builder() + .drm_format_modifier_properties(&mut drm_mods) + .build(); + let mut format_properties = FormatProperties2::builder() + .push_next(&mut modifier_props) + .build(); + unsafe { + self.instance.get_physical_device_format_properties2( + phy_dev, + format.vk_format, + &mut format_properties, + ); + }; + let mut mods = AHashMap::new(); + for modifier in drm_mods { + let render_max_extents = self.get_max_extents( + phy_dev, + format, + FRAMEBUFFER_FEATURES, + FRAMEBUFFER_USAGE, + &modifier, + )?; + let texture_max_extents = + self.get_max_extents(phy_dev, format, TEX_FEATURES, TEX_USAGE, &modifier)?; + mods.insert( + modifier.drm_format_modifier, + VulkanModifier { + modifier: modifier.drm_format_modifier, + planes: modifier.drm_format_modifier_plane_count as _, + features: modifier.drm_format_modifier_tiling_features, + render_max_extents, + texture_max_extents, + }, + ); + } + Ok(mods) + } + + fn get_max_extents( + &self, + phy_dev: PhysicalDevice, + format: &Format, + features: FormatFeatureFlags, + usage: ImageUsageFlags, + props: &DrmFormatModifierPropertiesEXT, + ) -> Result, VulkanError> { + if !props.drm_format_modifier_tiling_features.contains(features) { + return Ok(None); + } + let mut modifier_info = PhysicalDeviceImageDrmFormatModifierInfoEXT::builder() + .drm_format_modifier(props.drm_format_modifier) + .sharing_mode(SharingMode::EXCLUSIVE) + .build(); + let mut external_image_format_info = PhysicalDeviceExternalImageFormatInfo::builder() + .handle_type(ExternalMemoryHandleTypeFlags::DMA_BUF_EXT) + .build(); + let image_format_info = PhysicalDeviceImageFormatInfo2::builder() + .ty(ImageType::TYPE_2D) + .format(format.vk_format) + .usage(usage) + .tiling(ImageTiling::DRM_FORMAT_MODIFIER_EXT) + .push_next(&mut external_image_format_info) + .push_next(&mut modifier_info) + .build(); + + let mut external_image_format_props = ExternalImageFormatProperties::builder().build(); + let mut image_format_props = ImageFormatProperties2::builder() + .push_next(&mut external_image_format_props) + .build(); + + let res = unsafe { + self.instance.get_physical_device_image_format_properties2( + phy_dev, + &image_format_info, + &mut image_format_props, + ) + }; + + if let Err(e) = res { + return match e { + vk::Result::ERROR_FORMAT_NOT_SUPPORTED => Ok(None), + _ => Err(VulkanError::LoadImageProperties(e)), + }; + } + let importable = external_image_format_props + .external_memory_properties + .external_memory_features + .contains(ExternalMemoryFeatureFlags::IMPORTABLE); + if !importable { + return Ok(None); + } + + Ok(Some(VulkanMaxExtents { + width: image_format_props.image_format_properties.max_extent.width, + height: image_format_props.image_format_properties.max_extent.height, + })) + } +} diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs new file mode 100644 index 00000000..2c081231 --- /dev/null +++ b/src/gfx_apis/vulkan/image.rs @@ -0,0 +1,536 @@ +use { + crate::{ + format::Format, + gfx_api::{GfxApiOpt, GfxError, GfxFramebuffer, GfxImage, GfxTexture}, + gfx_apis::vulkan::{ + allocator::VulkanAllocation, device::VulkanDevice, format::VulkanMaxExtents, + renderer::VulkanRenderer, util::OnDrop, VulkanError, + }, + theme::Color, + utils::clonecell::CloneCell, + video::{ + dmabuf::{DmaBuf, DmaBufPlane, PlaneVec}, + Modifier, + }, + }, + ash::vk::{ + BindImageMemoryInfo, BindImagePlaneMemoryInfo, ComponentMapping, ComponentSwizzle, + DeviceMemory, DeviceSize, Extent3D, ExternalMemoryHandleTypeFlags, + ExternalMemoryImageCreateInfo, FormatFeatureFlags, Image, ImageAspectFlags, + ImageCreateFlags, ImageCreateInfo, ImageDrmFormatModifierExplicitCreateInfoEXT, + ImageLayout, ImageMemoryRequirementsInfo2, ImagePlaneMemoryRequirementsInfo, + ImageSubresourceRange, ImageTiling, ImageType, ImageUsageFlags, ImageView, + ImageViewCreateInfo, ImageViewType, ImportMemoryFdInfoKHR, MemoryAllocateInfo, + MemoryDedicatedAllocateInfo, MemoryPropertyFlags, MemoryRequirements2, SampleCountFlags, + SharingMode, SubresourceLayout, + }, + gpu_alloc::UsageFlags, + std::{ + any::Any, + cell::{Cell, RefCell}, + fmt::{Debug, Formatter}, + mem, + rc::Rc, + }, +}; + +pub struct VulkanDmaBufImageTemplate { + pub(super) renderer: Rc, + pub(super) format: &'static Format, + pub(super) width: u32, + pub(super) height: u32, + pub(super) modifier: Modifier, + pub(super) disjoint: bool, + pub(super) planes: PlaneVec, + pub(super) render_max_extents: Option, + pub(super) texture_max_extents: Option, +} + +pub struct VulkanImage { + pub(super) renderer: Rc, + pub(super) format: &'static Format, + pub(super) width: u32, + pub(super) height: u32, + pub(super) texture_view: ImageView, + pub(super) render_view: Option, + pub(super) image: Image, + pub(super) is_undefined: Cell, + pub(super) ty: VulkanImageMemory, + pub(super) render_ops: CloneCell>, +} + +pub enum VulkanImageMemory { + DmaBuf(VulkanDmaBufImage), + Internal(VulkanShmImage), +} + +pub struct VulkanDmaBufImage { + pub(super) template: Rc, + pub(super) mems: PlaneVec, +} + +pub struct VulkanShmImage { + pub(super) to_flush: RefCell>>, + pub(super) size: DeviceSize, + pub(super) stride: u32, + pub(super) _allocation: VulkanAllocation, +} + +impl Drop for VulkanDmaBufImage { + fn drop(&mut self) { + unsafe { + for &mem in &self.mems { + self.template.renderer.device.device.free_memory(mem, None); + } + } + } +} + +impl Drop for VulkanImage { + fn drop(&mut self) { + unsafe { + self.renderer + .device + .device + .destroy_image_view(self.texture_view, None); + if let Some(render_view) = self.render_view { + self.renderer + .device + .device + .destroy_image_view(render_view, None); + } + self.renderer.device.device.destroy_image(self.image, None); + } + } +} + +impl VulkanShmImage { + pub fn upload(&self, buffer: &[Cell]) -> Result<(), VulkanError> { + let buffer = buffer.iter().map(|b| b.get()).collect(); + *self.to_flush.borrow_mut() = Some(buffer); + Ok(()) + } +} + +impl VulkanRenderer { + pub fn create_shm_texture( + self: &Rc, + format: &'static Format, + width: i32, + height: i32, + stride: i32, + data: &[Cell], + ) -> Result, VulkanError> { + if width <= 0 || height <= 0 || stride <= 0 { + return Err(VulkanError::NonPositiveImageSize); + } + let width = width as u32; + let height = height as u32; + let stride = stride as u32; + let vk_format = self + .device + .formats + .get(&format.drm) + .ok_or(VulkanError::FormatNotSupported)?; + let shm = vk_format.shm.as_ref().ok_or(VulkanError::ShmNotSupported)?; + if width > shm.max_extents.width || height > shm.max_extents.height { + return Err(VulkanError::ImageTooLarge); + } + let size = stride.checked_mul(height).ok_or(VulkanError::ShmOverflow)?; + let create_info = ImageCreateInfo::builder() + .image_type(ImageType::TYPE_2D) + .format(format.vk_format) + .mip_levels(1) + .array_layers(1) + .tiling(ImageTiling::OPTIMAL) + .samples(SampleCountFlags::TYPE_1) + .sharing_mode(SharingMode::EXCLUSIVE) + .initial_layout(ImageLayout::UNDEFINED) + .extent(Extent3D { + width, + height, + depth: 1, + }) + .usage(ImageUsageFlags::SAMPLED | ImageUsageFlags::TRANSFER_DST) + .build(); + let image = unsafe { self.device.device.create_image(&create_info, None) }; + let image = image.map_err(VulkanError::CreateImage)?; + let destroy_image = OnDrop(|| unsafe { self.device.device.destroy_image(image, None) }); + let memory_requirements = + unsafe { self.device.device.get_image_memory_requirements(image) }; + let allocation = + self.allocator + .alloc(&memory_requirements, UsageFlags::FAST_DEVICE_ACCESS, false)?; + let res = unsafe { + self.device + .device + .bind_image_memory(image, allocation.memory, allocation.offset) + }; + res.map_err(VulkanError::BindImageMemory)?; + let image_view_create_info = ImageViewCreateInfo::builder() + .image(image) + .format(format.vk_format) + .view_type(ImageViewType::TYPE_2D) + .subresource_range(ImageSubresourceRange { + aspect_mask: ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + layer_count: 1, + }); + let view = unsafe { + self.device + .device + .create_image_view(&image_view_create_info, None) + }; + let view = view.map_err(VulkanError::CreateImageView)?; + let shm = VulkanShmImage { + to_flush: Default::default(), + size: size as u64, + stride, + _allocation: allocation, + }; + shm.upload(data)?; + destroy_image.forget(); + Ok(Rc::new(VulkanImage { + renderer: self.clone(), + format, + width, + height, + texture_view: view, + render_view: None, + image, + is_undefined: Cell::new(true), + ty: VulkanImageMemory::Internal(shm), + render_ops: Default::default(), + })) + } + + pub fn import_dmabuf( + self: &Rc, + dmabuf: &DmaBuf, + ) -> Result, VulkanError> { + let format = self + .device + .formats + .get(&dmabuf.format.drm) + .ok_or(VulkanError::FormatNotSupported)?; + let modifier = format + .modifiers + .get(&dmabuf.modifier) + .ok_or(VulkanError::ModifierNotSupported)?; + if dmabuf.width <= 0 || dmabuf.height <= 0 { + return Err(VulkanError::NonPositiveImageSize); + } + let width = dmabuf.width as u32; + let height = dmabuf.height as u32; + let can_render = match &modifier.render_max_extents { + None => false, + Some(t) => width <= t.width && height <= t.height, + }; + let can_texture = match &modifier.texture_max_extents { + None => false, + Some(t) => width <= t.width && height <= t.height, + }; + if !can_render && !can_texture { + if modifier.render_max_extents.is_none() && modifier.texture_max_extents.is_none() { + return Err(VulkanError::ModifierUseNotSupported); + } + return Err(VulkanError::ImageTooLarge); + } + if modifier.planes != dmabuf.planes.len() { + return Err(VulkanError::BadPlaneCount); + } + let disjoint = dmabuf.is_disjoint(); + if disjoint && !modifier.features.contains(FormatFeatureFlags::DISJOINT) { + return Err(VulkanError::DisjointNotSupported); + } + Ok(Rc::new(VulkanDmaBufImageTemplate { + renderer: self.clone(), + format: dmabuf.format, + width, + height, + modifier: dmabuf.modifier, + disjoint, + planes: dmabuf.planes.clone(), + render_max_extents: modifier.render_max_extents, + texture_max_extents: modifier.texture_max_extents, + })) + } +} + +impl VulkanDevice { + pub fn create_image_view( + &self, + image: Image, + format: &'static Format, + for_rendering: bool, + ) -> Result { + let create_info = ImageViewCreateInfo::builder() + .image(image) + .view_type(ImageViewType::TYPE_2D) + .format(format.vk_format) + .components(ComponentMapping { + r: ComponentSwizzle::IDENTITY, + g: ComponentSwizzle::IDENTITY, + b: ComponentSwizzle::IDENTITY, + a: match format.has_alpha || for_rendering { + true => ComponentSwizzle::IDENTITY, + false => ComponentSwizzle::ONE, + }, + }) + .subresource_range(ImageSubresourceRange { + aspect_mask: ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + layer_count: 1, + }); + let view = unsafe { self.device.create_image_view(&create_info, None) }; + view.map_err(VulkanError::CreateImageView) + } +} + +impl VulkanDmaBufImageTemplate { + pub fn create_framebuffer(self: &Rc) -> Result, VulkanError> { + self.create_image(true, None) + } + + pub fn create_texture( + self: &Rc, + shm: Option, + ) -> Result, VulkanError> { + self.create_image(false, shm) + } + + fn create_image( + self: &Rc, + for_rendering: bool, + shm: Option, + ) -> Result, VulkanError> { + let device = &self.renderer.device; + let max_extents = match for_rendering { + true => self.render_max_extents, + false => self.texture_max_extents, + }; + let max_extents = max_extents.ok_or(VulkanError::ModifierUseNotSupported)?; + if self.width > max_extents.width || self.height > max_extents.height { + return Err(VulkanError::ImageTooLarge); + } + let image = { + let plane_layouts: PlaneVec<_> = self + .planes + .iter() + .map(|p| SubresourceLayout { + offset: p.offset as _, + row_pitch: p.stride as _, + size: 0, + array_pitch: 0, + depth_pitch: 0, + }) + .collect(); + let mut mod_info = ImageDrmFormatModifierExplicitCreateInfoEXT::builder() + .drm_format_modifier(self.modifier) + .plane_layouts(&plane_layouts) + .build(); + let mut memory_image_create_info = ExternalMemoryImageCreateInfo::builder() + .handle_types(ExternalMemoryHandleTypeFlags::DMA_BUF_EXT) + .build(); + let flags = match self.disjoint { + true => ImageCreateFlags::DISJOINT, + false => ImageCreateFlags::empty(), + }; + let usage = match (for_rendering, shm.is_some()) { + (true, _) => ImageUsageFlags::COLOR_ATTACHMENT, + (false, false) => ImageUsageFlags::SAMPLED, + (false, true) => ImageUsageFlags::SAMPLED | ImageUsageFlags::TRANSFER_DST, + }; + let create_info = ImageCreateInfo::builder() + .image_type(ImageType::TYPE_2D) + .format(self.format.vk_format) + .mip_levels(1) + .array_layers(1) + .tiling(ImageTiling::DRM_FORMAT_MODIFIER_EXT) + .samples(SampleCountFlags::TYPE_1) + .sharing_mode(SharingMode::EXCLUSIVE) + .initial_layout(ImageLayout::UNDEFINED) + .extent(Extent3D { + width: self.width, + height: self.height, + depth: 1, + }) + .usage(usage) + .flags(flags) + .push_next(&mut memory_image_create_info) + .push_next(&mut mod_info) + .build(); + let image = unsafe { device.device.create_image(&create_info, None) }; + image.map_err(VulkanError::CreateImage)? + }; + let destroy_image = OnDrop(|| unsafe { device.device.destroy_image(image, None) }); + let num_device_memories = match self.disjoint { + true => self.planes.len(), + false => 1, + }; + let mut device_memories = PlaneVec::new(); + let mut free_device_memories = PlaneVec::new(); + let mut bind_image_plane_memory_infos = PlaneVec::new(); + for plane_idx in 0..num_device_memories { + let dma_buf_plane = &self.planes[plane_idx]; + let memory_fd_properties = unsafe { + device.external_memory_fd.get_memory_fd_properties( + ExternalMemoryHandleTypeFlags::DMA_BUF_EXT, + dma_buf_plane.fd.raw(), + ) + }; + let memory_fd_properties = + memory_fd_properties.map_err(VulkanError::MemoryFdProperties)?; + let mut image_memory_requirements_info = + ImageMemoryRequirementsInfo2::builder().image(image); + let mut image_plane_memory_requirements_info; + if self.disjoint { + let plane_aspect = match plane_idx { + 0 => ImageAspectFlags::MEMORY_PLANE_0_EXT, + 1 => ImageAspectFlags::MEMORY_PLANE_1_EXT, + 2 => ImageAspectFlags::MEMORY_PLANE_2_EXT, + 3 => ImageAspectFlags::MEMORY_PLANE_3_EXT, + _ => unreachable!(), + }; + image_plane_memory_requirements_info = + ImagePlaneMemoryRequirementsInfo::builder().plane_aspect(plane_aspect); + image_memory_requirements_info = image_memory_requirements_info + .push_next(&mut image_plane_memory_requirements_info); + bind_image_plane_memory_infos.push( + BindImagePlaneMemoryInfo::builder() + .plane_aspect(plane_aspect) + .build(), + ); + } + let mut memory_requirements = MemoryRequirements2::default(); + unsafe { + device.device.get_image_memory_requirements2( + &image_memory_requirements_info, + &mut memory_requirements, + ); + } + let memory_type_bits = memory_requirements.memory_requirements.memory_type_bits + & memory_fd_properties.memory_type_bits; + let memory_type_index = self + .renderer + .device + .find_memory_type(MemoryPropertyFlags::empty(), memory_type_bits) + .ok_or(VulkanError::MemoryType)?; + let fd = uapi::fcntl_dupfd_cloexec(dma_buf_plane.fd.raw(), 0) + .map_err(|e| VulkanError::Dupfd(e.into()))?; + let mut memory_dedicated_allocate_info = + MemoryDedicatedAllocateInfo::builder().image(image).build(); + let mut import_memory_fd_info = ImportMemoryFdInfoKHR::builder() + .fd(fd.raw()) + .handle_type(ExternalMemoryHandleTypeFlags::DMA_BUF_EXT) + .build(); + let memory_allocate_info = MemoryAllocateInfo::builder() + .allocation_size(memory_requirements.memory_requirements.size) + .memory_type_index(memory_type_index) + .push_next(&mut import_memory_fd_info) + .push_next(&mut memory_dedicated_allocate_info) + .build(); + let device_memory = + unsafe { device.device.allocate_memory(&memory_allocate_info, None) }; + let device_memory = device_memory.map_err(VulkanError::AllocateMemory)?; + fd.unwrap(); + device_memories.push(device_memory); + free_device_memories.push(OnDrop(move || unsafe { + device.device.free_memory(device_memory, None) + })); + } + let mut bind_image_memory_infos = Vec::with_capacity(num_device_memories); + for (i, mem) in device_memories.iter().copied().enumerate() { + let mut info = BindImageMemoryInfo::builder().image(image).memory(mem); + if self.disjoint { + info = info.push_next(&mut bind_image_plane_memory_infos[i]); + } + bind_image_memory_infos.push(info.build()); + } + let res = unsafe { device.device.bind_image_memory2(&bind_image_memory_infos) }; + res.map_err(VulkanError::BindImageMemory)?; + let texture_view = device.create_image_view(image, self.format, false)?; + let render_view = device.create_image_view(image, self.format, true)?; + free_device_memories.drain(..).for_each(mem::forget); + mem::forget(destroy_image); + Ok(Rc::new(VulkanImage { + renderer: self.renderer.clone(), + texture_view, + render_view: Some(render_view), + image, + width: self.width, + height: self.height, + render_ops: Default::default(), + ty: VulkanImageMemory::DmaBuf(VulkanDmaBufImage { + template: self.clone(), + mems: device_memories, + }), + format: self.format, + is_undefined: Cell::new(true), + })) + } +} + +impl GfxImage for VulkanDmaBufImageTemplate { + fn to_framebuffer(self: Rc) -> Result, GfxError> { + self.create_framebuffer() + .map(|v| v as _) + .map_err(|e| e.into()) + } + + fn to_texture(self: Rc) -> Result, GfxError> { + self.create_texture(None) + .map(|v| v as _) + .map_err(|e| e.into()) + } + + fn width(&self) -> i32 { + self.width as i32 + } + + fn height(&self) -> i32 { + self.height as i32 + } +} + +impl Debug for VulkanImage { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("VulkanDmaBufImage").finish_non_exhaustive() + } +} + +impl GfxFramebuffer for VulkanImage { + fn as_any(&self) -> &dyn Any { + self + } + + fn take_render_ops(&self) -> Vec { + self.render_ops.take() + } + + fn size(&self) -> (i32, i32) { + (self.width as _, self.height as _) + } + + fn render(&self, ops: Vec, clear: Option<&Color>) { + self.renderer.execute(self, &ops, clear).unwrap(); + } +} + +impl GfxTexture for VulkanImage { + fn size(&self) -> (i32, i32) { + (self.width as _, self.height as _) + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn into_any(self: Rc) -> Rc { + self + } +} diff --git a/src/gfx_apis/vulkan/instance.rs b/src/gfx_apis/vulkan/instance.rs new file mode 100644 index 00000000..56985968 --- /dev/null +++ b/src/gfx_apis/vulkan/instance.rs @@ -0,0 +1,228 @@ +use { + crate::{ + async_engine::AsyncEngine, + gfx_apis::vulkan::{util::OnDrop, VulkanError, VULKAN_VALIDATION}, + io_uring::IoUring, + }, + ahash::{AHashMap, AHashSet}, + ash::{ + extensions::ext::DebugUtils, + vk::{ + api_version_major, api_version_minor, api_version_patch, api_version_variant, + ApplicationInfo, Bool32, DebugUtilsMessageSeverityFlagsEXT, + DebugUtilsMessageTypeFlagsEXT, DebugUtilsMessengerCallbackDataEXT, + DebugUtilsMessengerCreateInfoEXT, DebugUtilsMessengerEXT, ExtDebugUtilsFn, + ExtValidationFeaturesFn, ExtensionProperties, InstanceCreateInfo, LayerProperties, + ValidationFeaturesEXT, API_VERSION_1_3, FALSE, + }, + Entry, Instance, LoadingError, + }, + isnt::std_1::collections::IsntHashMap2Ext, + log::Level, + once_cell::sync::Lazy, + std::{ + ffi::{c_void, CStr, CString}, + fmt::{Display, Formatter}, + iter::IntoIterator, + rc::Rc, + slice, + sync::Arc, + }, + uapi::{ustr, Ustr}, +}; + +pub struct VulkanInstance { + pub(super) _entry: &'static Entry, + pub(super) instance: Instance, + pub(super) debug_utils: DebugUtils, + pub(super) messenger: DebugUtilsMessengerEXT, + pub(super) eng: Rc, + pub(super) ring: Rc, +} + +impl VulkanInstance { + pub fn new( + eng: &Rc, + ring: &Rc, + validation: bool, + ) -> Result, VulkanError> { + static ENTRY: Lazy>> = + Lazy::new(|| unsafe { Entry::load() }.map_err(Arc::new)); + let entry = match &*ENTRY { + Ok(e) => e, + Err(e) => return Err(VulkanError::Load(e.clone())), + }; + let extensions = get_instance_extensions(entry, None)?; + for &ext in REQUIRED_INSTANCE_EXTENSIONS { + if extensions.not_contains_key(ext) { + return Err(VulkanError::MissingInstanceExtension(ext)); + } + } + let mut enabled_extensions: Vec<_> = REQUIRED_INSTANCE_EXTENSIONS + .iter() + .map(|c| c.as_ptr()) + .collect(); + let app_info = ApplicationInfo::builder() + .api_version(API_VERSION) + .application_name(ustr!("jay").as_c_str().unwrap()) + .application_version(1); + let mut severity = DebugUtilsMessageSeverityFlagsEXT::empty() + | DebugUtilsMessageSeverityFlagsEXT::ERROR + | DebugUtilsMessageSeverityFlagsEXT::WARNING; + if *VULKAN_VALIDATION { + severity |= DebugUtilsMessageSeverityFlagsEXT::INFO + | DebugUtilsMessageSeverityFlagsEXT::VERBOSE; + } + let types = DebugUtilsMessageTypeFlagsEXT::empty() + | DebugUtilsMessageTypeFlagsEXT::VALIDATION + | DebugUtilsMessageTypeFlagsEXT::PERFORMANCE + | DebugUtilsMessageTypeFlagsEXT::GENERAL; + let mut debug_info = DebugUtilsMessengerCreateInfoEXT::builder() + .message_severity(severity) + .message_type(types) + .pfn_user_callback(Some(debug_callback)); + let validation_features = [ + // ash::vk::ValidationFeatureEnableEXT::DEBUG_PRINTF, + // ash::vk::ValidationFeatureEnableEXT::BEST_PRACTICES, + // ash::vk::ValidationFeatureEnableEXT::SYNCHRONIZATION_VALIDATION, + // ash::vk::ValidationFeatureEnableEXT::GPU_ASSISTED, + ]; + let mut validation_info = + ValidationFeaturesEXT::builder().enabled_validation_features(&validation_features); + let mut create_info = InstanceCreateInfo::builder() + .application_info(&app_info) + .push_next(&mut debug_info); + let validation_layer_name = VALIDATION_LAYER.as_ptr(); + if validation { + if get_available_layers(entry)?.contains(VALIDATION_LAYER) { + create_info = + create_info.enabled_layer_names(slice::from_ref(&validation_layer_name)); + let extensions = get_instance_extensions(entry, Some(VALIDATION_LAYER))?; + if extensions.contains_key(ExtValidationFeaturesFn::name()) { + enabled_extensions.push(ExtValidationFeaturesFn::name().as_ptr()); + create_info = create_info.push_next(&mut validation_info); + } else { + log::warn!("{:?} is not available", ExtValidationFeaturesFn::name(),); + } + } else { + log::warn!( + "Vulkan validation was requested but validation layers are not available" + ); + } + } + create_info = create_info.enabled_extension_names(&enabled_extensions); + let instance = match unsafe { entry.create_instance(&create_info, None) } { + Ok(i) => i, + Err(e) => return Err(VulkanError::CreateInstance(e)), + }; + let destroy_instance = OnDrop(|| unsafe { instance.destroy_instance(None) }); + let debug_utils = DebugUtils::new(entry, &instance); + let messenger = unsafe { debug_utils.create_debug_utils_messenger(&debug_info, None) }; + let messenger = match messenger { + Ok(m) => m, + Err(e) => return Err(VulkanError::Messenger(e)), + }; + destroy_instance.forget(); + Ok(Rc::new(Self { + _entry: entry, + instance, + debug_utils, + messenger, + eng: eng.clone(), + ring: ring.clone(), + })) + } +} + +impl Drop for VulkanInstance { + fn drop(&mut self) { + unsafe { + self.debug_utils + .destroy_debug_utils_messenger(self.messenger, None); + self.instance.destroy_instance(None); + } + } +} + +const REQUIRED_INSTANCE_EXTENSIONS: &[&CStr] = &[ExtDebugUtilsFn::name()]; + +const VALIDATION_LAYER: &CStr = c"VK_LAYER_KHRONOS_validation"; + +pub type Extensions = AHashMap; + +fn get_instance_extensions(entry: &Entry, layer: Option<&CStr>) -> Result { + entry + .enumerate_instance_extension_properties(layer) + .map_err(VulkanError::InstanceExtensions) + .map(map_extension_properties) +} + +fn get_available_layers(entry: &Entry) -> Result, VulkanError> { + entry + .enumerate_instance_layer_properties() + .map_err(VulkanError::InstanceLayers) + .map(map_layer_properties) +} + +fn map_layer_properties(props: Vec) -> AHashSet { + props + .into_iter() + .map(|e| unsafe { CStr::from_ptr(e.layer_name.as_ptr()).to_owned() }) + .collect() +} + +pub fn map_extension_properties(props: Vec) -> Extensions { + props + .into_iter() + .map(|e| { + let s = unsafe { CStr::from_ptr(e.extension_name.as_ptr()) }; + (s.to_owned(), e.spec_version) + }) + .collect() +} + +unsafe extern "system" fn debug_callback( + message_severity: DebugUtilsMessageSeverityFlagsEXT, + _message_types: DebugUtilsMessageTypeFlagsEXT, + p_callback_data: *const DebugUtilsMessengerCallbackDataEXT, + _p_user_data: *mut c_void, +) -> Bool32 { + let _level = match message_severity { + DebugUtilsMessageSeverityFlagsEXT::ERROR => Level::Error, + DebugUtilsMessageSeverityFlagsEXT::WARNING => Level::Warn, + DebugUtilsMessageSeverityFlagsEXT::INFO => Level::Info, + DebugUtilsMessageSeverityFlagsEXT::VERBOSE => Level::Trace, + _ => Level::Warn, + }; + let data = &*p_callback_data; + let message = Ustr::from_ptr(data.p_message); + let message_id_name = if data.p_message_id_name.is_null() { + ustr!("") + } else { + Ustr::from_ptr(data.p_message_id_name) + }; + log::log!( + Level::Info, + "VULKAN: {} ({})", + message.display(), + message_id_name.display() + ); + FALSE +} + +pub struct ApiVersionDisplay(pub u32); + +impl Display for ApiVersionDisplay { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}.{}.{}.{}", + api_version_variant(self.0), + api_version_major(self.0), + api_version_minor(self.0), + api_version_patch(self.0), + ) + } +} + +pub const API_VERSION: u32 = API_VERSION_1_3; diff --git a/src/gfx_apis/vulkan/pipeline.rs b/src/gfx_apis/vulkan/pipeline.rs new file mode 100644 index 00000000..cd30ad92 --- /dev/null +++ b/src/gfx_apis/vulkan/pipeline.rs @@ -0,0 +1,182 @@ +use { + crate::{ + format::ARGB8888, + gfx_apis::vulkan::{ + descriptor::VulkanDescriptorSetLayout, device::VulkanDevice, shaders::VulkanShader, + util::OnDrop, VulkanError, + }, + }, + arrayvec::ArrayVec, + ash::vk::{ + BlendFactor, BlendOp, ColorComponentFlags, CullModeFlags, DynamicState, FrontFace, + GraphicsPipelineCreateInfo, Pipeline, PipelineCache, PipelineColorBlendAttachmentState, + PipelineColorBlendStateCreateInfo, PipelineDynamicStateCreateInfo, + PipelineInputAssemblyStateCreateInfo, PipelineLayout, PipelineLayoutCreateInfo, + PipelineMultisampleStateCreateInfo, PipelineRasterizationStateCreateInfo, + PipelineRenderingCreateInfo, PipelineShaderStageCreateInfo, + PipelineVertexInputStateCreateInfo, PipelineViewportStateCreateInfo, PolygonMode, + PrimitiveTopology, PushConstantRange, SampleCountFlags, ShaderStageFlags, + }, + std::{mem, rc::Rc, slice}, + uapi::ustr, +}; + +pub(super) struct VulkanPipeline { + pub(super) vert: Rc, + pub(super) _frag: Rc, + pub(super) frag_push_offset: u32, + pub(super) pipeline_layout: PipelineLayout, + pub(super) pipeline: Pipeline, + pub(super) _frag_descriptor_set_layout: Option>, +} + +pub(super) struct PipelineCreateInfo { + pub(super) vert: Rc, + pub(super) frag: Rc, + pub(super) alpha: bool, + pub(super) frag_descriptor_set_layout: Option>, +} + +impl VulkanDevice { + pub(super) fn create_pipeline( + &self, + info: PipelineCreateInfo, + ) -> Result, VulkanError> { + self.create_pipeline_(info, mem::size_of::() as _, mem::size_of::() as _) + } + + fn create_pipeline_( + &self, + info: PipelineCreateInfo, + vert_push_size: u32, + frag_push_size: u32, + ) -> Result, VulkanError> { + let pipeline_layout = { + let mut push_constant_ranges = ArrayVec::<_, 2>::new(); + let mut push_constant_offset = 0; + if vert_push_size > 0 { + push_constant_ranges.push( + PushConstantRange::builder() + .stage_flags(ShaderStageFlags::VERTEX) + .offset(0) + .size(vert_push_size) + .build(), + ); + push_constant_offset += vert_push_size; + } + if frag_push_size > 0 { + push_constant_ranges.push( + PushConstantRange::builder() + .stage_flags(ShaderStageFlags::FRAGMENT) + .offset(push_constant_offset) + .size(frag_push_size) + .build(), + ); + #[allow(unused_assignments)] + { + push_constant_offset += frag_push_size; + } + } + let mut descriptor_set_layouts = ArrayVec::<_, 1>::new(); + descriptor_set_layouts + .extend(info.frag_descriptor_set_layout.as_ref().map(|l| l.layout)); + let create_info = PipelineLayoutCreateInfo::builder() + .push_constant_ranges(&push_constant_ranges) + .set_layouts(&descriptor_set_layouts); + let layout = unsafe { self.device.create_pipeline_layout(&create_info, None) }; + layout.map_err(VulkanError::CreatePipelineLayout)? + }; + let destroy_layout = + OnDrop(|| unsafe { self.device.destroy_pipeline_layout(pipeline_layout, None) }); + let pipeline = { + let main = ustr!("main").as_c_str().unwrap(); + let stages = [ + PipelineShaderStageCreateInfo::builder() + .stage(ShaderStageFlags::VERTEX) + .module(info.vert.module) + .name(main) + .build(), + PipelineShaderStageCreateInfo::builder() + .stage(ShaderStageFlags::FRAGMENT) + .module(info.frag.module) + .name(main) + .build(), + ]; + let input_assembly_state = PipelineInputAssemblyStateCreateInfo::builder() + .topology(PrimitiveTopology::TRIANGLE_STRIP); + let vertex_input_state = PipelineVertexInputStateCreateInfo::builder(); + let rasterization_state = PipelineRasterizationStateCreateInfo::builder() + .polygon_mode(PolygonMode::FILL) + .cull_mode(CullModeFlags::BACK) + .line_width(1.0) + .front_face(FrontFace::COUNTER_CLOCKWISE); + let multisampling_state = PipelineMultisampleStateCreateInfo::builder() + .sample_shading_enable(false) + .rasterization_samples(SampleCountFlags::TYPE_1); + let mut blending = PipelineColorBlendAttachmentState::builder() + .color_write_mask(ColorComponentFlags::RGBA); + if info.alpha { + blending = blending + .blend_enable(true) + .src_color_blend_factor(BlendFactor::ONE) + .dst_color_blend_factor(BlendFactor::ONE_MINUS_SRC_ALPHA) + .color_blend_op(BlendOp::ADD) + .src_alpha_blend_factor(BlendFactor::ONE) + .dst_alpha_blend_factor(BlendFactor::ONE_MINUS_SRC_ALPHA) + .alpha_blend_op(BlendOp::ADD); + } + let color_blend_state = PipelineColorBlendStateCreateInfo::builder() + .attachments(slice::from_ref(&blending)); + let dynamic_states = [DynamicState::VIEWPORT, DynamicState::SCISSOR]; + let dynamic_state = + PipelineDynamicStateCreateInfo::builder().dynamic_states(&dynamic_states); + let viewport_state = PipelineViewportStateCreateInfo::builder() + .viewport_count(1) + .scissor_count(1); + let mut pipeline_rendering_create_info = PipelineRenderingCreateInfo::builder() + .color_attachment_formats(slice::from_ref(&ARGB8888.vk_format)); + let create_info = GraphicsPipelineCreateInfo::builder() + .push_next(&mut pipeline_rendering_create_info) + .stages(&stages) + .input_assembly_state(&input_assembly_state) + .vertex_input_state(&vertex_input_state) + .rasterization_state(&rasterization_state) + .multisample_state(&multisampling_state) + .color_blend_state(&color_blend_state) + .dynamic_state(&dynamic_state) + .viewport_state(&viewport_state) + .layout(pipeline_layout); + let pipelines = unsafe { + self.device.create_graphics_pipelines( + PipelineCache::null(), + slice::from_ref(&create_info), + None, + ) + }; + let mut pipelines = pipelines + .map_err(|e| e.1) + .map_err(VulkanError::CreatePipeline)?; + assert_eq!(pipelines.len(), 1); + pipelines.pop().unwrap() + }; + destroy_layout.forget(); + Ok(Rc::new(VulkanPipeline { + vert: info.vert, + _frag: info.frag, + frag_push_offset: vert_push_size, + pipeline_layout, + pipeline, + _frag_descriptor_set_layout: info.frag_descriptor_set_layout, + })) + } +} + +impl Drop for VulkanPipeline { + fn drop(&mut self) { + unsafe { + let device = &self.vert.device.device; + device.destroy_pipeline(self.pipeline, None); + device.destroy_pipeline_layout(self.pipeline_layout, None); + } + } +} diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs new file mode 100644 index 00000000..6462a35e --- /dev/null +++ b/src/gfx_apis/vulkan/renderer.rs @@ -0,0 +1,837 @@ +use { + crate::{ + async_engine::SpawnedFuture, + gfx_api::{AbsoluteRect, BufferPoint, BufferPoints, GfxApiOpt, GfxFormat, GfxTexture}, + gfx_apis::vulkan::{ + allocator::VulkanAllocator, + command::{VulkanCommandBuffer, VulkanCommandPool}, + device::VulkanDevice, + fence::VulkanFence, + image::{VulkanImage, VulkanImageMemory}, + pipeline::{PipelineCreateInfo, VulkanPipeline}, + semaphore::VulkanSemaphore, + shaders::{ + FillFragPushConstants, FillVertPushConstants, TexVertPushConstants, FILL_FRAG, + FILL_VERT, TEX_FRAG, TEX_VERT, + }, + staging::VulkanStagingBuffer, + VulkanError, + }, + io_uring::IoUring, + theme::Color, + utils::{copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, stack::Stack}, + video::dmabuf::{ + dma_buf_export_sync_file, dma_buf_import_sync_file, DMA_BUF_SYNC_READ, + DMA_BUF_SYNC_WRITE, + }, + }, + ahash::AHashMap, + ash::{ + vk::{ + AccessFlags2, AttachmentLoadOp, AttachmentStoreOp, BufferImageCopy2, + BufferMemoryBarrier2, ClearColorValue, ClearValue, CommandBuffer, + CommandBufferBeginInfo, CommandBufferSubmitInfo, CommandBufferUsageFlags, + CopyBufferToImageInfo2, DependencyInfoKHR, DescriptorImageInfo, DescriptorType, + Extent2D, Extent3D, ImageAspectFlags, ImageLayout, ImageMemoryBarrier2, + ImageMemoryBarrier2Builder, ImageSubresourceLayers, ImageSubresourceRange, + PipelineBindPoint, PipelineStageFlags2, Rect2D, RenderingAttachmentInfo, RenderingInfo, + SemaphoreSubmitInfo, SemaphoreSubmitInfoKHR, ShaderStageFlags, SubmitInfo2KHR, + Viewport, WriteDescriptorSet, QUEUE_FAMILY_FOREIGN_EXT, + }, + Device, + }, + isnt::std_1::collections::IsntHashMapExt, + std::{ + cell::{Cell, RefCell}, + fmt::{Debug, Formatter}, + mem, ptr, + rc::Rc, + slice, + }, + uapi::OwnedFd, +}; + +pub struct VulkanRenderer { + pub(super) formats: Rc>, + pub(super) device: Rc, + pub(super) fill_pipeline: Rc, + pub(super) tex_pipeline: Rc, + pub(super) command_pool: Rc, + pub(super) command_buffers: Stack>, + pub(super) wait_semaphores: Stack>, + pub(super) total_buffers: NumCell, + pub(super) memory: RefCell, + pub(super) pending_frames: CopyHashMap>, + pub(super) allocator: Rc, + pub(super) last_point: NumCell, +} + +#[derive(Default)] +pub(super) struct Memory { + sample: Vec>, + flush: Vec>, + flush_staging: Vec<(Rc, VulkanStagingBuffer)>, + textures: Vec>, + image_barriers: Vec, + shm_barriers: Vec, + wait_semaphores: Vec>, + wait_semaphore_infos: Vec, + release_fence: Option>, + release_syncfile: Option>, +} + +pub(super) struct PendingFrame { + point: u64, + renderer: Rc, + cmd: Cell>>, + _textures: Vec>, + _staging: Vec<(Rc, VulkanStagingBuffer)>, + wait_semaphores: Cell>>, + waiter: Cell>>, + _release_fence: Option>, +} + +impl VulkanDevice { + pub fn create_renderer(self: &Rc) -> Result, VulkanError> { + let fill_pipeline = self.create_pipeline::( + PipelineCreateInfo { + vert: self.create_shader(FILL_VERT)?, + frag: self.create_shader(FILL_FRAG)?, + alpha: true, + frag_descriptor_set_layout: None, + }, + )?; + let sampler = self.create_sampler()?; + let tex_descriptor_set_layout = self.create_descriptor_set_layout(&sampler)?; + let tex_pipeline = + self.create_pipeline::(PipelineCreateInfo { + vert: self.create_shader(TEX_VERT)?, + frag: self.create_shader(TEX_FRAG)?, + alpha: true, + frag_descriptor_set_layout: Some(tex_descriptor_set_layout.clone()), + })?; + let command_pool = self.create_command_pool()?; + let formats: AHashMap = self + .formats + .iter() + .map(|(drm, vk)| { + ( + *drm, + GfxFormat { + format: vk.format, + read_modifiers: vk + .modifiers + .values() + .filter(|m| m.texture_max_extents.is_some()) + .map(|m| m.modifier) + .collect(), + write_modifiers: vk + .modifiers + .values() + .filter(|m| m.render_max_extents.is_some()) + .map(|m| m.modifier) + .collect(), + }, + ) + }) + .collect(); + let allocator = self.create_allocator()?; + Ok(Rc::new(VulkanRenderer { + formats: Rc::new(formats), + device: self.clone(), + fill_pipeline, + tex_pipeline, + command_pool, + command_buffers: Default::default(), + wait_semaphores: Default::default(), + total_buffers: Default::default(), + memory: Default::default(), + pending_frames: Default::default(), + allocator, + last_point: Default::default(), + })) + } +} + +impl VulkanRenderer { + fn collect_memory(&self, opts: &[GfxApiOpt]) { + let mut memory = self.memory.borrow_mut(); + memory.sample.clear(); + memory.flush.clear(); + for cmd in opts { + if let GfxApiOpt::CopyTexture(c) = cmd { + let tex = c.tex.clone().into_vk(&self.device.device); + match &tex.ty { + VulkanImageMemory::DmaBuf(_) => memory.sample.push(tex.clone()), + VulkanImageMemory::Internal(shm) => { + if shm.to_flush.borrow_mut().is_some() { + memory.flush.push(tex.clone()); + } + } + } + memory.textures.push(tex); + } + } + } + + fn begin_command_buffer(&self, buf: CommandBuffer) -> Result<(), VulkanError> { + let begin_info = + CommandBufferBeginInfo::builder().flags(CommandBufferUsageFlags::ONE_TIME_SUBMIT); + unsafe { + self.device + .device + .begin_command_buffer(buf, &begin_info) + .map_err(VulkanError::BeginCommandBuffer) + } + } + + fn write_shm_staging_buffers(self: &Rc) -> Result<(), VulkanError> { + let mut memory = self.memory.borrow_mut(); + let memory = &mut *memory; + memory.flush_staging.clear(); + for img in &memory.flush { + let shm = match &img.ty { + VulkanImageMemory::DmaBuf(_) => unreachable!(), + VulkanImageMemory::Internal(s) => s, + }; + let staging = self.create_staging_buffer(shm.size, true, false, true)?; + let to_flush = shm.to_flush.borrow_mut(); + let to_flush = to_flush.as_ref().unwrap(); + staging.upload(|mem, size| unsafe { + let size = size.min(to_flush.len()); + ptr::copy_nonoverlapping(to_flush.as_ptr(), mem, size); + })?; + memory.flush_staging.push((img.clone(), staging)); + } + Ok(()) + } + + fn initial_barriers(&self, buf: CommandBuffer, fb: &VulkanImage) { + let mut memory = self.memory.borrow_mut(); + let memory = &mut *memory; + memory.image_barriers.clear(); + memory.shm_barriers.clear(); + let fb_image_memory_barrier = image_barrier() + .src_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) + .dst_queue_family_index(self.device.graphics_queue_idx) + .image(fb.image) + .old_layout(if fb.is_undefined.get() { + ImageLayout::UNDEFINED + } else { + ImageLayout::GENERAL + }) + .new_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .dst_access_mask(AccessFlags2::COLOR_ATTACHMENT_WRITE) + .dst_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .build(); + memory.image_barriers.push(fb_image_memory_barrier); + for img in &memory.sample { + let image_memory_barrier = image_barrier() + .src_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) + .dst_queue_family_index(self.device.graphics_queue_idx) + .image(img.image) + .old_layout(ImageLayout::GENERAL) + .new_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL) + .dst_access_mask(AccessFlags2::SHADER_SAMPLED_READ) + .dst_stage_mask(PipelineStageFlags2::FRAGMENT_SHADER) + .build(); + memory.image_barriers.push(image_memory_barrier); + } + for (img, staging) in &memory.flush_staging { + let image_memory_barrier = image_barrier() + .image(img.image) + .old_layout(if img.is_undefined.get() { + ImageLayout::UNDEFINED + } else { + ImageLayout::SHADER_READ_ONLY_OPTIMAL + }) + .new_layout(ImageLayout::TRANSFER_DST_OPTIMAL) + .dst_access_mask(AccessFlags2::TRANSFER_WRITE) + .dst_stage_mask(PipelineStageFlags2::TRANSFER) + .build(); + memory.image_barriers.push(image_memory_barrier); + let buffer_memory_barrier = BufferMemoryBarrier2::builder() + .buffer(staging.buffer) + .offset(0) + .size(staging.size) + .src_access_mask(AccessFlags2::HOST_WRITE) + .src_stage_mask(PipelineStageFlags2::HOST) + .dst_access_mask(AccessFlags2::TRANSFER_READ) + .dst_stage_mask(PipelineStageFlags2::TRANSFER) + .build(); + memory.shm_barriers.push(buffer_memory_barrier); + } + let dep_info = DependencyInfoKHR::builder() + .buffer_memory_barriers(&memory.shm_barriers) + .image_memory_barriers(&memory.image_barriers); + unsafe { + self.device.device.cmd_pipeline_barrier2(buf, &dep_info); + } + } + + fn copy_shm_to_image(&self, cmd: CommandBuffer) { + let memory = self.memory.borrow_mut(); + for (img, staging) in &memory.flush_staging { + let cpy = BufferImageCopy2::builder() + .buffer_image_height(img.height) + .buffer_row_length(img.width) + .image_extent(Extent3D { + width: img.width, + height: img.height, + depth: 1, + }) + .image_subresource(ImageSubresourceLayers { + aspect_mask: ImageAspectFlags::COLOR, + mip_level: 0, + base_array_layer: 0, + layer_count: 1, + }) + .build(); + let info = CopyBufferToImageInfo2::builder() + .src_buffer(staging.buffer) + .dst_image(img.image) + .dst_image_layout(ImageLayout::TRANSFER_DST_OPTIMAL) + .regions(slice::from_ref(&cpy)); + unsafe { + self.device.device.cmd_copy_buffer_to_image2(cmd, &info); + } + } + } + + fn secondary_barriers(&self, buf: CommandBuffer) { + let mut memory = self.memory.borrow_mut(); + let memory = &mut *memory; + if memory.flush.is_empty() { + return; + } + memory.image_barriers.clear(); + for img in &memory.flush { + let image_memory_barrier = image_barrier() + .image(img.image) + .old_layout(ImageLayout::TRANSFER_DST_OPTIMAL) + .new_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL) + .src_access_mask(AccessFlags2::TRANSFER_WRITE) + .src_stage_mask(PipelineStageFlags2::TRANSFER) + .dst_access_mask(AccessFlags2::SHADER_SAMPLED_READ) + .dst_stage_mask(PipelineStageFlags2::FRAGMENT_SHADER) + .build(); + memory.image_barriers.push(image_memory_barrier); + } + let dep_info = DependencyInfoKHR::builder().image_memory_barriers(&memory.image_barriers); + unsafe { + self.device.device.cmd_pipeline_barrier2(buf, &dep_info); + } + } + + fn begin_rendering(&self, buf: CommandBuffer, fb: &VulkanImage, clear: Option<&Color>) { + let rendering_attachment_info = { + let mut rai = RenderingAttachmentInfo::builder() + .image_view(fb.render_view.unwrap_or(fb.texture_view)) + .image_layout(ImageLayout::GENERAL) + .load_op(AttachmentLoadOp::LOAD) + .store_op(AttachmentStoreOp::STORE); + if let Some(clear) = clear { + rai = rai + .clear_value(ClearValue { + color: ClearColorValue { + float32: clear.to_array_linear(), + }, + }) + .load_op(AttachmentLoadOp::CLEAR); + } + rai + }; + let rendering_info = RenderingInfo::builder() + .render_area(Rect2D { + offset: Default::default(), + extent: Extent2D { + width: fb.width, + height: fb.height, + }, + }) + .layer_count(1) + .color_attachments(slice::from_ref(&rendering_attachment_info)); + unsafe { + self.device.device.cmd_begin_rendering(buf, &rendering_info); + } + } + + fn set_viewport(&self, buf: CommandBuffer, fb: &VulkanImage) { + let viewport = Viewport { + x: 0.0, + y: 0.0, + width: fb.width as _, + height: fb.height as _, + min_depth: 0.0, + max_depth: 1.0, + }; + let scissor = Rect2D { + offset: Default::default(), + extent: Extent2D { + width: fb.width, + height: fb.height, + }, + }; + unsafe { + self.device + .device + .cmd_set_viewport(buf, 0, slice::from_ref(&viewport)); + self.device + .device + .cmd_set_scissor(buf, 0, slice::from_ref(&scissor)); + } + } + + fn record_draws( + &self, + buf: CommandBuffer, + fb: &VulkanImage, + opts: &[GfxApiOpt], + ) -> Result<(), VulkanError> { + let dev = &self.device.device; + let mut current_pipeline = None; + let mut bind = |pipeline: &VulkanPipeline| { + if current_pipeline != Some(pipeline.pipeline) { + current_pipeline = Some(pipeline.pipeline); + unsafe { + dev.cmd_bind_pipeline(buf, PipelineBindPoint::GRAPHICS, pipeline.pipeline); + } + } + }; + let width = fb.width as f32; + let height = fb.height as f32; + for opt in opts { + match opt { + GfxApiOpt::Sync => {} + GfxApiOpt::FillRect(r) => { + bind(&self.fill_pipeline); + let vert = FillVertPushConstants { + pos: r.rect.to_vk(width, height), + }; + let frag = FillFragPushConstants { + color: r.color.to_array_linear(), + }; + unsafe { + dev.cmd_push_constants( + buf, + self.fill_pipeline.pipeline_layout, + ShaderStageFlags::VERTEX, + 0, + uapi::as_bytes(&vert), + ); + dev.cmd_push_constants( + buf, + self.fill_pipeline.pipeline_layout, + ShaderStageFlags::FRAGMENT, + self.fill_pipeline.frag_push_offset, + uapi::as_bytes(&frag), + ); + dev.cmd_draw(buf, 4, 1, 0, 0); + } + } + GfxApiOpt::CopyTexture(c) => { + let tex = c.tex.as_vk(&self.device.device); + bind(&self.tex_pipeline); + let vert = TexVertPushConstants { + pos: c.target.to_vk(width, height), + tex_pos: c.source.to_vk(), + }; + let image_info = DescriptorImageInfo::builder() + .image_view(tex.texture_view) + .image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL); + let write_descriptor_set = WriteDescriptorSet::builder() + .descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER) + .image_info(slice::from_ref(&image_info)) + .build(); + unsafe { + self.device.push_descriptor.cmd_push_descriptor_set( + buf, + PipelineBindPoint::GRAPHICS, + self.tex_pipeline.pipeline_layout, + 0, + slice::from_ref(&write_descriptor_set), + ); + dev.cmd_push_constants( + buf, + self.tex_pipeline.pipeline_layout, + ShaderStageFlags::VERTEX, + 0, + uapi::as_bytes(&vert), + ); + dev.cmd_draw(buf, 4, 1, 0, 0); + } + } + } + } + Ok(()) + } + + fn end_rendering(&self, buf: CommandBuffer) { + unsafe { + self.device.device.cmd_end_rendering(buf); + } + } + + fn final_barriers(&self, buf: CommandBuffer, fb: &VulkanImage) { + let mut memory = self.memory.borrow_mut(); + let memory = &mut *memory; + memory.image_barriers.clear(); + memory.shm_barriers.clear(); + let fb_image_memory_barrier = image_barrier() + .src_queue_family_index(self.device.graphics_queue_idx) + .dst_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) + .image(fb.image) + .old_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .new_layout(ImageLayout::GENERAL) + .src_access_mask( + AccessFlags2::COLOR_ATTACHMENT_WRITE | AccessFlags2::COLOR_ATTACHMENT_READ, + ) + .src_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .build(); + memory.image_barriers.push(fb_image_memory_barrier); + for img in &memory.sample { + let image_memory_barrier = image_barrier() + .src_queue_family_index(self.device.graphics_queue_idx) + .dst_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) + .old_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL) + .new_layout(ImageLayout::GENERAL) + .image(img.image) + .src_access_mask(AccessFlags2::SHADER_SAMPLED_READ) + .src_stage_mask(PipelineStageFlags2::FRAGMENT_SHADER) + .build(); + memory.image_barriers.push(image_memory_barrier); + } + let dep_info = DependencyInfoKHR::builder() + .image_memory_barriers(&memory.image_barriers) + .buffer_memory_barriers(&memory.shm_barriers); + unsafe { + self.device.device.cmd_pipeline_barrier2(buf, &dep_info); + } + } + + fn end_command_buffer(&self, buf: CommandBuffer) -> Result<(), VulkanError> { + unsafe { + self.device + .device + .end_command_buffer(buf) + .map_err(VulkanError::EndCommandBuffer) + } + } + + fn create_wait_semaphores(&self, fb: &VulkanImage) -> Result<(), VulkanError> { + let mut memory = self.memory.borrow_mut(); + let memory = &mut *memory; + memory.wait_semaphore_infos.clear(); + let import = |infos: &mut Vec, + semaphores: &mut Vec>, + img: &VulkanImage, + flag: u32| + -> Result<(), VulkanError> { + if let VulkanImageMemory::DmaBuf(buf) = &img.ty { + for plane in &buf.template.planes { + let fd = dma_buf_export_sync_file(&plane.fd, flag) + .map_err(VulkanError::IoctlExportSyncFile)?; + let semaphore = match self.wait_semaphores.pop() { + Some(s) => s, + _ => self.device.create_semaphore()?, + }; + semaphore.import_syncfile(fd)?; + infos.push( + SemaphoreSubmitInfo::builder() + .semaphore(semaphore.semaphore) + .stage_mask(PipelineStageFlags2::TOP_OF_PIPE) + .build(), + ); + semaphores.push(semaphore); + } + } + Ok(()) + }; + for texture in &memory.textures { + import( + &mut memory.wait_semaphore_infos, + &mut memory.wait_semaphores, + texture, + DMA_BUF_SYNC_READ, + )?; + } + import( + &mut memory.wait_semaphore_infos, + &mut memory.wait_semaphores, + fb, + DMA_BUF_SYNC_WRITE, + )?; + Ok(()) + } + + fn import_release_semaphore(&self, fb: &VulkanImage) { + let memory = self.memory.borrow(); + let syncfile = match memory.release_syncfile.as_ref() { + Some(syncfile) => syncfile, + _ => return, + }; + let import = |img: &VulkanImage, flag: u32| { + if let VulkanImageMemory::DmaBuf(buf) = &img.ty { + for plane in &buf.template.planes { + let res = dma_buf_import_sync_file(&plane.fd, flag, &syncfile) + .map_err(VulkanError::IoctlImportSyncFile); + if let Err(e) = res { + log::error!("Could not import syncfile into dmabuf: {}", ErrorFmt(e)); + log::warn!("Relying on implicit sync"); + } + } + } + }; + for texture in &memory.textures { + import(texture, DMA_BUF_SYNC_WRITE); + } + import(fb, DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE); + } + + fn submit(&self, buf: CommandBuffer) -> Result<(), VulkanError> { + let mut memory = self.memory.borrow_mut(); + let release_fence = self.device.create_fence()?; + let command_buffer_info = CommandBufferSubmitInfo::builder() + .command_buffer(buf) + .build(); + let submit_info = SubmitInfo2KHR::builder() + .wait_semaphore_infos(&memory.wait_semaphore_infos) + .command_buffer_infos(slice::from_ref(&command_buffer_info)) + .build(); + unsafe { + self.device + .device + .queue_submit2( + self.device.graphics_queue, + slice::from_ref(&submit_info), + release_fence.fence, + ) + .map_err(VulkanError::Submit)?; + } + let release_syncfile = match release_fence.export_syncfile() { + Ok(s) => Some(s), + Err(e) => { + log::error!("Could not export syncfile from fence: {}", ErrorFmt(e)); + None + } + }; + memory.release_fence = Some(release_fence); + memory.release_syncfile = release_syncfile; + Ok(()) + } + + fn store_layouts(&self, fb: &VulkanImage) { + fb.is_undefined.set(false); + let memory = self.memory.borrow_mut(); + for img in &memory.flush { + img.is_undefined.set(false); + let shm = match &img.ty { + VulkanImageMemory::DmaBuf(_) => unreachable!(), + VulkanImageMemory::Internal(s) => s, + }; + shm.to_flush.take(); + } + } + + fn create_pending_frame(self: &Rc, buf: Rc) { + let point = self.last_point.fetch_add(1) + 1; + let mut memory = self.memory.borrow_mut(); + let frame = Rc::new(PendingFrame { + point, + renderer: self.clone(), + cmd: Cell::new(Some(buf)), + _textures: mem::take(&mut memory.textures), + _staging: mem::take(&mut memory.flush_staging), + wait_semaphores: Cell::new(mem::take(&mut memory.wait_semaphores)), + waiter: Cell::new(None), + _release_fence: memory.release_fence.take(), + }); + self.pending_frames.set(frame.point, frame.clone()); + let future = self.device.instance.eng.spawn(await_release( + memory.release_syncfile.take(), + self.device.instance.ring.clone(), + frame.clone(), + self.clone(), + )); + frame.waiter.set(Some(future)); + } + + pub fn execute( + self: &Rc, + fb: &VulkanImage, + opts: &[GfxApiOpt], + clear: Option<&Color>, + ) -> Result<(), VulkanError> { + let res = self.try_execute(fb, opts, clear); + { + let mut memory = self.memory.borrow_mut(); + memory.flush.clear(); + memory.textures.clear(); + memory.flush_staging.clear(); + memory.sample.clear(); + memory.wait_semaphores.clear(); + memory.release_fence.take(); + memory.release_syncfile.take(); + } + res + } + + fn try_execute( + self: &Rc, + fb: &VulkanImage, + opts: &[GfxApiOpt], + clear: Option<&Color>, + ) -> Result<(), VulkanError> { + let buf = match self.command_buffers.pop() { + Some(b) => b, + _ => { + self.total_buffers.fetch_add(1); + self.command_pool.allocate_buffer()? + } + }; + self.collect_memory(opts); + self.begin_command_buffer(buf.buffer)?; + self.write_shm_staging_buffers()?; + self.initial_barriers(buf.buffer, fb); + self.copy_shm_to_image(buf.buffer); + self.secondary_barriers(buf.buffer); + self.begin_rendering(buf.buffer, fb, clear); + self.set_viewport(buf.buffer, fb); + self.record_draws(buf.buffer, fb, opts)?; + self.end_rendering(buf.buffer); + self.final_barriers(buf.buffer, fb); + self.end_command_buffer(buf.buffer)?; + self.create_wait_semaphores(fb)?; + self.submit(buf.buffer)?; + self.import_release_semaphore(fb); + self.store_layouts(fb); + self.create_pending_frame(buf); + Ok(()) + } + + fn block(&self) { + log::warn!("Blocking."); + unsafe { + if let Err(e) = self.device.device.device_wait_idle() { + log::error!("Could not wait for device idle: {}", ErrorFmt(e)); + } + } + } + + pub fn on_drop(&self) { + let mut pending_frames = self.pending_frames.lock(); + if pending_frames.is_not_empty() { + log::warn!("Context dropped with pending frames."); + self.block(); + } + pending_frames.values().for_each(|f| { + f.waiter.take(); + }); + pending_frames.clear(); + } +} + +impl Debug for VulkanRenderer { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("VulkanRenderer").finish_non_exhaustive() + } +} + +#[derive(Debug)] +pub struct TmpShmTexture(pub i32, pub i32); + +impl VulkanImage { + fn assert_device(&self, device: &Device) { + assert_eq!( + self.renderer.device.device.handle(), + device.handle(), + "Mixed vulkan device use" + ); + } +} + +impl dyn GfxTexture { + fn as_vk(&self, device: &Device) -> &VulkanImage { + let img: &VulkanImage = self + .as_any() + .downcast_ref() + .expect("Non-vulkan texture passed into vulkan"); + img.assert_device(device); + img + } + + pub(super) fn into_vk(self: Rc, device: &Device) -> Rc { + let img: Rc = self + .into_any() + .downcast() + .expect("Non-vulkan texture passed into vulkan"); + img.assert_device(device); + img + } +} + +impl AbsoluteRect { + fn to_vk(&self, width: f32, height: f32) -> [[f32; 2]; 4] { + let x1 = 2.0 * self.x1 / width - 1.0; + let x2 = 2.0 * self.x2 / width - 1.0; + let y1 = 2.0 * self.y1 / height - 1.0; + let y2 = 2.0 * self.y2 / height - 1.0; + [[x2, y1], [x1, y1], [x2, y2], [x1, y2]] + } +} + +impl BufferPoint { + fn to_vk(&self) -> [f32; 2] { + [self.x, self.y] + } +} + +impl BufferPoints { + fn to_vk(&self) -> [[f32; 2]; 4] { + [ + self.top_right.to_vk(), + self.top_left.to_vk(), + self.bottom_right.to_vk(), + self.bottom_left.to_vk(), + ] + } +} + +fn image_barrier() -> ImageMemoryBarrier2Builder<'static> { + ImageMemoryBarrier2::builder().subresource_range( + ImageSubresourceRange::builder() + .aspect_mask(ImageAspectFlags::COLOR) + .layer_count(1) + .level_count(1) + .build(), + ) +} + +async fn await_release( + syncfile: Option>, + ring: Rc, + frame: Rc, + renderer: Rc, +) { + let mut is_released = false; + if let Some(syncfile) = syncfile { + if let Err(e) = ring.readable(&syncfile).await { + log::error!( + "Could not wait for release semaphore to be signaled: {}", + ErrorFmt(e) + ); + } else { + is_released = true; + } + } + if !is_released { + frame.renderer.block(); + } + if let Some(buf) = frame.cmd.take() { + frame.renderer.command_buffers.push(buf); + } + for wait_semaphore in frame.wait_semaphores.take() { + frame.renderer.wait_semaphores.push(wait_semaphore); + } + renderer.pending_frames.remove(&frame.point); +} diff --git a/src/gfx_apis/vulkan/sampler.rs b/src/gfx_apis/vulkan/sampler.rs new file mode 100644 index 00000000..88852825 --- /dev/null +++ b/src/gfx_apis/vulkan/sampler.rs @@ -0,0 +1,42 @@ +use { + crate::gfx_apis::vulkan::{device::VulkanDevice, VulkanError}, + ash::vk::{ + BorderColor, Filter, Sampler, SamplerAddressMode, SamplerCreateInfo, SamplerMipmapMode, + }, + std::rc::Rc, +}; + +pub struct VulkanSampler { + pub(super) device: Rc, + pub(super) sampler: Sampler, +} + +impl VulkanDevice { + pub(super) fn create_sampler(self: &Rc) -> Result, VulkanError> { + let create_info = SamplerCreateInfo::builder() + .mag_filter(Filter::LINEAR) + .min_filter(Filter::LINEAR) + .mipmap_mode(SamplerMipmapMode::NEAREST) + .address_mode_u(SamplerAddressMode::REPEAT) + .address_mode_v(SamplerAddressMode::REPEAT) + .address_mode_w(SamplerAddressMode::REPEAT) + .max_anisotropy(1.0) + .min_lod(0.0) + .max_lod(0.25) + .border_color(BorderColor::FLOAT_TRANSPARENT_BLACK); + let sampler = unsafe { self.device.create_sampler(&create_info, None) }; + let sampler = sampler.map_err(VulkanError::CreateSampler)?; + Ok(Rc::new(VulkanSampler { + device: self.clone(), + sampler, + })) + } +} + +impl Drop for VulkanSampler { + fn drop(&mut self) { + unsafe { + self.device.device.destroy_sampler(self.sampler, None); + } + } +} diff --git a/src/gfx_apis/vulkan/semaphore.rs b/src/gfx_apis/vulkan/semaphore.rs new file mode 100644 index 00000000..1d7b3f3c --- /dev/null +++ b/src/gfx_apis/vulkan/semaphore.rs @@ -0,0 +1,54 @@ +use { + crate::gfx_apis::vulkan::{device::VulkanDevice, VulkanError}, + ash::vk::{ + ExternalSemaphoreHandleTypeFlags, ImportSemaphoreFdInfoKHR, Semaphore, SemaphoreCreateInfo, + SemaphoreImportFlags, + }, + std::{mem, rc::Rc}, + uapi::OwnedFd, +}; + +pub struct VulkanSemaphore { + pub(super) device: Rc, + pub(super) semaphore: Semaphore, +} + +impl Drop for VulkanSemaphore { + fn drop(&mut self) { + unsafe { + self.device.device.destroy_semaphore(self.semaphore, None); + } + } +} + +impl VulkanDevice { + pub fn create_semaphore(self: &Rc) -> Result, VulkanError> { + let sem = { + let create_info = SemaphoreCreateInfo::builder(); + let sem = unsafe { self.device.create_semaphore(&create_info, None) }; + sem.map_err(VulkanError::CreateSemaphore)? + }; + Ok(Rc::new(VulkanSemaphore { + device: self.clone(), + semaphore: sem, + })) + } +} + +impl VulkanSemaphore { + pub fn import_syncfile(&self, syncfile: OwnedFd) -> Result<(), VulkanError> { + let fd_info = ImportSemaphoreFdInfoKHR::builder() + .fd(syncfile.raw()) + .flags(SemaphoreImportFlags::TEMPORARY) + .handle_type(ExternalSemaphoreHandleTypeFlags::SYNC_FD) + .semaphore(self.semaphore); + let res = unsafe { + self.device + .external_semaphore_fd + .import_semaphore_fd(&fd_info) + }; + mem::forget(syncfile); + res.map_err(VulkanError::ImportSyncFile)?; + Ok(()) + } +} diff --git a/src/gfx_apis/vulkan/shaders.rs b/src/gfx_apis/vulkan/shaders.rs new file mode 100644 index 00000000..511895ac --- /dev/null +++ b/src/gfx_apis/vulkan/shaders.rs @@ -0,0 +1,67 @@ +use { + crate::gfx_apis::vulkan::{device::VulkanDevice, VulkanError}, + ash::vk::{ShaderModule, ShaderModuleCreateInfo}, + std::rc::Rc, + uapi::Packed, +}; + +pub const FILL_VERT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/fill.vert.spv")); +pub const FILL_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/fill.frag.spv")); +pub const TEX_VERT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/tex.vert.spv")); +pub const TEX_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/tex.frag.spv")); + +pub struct VulkanShader { + pub(super) device: Rc, + pub(super) module: ShaderModule, +} + +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct FillVertPushConstants { + pub pos: [[f32; 2]; 4], +} + +unsafe impl Packed for FillVertPushConstants {} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct FillFragPushConstants { + pub color: [f32; 4], +} + +unsafe impl Packed for FillFragPushConstants {} + +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct TexVertPushConstants { + pub pos: [[f32; 2]; 4], + pub tex_pos: [[f32; 2]; 4], +} + +unsafe impl Packed for TexVertPushConstants {} + +impl VulkanDevice { + pub(super) fn create_shader( + self: &Rc, + src: &[u8], + ) -> Result, VulkanError> { + let src: Vec = uapi::pod_iter(src).unwrap().collect(); + let create_info = ShaderModuleCreateInfo::builder().code(&src); + let module = unsafe { self.device.create_shader_module(&create_info, None) }; + module + .map_err(VulkanError::CreateShaderModule) + .map(|m| VulkanShader { + device: self.clone(), + module: m, + }) + .map(Rc::new) + } +} + +impl Drop for VulkanShader { + fn drop(&mut self) { + unsafe { + self.device.device.destroy_shader_module(self.module, None); + } + } +} diff --git a/src/gfx_apis/vulkan/shaders/fill.frag b/src/gfx_apis/vulkan/shaders/fill.frag new file mode 100644 index 00000000..04d99c83 --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/fill.frag @@ -0,0 +1,11 @@ +#version 450 + +layout(push_constant, std430) uniform Data { + layout(offset = 32) vec4 color; +} data; + +layout(location = 0) out vec4 out_color; + +void main() { + out_color = data.color; +} diff --git a/src/gfx_apis/vulkan/shaders/fill.vert b/src/gfx_apis/vulkan/shaders/fill.vert new file mode 100644 index 00000000..62abce65 --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/fill.vert @@ -0,0 +1,18 @@ +#version 450 +//#extension GL_EXT_debug_printf : enable + +layout(push_constant, std430) uniform Data { + layout(offset = 0) vec2 pos[4]; +} data; + +void main() { + vec2 pos; + switch (gl_VertexIndex) { + case 0: pos = data.pos[0]; break; + case 1: pos = data.pos[1]; break; + case 2: pos = data.pos[2]; break; + case 3: pos = data.pos[3]; break; + } + gl_Position = vec4(pos, 0.0, 1.0); +// debugPrintfEXT("gl_Position = %v4f", gl_Position); +} diff --git a/src/gfx_apis/vulkan/shaders/tex.frag b/src/gfx_apis/vulkan/shaders/tex.frag new file mode 100644 index 00000000..5d3b52e2 --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/tex.frag @@ -0,0 +1,9 @@ +#version 450 + +layout(set = 0, binding = 0) uniform sampler2D tex; +layout(location = 0) in vec2 tex_pos; +layout(location = 0) out vec4 out_color; + +void main() { + out_color = textureLod(tex, tex_pos, 0); +} diff --git a/src/gfx_apis/vulkan/shaders/tex.vert b/src/gfx_apis/vulkan/shaders/tex.vert new file mode 100644 index 00000000..5a013dd5 --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/tex.vert @@ -0,0 +1,21 @@ +#version 450 +//#extension GL_EXT_debug_printf : enable + +layout(push_constant, std430) uniform Data { + layout(offset = 0) vec2 pos[4]; + layout(offset = 32) vec2 tex_pos[4]; +} data; + +layout(location = 0) out vec2 tex_pos; + +void main() { + vec2 pos; + switch (gl_VertexIndex) { + case 0: pos = data.pos[0]; tex_pos = data.tex_pos[0]; break; + case 1: pos = data.pos[1]; tex_pos = data.tex_pos[1]; break; + case 2: pos = data.pos[2]; tex_pos = data.tex_pos[2]; break; + case 3: pos = data.pos[3]; tex_pos = data.tex_pos[3]; break; + } + gl_Position = vec4(pos, 0.0, 1.0); +// debugPrintfEXT("gl_Position = %v4f, tex_pos = %v2f", gl_Position, tex_pos); +} diff --git a/src/gfx_apis/vulkan/staging.rs b/src/gfx_apis/vulkan/staging.rs new file mode 100644 index 00000000..c0e4c045 --- /dev/null +++ b/src/gfx_apis/vulkan/staging.rs @@ -0,0 +1,89 @@ +use { + crate::gfx_apis::vulkan::{ + allocator::VulkanAllocation, device::VulkanDevice, renderer::VulkanRenderer, util::OnDrop, + VulkanError, + }, + ash::vk::{Buffer, BufferCreateInfo, BufferUsageFlags, MappedMemoryRange}, + gpu_alloc::UsageFlags, + std::rc::Rc, +}; + +pub struct VulkanStagingBuffer { + pub(super) device: Rc, + pub(super) allocation: VulkanAllocation, + pub(super) buffer: Buffer, + pub(super) size: u64, +} + +impl VulkanRenderer { + pub(super) fn create_staging_buffer( + self: &Rc, + size: u64, + upload: bool, + download: bool, + transient: bool, + ) -> Result { + let mut vk_usage = BufferUsageFlags::empty(); + let mut usage = UsageFlags::empty(); + if upload { + vk_usage |= BufferUsageFlags::TRANSFER_SRC; + usage |= UsageFlags::UPLOAD; + } + if download { + vk_usage |= BufferUsageFlags::TRANSFER_DST; + usage |= UsageFlags::DOWNLOAD; + } + if transient { + usage |= UsageFlags::TRANSIENT; + } + let buffer = { + let create_info = BufferCreateInfo::builder().size(size).usage(vk_usage); + let buffer = unsafe { self.device.device.create_buffer(&create_info, None) }; + buffer.map_err(VulkanError::CreateBuffer)? + }; + let destroy_buffer = OnDrop(|| unsafe { self.device.device.destroy_buffer(buffer, None) }); + let memory_requirements = + unsafe { self.device.device.get_buffer_memory_requirements(buffer) }; + let allocation = self.allocator.alloc(&memory_requirements, usage, true)?; + { + let res = unsafe { + self.device + .device + .bind_buffer_memory(buffer, allocation.memory, allocation.offset) + }; + res.map_err(VulkanError::BindBufferMemory)?; + } + destroy_buffer.forget(); + Ok(VulkanStagingBuffer { + device: self.device.clone(), + allocation, + buffer, + size, + }) + } +} + +impl VulkanStagingBuffer { + pub fn upload(&self, f: F) -> Result + where + F: FnOnce(*mut u8, usize) -> T, + { + let t = f(self.allocation.mem.unwrap(), self.size as usize); + let atom_mask = self.allocation.allocator.non_coherent_atom_mask; + let range = MappedMemoryRange::builder() + .memory(self.allocation.memory) + .offset(self.allocation.offset & !atom_mask) + .size((self.allocation.size + atom_mask) & !atom_mask) + .build(); + let res = unsafe { self.device.device.flush_mapped_memory_ranges(&[range]) }; + res.map_err(VulkanError::FlushMemory).map(|_| t) + } +} + +impl Drop for VulkanStagingBuffer { + fn drop(&mut self) { + unsafe { + self.device.device.destroy_buffer(self.buffer, None); + } + } +} diff --git a/src/gfx_apis/vulkan/util.rs b/src/gfx_apis/vulkan/util.rs new file mode 100644 index 00000000..a7a12d79 --- /dev/null +++ b/src/gfx_apis/vulkan/util.rs @@ -0,0 +1,17 @@ +use std::mem; + +pub struct OnDrop(pub F) +where + F: FnMut() + Copy; + +impl OnDrop { + pub fn forget(self) { + mem::forget(self); + } +} + +impl Drop for OnDrop { + fn drop(&mut self) { + (self.0)(); + } +} diff --git a/src/ifs/wl_buffer.rs b/src/ifs/wl_buffer.rs index f3312410..b59d7b9d 100644 --- a/src/ifs/wl_buffer.rs +++ b/src/ifs/wl_buffer.rs @@ -168,10 +168,10 @@ impl WlBuffer { }; match storage { WlBufferStorage::Shm { mem, stride } => { - self.texture.set(None); + let old = self.texture.take(); if let Some(ctx) = self.client.state.render_ctx.get() { let tex = mem.access(|mem| { - ctx.shmem_texture(mem, self.format, self.width, self.height, *stride) + ctx.shmem_texture(old, mem, self.format, self.width, self.height, *stride) })??; self.texture.set(Some(tex)); } diff --git a/src/it/test_backend.rs b/src/it/test_backend.rs index 6d2bfa3b..c5bd5758 100644 --- a/src/it/test_backend.rs +++ b/src/it/test_backend.rs @@ -10,7 +10,6 @@ use { compositor::TestFuture, fixed::Fixed, gfx_api::GfxError, - gfx_apis::create_gfx_context, it::test_error::TestResult, state::State, time::now_usec, @@ -178,7 +177,7 @@ impl TestBackend { } }; let drm = Drm::open_existing(file); - let ctx = match create_gfx_context(&drm) { + let ctx = match self.state.create_gfx_context(&drm, None) { Ok(ctx) => ctx, Err(e) => return Err(TestBackendError::RenderContext(e)), }; diff --git a/src/main.rs b/src/main.rs index bd871451..efb9ecfc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ c_variadic, // https://github.com/rust-lang/rust/issues/44930 thread_local, // https://github.com/rust-lang/rust/issues/29594 extern_types, // https://github.com/rust-lang/rust/issues/43467 + c_str_literals, // https://github.com/rust-lang/rust/issues/105723 )] #![allow( clippy::len_zero, diff --git a/src/portal/ptl_display.rs b/src/portal/ptl_display.rs index d1cd8861..17946f26 100644 --- a/src/portal/ptl_display.rs +++ b/src/portal/ptl_display.rs @@ -35,6 +35,7 @@ use { }, }, ahash::AHashMap, + jay_config::video::GfxApi, std::{ cell::{Cell, RefCell}, ops::Deref, @@ -169,16 +170,17 @@ impl UsrJayRenderCtxOwner for PortalDisplay { } if self.render_ctx.get().is_none() { let drm = Drm::open_existing(fd); - let ctx = match create_gfx_context(&drm) { - Ok(c) => c, - Err(e) => { - log::error!( - "Could not create render context from drm device: {}", - ErrorFmt(e) - ); - return; - } - }; + let ctx = + match create_gfx_context(&self.state.eng, &self.state.ring, &drm, GfxApi::OpenGl) { + Ok(c) => c, + Err(e) => { + log::error!( + "Could not create render context from drm device: {}", + ErrorFmt(e) + ); + return; + } + }; let ctx = Rc::new(PortalRenderCtx { dev_id, ctx }); self.render_ctx.set(Some(ctx.clone())); self.state.render_ctxs.set(dev_id, Rc::downgrade(&ctx)); diff --git a/src/renderer.rs b/src/renderer.rs index 59a2900b..0499c79b 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -398,6 +398,8 @@ impl Renderer<'_> { max_width, max_height, ); + } else { + log::info!("buffer.texture is none"); } } diff --git a/src/state.rs b/src/state.rs index 8179ca78..b48cfb92 100644 --- a/src/state.rs +++ b/src/state.rs @@ -13,7 +13,8 @@ use { cursor::{Cursor, ServerCursors}, dbus::Dbus, forker::ForkerProxy, - gfx_api::GfxContext, + gfx_api::{GfxContext, GfxError}, + gfx_apis::create_gfx_context, globals::{Globals, GlobalsError, WaylandGlobal}, ifs::{ ext_session_lock_v1::ExtSessionLockV1, @@ -43,6 +44,7 @@ use { errorfmt::ErrorFmt, fdcloser::FdCloser, linkedlist::LinkedList, numcell::NumCell, queue::AsyncQueue, refcounted::RefCounted, run_toplevel::RunToplevel, }, + video::drm::Drm, wheel::Wheel, wire::{JayRenderCtxId, JaySeatEventsId, JayWorkspaceWatcherId}, xkbcommon::{XkbContext, XkbKeymap}, @@ -50,7 +52,7 @@ use { }, ahash::AHashMap, bstr::ByteSlice, - jay_config::PciId, + jay_config::{video::GfxApi, PciId}, std::{ cell::{Cell, RefCell}, fmt::{Debug, Formatter}, @@ -124,6 +126,7 @@ pub struct State { pub render_ctx_watchers: CopyHashMap<(ClientId, JayRenderCtxId), Rc>, pub workspace_watchers: CopyHashMap<(ClientId, JayWorkspaceWatcherId), Rc>, pub default_workspace_capture: Cell, + pub default_gfx_api: Cell, } // impl Drop for State { @@ -251,6 +254,19 @@ impl NodeVisitorBase for UpdateTextTexturesVisitor { } impl State { + pub fn create_gfx_context( + &self, + drm: &Drm, + api: Option, + ) -> Result, GfxError> { + create_gfx_context( + &self.eng, + &self.ring, + drm, + api.unwrap_or(self.default_gfx_api.get()), + ) + } + pub fn add_output_scale(&self, scale: Scale) { if self.scales.add(scale) { self.output_scales_changed(); diff --git a/src/tasks/drmdev.rs b/src/tasks/drmdev.rs index c4b413d5..f5c58ff3 100644 --- a/src/tasks/drmdev.rs +++ b/src/tasks/drmdev.rs @@ -103,11 +103,13 @@ impl DrvDevHandler { if let Some(config) = self.state.config.get() { config.new_drm_dev(self.id); } + self.log_gfx_api(); 'outer: loop { #[allow(clippy::never_loop)] while let Some(event) = self.data.dev.event() { match event { DrmEvent::Removed => break 'outer, + DrmEvent::GfxApiChanged => self.log_gfx_api(), } } ae.triggered().await; @@ -121,4 +123,13 @@ impl DrvDevHandler { self.data.handler.set(None); self.state.drm_devs.remove(&self.id); } + + fn log_gfx_api(&self) { + let api = self.data.dev.gtx_api(); + log::info!( + "Using {:?} for device {}", + api, + self.data.devnode.as_deref().unwrap_or(""), + ) + } } diff --git a/src/text.rs b/src/text.rs index bc527a31..c4cb221e 100644 --- a/src/text.rs +++ b/src/text.rs @@ -210,9 +210,10 @@ fn render2( Ok(d) => d, Err(e) => return Err(TextError::ImageData(e)), }; + let old = old.map(|o| o.texture); match ctx .clone() - .shmem_texture(bytes, ARGB8888, width, height, data.image.stride()) + .shmem_texture(old, bytes, ARGB8888, width, height, data.image.stride()) { Ok(t) => Ok(TextTexture { config: Rc::new(config.to_static()), diff --git a/src/theme.rs b/src/theme.rs index 5e2360c0..f2cf9c50 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -72,6 +72,27 @@ impl Color { pub fn to_rgba_premultiplied(self) -> [u8; 4] { [to_u8(self.r), to_u8(self.g), to_u8(self.b), to_u8(self.a)] } + + #[allow(dead_code)] + pub fn to_array_srgb(self) -> [f32; 4] { + [self.r, self.g, self.b, self.a] + } + + pub fn to_array_linear(self) -> [f32; 4] { + fn to_linear(srgb: f32) -> f32 { + if srgb <= 0.04045 { + srgb / 12.92 + } else { + (srgb + 0.055 / 1.055).powf(2.4) + } + } + [ + to_linear(self.r), + to_linear(self.g), + to_linear(self.b), + self.a, + ] + } } impl From for Color { diff --git a/src/utils/clonecell.rs b/src/utils/clonecell.rs index 5d2c31ab..200bc9e2 100644 --- a/src/utils/clonecell.rs +++ b/src/utils/clonecell.rs @@ -59,7 +59,7 @@ impl CloneCell { } } -impl Default for CloneCell { +impl Default for CloneCell { fn default() -> Self { Self::new(Default::default()) } @@ -77,6 +77,8 @@ unsafe impl UnsafeCellCloneSafe for NodeRef {} unsafe impl UnsafeCellCloneSafe for () {} unsafe impl UnsafeCellCloneSafe for u64 {} unsafe impl UnsafeCellCloneSafe for i32 {} +unsafe impl UnsafeCellCloneSafe for u32 {} +unsafe impl UnsafeCellCloneSafe for usize {} unsafe impl UnsafeCellCloneSafe for (A, B) {} diff --git a/src/utils/stack.rs b/src/utils/stack.rs index 15a36d95..f415a8c8 100644 --- a/src/utils/stack.rs +++ b/src/utils/stack.rs @@ -1,5 +1,8 @@ use { - crate::utils::ptr_ext::{MutPtrExt, PtrExt}, + crate::utils::{ + clonecell::UnsafeCellCloneSafe, + ptr_ext::{MutPtrExt, PtrExt}, + }, std::{cell::UnsafeCell, mem}, }; @@ -28,7 +31,7 @@ impl Stack { pub fn to_vec(&self) -> Vec where - T: Clone, + T: UnsafeCellCloneSafe, { unsafe { let v = self.vec.get().deref(); diff --git a/src/video/dmabuf.rs b/src/video/dmabuf.rs index d361a398..1f8fe7ad 100644 --- a/src/video/dmabuf.rs +++ b/src/video/dmabuf.rs @@ -1,8 +1,8 @@ use { - crate::{format::Format, video::Modifier}, + crate::{format::Format, utils::oserror::OsError, video::Modifier}, arrayvec::ArrayVec, std::rc::Rc, - uapi::OwnedFd, + uapi::{c::ioctl, OwnedFd, _IOW, _IOWR}, }; #[derive(Clone)] @@ -24,3 +24,74 @@ pub struct DmaBuf { pub const MAX_PLANES: usize = 4; pub type PlaneVec = ArrayVec; + +impl DmaBuf { + pub fn is_disjoint(&self) -> bool { + if self.planes.len() <= 1 { + return false; + } + let stat = match uapi::fstat(self.planes[0].fd.raw()) { + Ok(s) => s, + _ => return true, + }; + for plane in &self.planes[1..] { + let stat2 = match uapi::fstat(plane.fd.raw()) { + Ok(s) => s, + _ => return true, + }; + if stat2.st_ino != stat.st_ino { + return true; + } + } + false + } +} + +const DMA_BUF_BASE: u64 = b'b' as _; + +#[allow(non_camel_case_types)] +#[repr(C)] +struct dma_buf_export_sync_file { + flags: u32, + fd: i32, +} + +#[allow(non_camel_case_types)] +#[repr(C)] +struct dma_buf_import_sync_file { + flags: u32, + fd: i32, +} + +pub const DMA_BUF_SYNC_READ: u32 = 1 << 0; +pub const DMA_BUF_SYNC_WRITE: u32 = 1 << 1; + +const DMA_BUF_IOCTL_EXPORT_SYNC_FILE: u64 = _IOWR::(DMA_BUF_BASE, 2); +const DMA_BUF_IOCTL_IMPORT_SYNC_FILE: u64 = _IOW::(DMA_BUF_BASE, 3); + +pub fn dma_buf_export_sync_file(dmabuf: &OwnedFd, flags: u32) -> Result { + let mut data = dma_buf_export_sync_file { flags, fd: -1 }; + let res = unsafe { ioctl(dmabuf.raw(), DMA_BUF_IOCTL_EXPORT_SYNC_FILE, &mut data) }; + if res != 0 { + Err(OsError::default()) + } else { + Ok(OwnedFd::new(data.fd)) + } +} + +pub fn dma_buf_import_sync_file( + dmabuf: &OwnedFd, + flags: u32, + sync_file: &OwnedFd, +) -> Result<(), OsError> { + let mut data = dma_buf_import_sync_file { + flags, + fd: sync_file.raw(), + }; + let res = unsafe { ioctl(dmabuf.raw(), DMA_BUF_IOCTL_IMPORT_SYNC_FILE, &mut data) }; + if res != 0 { + Err(OsError::default()) + } else { + Ok(()) + } +} diff --git a/src/video/drm.rs b/src/video/drm.rs index ca53b2d7..e1e09f68 100644 --- a/src/video/drm.rs +++ b/src/video/drm.rs @@ -175,6 +175,14 @@ impl Drm { get_nodes(self.fd.raw()).map_err(DrmError::GetNodes) } + pub fn get_render_node(&self) -> Result, DrmError> { + let nodes = self.get_nodes()?; + Ok(nodes + .get(&NodeType::Render) + .or_else(|| nodes.get(&NodeType::Primary)) + .map(|c| c.to_owned())) + } + pub fn version(&self) -> Result { get_version(self.fd.raw()).map_err(DrmError::Version) }