Add implementation to upload and serve wallpapers from GPU memory using Vulkan
Add handling for the Linux DMA-BUF Wayland protocol Refactor Wayland code for image loading and image equivalence Add Vulkan code using the ash crate dependency targeting Vulkan 1.1 For image DMA-BUF exporting use EXT_image_drm_format_modifier if available, fall back to linear images otherwise Add --gpu command line option Add Vulkan packages as optional dependencies to Arch Linux PKGBUILD
This commit is contained in:
parent
3ded435028
commit
5fd6cbe114
11 changed files with 2094 additions and 205 deletions
27
Cargo.lock
generated
27
Cargo.lock
generated
|
@ -85,6 +85,15 @@ version = "1.0.97"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
|
||||
|
||||
[[package]]
|
||||
name = "ash"
|
||||
version = "0.38.0+1.3.281"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f"
|
||||
dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
|
@ -552,6 +561,16 @@ version = "0.2.171"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.15"
|
||||
|
@ -614,6 +633,7 @@ name = "multibg-sway"
|
|||
version = "0.1.10"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ash",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"fast_image_resize",
|
||||
|
@ -622,6 +642,7 @@ dependencies = [
|
|||
"log",
|
||||
"niri-ipc",
|
||||
"rustix",
|
||||
"scopeguard",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smithay-client-toolkit",
|
||||
|
@ -828,6 +849,12 @@ version = "1.0.20"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
|
|
12
Cargo.toml
12
Cargo.toml
|
@ -15,25 +15,25 @@ exclude = ["/PKGBUILD", "/scripts/"]
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1.0.97"
|
||||
ash = "0.38.0"
|
||||
clap = { version = "4.5.3", features = ["derive"] }
|
||||
env_logger = "0.11.3"
|
||||
fast_image_resize = "5.0.0"
|
||||
libc = "0.2.171"
|
||||
log = "0.4.21"
|
||||
rustix = {version = "0.38.44", features = ["event", "pipe"] }
|
||||
niri-ipc = "=25.2.0"
|
||||
rustix = { version = "0.38.44", features = ["event", "fs", "pipe"] }
|
||||
scopeguard = "1.2.0"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
smithay-client-toolkit = { version = "0.19.2", default-features = false }
|
||||
swayipc = "3.0.2"
|
||||
niri-ipc = "=25.2.0"
|
||||
|
||||
[dependencies.image]
|
||||
version = "0.25.6"
|
||||
default-features = false
|
||||
features = ["bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "tiff", "webp"]
|
||||
|
||||
[dependencies.smithay-client-toolkit]
|
||||
version = "0.19.2"
|
||||
default-features = false
|
||||
|
||||
[features]
|
||||
default = ["avif"]
|
||||
avif = ["image/avif-native"]
|
||||
|
|
2
PKGBUILD
2
PKGBUILD
|
@ -12,6 +12,8 @@ optdepends=(
|
|||
'hyprland: supported window manager to set the wallpapers with'
|
||||
'niri: supported window manager to set the wallpapers with'
|
||||
'sway: supported window manager to set the wallpapers with'
|
||||
'vulkan-driver: upload and serve wallpapers from GPU memory'
|
||||
'vulkan-icd-loader: upload and serve wallpapers from GPU memory'
|
||||
)
|
||||
source=("$pkgname-$pkgver.tar.gz::https://static.crates.io/crates/$pkgname/$pkgname-$pkgver.crate")
|
||||
sha256sums=('2b087124ea07635e53d411e707f7d22f73c69b40f3986a42c841f9cc19fc2d51')
|
||||
|
|
|
@ -90,6 +90,9 @@ pub struct Cli {
|
|||
/// Wayland compositor to connect (autodetect by default)
|
||||
#[arg(long)]
|
||||
pub compositor: Option<crate::compositors::Compositor>,
|
||||
/// upload and serve wallpapers from GPU memory using Vulkan
|
||||
#[arg(long)]
|
||||
pub gpu: bool,
|
||||
/// directory with: wallpaper_dir/output/workspace_name.{jpg|png|...}
|
||||
pub wallpaper_dir: String,
|
||||
}
|
||||
|
|
302
src/gpu.rs
Normal file
302
src/gpu.rs
Normal file
|
@ -0,0 +1,302 @@
|
|||
#![allow(unsafe_op_in_unsafe_fn)]
|
||||
|
||||
// https://registry.khronos.org/vulkan/specs/latest/man/html/VK_EXT_image_drm_format_modifier.html
|
||||
|
||||
mod device;
|
||||
mod instance;
|
||||
mod memory;
|
||||
|
||||
use std::{
|
||||
ffi::CStr,
|
||||
os::fd::OwnedFd,
|
||||
rc::{Rc, Weak},
|
||||
slice,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use ash::{
|
||||
Device, Entry, Instance,
|
||||
ext::{
|
||||
debug_report,
|
||||
debug_utils,
|
||||
image_drm_format_modifier,
|
||||
},
|
||||
khr::external_memory_fd,
|
||||
vk::{
|
||||
Buffer,
|
||||
CommandBuffer,
|
||||
CommandPool,
|
||||
DebugReportCallbackEXT,
|
||||
DebugUtilsMessengerEXT,
|
||||
DeviceMemory,
|
||||
DrmFormatModifierPropertiesEXT,
|
||||
ExtensionProperties,
|
||||
Extent2D,
|
||||
Image,
|
||||
PhysicalDevice,
|
||||
PhysicalDeviceMemoryProperties,
|
||||
Queue,
|
||||
}
|
||||
};
|
||||
use log::{debug, error};
|
||||
use rustix::fs::Dev;
|
||||
|
||||
use device::device;
|
||||
use instance::instance;
|
||||
use memory::{upload, uploader};
|
||||
|
||||
pub struct Gpu {
|
||||
instance: Rc<GpuInstance>,
|
||||
devices: Vec<Weak<GpuDevice>>,
|
||||
}
|
||||
|
||||
impl Gpu {
|
||||
pub fn new() -> anyhow::Result<Gpu> {
|
||||
let instance = Rc::new(unsafe {
|
||||
instance()
|
||||
}.context("Failed to create Vulkan instance")?);
|
||||
let devices = Vec::new();
|
||||
Ok(Gpu { instance, devices })
|
||||
}
|
||||
|
||||
pub fn uploader(
|
||||
&mut self,
|
||||
dmabuf_drm_dev: Option<Dev>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
drm_format_modifiers: Vec<u64>,
|
||||
) -> anyhow::Result<GpuUploader> {
|
||||
unsafe {
|
||||
let mut selected = self.select_device(dmabuf_drm_dev);
|
||||
if selected.is_none() {
|
||||
let new_device = Rc::new(device(&self.instance, dmabuf_drm_dev)
|
||||
.context("Failed to create new device")?);
|
||||
self.devices.push(Rc::downgrade(&new_device));
|
||||
selected = Some(new_device);
|
||||
}
|
||||
let gpu_device = selected.unwrap();
|
||||
uploader(gpu_device, width, height, drm_format_modifiers)
|
||||
.context("Failed to create GPU uploader")
|
||||
}
|
||||
}
|
||||
|
||||
fn select_device(
|
||||
&mut self,
|
||||
dmabuf_drm_dev: Option<Dev>
|
||||
) -> Option<Rc<GpuDevice>> {
|
||||
let mut ret = None;
|
||||
self.devices.retain(|weak_gpu_device| {
|
||||
if let Some(gpu_device) = weak_gpu_device.upgrade() {
|
||||
if ret.is_none()
|
||||
&& gpu_device.dmabuf_drm_dev_eq(dmabuf_drm_dev)
|
||||
{
|
||||
ret = Some(gpu_device)
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
struct GpuInstance {
|
||||
_entry: Entry,
|
||||
instance: Instance,
|
||||
debug: Debug,
|
||||
}
|
||||
|
||||
impl Drop for GpuInstance {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
match &self.debug {
|
||||
Debug::Utils { instance, messenger } => {
|
||||
instance.destroy_debug_utils_messenger(*messenger, None);
|
||||
},
|
||||
Debug::Report { instance, callback } => {
|
||||
#[allow(deprecated)]
|
||||
instance.destroy_debug_report_callback(*callback, None);
|
||||
}
|
||||
Debug::None => (),
|
||||
};
|
||||
self.instance.destroy_instance(None);
|
||||
debug!("Vulkan context has been cleaned up");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Debug {
|
||||
Utils {
|
||||
instance: debug_utils::Instance,
|
||||
messenger: DebugUtilsMessengerEXT,
|
||||
},
|
||||
Report {
|
||||
instance: debug_report::Instance,
|
||||
callback: DebugReportCallbackEXT,
|
||||
},
|
||||
None,
|
||||
}
|
||||
|
||||
struct GpuDevice {
|
||||
gpu_instance: Rc<GpuInstance>,
|
||||
physdev: PhysicalDevice,
|
||||
primary_drm_dev: Option<Dev>,
|
||||
render_drm_dev: Option<Dev>,
|
||||
dmabuf_drm_dev: Option<Dev>,
|
||||
memory_props: PhysicalDeviceMemoryProperties,
|
||||
drm_format_props: Option<Vec<DrmFormatModifierPropertiesEXT>>,
|
||||
device: Device,
|
||||
external_memory_fd_device: external_memory_fd::Device,
|
||||
image_drm_format_modifier_device: Option<image_drm_format_modifier::Device>,
|
||||
queue_family_index: u32,
|
||||
queue: Queue,
|
||||
command_pool: CommandPool,
|
||||
command_buffer: CommandBuffer,
|
||||
}
|
||||
|
||||
impl Drop for GpuDevice {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if let Err(e) = self.device.device_wait_idle() {
|
||||
error!("Failed to wait device idle: {e}");
|
||||
};
|
||||
self.device.destroy_command_pool(self.command_pool, None);
|
||||
self.device.destroy_device(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GpuDevice {
|
||||
fn dmabuf_drm_dev_eq(&self, drm_dev: Option<Dev>) -> bool {
|
||||
if drm_dev.is_some() {
|
||||
assert!(self.dmabuf_drm_dev.is_some());
|
||||
drm_dev == self.dmabuf_drm_dev
|
||||
|| drm_dev == self.render_drm_dev
|
||||
|| drm_dev == self.primary_drm_dev
|
||||
} else {
|
||||
assert!(self.dmabuf_drm_dev.is_none());
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GpuUploader {
|
||||
gpu_device: Rc<GpuDevice>,
|
||||
buffer: Buffer,
|
||||
memory: DeviceMemory,
|
||||
ptr: *mut u8,
|
||||
len: usize,
|
||||
extent: Extent2D,
|
||||
drm_format_modifiers: Vec<u64>,
|
||||
}
|
||||
|
||||
impl Drop for GpuUploader {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let device = &self.gpu_device.device;
|
||||
device.unmap_memory(self.memory);
|
||||
device.free_memory(self.memory, None);
|
||||
device.destroy_buffer(self.buffer, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GpuUploader {
|
||||
pub fn staging_buffer(&mut self) -> &mut [u8] {
|
||||
unsafe { slice::from_raw_parts_mut(self.ptr, self.len) }
|
||||
}
|
||||
|
||||
pub fn upload(&mut self) -> anyhow::Result<GpuWallpaper> {
|
||||
unsafe { upload(self) }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GpuWallpaper {
|
||||
pub drm_format_modifier: u64,
|
||||
pub memory_planes_len: usize,
|
||||
pub memory_planes: [MemoryPlane; 4],
|
||||
pub gpu_memory: GpuMemory,
|
||||
pub fd: OwnedFd,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct MemoryPlane {
|
||||
pub offset: u64,
|
||||
pub stride: u64,
|
||||
}
|
||||
|
||||
pub struct GpuMemory {
|
||||
gpu_device: Rc<GpuDevice>,
|
||||
image: Image,
|
||||
memory: DeviceMemory,
|
||||
size: usize,
|
||||
drm_format_modifier: u64,
|
||||
}
|
||||
|
||||
impl Drop for GpuMemory {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
self.gpu_device.device.destroy_image(self.image, None);
|
||||
self.gpu_device.device.free_memory(self.memory, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GpuMemory {
|
||||
pub fn gpu_uploader_eq(&self, gpu_uploader: &GpuUploader) -> bool {
|
||||
self.dmabuf_feedback_eq(
|
||||
gpu_uploader.gpu_device.dmabuf_drm_dev,
|
||||
gpu_uploader.drm_format_modifiers.as_slice(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn dmabuf_feedback_eq(
|
||||
&self,
|
||||
dmabuf_drm_dev: Option<Dev>,
|
||||
drm_format_modifiers: &[u64]
|
||||
) -> bool {
|
||||
self.gpu_device.dmabuf_drm_dev_eq(dmabuf_drm_dev)
|
||||
&& drm_format_modifiers.contains(&self.drm_format_modifier)
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
// Fourcc codes are based on libdrm drm_fourcc.h
|
||||
// https://gitlab.freedesktop.org/mesa/drm/-/blob/main/include/drm/drm_fourcc.h
|
||||
// /usr/include/libdrm/drm_fourcc.h
|
||||
// under license MIT
|
||||
pub const fn fourcc_code(a: u8, b: u8, c: u8, d: u8) -> u32 {
|
||||
(a as u32) | (b as u32) << 8 | (c as u32) << 16 | (d as u32) << 24
|
||||
}
|
||||
|
||||
pub const fn fourcc_mod_code(vendor: u64, val: u64) -> u64 {
|
||||
(vendor << 56) | (val & 0x00ff_ffff_ffff_ffff)
|
||||
}
|
||||
|
||||
// pub const DRM_FORMAT_INVALID: u32 = 0;
|
||||
pub const DRM_FORMAT_XRGB8888: u32 = fourcc_code(b'X', b'R', b'2', b'4');
|
||||
// pub const DRM_FORMAT_ARGB8888: u32 = fourcc_code(b'A', b'R', b'2', b'4');
|
||||
|
||||
pub const DRM_FORMAT_MOD_VENDOR_NONE: u64 = 0;
|
||||
// pub const DRM_FORMAT_RESERVED: u64 = (1 << 56) - 1;
|
||||
|
||||
// pub const DRM_FORMAT_MOD_INVALID: u64 = fourcc_mod_code(
|
||||
// DRM_FORMAT_MOD_VENDOR_NONE,
|
||||
// DRM_FORMAT_RESERVED,
|
||||
// );
|
||||
pub const DRM_FORMAT_MOD_LINEAR: u64 = fourcc_mod_code(
|
||||
DRM_FORMAT_MOD_VENDOR_NONE,
|
||||
0,
|
||||
);
|
||||
|
||||
fn has_extension(extensions: &[ExtensionProperties], name: &CStr) -> bool {
|
||||
extensions.iter().any(|ext| ext.extension_name_as_c_str() == Ok(name))
|
||||
}
|
||||
|
||||
pub fn fmt_modifier(drm_format_modifier: u64) -> String {
|
||||
format!("{drm_format_modifier:016x}")
|
||||
}
|
420
src/gpu/device.rs
Normal file
420
src/gpu/device.rs
Normal file
|
@ -0,0 +1,420 @@
|
|||
use std::{
|
||||
ffi::{CStr, c_char},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use anyhow::{bail, Context};
|
||||
use ash::{
|
||||
Instance,
|
||||
ext::{
|
||||
external_memory_dma_buf,
|
||||
image_drm_format_modifier,
|
||||
physical_device_drm,
|
||||
queue_family_foreign,
|
||||
},
|
||||
khr::{
|
||||
driver_properties,
|
||||
external_memory_fd,
|
||||
image_format_list,
|
||||
},
|
||||
vk::{
|
||||
self,
|
||||
api_version_variant,
|
||||
api_version_major,
|
||||
api_version_minor,
|
||||
api_version_patch,
|
||||
CommandBufferAllocateInfo,
|
||||
CommandBufferLevel,
|
||||
CommandPoolCreateFlags,
|
||||
CommandPoolCreateInfo,
|
||||
DeviceCreateInfo,
|
||||
DeviceQueueCreateInfo,
|
||||
DrmFormatModifierPropertiesEXT,
|
||||
DrmFormatModifierPropertiesListEXT,
|
||||
ExtensionProperties,
|
||||
Format,
|
||||
FormatProperties2,
|
||||
PhysicalDevice,
|
||||
PhysicalDeviceDriverProperties,
|
||||
PhysicalDeviceDrmPropertiesEXT,
|
||||
PhysicalDeviceProperties,
|
||||
PhysicalDeviceProperties2,
|
||||
PhysicalDeviceType,
|
||||
QueueFlags,
|
||||
}
|
||||
};
|
||||
use log::{debug, error, warn};
|
||||
use rustix::fs::{Dev, major, makedev, minor};
|
||||
use scopeguard::{guard, ScopeGuard};
|
||||
|
||||
use super::{GpuDevice, GpuInstance, has_extension};
|
||||
|
||||
struct PhysdevInfo {
|
||||
physdev: PhysicalDevice,
|
||||
props: PhysicalDeviceProperties,
|
||||
extensions: Extensions,
|
||||
primary: Option<Dev>,
|
||||
render: Option<Dev>,
|
||||
dmabuf_dev: Option<Dev>,
|
||||
score: u32,
|
||||
}
|
||||
|
||||
const SCORE_MATCHES_DRM_DEV: u32 = 1 << 3;
|
||||
const SCORE_DISCRETE_GPU: u32 = 1 << 2;
|
||||
const SCORE_INTEGRATED_GPU: u32 = 1 << 1;
|
||||
const SCORE_VIRTUAL_GPU: u32 = 1 << 0;
|
||||
|
||||
pub unsafe fn device(
|
||||
gpu_instance: &Rc<GpuInstance>,
|
||||
dmabuf_drm_dev: Option<Dev>,
|
||||
) -> anyhow::Result<GpuDevice> {
|
||||
let instance = &gpu_instance.instance;
|
||||
let physdevs = instance.enumerate_physical_devices()
|
||||
.context("Failed to enumerate physical devices")?;
|
||||
let count = physdevs.len();
|
||||
if count == 0 {
|
||||
bail!("No physical devices found. Make sure you have a Vulkan driver \
|
||||
installed for your GPU and this application has permisson to \
|
||||
access graphics devices");
|
||||
}
|
||||
let mut physdev_infos = physdevs.into_iter()
|
||||
.filter_map(|physdev| physdev_info(instance, dmabuf_drm_dev, physdev))
|
||||
.collect::<Vec<_>>();
|
||||
physdev_infos.sort_by_key(|info| u32::MAX - info.score);
|
||||
let Some(max_score) = physdev_infos.first().map(|info| info.score) else {
|
||||
bail!("No physical devices could be probed")
|
||||
};
|
||||
physdev_infos.retain(|info| info.score == max_score);
|
||||
if physdev_infos.len() == 1 {
|
||||
debug!("Probed {} physical device(s), max score {}", count, max_score);
|
||||
return device_with_physdev(gpu_instance, physdev_infos.pop().unwrap())
|
||||
}
|
||||
warn!("Filtered multiple physical devices, {} out of {} with max score {}",
|
||||
physdev_infos.len(), count, max_score);
|
||||
let mut gpu_device_ok = None;
|
||||
let mut errors = Vec::new();
|
||||
for physdev_info in physdev_infos {
|
||||
match device_with_physdev(gpu_instance, physdev_info) {
|
||||
Ok(gpu_device) => {
|
||||
gpu_device_ok = Some(gpu_device);
|
||||
break
|
||||
},
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
}
|
||||
if let Some(gpu_device) = gpu_device_ok {
|
||||
for e in errors {
|
||||
warn!("{e:#}");
|
||||
}
|
||||
Ok(gpu_device)
|
||||
} else {
|
||||
for e in errors {
|
||||
error!("{e:#}");
|
||||
}
|
||||
bail!("Failed to set up device with all filtered physical devices");
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn physdev_info(
|
||||
instance: &Instance,
|
||||
dmabuf_dev: Option<Dev>,
|
||||
physdev: PhysicalDevice,
|
||||
) -> Option<PhysdevInfo> {
|
||||
let extension_props_vec = match instance
|
||||
.enumerate_device_extension_properties(physdev)
|
||||
{
|
||||
Ok(ext_props) => ext_props,
|
||||
Err(e) => {
|
||||
let props = instance.get_physical_device_properties(physdev);
|
||||
let name = props.device_name_as_c_str().unwrap_or(c"unknown");
|
||||
let typ = props.device_type;
|
||||
error!("Failed to enumerate device extension properties
|
||||
for physical device {name:?} (type {typ:?}): {e}");
|
||||
return None
|
||||
}
|
||||
};
|
||||
let extensions = Extensions::new(extension_props_vec);
|
||||
let mut props2_chain = PhysicalDeviceProperties2::default();
|
||||
let has_drm_props = extensions.has(physical_device_drm::NAME);
|
||||
let mut drm_props = PhysicalDeviceDrmPropertiesEXT::default();
|
||||
if has_drm_props {
|
||||
props2_chain = props2_chain.push_next(&mut drm_props);
|
||||
}
|
||||
let has_driver_props = extensions.has(driver_properties::NAME);
|
||||
let mut driver_props = PhysicalDeviceDriverProperties::default();
|
||||
if has_driver_props {
|
||||
props2_chain = props2_chain.push_next(&mut driver_props);
|
||||
}
|
||||
instance.get_physical_device_properties2(physdev, &mut props2_chain);
|
||||
let props = props2_chain.properties;
|
||||
let name = props.device_name_as_c_str().unwrap_or(c"unknown");
|
||||
let typ = props.device_type;
|
||||
debug!("Probing physical device {name:?} (type {typ:?})");
|
||||
let mut score = 0u32;
|
||||
match typ {
|
||||
PhysicalDeviceType::DISCRETE_GPU => score |= SCORE_DISCRETE_GPU,
|
||||
PhysicalDeviceType::INTEGRATED_GPU => score |= SCORE_INTEGRATED_GPU,
|
||||
PhysicalDeviceType::VIRTUAL_GPU => score |= SCORE_VIRTUAL_GPU,
|
||||
_ => (),
|
||||
}
|
||||
if has_driver_props {
|
||||
debug!("Physical device driver: {:?}, {:?}",
|
||||
driver_props.driver_name_as_c_str().unwrap_or(c"unknown"),
|
||||
driver_props.driver_info_as_c_str().unwrap_or(c"unknown"));
|
||||
} else {
|
||||
debug!("VK_KHR_driver_properties unavailable");
|
||||
}
|
||||
let (mut primary, mut render) = (None, None);
|
||||
if has_drm_props {
|
||||
if drm_props.has_primary == vk::TRUE {
|
||||
primary = Some(makedev(
|
||||
drm_props.primary_major as _,
|
||||
drm_props.primary_minor as _,
|
||||
));
|
||||
}
|
||||
if drm_props.has_render == vk::TRUE {
|
||||
render = Some(makedev(
|
||||
drm_props.render_major as _,
|
||||
drm_props.render_minor as _,
|
||||
));
|
||||
}
|
||||
debug!("Physical device DRM devs: primary {}, render {}",
|
||||
fmt_dev_option(primary), fmt_dev_option(render));
|
||||
// Note [1]
|
||||
if dmabuf_dev.is_some()
|
||||
&& (dmabuf_dev == primary || dmabuf_dev == render)
|
||||
{
|
||||
score |= SCORE_MATCHES_DRM_DEV;
|
||||
debug!("Physical device matched with the DMA-BUF feedback DRM dev");
|
||||
} else {
|
||||
debug!("Could not match physical device with the DMA-BUF feedback \
|
||||
DRM dev");
|
||||
}
|
||||
} else {
|
||||
debug!("VK_EXT_physical_device_drm unavailable");
|
||||
}
|
||||
Some(PhysdevInfo {
|
||||
physdev,
|
||||
props,
|
||||
extensions,
|
||||
primary,
|
||||
render,
|
||||
dmabuf_dev,
|
||||
score,
|
||||
})
|
||||
}
|
||||
|
||||
unsafe fn device_with_physdev(
|
||||
gpu_instance: &Rc<GpuInstance>,
|
||||
physdev_info: PhysdevInfo,
|
||||
) -> anyhow::Result<GpuDevice> {
|
||||
let name = physdev_info.props
|
||||
.device_name_as_c_str().unwrap_or(c"unknown").to_owned();
|
||||
let typ = physdev_info.props.device_type;
|
||||
let score = physdev_info.score;
|
||||
debug!("Setting up device with physical device {name:?} (type {typ:?})");
|
||||
let gpu_device = try_device_with_physdev(gpu_instance, physdev_info)
|
||||
.with_context(|| format!(
|
||||
"Failed to set up device with physical device {:?} (type {:?})",
|
||||
name, typ
|
||||
))?;
|
||||
if score & SCORE_MATCHES_DRM_DEV == 0 {
|
||||
// We get here if either
|
||||
// - using Wayland protocol Linux DMA-BUF version < 4
|
||||
// - device has no VK_EXT_physical_device_drm
|
||||
warn!("IMPORTANT: Failed to ensure that we select the same GPU where \
|
||||
the compositor is running based on the DRM device numbers. About \
|
||||
to use physical device {:?} (type {:?}). If this is incorrect \
|
||||
then please restart without the --gpu option and open an issue",
|
||||
name, typ);
|
||||
}
|
||||
Ok(gpu_device)
|
||||
}
|
||||
|
||||
unsafe fn try_device_with_physdev(
|
||||
gpu_instance: &Rc<GpuInstance>,
|
||||
physdev_info: PhysdevInfo,
|
||||
) -> anyhow::Result<GpuDevice> {
|
||||
let instance = &gpu_instance.instance;
|
||||
let PhysdevInfo {
|
||||
physdev,
|
||||
props,
|
||||
mut extensions,
|
||||
primary,
|
||||
render,
|
||||
dmabuf_dev,
|
||||
..
|
||||
} = physdev_info;
|
||||
let variant = api_version_variant(props.api_version);
|
||||
let major = api_version_major(props.api_version);
|
||||
let minor = api_version_minor(props.api_version);
|
||||
let patch = api_version_patch(props.api_version);
|
||||
if variant != 0 || major != 1 || minor < 1 {
|
||||
bail!("Need Vulkan device variant 0 version 1.1.0 or compatible,
|
||||
found variant {variant} version {major}.{minor}.{patch}");
|
||||
}
|
||||
debug!("Vulkan device supports version {major}.{minor}.{patch}");
|
||||
let memory_props = instance
|
||||
.get_physical_device_memory_properties(physdev);
|
||||
let queue_family_props = instance
|
||||
.get_physical_device_queue_family_properties(physdev);
|
||||
let queue_family_index = queue_family_props.iter()
|
||||
.position(|props| {
|
||||
props.queue_flags.contains(QueueFlags::GRAPHICS)
|
||||
&& props.queue_count > 0
|
||||
})
|
||||
.context("Failed to find an appropriate queue family")? as u32;
|
||||
// Device extension dependency chains with Vulkan 1.1
|
||||
// app --> EXT_external_memory_dma_buf -> KHR_external_memory_fd
|
||||
// \-> EXT_queue_family_foreign
|
||||
// \-> (optional) EXT_image_drm_format_modifier -> KHR_image_format_list
|
||||
// EXT_image_drm_format_modifier is notably unsupported by
|
||||
// - AMD GFX8 and older
|
||||
// - end-of-life Nvidia GPUs which never got driver version 515
|
||||
extensions.try_enable(external_memory_fd::NAME)
|
||||
.context("KHR_external_memory_fd unavailable")?;
|
||||
extensions.try_enable(external_memory_dma_buf::NAME)
|
||||
.context("EXT_external_memory_dma_buf unavailable")?;
|
||||
extensions.try_enable(queue_family_foreign::NAME)
|
||||
.context("EXT_queue_family_foreign unavailable")?;
|
||||
extensions.try_enable(image_format_list::NAME)
|
||||
.context("KHR_image_format_list unavailable")?;
|
||||
let has_image_drm_format_modifier = extensions
|
||||
.try_enable(image_drm_format_modifier::NAME).is_some();
|
||||
let device = guard(
|
||||
instance.create_device(
|
||||
physdev,
|
||||
&DeviceCreateInfo::default()
|
||||
.queue_create_infos(&[DeviceQueueCreateInfo::default()
|
||||
.queue_family_index(queue_family_index)
|
||||
.queue_priorities(&[1.0])]
|
||||
)
|
||||
.enabled_extension_names(extensions.enabled()),
|
||||
None
|
||||
).context("Failed to create device")?,
|
||||
|device| device.destroy_device(None),
|
||||
);
|
||||
let external_memory_fd_device =
|
||||
external_memory_fd::Device::new(instance, &device);
|
||||
let image_drm_format_modifier_device = if has_image_drm_format_modifier {
|
||||
Some(image_drm_format_modifier::Device::new(instance, &device))
|
||||
} else {
|
||||
debug!("EXT_image_drm_format_modifier unavailable");
|
||||
None
|
||||
};
|
||||
let queue = device.get_device_queue(queue_family_index, 0);
|
||||
let command_pool = guard(
|
||||
device.create_command_pool(
|
||||
&CommandPoolCreateInfo::default()
|
||||
.flags(CommandPoolCreateFlags::RESET_COMMAND_BUFFER)
|
||||
.queue_family_index(queue_family_index),
|
||||
None
|
||||
).context("Failed to create command pool")?,
|
||||
|command_pool| device.destroy_command_pool(command_pool, None)
|
||||
);
|
||||
let command_buffer = guard(
|
||||
device.allocate_command_buffers(
|
||||
&CommandBufferAllocateInfo::default()
|
||||
.command_buffer_count(1)
|
||||
.command_pool(*command_pool)
|
||||
.level(CommandBufferLevel::PRIMARY)
|
||||
).context("Failed to allocate command buffer")?[0],
|
||||
|command_buffer|
|
||||
device.free_command_buffers(*command_pool, &[command_buffer]),
|
||||
);
|
||||
let drm_format_props = if has_image_drm_format_modifier {
|
||||
Some(drm_format_props_b8g8r8a8_srgb(instance, physdev))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(GpuDevice {
|
||||
command_buffer: ScopeGuard::into_inner(command_buffer),
|
||||
command_pool: ScopeGuard::into_inner(command_pool),
|
||||
device: ScopeGuard::into_inner(device),
|
||||
external_memory_fd_device,
|
||||
image_drm_format_modifier_device,
|
||||
physdev,
|
||||
memory_props,
|
||||
drm_format_props,
|
||||
primary_drm_dev: primary,
|
||||
render_drm_dev: render,
|
||||
dmabuf_drm_dev: dmabuf_dev,
|
||||
queue,
|
||||
queue_family_index,
|
||||
gpu_instance: Rc::clone(gpu_instance),
|
||||
})
|
||||
}
|
||||
|
||||
struct Extensions {
|
||||
props: Vec<ExtensionProperties>,
|
||||
enabled: Vec<*const c_char>,
|
||||
}
|
||||
|
||||
impl Extensions {
|
||||
fn new(props: Vec<ExtensionProperties>) -> Extensions {
|
||||
Extensions { props, enabled: Vec::new() }
|
||||
}
|
||||
|
||||
fn has(&self, name: &CStr) -> bool {
|
||||
has_extension(&self.props, name)
|
||||
}
|
||||
|
||||
fn try_enable(&mut self, extension: &CStr) -> Option<()> {
|
||||
if self.props.iter().any(|ext|
|
||||
ext.extension_name_as_c_str() == Ok(extension)
|
||||
) {
|
||||
self.enabled.push(extension.as_ptr());
|
||||
Some(())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn enabled(&self) -> &[*const c_char] {
|
||||
&self.enabled
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_dev_option(dev: Option<Dev>) -> String {
|
||||
if let Some(dev) = dev {
|
||||
format!("{}:{}", major(dev), minor(dev))
|
||||
} else {
|
||||
"unavailable".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn drm_format_props_b8g8r8a8_srgb(
|
||||
instance: &Instance,
|
||||
physdev: PhysicalDevice,
|
||||
) -> Vec<DrmFormatModifierPropertiesEXT> {
|
||||
let mut drm_format_props_list =
|
||||
DrmFormatModifierPropertiesListEXT::default();
|
||||
instance.get_physical_device_format_properties2(
|
||||
physdev,
|
||||
Format::B8G8R8A8_SRGB,
|
||||
&mut FormatProperties2::default().push_next(&mut drm_format_props_list),
|
||||
);
|
||||
let mut drm_format_props = vec![
|
||||
DrmFormatModifierPropertiesEXT::default();
|
||||
drm_format_props_list.drm_format_modifier_count as usize
|
||||
];
|
||||
drm_format_props_list = drm_format_props_list
|
||||
.drm_format_modifier_properties(&mut drm_format_props);
|
||||
let mut format_props_chain = FormatProperties2::default()
|
||||
.push_next(&mut drm_format_props_list);
|
||||
instance.get_physical_device_format_properties2(
|
||||
physdev,
|
||||
Format::B8G8R8A8_SRGB,
|
||||
&mut format_props_chain,
|
||||
);
|
||||
drm_format_props
|
||||
}
|
||||
|
||||
// [1] Wayland DMA-BUF says for the feedback main device and for the tranche
|
||||
// target device one must not compare the dev_t and should use drmDevicesEqual
|
||||
// from libdrm.so instead to find the same GPU. But neither Mesa Vulkan WSI
|
||||
// Wayland nor wlroots Vulkan renderer does that, they both use
|
||||
// PhysicalDeviceDrmPropertiesEXT the same way we do here. So this is probably
|
||||
// fine (because it provides both the primary and the render DRM node to
|
||||
// compare against not just one of them (?)). Do we need a fallback using
|
||||
// libdrm drmDevicesEqual?
|
232
src/gpu/instance.rs
Normal file
232
src/gpu/instance.rs
Normal file
|
@ -0,0 +1,232 @@
|
|||
use std::{
|
||||
backtrace::Backtrace,
|
||||
borrow::Cow,
|
||||
ffi::{c_void, CStr},
|
||||
ptr,
|
||||
};
|
||||
|
||||
use anyhow::{bail, Context};
|
||||
use ash::{
|
||||
Entry,
|
||||
ext::{
|
||||
debug_report,
|
||||
debug_utils,
|
||||
},
|
||||
vk::{
|
||||
self,
|
||||
api_version_variant,
|
||||
api_version_major,
|
||||
api_version_minor,
|
||||
api_version_patch,
|
||||
ApplicationInfo,
|
||||
Bool32,
|
||||
DebugReportCallbackCreateInfoEXT,
|
||||
DebugReportFlagsEXT,
|
||||
DebugReportObjectTypeEXT,
|
||||
DebugUtilsMessengerCallbackDataEXT,
|
||||
DebugUtilsMessengerCreateInfoEXT,
|
||||
DebugUtilsMessageSeverityFlagsEXT,
|
||||
DebugUtilsMessageTypeFlagsEXT,
|
||||
InstanceCreateInfo,
|
||||
LayerProperties,
|
||||
make_api_version,
|
||||
}
|
||||
};
|
||||
use log::{debug, error, info, warn};
|
||||
|
||||
use super::{Debug, GpuInstance, has_extension};
|
||||
|
||||
const APP_VK_NAME: &CStr = match CStr::from_bytes_with_nul(
|
||||
concat!(env!("CARGO_PKG_NAME"), '\0').as_bytes()
|
||||
) {
|
||||
Ok(val) => val,
|
||||
Err(_) => panic!(),
|
||||
};
|
||||
const APP_VK_VERSION: u32 = make_api_version(
|
||||
0,
|
||||
parse_decimal(env!("CARGO_PKG_VERSION_MAJOR")),
|
||||
parse_decimal(env!("CARGO_PKG_VERSION_MINOR")),
|
||||
parse_decimal(env!("CARGO_PKG_VERSION_PATCH")),
|
||||
);
|
||||
const LAYER_VALIDATION: &CStr = c"VK_LAYER_KHRONOS_validation";
|
||||
const VULKAN_VERSION_TARGET: u32 = make_api_version(0, 1, 1, 0);
|
||||
|
||||
pub unsafe fn instance() -> anyhow::Result<GpuInstance> {
|
||||
let entry = Entry::load()
|
||||
.context("Failed to load Vulkan shared libraries. Make sure you have \
|
||||
the Vulkan loader and a Vulkan driver for your GPU installed")?;
|
||||
let instance_version = entry.try_enumerate_instance_version()
|
||||
.context("Failed to enumerate instance version")?
|
||||
.unwrap_or_else(|| make_api_version(0, 1, 0, 0));
|
||||
let variant = api_version_variant(instance_version);
|
||||
let major = api_version_major(instance_version);
|
||||
let minor = api_version_minor(instance_version);
|
||||
let patch = api_version_patch(instance_version);
|
||||
if variant != 0 || major != 1 || minor < 1 {
|
||||
bail!("Need Vulkan instance variant 0 version 1.1.0 or compatible,
|
||||
found variant {variant} version {major}.{minor}.{patch}");
|
||||
}
|
||||
debug!("Vulkan instance supports version {major}.{minor}.{patch}");
|
||||
let instance_layer_props = entry.enumerate_instance_layer_properties()
|
||||
.context("Failed to enumerate instance layers")?;
|
||||
let instance_extension_props = entry
|
||||
.enumerate_instance_extension_properties(None)
|
||||
.context("Failed to enumerate instance extensions")?;
|
||||
let mut instance_layers = Vec::new();
|
||||
let mut extension_layers = Vec::new();
|
||||
let mut has_debug_utils = false;
|
||||
let mut has_debug_report = false;
|
||||
if log::log_enabled!(log::Level::Debug) {
|
||||
debug!("Running with log level DEBUG or higher \
|
||||
so trying to enable Vulkan validation layers");
|
||||
if has_layer(&instance_layer_props, LAYER_VALIDATION) {
|
||||
instance_layers.push(LAYER_VALIDATION.as_ptr());
|
||||
info!("Enabling VK_LAYER_KHRONOS_validation");
|
||||
} else {
|
||||
warn!("VK_LAYER_KHRONOS_validation unavailable");
|
||||
}
|
||||
if has_extension(&instance_extension_props, debug_utils::NAME) {
|
||||
debug!("Enabling VK_EXT_debug_utils");
|
||||
has_debug_utils = true;
|
||||
extension_layers.push(debug_utils::NAME.as_ptr());
|
||||
} else if has_extension(&instance_extension_props, debug_report::NAME) {
|
||||
debug!("Enabling VK_EXT_debug_report");
|
||||
has_debug_report = true;
|
||||
extension_layers.push(debug_report::NAME.as_ptr());
|
||||
} else {
|
||||
warn!("VK_EXT_debug_utils and VK_EXT_debug_report unavailable");
|
||||
}
|
||||
}
|
||||
let instance = entry.create_instance(
|
||||
&InstanceCreateInfo::default()
|
||||
.application_info(&ApplicationInfo::default()
|
||||
.application_name(APP_VK_NAME)
|
||||
.application_version(APP_VK_VERSION)
|
||||
.engine_name(APP_VK_NAME)
|
||||
.engine_version(APP_VK_VERSION)
|
||||
.api_version(VULKAN_VERSION_TARGET)
|
||||
)
|
||||
.enabled_layer_names(&instance_layers)
|
||||
.enabled_extension_names(&extension_layers),
|
||||
None,
|
||||
).context("Failed to create instance")?;
|
||||
let mut debug = Debug::None;
|
||||
if has_debug_utils {
|
||||
let instance = debug_utils::Instance::new(&entry, &instance);
|
||||
match instance.create_debug_utils_messenger(
|
||||
&DebugUtilsMessengerCreateInfoEXT::default()
|
||||
.message_severity(
|
||||
DebugUtilsMessageSeverityFlagsEXT::ERROR
|
||||
| DebugUtilsMessageSeverityFlagsEXT::WARNING
|
||||
)
|
||||
.message_type(
|
||||
DebugUtilsMessageTypeFlagsEXT::GENERAL
|
||||
| DebugUtilsMessageTypeFlagsEXT::VALIDATION
|
||||
| DebugUtilsMessageTypeFlagsEXT::PERFORMANCE,
|
||||
)
|
||||
.pfn_user_callback(Some(debug_utils_callback)),
|
||||
None
|
||||
) {
|
||||
Ok(messenger) =>
|
||||
debug = Debug::Utils { instance, messenger },
|
||||
Err(e) =>
|
||||
error!("Failed to create Vulkan debug utils messenger: {e}"),
|
||||
};
|
||||
} else if has_debug_report {
|
||||
let instance = debug_report::Instance::new(&entry, &instance);
|
||||
#[allow(deprecated)]
|
||||
match instance.create_debug_report_callback(
|
||||
&DebugReportCallbackCreateInfoEXT::default()
|
||||
.flags(DebugReportFlagsEXT::WARNING
|
||||
| DebugReportFlagsEXT::PERFORMANCE_WARNING
|
||||
| DebugReportFlagsEXT::ERROR)
|
||||
.pfn_callback(Some(debug_report_callback))
|
||||
.user_data(ptr::null_mut()),
|
||||
None,
|
||||
) {
|
||||
Ok(callback) =>
|
||||
debug = Debug::Report { instance, callback },
|
||||
Err(e) =>
|
||||
error!("Failed to create Vulkan debug report callback: {e}"),
|
||||
};
|
||||
}
|
||||
Ok(GpuInstance {
|
||||
_entry: entry,
|
||||
instance,
|
||||
debug,
|
||||
})
|
||||
}
|
||||
|
||||
const fn parse_decimal(src: &str) -> u32 {
|
||||
match u32::from_str_radix(src, 10) {
|
||||
Ok(val) => val,
|
||||
Err(_) => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn has_layer(layers: &[LayerProperties], name: &CStr) -> bool {
|
||||
layers.iter().any(|layer| layer.layer_name_as_c_str() == Ok(name))
|
||||
}
|
||||
|
||||
unsafe extern "system" fn debug_utils_callback(
|
||||
message_severity: DebugUtilsMessageSeverityFlagsEXT,
|
||||
message_type: DebugUtilsMessageTypeFlagsEXT,
|
||||
p_callback_data: *const DebugUtilsMessengerCallbackDataEXT<'_>,
|
||||
_user_data: *mut c_void,
|
||||
) -> Bool32 {
|
||||
if p_callback_data.is_null() {
|
||||
error!("Vulkan: null data");
|
||||
return vk::FALSE
|
||||
}
|
||||
let callback_data = *p_callback_data;
|
||||
let message = if callback_data.p_message.is_null() {
|
||||
Cow::from("null message")
|
||||
} else {
|
||||
CStr::from_ptr(callback_data.p_message).to_string_lossy()
|
||||
};
|
||||
match message_severity {
|
||||
DebugUtilsMessageSeverityFlagsEXT::ERROR => {
|
||||
let backtrace = Backtrace::force_capture();
|
||||
error!("Vulkan: {message}\nBacktrace:\n{backtrace}");
|
||||
},
|
||||
DebugUtilsMessageSeverityFlagsEXT::WARNING => {
|
||||
warn!("Vulkan: {message}");
|
||||
},
|
||||
severity => {
|
||||
error!("Unexpected Vulkan message {:?} {:?}: {}",
|
||||
message_type, severity, message);
|
||||
},
|
||||
};
|
||||
vk::FALSE
|
||||
}
|
||||
|
||||
unsafe extern "system" fn debug_report_callback(
|
||||
flags: DebugReportFlagsEXT,
|
||||
_object_type: DebugReportObjectTypeEXT,
|
||||
_object: u64,
|
||||
_location: usize,
|
||||
_message_code: i32,
|
||||
_p_layer_prefix: *const i8,
|
||||
p_message: *const i8,
|
||||
_p_user_data: *mut c_void
|
||||
) -> u32 {
|
||||
let message = if p_message.is_null() {
|
||||
Cow::from("null message")
|
||||
} else {
|
||||
CStr::from_ptr(p_message).to_string_lossy()
|
||||
};
|
||||
match flags {
|
||||
DebugReportFlagsEXT::ERROR => {
|
||||
let backtrace = Backtrace::force_capture();
|
||||
error!("Vulkan: {message}\nBacktrace:\n{backtrace}");
|
||||
},
|
||||
DebugReportFlagsEXT::WARNING
|
||||
| DebugReportFlagsEXT::PERFORMANCE_WARNING => {
|
||||
warn!("Vulkan: {message}");
|
||||
},
|
||||
flag => {
|
||||
error!("Unexpected Vulkan message {flag:?}: {message}");
|
||||
}
|
||||
};
|
||||
vk::FALSE
|
||||
}
|
441
src/gpu/memory.rs
Normal file
441
src/gpu/memory.rs
Normal file
|
@ -0,0 +1,441 @@
|
|||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use std::{
|
||||
os::fd::{FromRawFd, OwnedFd},
|
||||
rc::Rc,
|
||||
slice,
|
||||
};
|
||||
|
||||
use anyhow::{bail, Context};
|
||||
use ash::{
|
||||
Instance,
|
||||
vk::{
|
||||
AccessFlags,
|
||||
BufferCreateFlags,
|
||||
BufferCreateInfo,
|
||||
BufferImageCopy,
|
||||
BufferUsageFlags,
|
||||
CommandBufferBeginInfo,
|
||||
CommandBufferResetFlags,
|
||||
DependencyFlags,
|
||||
DeviceSize,
|
||||
DrmFormatModifierPropertiesEXT,
|
||||
ExportMemoryAllocateInfo,
|
||||
Extent2D,
|
||||
ExternalMemoryHandleTypeFlags,
|
||||
ExternalMemoryImageCreateInfo,
|
||||
Fence,
|
||||
Format,
|
||||
FormatFeatureFlags,
|
||||
ImageAspectFlags,
|
||||
ImageCreateFlags,
|
||||
ImageCreateInfo,
|
||||
ImageDrmFormatModifierListCreateInfoEXT,
|
||||
ImageDrmFormatModifierPropertiesEXT,
|
||||
ImageFormatProperties2,
|
||||
ImageLayout,
|
||||
ImageMemoryBarrier,
|
||||
ImageSubresource,
|
||||
ImageSubresourceLayers,
|
||||
ImageSubresourceRange,
|
||||
ImageTiling,
|
||||
ImageType,
|
||||
ImageUsageFlags,
|
||||
MemoryAllocateInfo,
|
||||
MemoryGetFdInfoKHR,
|
||||
MemoryMapFlags,
|
||||
MemoryPropertyFlags,
|
||||
MemoryRequirements,
|
||||
PhysicalDevice,
|
||||
PhysicalDeviceImageDrmFormatModifierInfoEXT,
|
||||
PhysicalDeviceImageFormatInfo2,
|
||||
PhysicalDeviceMemoryProperties,
|
||||
PipelineStageFlags,
|
||||
QUEUE_FAMILY_FOREIGN_EXT,
|
||||
SampleCountFlags,
|
||||
SharingMode,
|
||||
SubmitInfo,
|
||||
}
|
||||
};
|
||||
use log::debug;
|
||||
use scopeguard::{guard, ScopeGuard};
|
||||
|
||||
use super::{
|
||||
DRM_FORMAT_MOD_LINEAR, fmt_modifier,
|
||||
GpuDevice, GpuMemory, GpuUploader, GpuWallpaper,
|
||||
MemoryPlane,
|
||||
};
|
||||
|
||||
pub unsafe fn uploader(
|
||||
gpu_device: Rc<GpuDevice>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
drm_format_modifiers: Vec<u64>,
|
||||
) -> anyhow::Result<GpuUploader> {
|
||||
let GpuDevice {
|
||||
gpu_instance,
|
||||
memory_props,
|
||||
drm_format_props,
|
||||
device,
|
||||
..
|
||||
} = gpu_device.as_ref();
|
||||
let instance = &gpu_instance.instance;
|
||||
let physdev = gpu_device.physdev;
|
||||
let queue_family_index = gpu_device.queue_family_index;
|
||||
let size = width as DeviceSize * height as DeviceSize * 4;
|
||||
let mut filtered_modifiers = Vec::with_capacity(drm_format_modifiers.len());
|
||||
if let Some(drm_format_props) = drm_format_props {
|
||||
for &drm_format_modifier in drm_format_modifiers.iter() {
|
||||
match filter_modifier(
|
||||
instance, physdev, queue_family_index, drm_format_props,
|
||||
width, height, size, drm_format_modifier,
|
||||
) {
|
||||
Ok(()) => filtered_modifiers.push(drm_format_modifier),
|
||||
Err(e) => debug!("Cannot use DRM format modifier {}: {:#}",
|
||||
fmt_modifier(drm_format_modifier), e),
|
||||
}
|
||||
if filtered_modifiers.is_empty() {
|
||||
bail!("None of the DRM format modifiers can be \
|
||||
used for image creation");
|
||||
}
|
||||
}
|
||||
} else if drm_format_modifiers.contains(&DRM_FORMAT_MOD_LINEAR) {
|
||||
debug!("Image creation can only use DRM_FORMAT_MOD_LINEAR");
|
||||
} else {
|
||||
bail!("VK_EXT_physical_device_drm unavailable and \
|
||||
no DRM_FORMAT_MOD_LINEAR was proposed for image creation");
|
||||
}
|
||||
debug!("Image creation can use DRM format modifiers: {}",
|
||||
filtered_modifiers.iter()
|
||||
.map(|&modifier| fmt_modifier(modifier))
|
||||
.collect::<Vec<_>>().join(", "));
|
||||
let buffer = guard(
|
||||
device.create_buffer(
|
||||
&BufferCreateInfo::default()
|
||||
.flags(BufferCreateFlags::empty())
|
||||
.size(size)
|
||||
.usage(BufferUsageFlags::TRANSFER_SRC)
|
||||
.sharing_mode(SharingMode::EXCLUSIVE)
|
||||
.queue_family_indices(slice::from_ref(&queue_family_index)),
|
||||
None
|
||||
).context("Failed to create staging buffer")?,
|
||||
|buffer| device.destroy_buffer(buffer, None),
|
||||
);
|
||||
let buffer_memory_req = device.get_buffer_memory_requirements(*buffer);
|
||||
let buffer_memory_index = find_memorytype_index(
|
||||
&buffer_memory_req,
|
||||
memory_props,
|
||||
MemoryPropertyFlags::HOST_VISIBLE | MemoryPropertyFlags::HOST_COHERENT
|
||||
| MemoryPropertyFlags::HOST_CACHED,
|
||||
).context("Cannot find suitable device memory type for staging buffer")?;
|
||||
let memory = guard(
|
||||
device.allocate_memory(
|
||||
&MemoryAllocateInfo::default()
|
||||
.allocation_size(buffer_memory_req.size)
|
||||
.memory_type_index(buffer_memory_index),
|
||||
None
|
||||
).context("Failed to allocate memory for staging buffer")?,
|
||||
|memory| device.free_memory(memory, None),
|
||||
);
|
||||
device.bind_buffer_memory(*buffer, *memory, 0)
|
||||
.context("Failed to bind memory to staging buffer")?;
|
||||
let ptr = device.map_memory(
|
||||
*memory,
|
||||
0,
|
||||
buffer_memory_req.size,
|
||||
MemoryMapFlags::empty()
|
||||
).context("Failed to map staging buffer memory")?;
|
||||
Ok(GpuUploader {
|
||||
memory: ScopeGuard::into_inner(memory),
|
||||
buffer: ScopeGuard::into_inner(buffer),
|
||||
ptr: ptr.cast(),
|
||||
len: buffer_memory_req.size as usize,
|
||||
extent: Extent2D { width, height },
|
||||
drm_format_modifiers: filtered_modifiers,
|
||||
gpu_device,
|
||||
})
|
||||
}
|
||||
|
||||
unsafe fn filter_modifier(
|
||||
instance: &Instance,
|
||||
physdev: PhysicalDevice,
|
||||
queue_family_index: u32,
|
||||
drm_format_props: &[DrmFormatModifierPropertiesEXT],
|
||||
width: u32,
|
||||
height: u32,
|
||||
size: DeviceSize,
|
||||
drm_format_modifier: u64,
|
||||
) -> anyhow::Result<()> {
|
||||
let format_props = drm_format_props.iter()
|
||||
.find(|props| props.drm_format_modifier == drm_format_modifier)
|
||||
.context("This modifier is unsupported by this Vulkan context")?;
|
||||
if !format_props.drm_format_modifier_tiling_features
|
||||
.contains(FormatFeatureFlags::TRANSFER_DST)
|
||||
{
|
||||
bail!("FormatFeatureFlag TRANSFER_DST unsupported");
|
||||
}
|
||||
let mut image_format_props2 = ImageFormatProperties2::default();
|
||||
let mut image_drm_info =
|
||||
PhysicalDeviceImageDrmFormatModifierInfoEXT::default()
|
||||
.drm_format_modifier(drm_format_modifier)
|
||||
.sharing_mode(SharingMode::EXCLUSIVE)
|
||||
.queue_family_indices(slice::from_ref(&queue_family_index));
|
||||
instance.get_physical_device_image_format_properties2(
|
||||
physdev,
|
||||
&PhysicalDeviceImageFormatInfo2::default()
|
||||
.format(Format::B8G8R8A8_SRGB)
|
||||
.ty(ImageType::TYPE_2D)
|
||||
.tiling(ImageTiling::DRM_FORMAT_MODIFIER_EXT)
|
||||
.usage(ImageUsageFlags::TRANSFER_DST)
|
||||
.flags(ImageCreateFlags::empty())
|
||||
.push_next(&mut image_drm_info),
|
||||
&mut image_format_props2,
|
||||
).context("The needed image format is unsupported for this modifier")?;
|
||||
let image_format_props = image_format_props2.image_format_properties;
|
||||
if image_format_props.max_extent.depth < 1
|
||||
|| image_format_props.max_mip_levels < 1
|
||||
|| image_format_props.max_array_layers < 1
|
||||
|| !image_format_props.sample_counts.contains(SampleCountFlags::TYPE_1)
|
||||
{
|
||||
bail!("The needed image format is unsupported for this modifier")
|
||||
}
|
||||
let max_width = image_format_props.max_extent.width;
|
||||
let max_height = image_format_props.max_extent.width;
|
||||
let max_size = image_format_props.max_resource_size;
|
||||
if width > max_width {
|
||||
bail!("Needed image width {width} is greter then the max supported \
|
||||
image width {max_width}")
|
||||
}
|
||||
if height > max_height {
|
||||
bail!("Needed image height {height} is greter then the max supported \
|
||||
image height {max_height}")
|
||||
}
|
||||
if size > max_size {
|
||||
bail!("Needed image size {size} bytes is greter then the max supported \
|
||||
image size {max_width} bytes")
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// XXX: we could check if dedicated allocation is needed:
|
||||
// https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_dedicated_allocation.html
|
||||
pub unsafe fn upload(
|
||||
uploader: &mut GpuUploader,
|
||||
) -> anyhow::Result<GpuWallpaper> {
|
||||
let GpuUploader {
|
||||
gpu_device,
|
||||
drm_format_modifiers,
|
||||
..
|
||||
} = uploader;
|
||||
let GpuDevice {
|
||||
memory_props,
|
||||
drm_format_props,
|
||||
device,
|
||||
external_memory_fd_device,
|
||||
image_drm_format_modifier_device,
|
||||
..
|
||||
} = gpu_device.as_ref();
|
||||
let extent = uploader.extent;
|
||||
let buffer = uploader.buffer;
|
||||
let queue_family_index = gpu_device.queue_family_index;
|
||||
let command_buffer = gpu_device.command_buffer;
|
||||
let queue = gpu_device.queue;
|
||||
let mut external_memory_info = ExternalMemoryImageCreateInfo::default()
|
||||
.handle_types(ExternalMemoryHandleTypeFlags::DMA_BUF_EXT);
|
||||
let mut modifier_list_info =
|
||||
ImageDrmFormatModifierListCreateInfoEXT::default()
|
||||
.drm_format_modifiers(drm_format_modifiers);
|
||||
let mut image_create_info = ImageCreateInfo::default()
|
||||
.flags(ImageCreateFlags::empty())
|
||||
.image_type(ImageType::TYPE_2D)
|
||||
.format(Format::B8G8R8A8_SRGB)
|
||||
.extent(extent.into())
|
||||
.mip_levels(1)
|
||||
.array_layers(1)
|
||||
.samples(SampleCountFlags::TYPE_1)
|
||||
.usage(ImageUsageFlags::TRANSFER_DST)
|
||||
.sharing_mode(SharingMode::EXCLUSIVE)
|
||||
.queue_family_indices(slice::from_ref(&queue_family_index))
|
||||
.initial_layout(ImageLayout::UNDEFINED)
|
||||
.push_next(&mut external_memory_info);
|
||||
if image_drm_format_modifier_device.is_some() {
|
||||
image_create_info = image_create_info
|
||||
.tiling(ImageTiling::DRM_FORMAT_MODIFIER_EXT)
|
||||
.push_next(&mut modifier_list_info);
|
||||
} else {
|
||||
image_create_info = image_create_info.tiling(ImageTiling::LINEAR);
|
||||
}
|
||||
let image = guard(
|
||||
device.create_image(&image_create_info, None)
|
||||
.context("Failed to create image")?,
|
||||
|image| device.destroy_image(image, None),
|
||||
);
|
||||
let image_memory_req = device.get_image_memory_requirements(*image);
|
||||
let image_memory_index = find_memorytype_index(
|
||||
&image_memory_req,
|
||||
memory_props,
|
||||
MemoryPropertyFlags::DEVICE_LOCAL,
|
||||
).context("Failed to find memorytype index for image")?;
|
||||
let image_memory = guard(
|
||||
device.allocate_memory(
|
||||
&MemoryAllocateInfo::default()
|
||||
.allocation_size(image_memory_req.size)
|
||||
.memory_type_index(image_memory_index)
|
||||
.push_next(&mut ExportMemoryAllocateInfo::default()
|
||||
.handle_types(ExternalMemoryHandleTypeFlags::DMA_BUF_EXT)
|
||||
),
|
||||
None
|
||||
).context("Failed to allocate memory for image")?,
|
||||
|memory| device.free_memory(memory, None),
|
||||
);
|
||||
device.bind_image_memory(*image, *image_memory, 0)
|
||||
.context("Failed to bind image memory")?;
|
||||
device.reset_command_buffer(
|
||||
command_buffer,
|
||||
CommandBufferResetFlags::empty()
|
||||
).context("Failed to reset command buffer")?;
|
||||
device.begin_command_buffer(
|
||||
command_buffer,
|
||||
&CommandBufferBeginInfo::default()
|
||||
) .context("Failed to begin command buffer")?;
|
||||
device.cmd_pipeline_barrier(
|
||||
command_buffer,
|
||||
PipelineStageFlags::TOP_OF_PIPE,
|
||||
PipelineStageFlags::TRANSFER,
|
||||
DependencyFlags::empty(),
|
||||
&[],
|
||||
&[],
|
||||
&[ImageMemoryBarrier::default()
|
||||
.src_access_mask(AccessFlags::NONE)
|
||||
.dst_access_mask(AccessFlags::TRANSFER_WRITE)
|
||||
.old_layout(ImageLayout::UNDEFINED)
|
||||
.new_layout(ImageLayout::GENERAL)
|
||||
.src_queue_family_index(queue_family_index)
|
||||
.dst_queue_family_index(queue_family_index)
|
||||
.image(*image)
|
||||
.subresource_range(ImageSubresourceRange::default()
|
||||
.aspect_mask(ImageAspectFlags::COLOR)
|
||||
.level_count(1)
|
||||
.layer_count(1)
|
||||
)
|
||||
],
|
||||
);
|
||||
device.cmd_copy_buffer_to_image(
|
||||
command_buffer,
|
||||
buffer,
|
||||
*image,
|
||||
ImageLayout::GENERAL,
|
||||
&[BufferImageCopy::default()
|
||||
.image_subresource(ImageSubresourceLayers::default()
|
||||
.aspect_mask(ImageAspectFlags::COLOR)
|
||||
.layer_count(1)
|
||||
)
|
||||
.image_extent(extent.into())
|
||||
]
|
||||
);
|
||||
// https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#resources-external-sharing
|
||||
device.cmd_pipeline_barrier(
|
||||
command_buffer,
|
||||
PipelineStageFlags::TRANSFER,
|
||||
PipelineStageFlags::BOTTOM_OF_PIPE,
|
||||
DependencyFlags::empty(),
|
||||
&[],
|
||||
&[],
|
||||
&[ImageMemoryBarrier::default()
|
||||
.src_access_mask(AccessFlags::TRANSFER_WRITE)
|
||||
.dst_access_mask(AccessFlags::NONE)
|
||||
.old_layout(ImageLayout::GENERAL)
|
||||
.new_layout(ImageLayout::GENERAL)
|
||||
.src_queue_family_index(queue_family_index)
|
||||
.dst_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT)
|
||||
.image(*image)
|
||||
.subresource_range(ImageSubresourceRange::default()
|
||||
.aspect_mask(ImageAspectFlags::COLOR)
|
||||
.level_count(1)
|
||||
.layer_count(1)
|
||||
)
|
||||
],
|
||||
);
|
||||
device.end_command_buffer(command_buffer)
|
||||
.context("Failed to end command buffer")?;
|
||||
device.queue_submit(
|
||||
queue,
|
||||
&[SubmitInfo::default().command_buffers(&[command_buffer])],
|
||||
Fence::null(),
|
||||
).context("Failed to submit queue")?;
|
||||
device.queue_wait_idle(queue).context("Failed to wait queue idle")?;
|
||||
let mut drm_format_modifier = DRM_FORMAT_MOD_LINEAR;
|
||||
let mut memory_plane_count = 1;
|
||||
let mut aspect_masks = [ImageAspectFlags::COLOR; 4];
|
||||
if let Some(modifier_device) = image_drm_format_modifier_device {
|
||||
let mut props = ImageDrmFormatModifierPropertiesEXT::default();
|
||||
modifier_device
|
||||
.get_image_drm_format_modifier_properties(*image, &mut props)
|
||||
.context("Failed to get image drm format modifier properties")?;
|
||||
drm_format_modifier = props.drm_format_modifier;
|
||||
debug!("Image created with DRM format modifier {}",
|
||||
fmt_modifier(drm_format_modifier));
|
||||
let format_prop = drm_format_props.as_ref().unwrap().iter().find(|f|
|
||||
f.drm_format_modifier == drm_format_modifier
|
||||
).context("Failed to find DRM format modifier properties")?;
|
||||
memory_plane_count = format_prop
|
||||
.drm_format_modifier_plane_count as usize;
|
||||
aspect_masks = [
|
||||
ImageAspectFlags::MEMORY_PLANE_0_EXT,
|
||||
ImageAspectFlags::MEMORY_PLANE_1_EXT,
|
||||
ImageAspectFlags::MEMORY_PLANE_2_EXT,
|
||||
ImageAspectFlags::MEMORY_PLANE_3_EXT,
|
||||
];
|
||||
}
|
||||
let mut memory_planes = [MemoryPlane::default(); 4];
|
||||
for memory_plan_index in 0..memory_plane_count {
|
||||
let subresource_layout = device.get_image_subresource_layout(
|
||||
*image,
|
||||
ImageSubresource::default()
|
||||
.aspect_mask(aspect_masks[memory_plan_index])
|
||||
.mip_level(0)
|
||||
.array_layer(0)
|
||||
);
|
||||
memory_planes[memory_plan_index] = MemoryPlane {
|
||||
offset: subresource_layout.offset,
|
||||
stride: subresource_layout.row_pitch,
|
||||
};
|
||||
}
|
||||
let raw_fd = external_memory_fd_device.get_memory_fd(
|
||||
&MemoryGetFdInfoKHR::default()
|
||||
.memory(*image_memory)
|
||||
.handle_type(ExternalMemoryHandleTypeFlags::DMA_BUF_EXT)
|
||||
).context("Failed to get memory fd")?;
|
||||
if raw_fd < 0 {
|
||||
bail!("Got invalid memory fd {raw_fd}")
|
||||
}
|
||||
let fd = OwnedFd::from_raw_fd(raw_fd);
|
||||
Ok(GpuWallpaper {
|
||||
drm_format_modifier,
|
||||
memory_planes_len: memory_plane_count,
|
||||
memory_planes,
|
||||
gpu_memory: GpuMemory {
|
||||
memory: ScopeGuard::into_inner(image_memory),
|
||||
size: uploader.len,
|
||||
image: ScopeGuard::into_inner(image),
|
||||
gpu_device: Rc::clone(&uploader.gpu_device),
|
||||
drm_format_modifier,
|
||||
},
|
||||
fd,
|
||||
})
|
||||
}
|
||||
|
||||
fn find_memorytype_index(
|
||||
memory_req: &MemoryRequirements,
|
||||
memory_prop: &PhysicalDeviceMemoryProperties,
|
||||
flags: MemoryPropertyFlags,
|
||||
) -> Option<u32> {
|
||||
memory_prop.memory_types[..memory_prop.memory_type_count as _]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(index, memory_type)| {
|
||||
(1 << index) & memory_req.memory_type_bits != 0
|
||||
&& memory_type.property_flags & flags == flags
|
||||
})
|
||||
.map(|(index, _memory_type)| index as _)
|
||||
}
|
11
src/image.rs
11
src/image.rs
|
@ -73,7 +73,7 @@ pub fn output_wallpaper_files(
|
|||
|
||||
pub fn load_wallpaper(
|
||||
path: &Path,
|
||||
dst: &mut [u8],
|
||||
buffer: &mut [u8],
|
||||
surface_width: u32,
|
||||
surface_height: u32,
|
||||
surface_stride: usize,
|
||||
|
@ -81,6 +81,11 @@ pub fn load_wallpaper(
|
|||
color_transform: ColorTransform,
|
||||
resizer: &mut Resizer,
|
||||
) -> anyhow::Result<()> {
|
||||
let surface_size = surface_stride * surface_height as usize;
|
||||
let Some(dst) = buffer.get_mut(..surface_size) else {
|
||||
bail!("Provided buffer size {} smaller than wallpaper image size {}",
|
||||
buffer.len(), surface_size);
|
||||
};
|
||||
let reader = ImageReader::open(path)
|
||||
.context("Failed to open image file")?
|
||||
.with_guessed_format()
|
||||
|
@ -101,7 +106,6 @@ pub fn load_wallpaper(
|
|||
if image_width == 0 || image_height == 0 || image_size > isize::MAX as u64 {
|
||||
bail!("Image has invalid dimensions {image_width}x{image_height}")
|
||||
};
|
||||
let image_size = image_size as usize;
|
||||
debug!("Image {image_width}x{image_height} {image_color_type:?}");
|
||||
if image_color_type.has_alpha() {
|
||||
warn!("Image has alpha channel which will be ignored");
|
||||
|
@ -120,8 +124,7 @@ pub fn load_wallpaper(
|
|||
&& surface_row_len == surface_stride
|
||||
{
|
||||
debug!("Decoding image directly to destination buffer");
|
||||
decoder.read_image(&mut dst[..image_size])
|
||||
.context("Failed to decode image")?;
|
||||
decoder.read_image(dst).context("Failed to decode image")?;
|
||||
return Ok(());
|
||||
}
|
||||
let mut image = DynamicImage::from_decoder(decoder)
|
||||
|
|
71
src/main.rs
71
src/main.rs
|
@ -1,5 +1,6 @@
|
|||
mod compositors;
|
||||
mod cli;
|
||||
mod gpu;
|
||||
mod image;
|
||||
mod poll;
|
||||
mod signal;
|
||||
|
@ -23,6 +24,7 @@ use rustix::{
|
|||
};
|
||||
use smithay_client_toolkit::{
|
||||
compositor::CompositorState,
|
||||
dmabuf::DmabufState,
|
||||
output::OutputState,
|
||||
registry::RegistryState,
|
||||
shell::wlr_layer::LayerShell,
|
||||
|
@ -41,6 +43,7 @@ use crate::{
|
|||
cli::{Cli, PixelFormat},
|
||||
compositors::{Compositor, ConnectionTask, WorkspaceVisible},
|
||||
image::ColorTransform,
|
||||
gpu::Gpu,
|
||||
poll::{Poll, Waker},
|
||||
signal::SignalPipe,
|
||||
wayland::BackgroundLayer,
|
||||
|
@ -54,37 +57,14 @@ pub struct State {
|
|||
pub layer_shell: LayerShell,
|
||||
pub viewporter: WpViewporter,
|
||||
pub wallpaper_dir: PathBuf,
|
||||
pub force_xrgb8888: bool,
|
||||
pub pixel_format: Option<wl_shm::Format>,
|
||||
pub shm_format: wl_shm::Format,
|
||||
pub background_layers: Vec<BackgroundLayer>,
|
||||
pub compositor_connection_task: ConnectionTask,
|
||||
pub color_transform: ColorTransform,
|
||||
pub dmabuf_state: DmabufState,
|
||||
pub gpu: Option<Gpu>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn pixel_format(&mut self) -> wl_shm::Format
|
||||
{
|
||||
*self.pixel_format.get_or_insert_with(|| {
|
||||
|
||||
if !self.force_xrgb8888 {
|
||||
// Consume less gpu memory by using Bgr888 if available,
|
||||
// fall back to the always supported Xrgb8888 otherwise
|
||||
for format in self.shm.formats() {
|
||||
if let wl_shm::Format::Bgr888 = format {
|
||||
debug!("Using pixel format: {:?}", format);
|
||||
return *format
|
||||
}
|
||||
// XXX: One may add Rgb888 and HDR support here
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Using default pixel format: Xrgb8888");
|
||||
wl_shm::Format::Xrgb8888
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn main() -> Result<(), ()> {
|
||||
run().map_err(|e| { error!("{e:#}"); })
|
||||
}
|
||||
|
@ -122,12 +102,45 @@ fn run() -> anyhow::Result<()> {
|
|||
let compositor_state = CompositorState::bind(&globals, &qh).unwrap();
|
||||
let layer_shell = LayerShell::bind(&globals, &qh).unwrap();
|
||||
let shm = Shm::bind(&globals, &qh).unwrap();
|
||||
let mut shm_format = wl_shm::Format::Xrgb8888;
|
||||
if cli.pixelformat != Some(PixelFormat::Baseline) {
|
||||
// Consume less gpu memory by using Bgr888 if available,
|
||||
// fall back to the always supported Xrgb8888 otherwise
|
||||
if shm.formats().contains(&wl_shm::Format::Bgr888) {
|
||||
shm_format = wl_shm::Format::Bgr888;
|
||||
}
|
||||
}
|
||||
debug!("Using shm format: {shm_format:?}");
|
||||
|
||||
let registry_state = RegistryState::new(&globals);
|
||||
|
||||
let viewporter: WpViewporter = registry_state
|
||||
.bind_one(&qh, 1..=1, ()).expect("wp_viewporter not available");
|
||||
|
||||
let dmabuf_state = DmabufState::new(&globals, &qh);
|
||||
let mut gpu = None;
|
||||
if cli.gpu {
|
||||
if let Some(version) = dmabuf_state.version() {
|
||||
if version >= 4 {
|
||||
debug!("Using Linux DMA-BUF version {version}");
|
||||
} else {
|
||||
warn!("Only legacy Linux DMA-BUF version {version} is \
|
||||
available from the compositor where it gives no \
|
||||
information about which GPU it uses.");
|
||||
// TODO handle this better by providing cli options
|
||||
// to choose DRM device by major:minor or /dev path
|
||||
}
|
||||
match Gpu::new() {
|
||||
Ok(val) => gpu = Some(val),
|
||||
Err(e) =>
|
||||
error!("Failed to set up GPU, disabling GPU use: {e:#}"),
|
||||
}
|
||||
} else {
|
||||
error!("Wayland protocol Linux DMA-BUF is unavailable \
|
||||
from the compositor, disabling GPU use");
|
||||
}
|
||||
}
|
||||
|
||||
// Sync tools for sway ipc tasks
|
||||
let (tx, rx) = channel();
|
||||
let waker = Arc::new(Waker::new().unwrap());
|
||||
|
@ -144,15 +157,15 @@ fn run() -> anyhow::Result<()> {
|
|||
layer_shell,
|
||||
viewporter,
|
||||
wallpaper_dir,
|
||||
force_xrgb8888: cli.pixelformat
|
||||
.is_some_and(|p| p == PixelFormat::Baseline),
|
||||
pixel_format: None,
|
||||
shm_format,
|
||||
background_layers: Vec::new(),
|
||||
compositor_connection_task: ConnectionTask::new(
|
||||
compositor,
|
||||
tx.clone(), Arc::clone(&waker)
|
||||
),
|
||||
color_transform,
|
||||
dmabuf_state,
|
||||
gpu,
|
||||
};
|
||||
|
||||
event_queue.roundtrip(&mut state).unwrap();
|
||||
|
|
756
src/wayland.rs
756
src/wayland.rs
|
@ -1,14 +1,18 @@
|
|||
use std::{
|
||||
cell::Cell,
|
||||
cell::RefCell,
|
||||
os::fd::AsFd,
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
rc::{Rc, Weak},
|
||||
};
|
||||
|
||||
use anyhow::{bail, Context};
|
||||
use log::{debug, error, warn};
|
||||
use rustix::fs::{Dev, major, minor};
|
||||
use smithay_client_toolkit::{
|
||||
delegate_compositor, delegate_layer, delegate_output, delegate_registry,
|
||||
delegate_shm,
|
||||
delegate_compositor, delegate_dmabuf, delegate_layer, delegate_output,
|
||||
delegate_registry, delegate_shm,
|
||||
compositor::{CompositorHandler, Region},
|
||||
dmabuf::{DmabufFeedback, DmabufHandler, DmabufState},
|
||||
output::{OutputHandler, OutputState},
|
||||
registry::{ProvidesRegistryState, RegistryState},
|
||||
registry_handlers,
|
||||
|
@ -33,13 +37,23 @@ use smithay_client_toolkit::reexports::client::{
|
|||
wl_surface::WlSurface,
|
||||
},
|
||||
};
|
||||
use smithay_client_toolkit::reexports::protocols::wp::viewporter::client::{
|
||||
use smithay_client_toolkit::reexports::protocols::wp::{
|
||||
linux_dmabuf::zv1::client::{
|
||||
zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
|
||||
zwp_linux_buffer_params_v1::{self, ZwpLinuxBufferParamsV1},
|
||||
},
|
||||
viewporter::client::{
|
||||
wp_viewport::WpViewport,
|
||||
wp_viewporter::WpViewporter
|
||||
}
|
||||
};
|
||||
|
||||
use crate::{
|
||||
State,
|
||||
gpu::{
|
||||
DRM_FORMAT_XRGB8888, fmt_modifier,
|
||||
GpuMemory, GpuUploader, GpuWallpaper,
|
||||
},
|
||||
image::{load_wallpaper, output_wallpaper_files, WallpaperFile},
|
||||
};
|
||||
|
||||
|
@ -91,6 +105,119 @@ impl CompositorHandler for State
|
|||
}
|
||||
}
|
||||
|
||||
impl DmabufHandler for State {
|
||||
fn dmabuf_state(&mut self) -> &mut DmabufState {
|
||||
&mut self.dmabuf_state
|
||||
}
|
||||
|
||||
fn dmabuf_feedback(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
qh: &QueueHandle<Self>,
|
||||
proxy: &ZwpLinuxDmabufFeedbackV1,
|
||||
feedback: DmabufFeedback,
|
||||
) {
|
||||
let Some(bg_layer_pos) = self.background_layers.iter()
|
||||
.position(|bg_layer|
|
||||
bg_layer.dmabuf_feedback.as_ref() == Some(proxy)
|
||||
)
|
||||
else {
|
||||
error!("Received unexpected Linux DMA-BUF feedback");
|
||||
return
|
||||
};
|
||||
if let Err(e) = handle_dmabuf_feedback(
|
||||
self,
|
||||
qh,
|
||||
feedback,
|
||||
bg_layer_pos
|
||||
) {
|
||||
error!("Failed to proceed with DMA-BUF feedback, \
|
||||
falling back to shm: {e:#}");
|
||||
fallback_shm_load_wallpapers(self, qh, bg_layer_pos);
|
||||
}
|
||||
}
|
||||
|
||||
fn created(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
params: &ZwpLinuxBufferParamsV1,
|
||||
buffer: WlBuffer,
|
||||
) {
|
||||
for bg_layer in self.background_layers.iter_mut() {
|
||||
for workspace_bg in bg_layer.workspace_backgrounds.iter_mut() {
|
||||
let wallpaper = &workspace_bg.wallpaper;
|
||||
let mut wallpaper_borrow = wallpaper.borrow_mut();
|
||||
if wallpaper_borrow.memory.dmabuf_params_destroy_eq(params) {
|
||||
wallpaper_borrow.wl_buffer = Some(buffer);
|
||||
debug!("Created Linux DMA-BUF buffer for wallpaper \
|
||||
file {:?}", wallpaper_borrow.canon_path);
|
||||
drop(wallpaper_borrow);
|
||||
if let Some(queued_weak) = &bg_layer.queued_wallpaper {
|
||||
if let Some(queued) = queued_weak.upgrade() {
|
||||
if Rc::ptr_eq(&queued, wallpaper) {
|
||||
let name = workspace_bg.workspace_name.clone();
|
||||
bg_layer.draw_workspace_bg(&name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
error!("Received unexpected created Linux DMA-BUF buffer");
|
||||
}
|
||||
|
||||
fn failed(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
qh: &QueueHandle<Self>,
|
||||
params: &ZwpLinuxBufferParamsV1,
|
||||
) {
|
||||
error!("Failed to create a Linux DMA-BUF buffer");
|
||||
let mut failed_bg_layer_indecies = Vec::new();
|
||||
for (i, bg_layer) in self.background_layers.iter_mut().enumerate() {
|
||||
for workspace_bg in bg_layer.workspace_backgrounds.iter_mut() {
|
||||
let mut wallpaper = workspace_bg.wallpaper.borrow_mut();
|
||||
if wallpaper.memory.dmabuf_params_destroy_eq(params) {
|
||||
error!("Falling back to shm and reloading wallpapers \
|
||||
for output {}", bg_layer.output_name);
|
||||
failed_bg_layer_indecies.push(i);
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for index in failed_bg_layer_indecies {
|
||||
fallback_shm_load_wallpapers(self, qh, index);
|
||||
}
|
||||
}
|
||||
|
||||
fn released(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
buffer: &WlBuffer
|
||||
) {
|
||||
for bg in self.background_layers.iter_mut()
|
||||
.flat_map(|bg_layer| &mut bg_layer.workspace_backgrounds)
|
||||
{
|
||||
let mut wallpaper = bg.wallpaper.borrow_mut();
|
||||
if wallpaper.wl_buffer.as_ref() == Some(buffer) {
|
||||
if let Some(new_count) = wallpaper.active_count.checked_sub(1) {
|
||||
debug!("Compositor released the DMA-BUF wl_buffer of {:?}",
|
||||
wallpaper.canon_path);
|
||||
wallpaper.active_count = new_count;
|
||||
} else {
|
||||
error!("Unexpected release event for the DMA-BUF \
|
||||
wl_buffer of {:?}", wallpaper.canon_path);
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
warn!("Release event for already destroyed DMA-BUF wl_buffer");
|
||||
}
|
||||
}
|
||||
|
||||
impl LayerShellHandler for State
|
||||
{
|
||||
fn closed(
|
||||
|
@ -272,131 +399,63 @@ logical size: {}x{}, transform: {:?}",
|
|||
|
||||
layer.commit();
|
||||
|
||||
let pixel_format = self.pixel_format();
|
||||
let output_dir = self.wallpaper_dir.join(&output_name);
|
||||
debug!("Looking for wallpapers for new output {} in {:?}",
|
||||
output_name, output_dir);
|
||||
let wallpaper_files = match output_wallpaper_files(&output_dir) {
|
||||
Ok(wallpaper_files) => wallpaper_files,
|
||||
Err(e) => {
|
||||
error!("Failed to get wallpapers for new output {output_name} \
|
||||
form {output_dir:?}: {e:#}");
|
||||
return
|
||||
}
|
||||
};
|
||||
let mut workspace_backgrounds = Vec::new();
|
||||
let mut resizer = fast_image_resize::Resizer::new();
|
||||
let mut reused_count = 0usize;
|
||||
let mut loaded_count = 0usize;
|
||||
let mut error_count = 0usize;
|
||||
for wallpaper_file in wallpaper_files {
|
||||
if log::log_enabled!(log::Level::Debug) {
|
||||
if wallpaper_file.path == wallpaper_file.canon_path {
|
||||
debug!("Wallpaper file {:?} for workspace {}",
|
||||
wallpaper_file.path, wallpaper_file.workspace);
|
||||
} else {
|
||||
debug!("Wallpaper file {:?} -> {:?} for workspace {}",
|
||||
wallpaper_file.path, wallpaper_file.canon_path,
|
||||
wallpaper_file.workspace);
|
||||
}
|
||||
}
|
||||
if let Some(wallpaper) = find_equal_output_wallpaper(
|
||||
&workspace_backgrounds,
|
||||
&wallpaper_file
|
||||
) {
|
||||
workspace_backgrounds.push(WorkspaceBackground {
|
||||
workspace_name: wallpaper_file.workspace,
|
||||
wallpaper
|
||||
});
|
||||
reused_count += 1;
|
||||
continue
|
||||
}
|
||||
if let Some(wallpaper) = find_equal_wallpaper(
|
||||
&self.background_layers,
|
||||
width,
|
||||
height,
|
||||
info.transform,
|
||||
&wallpaper_file
|
||||
) {
|
||||
workspace_backgrounds.push(WorkspaceBackground {
|
||||
workspace_name: wallpaper_file.workspace,
|
||||
wallpaper
|
||||
});
|
||||
reused_count += 1;
|
||||
continue
|
||||
}
|
||||
let stride = match pixel_format {
|
||||
wl_shm::Format::Xrgb8888 => width as usize * 4,
|
||||
wl_shm::Format::Bgr888 => {
|
||||
// Align buffer stride to both 4 and pixel format
|
||||
// block size. Not being aligned to 4 caused
|
||||
// https://github.com/gergo-salyi/multibg-sway/issues/6
|
||||
(width as usize * 3).next_multiple_of(4)
|
||||
let mut dmabuf_feedback = None;
|
||||
let mut gpu_uploader = None;
|
||||
if let Some(gpu) = self.gpu.as_mut() {
|
||||
if self.dmabuf_state.version().unwrap() >= 4 {
|
||||
match self.dmabuf_state.get_surface_feedback(surface, qh) {
|
||||
Ok(feedback) => {
|
||||
debug!("Requesting Linux DMA-BUF surface feedback \
|
||||
for output {}", output_name);
|
||||
dmabuf_feedback = Some(feedback);
|
||||
},
|
||||
_ => unreachable!()
|
||||
};
|
||||
let shm_size = stride * height as usize;
|
||||
let mut shm_pool = match RawPool::new(shm_size, &self.shm) {
|
||||
Ok(shm_pool) => shm_pool,
|
||||
Err(e) => {
|
||||
error!("Failed to create shm pool: {e}");
|
||||
error_count += 1;
|
||||
continue
|
||||
error!("Failed to request Linux DMA-BUF surface \
|
||||
feedback for the surface on output {}: {}",
|
||||
output_name, e);
|
||||
},
|
||||
}
|
||||
};
|
||||
if let Err(e) = load_wallpaper(
|
||||
&wallpaper_file.path,
|
||||
&mut shm_pool.mmap()[..shm_size],
|
||||
} else {
|
||||
let drm_format_modifiers = self.dmabuf_state.modifiers().iter()
|
||||
.filter(|dmabuf_format|
|
||||
dmabuf_format.format == DRM_FORMAT_XRGB8888
|
||||
)
|
||||
.map(|dmabuf_format| dmabuf_format.modifier)
|
||||
.collect::<Vec<_>>();
|
||||
match gpu.uploader(
|
||||
None,
|
||||
width as u32,
|
||||
height as u32,
|
||||
stride,
|
||||
pixel_format,
|
||||
self.color_transform,
|
||||
&mut resizer
|
||||
drm_format_modifiers,
|
||||
) {
|
||||
error!("Failed to load wallpaper: {e:#}");
|
||||
error_count += 1;
|
||||
continue
|
||||
Ok(uploader) => gpu_uploader = Some(uploader),
|
||||
Err(e) => error!("Failed to obtain GPU uploader: {e:#}"),
|
||||
};
|
||||
}
|
||||
let wl_buffer = shm_pool.create_buffer(
|
||||
0,
|
||||
width,
|
||||
height,
|
||||
stride.try_into().unwrap(),
|
||||
pixel_format,
|
||||
(),
|
||||
qh
|
||||
);
|
||||
workspace_backgrounds.push(WorkspaceBackground {
|
||||
workspace_name: wallpaper_file.workspace,
|
||||
wallpaper: Rc::new(Wallpaper {
|
||||
wl_buffer,
|
||||
active_count: Cell::new(0),
|
||||
shm_pool,
|
||||
canon_path: wallpaper_file.canon_path,
|
||||
canon_modified: wallpaper_file.canon_modified,
|
||||
})
|
||||
});
|
||||
loaded_count += 1;
|
||||
}
|
||||
debug!("Wallpapers for new output: {} reused, {} loaded, {} errors",
|
||||
reused_count, loaded_count, error_count);
|
||||
debug!("Wallpapers are available for workspaces: {}",
|
||||
workspace_backgrounds.iter()
|
||||
.map(|bg| bg.workspace_name.as_str())
|
||||
.collect::<Vec<_>>().join(", "));
|
||||
let is_dmabuf_feedback = dmabuf_feedback.is_some();
|
||||
let bg_layer_index = self.background_layers.len();
|
||||
self.background_layers.push(BackgroundLayer {
|
||||
output_name,
|
||||
width,
|
||||
height,
|
||||
layer,
|
||||
configured: false,
|
||||
workspace_backgrounds,
|
||||
current_workspace: None,
|
||||
workspace_backgrounds: Vec::new(),
|
||||
current_wallpaper: None,
|
||||
queued_wallpaper: None,
|
||||
transform: info.transform,
|
||||
viewport,
|
||||
dmabuf_feedback,
|
||||
});
|
||||
print_memory_stats(&self.background_layers);
|
||||
if !is_dmabuf_feedback {
|
||||
load_wallpapers(
|
||||
self,
|
||||
qh,
|
||||
bg_layer_index,
|
||||
gpu_uploader,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_output(
|
||||
|
@ -601,6 +660,7 @@ impl ShmHandler for State {
|
|||
}
|
||||
|
||||
delegate_compositor!(State);
|
||||
delegate_dmabuf!(State);
|
||||
delegate_layer!(State);
|
||||
delegate_output!(State);
|
||||
delegate_registry!(State);
|
||||
|
@ -641,22 +701,22 @@ impl Dispatch<WlBuffer, ()> for State {
|
|||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<Self>,
|
||||
) {
|
||||
for bg_layer in state.background_layers.iter_mut() {
|
||||
for bg in bg_layer.workspace_backgrounds.iter_mut() {
|
||||
if bg.wallpaper.wl_buffer == *proxy {
|
||||
let active_count = bg.wallpaper.active_count.get();
|
||||
if let Some(new_count) = active_count.checked_sub(1) {
|
||||
debug!("Compositor released the wl_shm wl_buffer \
|
||||
of {:?}", bg.wallpaper.canon_path);
|
||||
bg.wallpaper.active_count.set(new_count);
|
||||
for bg in state.background_layers.iter_mut()
|
||||
.flat_map(|bg_layer| &mut bg_layer.workspace_backgrounds)
|
||||
{
|
||||
let mut wallpaper = bg.wallpaper.borrow_mut();
|
||||
if wallpaper.wl_buffer.as_ref() == Some(proxy) {
|
||||
if let Some(new_count) = wallpaper.active_count.checked_sub(1) {
|
||||
debug!("Compositor released the wl_shm wl_buffer of {:?}",
|
||||
wallpaper.canon_path);
|
||||
wallpaper.active_count = new_count;
|
||||
} else {
|
||||
error!("Unexpected release event for the wl_shm \
|
||||
wl_buffer of {:?}", bg.wallpaper.canon_path);
|
||||
wl_buffer of {:?}", wallpaper.canon_path);
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
warn!("Release event for already destroyed wl_shm wl_buffer");
|
||||
}
|
||||
}
|
||||
|
@ -668,10 +728,13 @@ pub struct BackgroundLayer {
|
|||
pub layer: LayerSurface,
|
||||
pub configured: bool,
|
||||
pub workspace_backgrounds: Vec<WorkspaceBackground>,
|
||||
pub current_workspace: Option<String>,
|
||||
pub current_wallpaper: Option<Rc<RefCell<Wallpaper>>>,
|
||||
pub queued_wallpaper: Option<Weak<RefCell<Wallpaper>>>,
|
||||
pub transform: Transform,
|
||||
pub viewport: Option<WpViewport>,
|
||||
pub dmabuf_feedback: Option<ZwpLinuxDmabufFeedbackV1>,
|
||||
}
|
||||
|
||||
impl BackgroundLayer
|
||||
{
|
||||
pub fn draw_workspace_bg(&mut self, workspace_name: &str)
|
||||
|
@ -684,12 +747,6 @@ impl BackgroundLayer
|
|||
return;
|
||||
}
|
||||
|
||||
if self.current_workspace.as_deref() == Some(workspace_name) {
|
||||
debug!("Skipping draw on output {} for workspace {} because its \
|
||||
wallpaper is already set", self.output_name, workspace_name);
|
||||
return
|
||||
}
|
||||
|
||||
let Some(workspace_bg) = self.workspace_backgrounds.iter()
|
||||
.find(|workspace_bg| workspace_bg.workspace_name == workspace_name)
|
||||
.or_else(|| self.workspace_backgrounds.iter()
|
||||
|
@ -706,19 +763,36 @@ impl BackgroundLayer
|
|||
);
|
||||
return;
|
||||
};
|
||||
let wallpaper = &workspace_bg.wallpaper;
|
||||
|
||||
if let Some(current) = &self.current_wallpaper {
|
||||
if Rc::ptr_eq(current, wallpaper) {
|
||||
debug!("Skipping draw on output {} for workspace {} \
|
||||
because its wallpaper is already set",
|
||||
self.output_name, workspace_name);
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let mut wallpaper_borrow = wallpaper.borrow_mut();
|
||||
let Some(wl_buffer) = wallpaper_borrow.wl_buffer.as_ref() else {
|
||||
debug!("Wallpaper for output {} workspace {} is not ready yet",
|
||||
self.output_name, workspace_name);
|
||||
self.queued_wallpaper = Some(Rc::downgrade(wallpaper));
|
||||
return
|
||||
};
|
||||
|
||||
// Attach and commit to new workspace background
|
||||
self.layer.attach(Some(&workspace_bg.wallpaper.wl_buffer), 0, 0);
|
||||
workspace_bg.wallpaper.active_count.set(
|
||||
workspace_bg.wallpaper.active_count.get() + 1
|
||||
);
|
||||
self.layer.attach(Some(wl_buffer), 0, 0);
|
||||
wallpaper_borrow.active_count += 1;
|
||||
|
||||
// Damage the entire surface
|
||||
self.layer.wl_surface().damage_buffer(0, 0, self.width, self.height);
|
||||
|
||||
self.layer.commit();
|
||||
|
||||
self.current_workspace = Some(workspace_name.to_string());
|
||||
self.current_wallpaper = Some(Rc::clone(wallpaper));
|
||||
self.queued_wallpaper = None;
|
||||
|
||||
debug!(
|
||||
"Setting wallpaper on output '{}' for workspace: {}",
|
||||
|
@ -729,24 +803,65 @@ impl BackgroundLayer
|
|||
|
||||
pub struct WorkspaceBackground {
|
||||
pub workspace_name: String,
|
||||
pub wallpaper: Rc<Wallpaper>,
|
||||
pub wallpaper: Rc<RefCell<Wallpaper>>,
|
||||
}
|
||||
|
||||
pub struct Wallpaper {
|
||||
pub wl_buffer: WlBuffer,
|
||||
pub active_count: Cell<usize>,
|
||||
pub shm_pool: RawPool,
|
||||
pub wl_buffer: Option<WlBuffer>,
|
||||
pub active_count: usize,
|
||||
pub memory: Memory,
|
||||
pub canon_path: PathBuf,
|
||||
pub canon_modified: u128,
|
||||
}
|
||||
|
||||
impl Drop for Wallpaper {
|
||||
fn drop(&mut self) {
|
||||
if self.active_count.get() != 0 {
|
||||
warn!("Destroying a {} times active wl_buffer of wallpaper {:?}",
|
||||
self.active_count.get(), self.canon_path);
|
||||
if let Some(wl_buffer) = &self.wl_buffer {
|
||||
if self.active_count != 0 {
|
||||
warn!("Destroying a {} times active wl_buffer of \
|
||||
wallpaper {:?}", self.active_count, self.canon_path);
|
||||
}
|
||||
self.wl_buffer.destroy();
|
||||
wl_buffer.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Memory {
|
||||
WlShm { pool: RawPool },
|
||||
Dmabuf { gpu_memory: GpuMemory, params: Option<ZwpLinuxBufferParamsV1> },
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
fn gpu_uploader_eq(&self, gpu_uploader: Option<&GpuUploader>) -> bool {
|
||||
if let Some(gpu_uploader) = gpu_uploader {
|
||||
match self {
|
||||
Memory::WlShm { .. } => false,
|
||||
Memory::Dmabuf { gpu_memory, .. } => {
|
||||
gpu_memory.gpu_uploader_eq(gpu_uploader)
|
||||
},
|
||||
}
|
||||
} else {
|
||||
match self {
|
||||
Memory::WlShm { .. } => true,
|
||||
Memory::Dmabuf { .. } => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dmabuf_params_destroy_eq(
|
||||
&mut self,
|
||||
other_params: &ZwpLinuxBufferParamsV1
|
||||
) -> bool {
|
||||
if let Memory::Dmabuf { params: params_option, .. } = self {
|
||||
if let Some(params) = params_option {
|
||||
if params == other_params {
|
||||
params.destroy();
|
||||
*params_option = None;
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -759,16 +874,19 @@ fn find_equal_wallpaper(
|
|||
width: i32,
|
||||
height: i32,
|
||||
transform: Transform,
|
||||
wallpaper_file: &WallpaperFile
|
||||
) -> Option<Rc<Wallpaper>> {
|
||||
wallpaper_file: &WallpaperFile,
|
||||
gpu_uploader: Option<&GpuUploader>,
|
||||
) -> Option<Rc<RefCell<Wallpaper>>> {
|
||||
for bg_layer in background_layers {
|
||||
if bg_layer.width == width
|
||||
&& bg_layer.height == height
|
||||
&& bg_layer.transform == transform
|
||||
{
|
||||
for bg in &bg_layer.workspace_backgrounds {
|
||||
if bg.wallpaper.canon_modified == wallpaper_file.canon_modified
|
||||
&& bg.wallpaper.canon_path == wallpaper_file.canon_path
|
||||
let wallpaper = bg.wallpaper.borrow();
|
||||
if wallpaper.canon_modified == wallpaper_file.canon_modified
|
||||
&& wallpaper.canon_path == wallpaper_file.canon_path
|
||||
&& wallpaper.memory.gpu_uploader_eq(gpu_uploader)
|
||||
{
|
||||
debug!("Reusing the wallpaper of output {} workspace {}",
|
||||
bg_layer.output_name, bg.workspace_name);
|
||||
|
@ -782,11 +900,14 @@ fn find_equal_wallpaper(
|
|||
|
||||
fn find_equal_output_wallpaper(
|
||||
workspace_backgrounds: &[WorkspaceBackground],
|
||||
wallpaper_file: &WallpaperFile
|
||||
) -> Option<Rc<Wallpaper>> {
|
||||
wallpaper_file: &WallpaperFile,
|
||||
gpu_uploader: Option<&GpuUploader>,
|
||||
) -> Option<Rc<RefCell<Wallpaper>>> {
|
||||
for bg in workspace_backgrounds {
|
||||
if bg.wallpaper.canon_modified == wallpaper_file.canon_modified
|
||||
&& bg.wallpaper.canon_path == wallpaper_file.canon_path
|
||||
let wallpaper = bg.wallpaper.borrow();
|
||||
if wallpaper.canon_modified == wallpaper_file.canon_modified
|
||||
&& wallpaper.canon_path == wallpaper_file.canon_path
|
||||
&& wallpaper.memory.gpu_uploader_eq(gpu_uploader)
|
||||
{
|
||||
debug!("Reusing the wallpaper of workspace {}",
|
||||
bg.workspace_name);
|
||||
|
@ -800,15 +921,340 @@ fn print_memory_stats(background_layers: &[BackgroundLayer]) {
|
|||
if log::log_enabled!(log::Level::Debug) {
|
||||
let mut wl_shm_count = 0.0f32;
|
||||
let mut wl_shm_size = 0.0f32;
|
||||
let mut dmabuf_count = 0.0f32;
|
||||
let mut dmabuf_size = 0.0f32;
|
||||
for bg_layer in background_layers {
|
||||
for bg in &bg_layer.workspace_backgrounds {
|
||||
let factor = 1.0 / Rc::strong_count(&bg.wallpaper) as f32;
|
||||
match &bg.wallpaper.borrow().memory {
|
||||
Memory::WlShm { pool } => {
|
||||
wl_shm_count += factor;
|
||||
wl_shm_size += factor * bg.wallpaper.shm_pool.len() as f32;
|
||||
wl_shm_size += factor * pool.len() as f32;
|
||||
},
|
||||
Memory::Dmabuf { gpu_memory, .. } => {
|
||||
dmabuf_count += factor;
|
||||
dmabuf_size += factor * gpu_memory.size() as f32;
|
||||
},
|
||||
}
|
||||
}
|
||||
let count = (wl_shm_count + 0.5) as usize;
|
||||
let size_kb = (wl_shm_size + 0.5) as usize / 1024;
|
||||
debug!("Memory use: {size_kb} KiB from {count} wl_shm pools");
|
||||
}
|
||||
let wl_shm_count = (wl_shm_count + 0.5) as usize;
|
||||
let wl_shm_size_kb = (wl_shm_size + 0.5) as usize / 1024;
|
||||
let dmabuf_count = (dmabuf_count + 0.5) as usize;
|
||||
let dmabuf_size_kb = (dmabuf_size + 0.5) as usize / 1024;
|
||||
debug!("Memory use: {wl_shm_size_kb} KiB from {wl_shm_count} wl_shm \
|
||||
pools, {dmabuf_size_kb} KiB from {dmabuf_count} DMA-BUFs");
|
||||
}
|
||||
}
|
||||
|
||||
fn fallback_shm_load_wallpapers(
|
||||
state: &mut State,
|
||||
qh: &QueueHandle<State>,
|
||||
bg_layer_index: usize,
|
||||
) {
|
||||
let bg_layer = &mut state.background_layers[bg_layer_index];
|
||||
bg_layer.dmabuf_feedback = None;
|
||||
bg_layer.workspace_backgrounds.clear();
|
||||
load_wallpapers(
|
||||
state,
|
||||
qh,
|
||||
bg_layer_index,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
fn load_wallpapers(
|
||||
state: &mut State,
|
||||
qh: &QueueHandle<State>,
|
||||
bg_layer_index: usize,
|
||||
mut gpu_uploader: Option<GpuUploader>,
|
||||
) {
|
||||
let bg_layer = &state.background_layers[bg_layer_index];
|
||||
let output_name = bg_layer.output_name.as_str();
|
||||
let width = bg_layer.width;
|
||||
let height = bg_layer.height;
|
||||
let transform = bg_layer.transform;
|
||||
let output_dir = state.wallpaper_dir.join(output_name);
|
||||
debug!("Looking for wallpapers for new output {} in {:?}",
|
||||
output_name, output_dir);
|
||||
let wallpaper_files = match output_wallpaper_files(&output_dir) {
|
||||
Ok(wallpaper_files) => wallpaper_files,
|
||||
Err(e) => {
|
||||
error!("Failed to get wallpapers for new output {output_name} \
|
||||
form {output_dir:?}: {e:#}");
|
||||
return
|
||||
}
|
||||
};
|
||||
let shm_stride = match state.shm_format {
|
||||
wl_shm::Format::Xrgb8888 => width as usize * 4,
|
||||
wl_shm::Format::Bgr888 => {
|
||||
// Align buffer stride to both 4 and pixel format
|
||||
// block size. Not being aligned to 4 caused
|
||||
// https://github.com/gergo-salyi/multibg-sway/issues/6
|
||||
(width as usize * 3).next_multiple_of(4)
|
||||
},
|
||||
_ => unreachable!()
|
||||
};
|
||||
let shm_size = shm_stride * height as usize;
|
||||
let mut workspace_backgrounds = Vec::new();
|
||||
let mut resizer = fast_image_resize::Resizer::new();
|
||||
let mut reused_count = 0usize;
|
||||
let mut loaded_count = 0usize;
|
||||
let mut error_count = 0usize;
|
||||
for wallpaper_file in wallpaper_files {
|
||||
if log::log_enabled!(log::Level::Debug) {
|
||||
if wallpaper_file.path == wallpaper_file.canon_path {
|
||||
debug!("Wallpaper file {:?} for workspace {}",
|
||||
wallpaper_file.path, wallpaper_file.workspace);
|
||||
} else {
|
||||
debug!("Wallpaper file {:?} -> {:?} for workspace {}",
|
||||
wallpaper_file.path, wallpaper_file.canon_path,
|
||||
wallpaper_file.workspace);
|
||||
}
|
||||
}
|
||||
if let Some(wallpaper) = find_equal_output_wallpaper(
|
||||
&workspace_backgrounds,
|
||||
&wallpaper_file,
|
||||
gpu_uploader.as_ref(),
|
||||
) {
|
||||
workspace_backgrounds.push(WorkspaceBackground {
|
||||
workspace_name: wallpaper_file.workspace,
|
||||
wallpaper,
|
||||
});
|
||||
reused_count += 1;
|
||||
continue
|
||||
}
|
||||
if let Some(wallpaper) = find_equal_wallpaper(
|
||||
&state.background_layers,
|
||||
width,
|
||||
height,
|
||||
transform,
|
||||
&wallpaper_file,
|
||||
gpu_uploader.as_ref(),
|
||||
) {
|
||||
workspace_backgrounds.push(WorkspaceBackground {
|
||||
workspace_name: wallpaper_file.workspace,
|
||||
wallpaper,
|
||||
});
|
||||
reused_count += 1;
|
||||
continue
|
||||
}
|
||||
if let Some(uploader) = gpu_uploader.as_mut() {
|
||||
if let Err(e) = load_wallpaper(
|
||||
&wallpaper_file.path,
|
||||
uploader.staging_buffer(),
|
||||
width as u32,
|
||||
height as u32,
|
||||
width as usize * 4,
|
||||
wl_shm::Format::Xrgb8888,
|
||||
state.color_transform,
|
||||
&mut resizer
|
||||
) {
|
||||
error!("Failed to load wallpaper: {e:#}");
|
||||
error_count += 1;
|
||||
continue
|
||||
}
|
||||
match uploader.upload() {
|
||||
Ok(gpu_wallpaper) => {
|
||||
let wallpaper = wallpaper_dmabuf(
|
||||
&state.dmabuf_state,
|
||||
qh,
|
||||
gpu_wallpaper,
|
||||
width,
|
||||
height,
|
||||
wallpaper_file.canon_path,
|
||||
wallpaper_file.canon_modified,
|
||||
);
|
||||
workspace_backgrounds.push(WorkspaceBackground {
|
||||
workspace_name: wallpaper_file.workspace,
|
||||
wallpaper,
|
||||
});
|
||||
loaded_count += 1;
|
||||
continue
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to upload wallpaper to GPU: {e:#}");
|
||||
gpu_uploader = None;
|
||||
// fall back to shm
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut shm_pool = match RawPool::new(shm_size, &state.shm) {
|
||||
Ok(shm_pool) => shm_pool,
|
||||
Err(e) => {
|
||||
error!("Failed to create shm pool: {e}");
|
||||
error_count += 1;
|
||||
continue
|
||||
}
|
||||
};
|
||||
if let Err(e) = load_wallpaper(
|
||||
&wallpaper_file.path,
|
||||
shm_pool.mmap(),
|
||||
width as u32,
|
||||
height as u32,
|
||||
shm_stride,
|
||||
state.shm_format,
|
||||
state.color_transform,
|
||||
&mut resizer
|
||||
) {
|
||||
error!("Failed to load wallpaper: {e:#}");
|
||||
error_count += 1;
|
||||
continue
|
||||
}
|
||||
let wl_buffer = shm_pool.create_buffer(
|
||||
0,
|
||||
width,
|
||||
height,
|
||||
shm_stride.try_into().unwrap(),
|
||||
state.shm_format,
|
||||
(),
|
||||
qh
|
||||
);
|
||||
workspace_backgrounds.push(WorkspaceBackground {
|
||||
workspace_name: wallpaper_file.workspace,
|
||||
wallpaper: Rc::new(RefCell::new(Wallpaper {
|
||||
wl_buffer: Some(wl_buffer),
|
||||
active_count: 0,
|
||||
memory: Memory::WlShm { pool: shm_pool },
|
||||
canon_path: wallpaper_file.canon_path,
|
||||
canon_modified: wallpaper_file.canon_modified,
|
||||
})),
|
||||
});
|
||||
loaded_count += 1;
|
||||
}
|
||||
debug!("Wallpapers for new output: {} reused, {} loaded, {} errors",
|
||||
reused_count, loaded_count, error_count);
|
||||
debug!("Wallpapers are available for workspaces: {}",
|
||||
workspace_backgrounds.iter()
|
||||
.map(|bg| bg.workspace_name.as_str())
|
||||
.collect::<Vec<_>>().join(", "));
|
||||
state.background_layers[bg_layer_index].workspace_backgrounds =
|
||||
workspace_backgrounds;
|
||||
print_memory_stats(&state.background_layers);
|
||||
}
|
||||
|
||||
fn handle_dmabuf_feedback(
|
||||
state: &mut State,
|
||||
qh: &QueueHandle<State>,
|
||||
feedback: DmabufFeedback,
|
||||
bg_layer_pos: usize,
|
||||
) -> anyhow::Result<()> {
|
||||
let bg_layer = &mut state.background_layers[bg_layer_pos];
|
||||
let main_dev = feedback.main_device();
|
||||
let format_table = feedback.format_table();
|
||||
let tranches = feedback.tranches();
|
||||
debug!("Linux DMA-BUF feedback for output {}, main device {}:{}, \
|
||||
{} format table entries, {} tranches", &bg_layer.output_name,
|
||||
major(main_dev), minor(main_dev),
|
||||
format_table.len(), tranches.len());
|
||||
if tranches.is_empty() {
|
||||
bail!("Linux DMA-BUF feedback has 0 tranches");
|
||||
}
|
||||
let mut selected = None;
|
||||
for (index, tranche) in tranches.iter().enumerate() {
|
||||
let target_dev = tranche.device;
|
||||
debug!("Tranche {index} target device {}:{}",
|
||||
major(target_dev), minor(target_dev));
|
||||
if selected.is_none() && target_dev == main_dev {
|
||||
selected = Some((index, tranche.formats.as_slice()));
|
||||
}
|
||||
}
|
||||
let Some((index, formats)) = selected else {
|
||||
bail!("No tranche has the main device as target device");
|
||||
};
|
||||
debug!("Selected tranche {}, it has {} dmabuf formats",
|
||||
index, formats.len());
|
||||
let mut drm_format_modifiers = Vec::new();
|
||||
for index in formats {
|
||||
let Some(dmabuf_format) = format_table.get(*index as usize) else {
|
||||
error!("Format index {index} is out of bounds");
|
||||
continue
|
||||
};
|
||||
if dmabuf_format.format == DRM_FORMAT_XRGB8888 {
|
||||
drm_format_modifiers.push(dmabuf_format.modifier);
|
||||
}
|
||||
}
|
||||
if drm_format_modifiers.is_empty() {
|
||||
bail!("Selected tranche has no modifiers for DRM_FORMAT_XRGB8888");
|
||||
}
|
||||
debug!("Modifiers for DRM_FORMAT_XRGB8888: {}",
|
||||
drm_format_modifiers.iter()
|
||||
.map(|&modifier| fmt_modifier(modifier))
|
||||
.collect::<Vec<_>>().join(", "));
|
||||
let dmabuf_drm_dev = Some(main_dev as Dev);
|
||||
if !bg_layer.workspace_backgrounds.is_empty()
|
||||
&& bg_layer.workspace_backgrounds.iter().all(|bg| {
|
||||
let memory = &bg.wallpaper.borrow().memory;
|
||||
if let Memory::Dmabuf { gpu_memory, .. } = memory {
|
||||
gpu_memory.dmabuf_feedback_eq(
|
||||
dmabuf_drm_dev,
|
||||
&drm_format_modifiers
|
||||
)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
{
|
||||
debug!("Ignoring DMA-BUF feedback with no changes");
|
||||
return Ok(())
|
||||
}
|
||||
let gpu_uploader = state.gpu.as_mut().unwrap().uploader(
|
||||
dmabuf_drm_dev,
|
||||
bg_layer.width as u32,
|
||||
bg_layer.height as u32,
|
||||
drm_format_modifiers
|
||||
).context("Failed to create GPU uploader")?;
|
||||
if !bg_layer.workspace_backgrounds.is_empty() {
|
||||
debug!("DMA-BUF feedback changed, reloading wallpapers");
|
||||
bg_layer.workspace_backgrounds.clear();
|
||||
}
|
||||
load_wallpapers(
|
||||
state,
|
||||
qh,
|
||||
bg_layer_pos,
|
||||
Some(gpu_uploader),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wallpaper_dmabuf(
|
||||
dmabuf_state: &DmabufState,
|
||||
qh: &QueueHandle<State>,
|
||||
gpu_wallpaper: GpuWallpaper,
|
||||
width: i32,
|
||||
height: i32,
|
||||
canon_path: PathBuf,
|
||||
canon_modified: u128,
|
||||
) -> Rc<RefCell<Wallpaper>> {
|
||||
let GpuWallpaper {
|
||||
drm_format_modifier,
|
||||
memory_planes_len,
|
||||
memory_planes,
|
||||
gpu_memory,
|
||||
fd,
|
||||
} = gpu_wallpaper;
|
||||
let dmabuf_params = dmabuf_state.create_params(qh).unwrap();
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for memory_plane_index in 0..memory_planes_len {
|
||||
dmabuf_params.add(
|
||||
fd.as_fd(),
|
||||
memory_plane_index as u32,
|
||||
memory_planes[memory_plane_index].offset as u32,
|
||||
memory_planes[memory_plane_index].stride as u32,
|
||||
drm_format_modifier,
|
||||
);
|
||||
}
|
||||
let params = dmabuf_params.create(
|
||||
width,
|
||||
height,
|
||||
DRM_FORMAT_XRGB8888,
|
||||
zwp_linux_buffer_params_v1::Flags::empty(),
|
||||
);
|
||||
Rc::new(RefCell::new(Wallpaper {
|
||||
wl_buffer: None,
|
||||
active_count: 0,
|
||||
memory: Memory::Dmabuf { gpu_memory, params: Some(params) },
|
||||
canon_path,
|
||||
canon_modified,
|
||||
}))
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue