feat: wayland

This commit is contained in:
Nova
2025-07-05 19:51:40 -07:00
parent 7b126557df
commit f4d08dac9c
51 changed files with 3941 additions and 2691 deletions

View File

@@ -0,0 +1,200 @@
use super::buffer_params::BufferParams;
use crate::wayland::{GraphicsInfo, Message, MessageSink, core::buffer::Buffer};
use drm_fourcc::DrmFourcc;
use khronos_egl::{self as egl, ClientBuffer};
use mint::Vector2;
use std::{
os::fd::AsRawFd,
sync::{Arc, OnceLock},
};
use stereokit_rust::tex::{Tex, TexFormat, TexType};
use waynest::server::protocol::stable::linux_dmabuf_v1::zwp_linux_buffer_params_v1::Flags;
// EGL extension constants for DMA-BUF
const EGL_WIDTH: i32 = 0x3057;
const EGL_HEIGHT: i32 = 0x3056;
const EGL_LINUX_DRM_FOURCC_EXT: i32 = 0x3272;
const EGL_DMA_BUF_PLANE0_FD_EXT: i32 = 0x3373;
const EGL_DMA_BUF_PLANE0_OFFSET_EXT: i32 = 0x3273;
const EGL_DMA_BUF_PLANE0_PITCH_EXT: i32 = 0x3275;
const EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT: i32 = 0x3443;
const EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT: i32 = 0x3444;
const EGL_LINUX_DMA_BUF_EXT: i32 = 0x3270;
const EGL_NO_BUFFER: *mut std::ffi::c_void = std::ptr::null_mut();
/// Parameters for a shared memory buffer
#[derive(Debug)]
pub struct DmabufBacking {
params: Arc<BufferParams>,
message_sink: Option<MessageSink>,
size: Vector2<usize>,
format: DrmFourcc,
_flags: Flags,
tex: OnceLock<Tex>,
}
impl DmabufBacking {
pub fn new(
params: Arc<BufferParams>,
message_sink: Option<MessageSink>,
size: Vector2<usize>,
format: DrmFourcc,
flags: Flags,
) -> Self {
tracing::info!(
"Creating new DmabufBacking with BufferParams {:?}",
params.id
);
Self {
params,
message_sink,
size,
format,
_flags: flags,
tex: OnceLock::new(),
}
}
fn import_dmabuf(&self, graphics_info: &mut GraphicsInfo) -> Result<Tex, khronos_egl::Error> {
// Sanity check for required EGL extensions
let extensions = graphics_info
.instance
.query_string(Some(graphics_info.display), egl::EXTENSIONS)?;
let extensions_str = extensions.to_string_lossy();
let extensions_list: Vec<&str> = extensions_str.split_whitespace().collect();
if !extensions_list.contains(&"EGL_EXT_image_dma_buf_import") {
tracing::error!("EGL extension EGL_EXT_image_dma_buf_import is not supported");
return Err(khronos_egl::Error::BadParameter);
}
if !extensions_list.contains(&"EGL_EXT_image_dma_buf_import_modifiers") {
tracing::error!(
"EGL extension EGL_EXT_image_dma_buf_import_modifiers is not supported"
);
return Err(khronos_egl::Error::BadParameter);
}
let mut tex = Tex::new(
TexType::ImageNomips | TexType::Dynamic,
TexFormat::RGBA32,
nanoid::nanoid!(),
);
tracing::info!(format=?self.format, "Wayland: Updating DMABuf tex");
// Get plane info from params
let planes = self.params.lock_planes();
let Some(plane) = planes.get(&0) else {
tracing::error!(
"Wayland: Failed to get plane 0 from BufferParams {:?}",
self.params.id
);
return Err(khronos_egl::Error::BadParameter);
};
tracing::info!(
"Using plane 0 with fd {} from BufferParams {:?}",
plane.fd.as_raw_fd(),
self.params.id
);
// Create EGL image
let image = match graphics_info.instance.create_image(
graphics_info.display,
graphics_info.context,
EGL_LINUX_DMA_BUF_EXT as u32,
unsafe { ClientBuffer::from_ptr(EGL_NO_BUFFER) },
&[
EGL_LINUX_DRM_FOURCC_EXT as usize,
self.format as usize,
EGL_DMA_BUF_PLANE0_FD_EXT as usize,
plane.fd.as_raw_fd() as usize, // EGL will dup() this fd internally
EGL_DMA_BUF_PLANE0_OFFSET_EXT as usize,
plane.offset as usize,
EGL_DMA_BUF_PLANE0_PITCH_EXT as usize,
plane.stride as usize,
egl::ATTRIB_NONE,
],
) {
Ok(image) => image,
Err(e) => {
tracing::error!(
"Wayland: Failed to create EGL image. Error: {:?}, Params: size=({:?}, {:?}), format={:?}, fd={}, stride={}, offset={}",
e,
self.size.x,
self.size.y,
self.format,
plane.fd.as_raw_fd(),
plane.stride,
plane.offset
);
return Err(e);
}
};
// The cloned fd will be consumed by create_image, so we don't need to explicitly close it
// Create and bind GL texture
let mut gl_tex = 0;
unsafe {
gl::GenTextures(1, &mut gl_tex);
if gl_tex == 0 {
tracing::error!("Wayland: Failed to generate GL texture.");
return Err(khronos_egl::Error::BadParameter);
}
gl::BindTexture(gl::TEXTURE_2D, gl_tex);
}
// Set the native texture handle directly
// Mesa will handle the OES texture implicitly
unsafe {
tex.set_native_surface(
gl_tex as *mut std::os::raw::c_void,
TexType::ImageNomips | TexType::Dynamic,
0x8058, // GL_RGBA8
self.size.x as i32,
self.size.y as i32,
1, // single surface
true, // we own this texture
)
};
// Clean up EGL image
if let Err(e) = graphics_info
.instance
.destroy_image(graphics_info.display, image)
{
tracing::error!("Wayland: Failed to destroy EGL image. Error: {:?}", e);
}
Ok(tex)
}
pub fn init_tex(&self, graphics_info: &Arc<GraphicsInfo>, buffer: Arc<Buffer>) {
if self.tex.get().is_none() {
match self.import_dmabuf(graphics_info) {
Ok(tex) => {
let _ = self.tex.set(tex);
let _ = self
.message_sink
.as_ref()
.unwrap()
.send(Message::DmabufImportSuccess(self.params.clone(), buffer));
}
Err(e) => {
tracing::error!("Wayland: Error initializing DMABuf tex: {:?}", e);
let _ = self
.message_sink
.as_ref()
.unwrap()
.send(Message::DmabufImportFailure(self.params.clone()));
}
};
}
}
pub fn get_tex(&self) -> Option<&Tex> {
self.tex.get()
}
pub fn size(&self) -> Vector2<usize> {
self.size
}
}

