Files
server/src/wayland/core/surface.rs
2025-07-08 00:37:29 +02:00

368 lines
9.5 KiB
Rust

use super::{buffer::Buffer, callback::Callback};
use crate::{
BevyMaterial,
core::registry::Registry,
nodes::{drawable::model::ModelPart, items::panel::Geometry},
wayland::{
Message, MessageSink,
util::{ClientExt, DoubleBuffer},
xdg::{popup::Popup, toplevel::Toplevel},
},
};
use bevy::{
asset::{Assets, Handle},
image::Image,
};
use mint::Vector2;
use parking_lot::Mutex;
use std::sync::{Arc, OnceLock};
use waynest::{
server::{
Client, Dispatcher, Result,
protocol::core::wayland::{wl_output::Transform, wl_surface::*},
},
wire::ObjectId,
};
pub static WL_SURFACE_REGISTRY: Registry<Surface> = Registry::new();
#[derive(Debug, Clone)]
pub enum SurfaceRole {
XdgToplevel(Arc<Toplevel>),
XDGPopup(Arc<Popup>),
}
#[derive(Debug, Clone)]
pub struct SurfaceState {
pub buffer: Option<Arc<Buffer>>,
pub density: f32,
pub geometry: Option<Geometry>,
pub min_size: Option<Vector2<u32>>,
pub max_size: Option<Vector2<u32>>,
clean_lock: OnceLock<()>,
}
impl Default for SurfaceState {
fn default() -> Self {
Self {
buffer: Default::default(),
density: 1.0,
geometry: None,
min_size: None,
max_size: None,
clean_lock: Default::default(),
}
}
}
// if returning false, don't run this callback again... just remove it
pub type OnCommitCallback = Box<dyn Fn(&Surface, &SurfaceState) -> bool + Send + Sync>;
#[derive(Dispatcher)]
pub struct Surface {
pub id: ObjectId,
state: Mutex<DoubleBuffer<SurfaceState>>,
pub message_sink: MessageSink,
pub role: Mutex<Option<SurfaceRole>>,
frame_callback_object: Mutex<Option<Arc<Callback>>>,
on_commit_handlers: Mutex<Vec<OnCommitCallback>>,
material: OnceLock<Handle<BevyMaterial>>,
pending_material_applications: Registry<ModelPart>,
}
impl std::fmt::Debug for Surface {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Surface")
.field("state", &self.state)
.field("message_sink", &self.message_sink)
.field("role", &self.role)
.field("frame_callback_object", &self.frame_callback_object)
.field(
"on_commit_handlers",
&format!("<{} handlers>", self.on_commit_handlers.lock().len()),
)
.finish()
}
}
impl Surface {
pub fn new(client: &Client, id: ObjectId) -> Self {
Surface {
id,
state: Default::default(),
message_sink: client.message_sink(),
role: Mutex::new(None),
frame_callback_object: Default::default(),
on_commit_handlers: Mutex::new(Vec::new()),
material: OnceLock::new(),
pending_material_applications: Registry::new(),
}
}
pub fn pending_state(&self) -> parking_lot::MutexGuard<'_, DoubleBuffer<SurfaceState>> {
self.state.lock()
}
pub fn add_commit_handler<F: Fn(&Surface, &SurfaceState) -> bool + Send + Sync + 'static>(
&self,
handler: F,
) {
let mut handlers = self.on_commit_handlers.lock();
handlers.push(Box::new(handler));
}
pub fn update_graphics(
&self,
materials: &mut Assets<BevyMaterial>,
images: &mut Assets<Image>,
) {
let state_lock = self.state.lock();
if state_lock.current().clean_lock.get().is_some() {
// then we don't need to reupload the texture
return;
}
let Some(buffer) = state_lock.current().buffer.clone() else {
return;
};
let material = self.material.get_or_init(|| {
// // Set default shader parameters
// let mut params = mat_wrapper.0.get_all_param_info();
// params.set_vec2("uv_scale", stereokit_rust::maths::Vec2::new(1.0, 1.0));
// params.set_vec2("uv_offset", stereokit_rust::maths::Vec2::new(0.0, 0.0));
// params.set_float("fcFactor", 1.0);
// params.set_float("ripple", 4.0);
// params.set_float("alpha_min", 0.0);
// params.set_float("alpha_max", 1.0);
let material = BevyMaterial::default();
materials.add(material)
});
if let Some(new_tex) = buffer.update_tex(images) {
materials
.get_mut(material)
.unwrap()
.base_color_texture
.replace(new_tex);
// For SHM buffers, we can release immediately after copying to GPU
if buffer.can_release_after_update() {
let _ = self.message_sink.send(Message::ReleaseBuffer(buffer));
}
}
self.apply_surface_materials();
let _ = state_lock.current().clean_lock.set(());
}
pub fn apply_material(&self, model_part: &Arc<ModelPart>) {
// tracing::info!("uwu applying material");
self.pending_material_applications.add_raw(model_part)
}
fn apply_surface_materials(&self) {
let Some(mat) = self.material.get() else {
return;
};
for model_node in self.pending_material_applications.get_valid_contents() {
model_node.replace_material(mat.clone());
}
self.pending_material_applications.clear();
}
fn mark_dirty(&self) {
self.state.lock().pending.clean_lock = Default::default();
}
pub fn current_state(&self) -> SurfaceState {
self.state.lock().current().clone()
}
pub fn frame_event(&self) {
if let Some(callback_obj) = self.frame_callback_object.lock().take() {
let _ = self.message_sink.send(Message::Frame(callback_obj));
}
}
// pub fn size(&self) -> Option<Vector2<u32>> {
// self.state
// .lock()
// .current()
// .buffer
// .as_ref()
// .map(|b| [b.size.x as u32, b.size.y as u32].into())
// }
// pub async fn release_old_buffer(&self, client: &mut Client) -> Result<()> {
// let (old_buffer, object) = {
// let lock = self.state.lock();
// let Some(old_buffer) = lock.current().buffer.clone() else {
// return Ok(());
// };
// let new_buffer = lock.pending.buffer.as_ref();
// if new_buffer.map(Arc::as_ptr) == Some(Arc::as_ptr(&old_buffer)) {
// return Ok(());
// }
// drop(lock);
// (old_buffer.clone(), old_buffer.id)
// };
// old_buffer.release(client, object).await?;
// Ok(())
// }
}
impl WlSurface for Surface {
/// https://wayland.app/protocols/wayland#wl_surface:request:attach
async fn attach(
&self,
client: &mut Client,
_sender_id: ObjectId,
buffer: Option<ObjectId>,
_x: i32,
_y: i32,
) -> Result<()> {
self.state.lock().pending.buffer = buffer.and_then(|b| client.get::<Buffer>(b));
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:damage
async fn damage(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_x: i32,
_y: i32,
_width: i32,
_height: i32,
) -> Result<()> {
// should be more intelligent about this but for now just make it copy everything to gpu next frame again
self.mark_dirty();
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:frame
async fn frame(
&self,
client: &mut Client,
_sender_id: ObjectId,
callback_id: ObjectId,
) -> Result<()> {
let callback = client.insert(callback_id, Callback(callback_id));
self.frame_callback_object.lock().replace(callback);
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:set_opaque_region
async fn set_opaque_region(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_region: Option<ObjectId>,
) -> Result<()> {
// nothing we can really do to repaint behind this so ignore it
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:set_input_region
async fn set_input_region(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_region: Option<ObjectId>,
) -> Result<()> {
// too complicated to implement this for now so who the hell cares
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:commit
async fn commit(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
{
let mut lock = self.state.lock();
// If we're getting a new buffer and the current one is DMA-BUF, release it
if let Some(new_buffer) = &lock.pending.buffer {
if let Some(current_buffer) = &lock.current().buffer {
// Don't release if it's the same buffer being reused
if !Arc::ptr_eq(new_buffer, current_buffer)
&& !current_buffer.can_release_after_update()
{
let _ = self
.message_sink
.send(Message::ReleaseBuffer(current_buffer.clone()));
}
}
}
let dirty = lock.current().clean_lock.get().is_none()
|| lock.pending.clean_lock.take().is_none();
lock.apply();
if !dirty {
let _ = lock.current().clean_lock.set(());
}
}
let current_state = self.current_state();
let mut handlers = self.on_commit_handlers.lock();
handlers.retain(|f| (f)(self, &current_state));
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:set_buffer_transform
async fn set_buffer_transform(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_transform: Transform,
) -> Result<()> {
// we just don't have the output transform or fullscreen at all so this optimization is never needed
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:set_buffer_scale
async fn set_buffer_scale(
&self,
_client: &mut Client,
_sender_id: ObjectId,
scale: i32,
) -> Result<()> {
self.state.lock().pending.density = scale as f32;
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:damage_buffer
async fn damage_buffer(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_x: i32,
_y: i32,
_width: i32,
_height: i32,
) -> Result<()> {
// we should upload only chunks to the gpu and do subimage copy but that's a lot rn so we won't
self.mark_dirty();
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:offset
async fn offset(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_x: i32,
_y: i32,
) -> Result<()> {
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:destroy
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}
impl Drop for Surface {
fn drop(&mut self) {
self.role.lock().take();
}
}