View File

@@ -0,0 +1,163 @@
use super::buffer_backing::DmabufBacking;
use crate::wayland::{
core::buffer::{Buffer, BufferBacking},
util::ClientExt,
};
use drm_fourcc::DrmFourcc;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use std::os::fd::{AsRawFd, OwnedFd};
use waynest::{
server::{
Client, Dispatcher, Result,
protocol::stable::linux_dmabuf_v1::zwp_linux_buffer_params_v1::{
Flags, ZwpLinuxBufferParamsV1,
},
},
wire::ObjectId,
};
/// A single plane in a DMA-BUF buffer
#[derive(Debug)]
pub struct DmabufPlane {
pub fd: OwnedFd,
pub offset: u32,
pub stride: u32,
pub _modifier: u64,
}
/// Parameters for creating a DMA-BUF-based wl_buffer
///
/// This is a temporary object that collects dmabufs and other parameters
/// that together form a single logical buffer. The object may eventually
/// create one wl_buffer unless cancelled by destroying it.
#[derive(Debug, Dispatcher)]
pub struct BufferParams {
pub id: ObjectId,
planes: Mutex<FxHashMap<u32, DmabufPlane>>,
}
impl BufferParams {
pub fn new(id: ObjectId) -> Self {
tracing::info!("Creating new BufferParams with id {:?}", id);
Self {
id,
planes: Mutex::new(FxHashMap::default()),
}
}
pub fn lock_planes(&self) -> parking_lot::MutexGuard<'_, FxHashMap<u32, DmabufPlane>> {
self.planes.lock()
}
}
impl ZwpLinuxBufferParamsV1 for BufferParams {
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
tracing::info!("Destroying BufferParams {:?}", self.id);
Ok(())
}
async fn add(
&self,
_client: &mut Client,
_sender_id: ObjectId,
fd: OwnedFd,
plane_idx: u32,
offset: u32,
stride: u32,
modifier_hi: u32,
modifier_lo: u32,
) -> Result<()> {
let fd_num = fd.as_raw_fd();
tracing::info!(
"Adding plane {} with fd {} to BufferParams {:?}",
plane_idx,
fd_num,
self.id
);
let mut planes = self.planes.lock();
// Check if plane index is already set
if planes.contains_key(&plane_idx) {
tracing::error!(
"Plane {} already exists in BufferParams {:?}",
plane_idx,
self.id
);
return Err(waynest::server::Error::MissingObject(self.id));
}
// Create plane with the provided parameters
let plane = DmabufPlane {
fd,
offset,
stride,
_modifier: ((modifier_hi as u64) << 32) | (modifier_lo as u64),
};
// Store the plane
planes.insert(plane_idx, plane);
Ok(())
}
async fn create(
&self,
client: &mut Client,
_sender_id: ObjectId,
width: i32,
height: i32,
format: u32,
flags: Flags,
) -> Result<()> {
tracing::info!("Creating buffer from BufferParams {:?}", self.id);
// Create the buffer with DMA-BUF backing using self as the backing
let size = [width as usize, height as usize].into();
let backing = DmabufBacking::new(
client.get::<Self>(self.id).unwrap(),
Some(client.display().message_sink.clone()),
size,
DrmFourcc::try_from(format).unwrap(),
flags,
);
let id = client.display().next_server_id();
Buffer::new(client, id, BufferBacking::Dmabuf(backing));
Ok(())
}
async fn create_immed(
&self,
client: &mut Client,
_sender_id: ObjectId,
buffer_id: ObjectId,
width: i32,
height: i32,
format: u32,
flags: Flags,
) -> Result<()> {
// Create the buffer with DMA-BUF backing using self as the backing
let size = [width as usize, height as usize].into();
let backing = DmabufBacking::new(
client.get::<Self>(self.id).unwrap(),
None,
size,
DrmFourcc::try_from(format).unwrap(),
flags,
);
Buffer::new(client, buffer_id, BufferBacking::Dmabuf(backing));
Ok(())
}
}
impl Drop for BufferParams {
fn drop(&mut self) {
let planes = self.planes.get_mut();
tracing::info!("BufferParams being dropped with {} planes", planes.len());
for (idx, plane) in planes.iter() {
tracing::info!("Dropping plane {} with fd {}", idx, plane.fd.as_raw_fd());
}
planes.clear();
}
}

View File

@@ -0,0 +1,92 @@
use drm_fourcc::DrmFourcc;
use memfd::MemfdOptions;
use std::{
io::Write,
os::fd::{FromRawFd, IntoRawFd, OwnedFd},
};
use waynest::{
server::{
Client, Dispatcher, Result,
protocol::stable::linux_dmabuf_v1::zwp_linux_dmabuf_feedback_v1::{
TrancheFlags, ZwpLinuxDmabufFeedbackV1,
},
},
wire::ObjectId,
};
#[derive(Debug, Dispatcher)]
pub struct DmabufFeedback;
impl DmabufFeedback {
pub async fn send_params(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> {
// Send format table first
self.send_format_table(client, sender_id).await?;
// let graphics_info = &client.display().graphics_info;
// Get the DRM device file path using the new method
// let device_file = graphics_info.get_drm_device_file_path()?;
// let dev_stat = std::fs::metadata(device_file)?;
// let dev_id = dev_stat.rdev().to_ne_bytes().to_vec();
// self.main_device(client, sender_id, dev_id.clone()).await?;
// Send single tranche with same device since we only support the main GPU
// self.tranche_target_device(client, sender_id, dev_id)
// .await?;
// We only have one format at index 0
let indices = vec![0u16]
.into_iter()
.flat_map(|i| i.to_ne_bytes())
.collect();
self.tranche_formats(client, sender_id, indices).await?;
// No special flags needed for simple EGL texture usage
self.tranche_flags(client, sender_id, TrancheFlags::empty())
.await?;
// Mark tranche complete
self.tranche_done(client, sender_id).await?;
// Mark overall feedback complete
self.done(client, sender_id).await?;
Ok(())
}
pub async fn send_format_table(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> {
// Create a temporary file for the format table
let size = 16u32; // One format+modifier pair
let mfd = MemfdOptions::default()
.create("stardustxr-format-table")
.map_err(|e| waynest::server::Error::Custom(e.to_string()))?;
mfd.as_file().set_len(size as u64)?;
// Format + modifier pair (16 bytes):
// - format: u32
// - padding: 4 bytes
// - modifier: u64
let format = DrmFourcc::Xrgb8888 as u32; // This is what clients typically want
let modifier: u64 = 0; // Linear modifier
// Write the format+modifier pair
mfd.as_file().write_all(&format.to_ne_bytes())?;
mfd.as_file().write_all(&modifier.to_ne_bytes())?;
self.format_table(
client,
sender_id,
unsafe { OwnedFd::from_raw_fd(mfd.into_raw_fd()) },
size,
)
.await?;
Ok(())
}
}
impl ZwpLinuxDmabufFeedbackV1 for DmabufFeedback {
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}

108
src/wayland/dmabuf/mod.rs Normal file
View File

@@ -0,0 +1,108 @@
pub mod buffer_backing;
pub mod buffer_params;
pub mod feedback;
use buffer_params::BufferParams;
use drm_fourcc::DrmFourcc;
use feedback::DmabufFeedback;
use waynest::{
server::{
Client, Dispatcher, Result,
protocol::stable::linux_dmabuf_v1::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
},
wire::ObjectId,
};
use crate::core::registry::Registry;
/// Main DMA-BUF interface implementation
///
/// This interface allows clients to create wl_buffers from DMA-BUFs.
/// It handles:
/// - Format/modifier advertisement
/// - Buffer parameter creation
/// - Default/surface-specific feedback
///
/// The implementation ensures:
/// - Coherency for read access in dmabuf data
/// - Proper lifetime management of dmabuf file descriptors
/// - Safe handling of buffer attachments
#[derive(Dispatcher)]
pub struct Dmabuf {
// Track supported formats and modifiers
// formats: Mutex<FxHashSet<DrmFormat>>,
// Track active buffer parameters objects by their ID
active_params: Registry<BufferParams>,
}
impl Dmabuf {
/// Create a new DMA-BUF interface instance
pub fn new() -> Self {
// let mut formats = FxHashSet::default();
Self {
// formats: Mutex::new(formats),
active_params: Registry::new(),
}
}
pub async fn send_modifiers(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> {
let format = DrmFourcc::Xrgb8888 as u32;
let modifier_hi = 0u32; // Linear modifier high 32 bits
let modifier_lo = 0u32; // Linear modifier low 32 bits
self.modifier(client, sender_id, format, modifier_hi, modifier_lo)
.await?;
Ok(())
}
/// Remove a buffer parameters object from tracking
pub(crate) fn remove_params(&self, params_id: ObjectId) {
self.active_params.retain(|params| params.id != params_id);
}
}
impl ZwpLinuxDmabufV1 for Dmabuf {
async fn destroy(&self, _client: &mut Client, sender_id: ObjectId) -> Result<()> {
self.remove_params(sender_id);
Ok(())
}
async fn create_params(
&self,
client: &mut Client,
_sender_id: ObjectId,
params_id: ObjectId,
) -> Result<()> {
// Create new buffer parameters object
let params = client.insert(params_id, BufferParams::new(params_id));
self.active_params.add_raw(&params);
Ok(())
}
async fn get_default_feedback(
&self,
client: &mut Client,
_sender_id: ObjectId,
id: ObjectId,
) -> Result<()> {
// Create feedback object for default (non-surface-specific) settings
let feedback = client.insert(id, DmabufFeedback);
feedback.send_params(client, id).await?;
Ok(())
}
async fn get_surface_feedback(
&self,
client: &mut Client,
_sender_id: ObjectId,
id: ObjectId,
_surface: ObjectId,
) -> Result<()> {
// Create feedback object for surface-specific settings
// Note: Surface-specific feedback could be optimized based on the surface's
// requirements, but for now we use the same feedback as default
let feedback = client.insert(id, DmabufFeedback);
feedback.send_params(client, id).await?;
Ok(())
}
}