diff options
| -rw-r--r-- | flake.lock | 30 | ||||
| -rw-r--r-- | src/handlers/mod.rs | 15 | ||||
| -rw-r--r-- | src/niri.rs | 94 | ||||
| -rw-r--r-- | src/protocols/mod.rs | 1 | ||||
| -rw-r--r-- | src/protocols/screencopy.rs | 386 | ||||
| -rw-r--r-- | src/render_helpers/mod.rs | 102 | ||||
| -rw-r--r-- | src/render_helpers/offscreen.rs | 1 |
7 files changed, 566 insertions, 63 deletions
@@ -7,11 +7,11 @@ ] }, "locked": { - "lastModified": 1707685877, - "narHash": "sha256-XoXRS+5whotelr1rHiZle5t5hDg9kpguS5yk8c8qzOc=", + "lastModified": 1709610799, + "narHash": "sha256-5jfLQx0U9hXbi2skYMGodDJkIgffrjIOgMRjZqms2QE=", "owner": "ipetkov", "repo": "crane", - "rev": "2c653e4478476a52c6aa3ac0495e4dea7449ea0e", + "rev": "81c393c776d5379c030607866afef6406ca1be57", "type": "github" }, "original": { @@ -28,11 +28,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1706768574, - "narHash": "sha256-4o6TMpzBHO659EiJTzd/EGQGUDdbgwKwhqf3u6b23U8=", + "lastModified": 1709274179, + "narHash": "sha256-O6EC6QELBLHzhdzBOJj0chx8AOcd4nDRECIagfT5Nd0=", "owner": "nix-community", "repo": "fenix", - "rev": "668102037129923cd0fc239d864fce71eabdc6a3", + "rev": "4be608f4f81d351aacca01b21ffd91028c23cc22", "type": "github" }, "original": { @@ -47,11 +47,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "lastModified": 1709126324, + "narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=", "owner": "numtide", "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "rev": "d465f4819400de7c8d874d50b982301f28a84605", "type": "github" }, "original": { @@ -77,11 +77,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1707619277, - "narHash": "sha256-vKnYD5GMQbNQyyQm4wRlqi+5n0/F1hnvqSQgaBy4BqY=", + "lastModified": 1709386671, + "narHash": "sha256-VPqfBnIJ+cfa78pd4Y5Cr6sOWVW8GYHRVucxJGmRf8Q=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "f3a93440fbfff8a74350f4791332a19282cc6dc8", + "rev": "fa9a51752f1b5de583ad5213eb621be071806663", "type": "github" }, "original": { @@ -103,11 +103,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1706735270, - "narHash": "sha256-IJk+UitcJsxzMQWm9pa1ZbJBriQ4ginXOlPyVq+Cu40=", + "lastModified": 1709219524, + "narHash": "sha256-8HHRXm4kYQLdUohNDUuCC3Rge7fXrtkjBUf0GERxrkM=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "42cb1a2bd79af321b0cc503d2960b73f34e2f92b", + "rev": "9efa23c4dacee88b93540632eb3d88c5dfebfe17", "type": "github" }, "original": { diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 8e92ea6e..39173471 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -54,12 +54,13 @@ use smithay::{ delegate_text_input_manager, delegate_virtual_keyboard_manager, }; -use crate::delegate_foreign_toplevel; use crate::niri::{ClientState, State}; use crate::protocols::foreign_toplevel::{ self, ForeignToplevelHandler, ForeignToplevelManagerState, }; +use crate::protocols::screencopy::{Screencopy, ScreencopyHandler}; use crate::utils::output_size; +use crate::{delegate_foreign_toplevel, delegate_screencopy}; impl SeatHandler for State { type KeyboardFocus = WlSurface; @@ -380,6 +381,18 @@ impl ForeignToplevelHandler for State { } delegate_foreign_toplevel!(State); +impl ScreencopyHandler for State { + fn frame(&mut self, screencopy: Screencopy) { + if let Err(err) = self + .niri + .render_for_screencopy(&mut self.backend, screencopy) + { + warn!("error rendering for screencopy: {err:?}"); + } + } +} +delegate_screencopy!(State); + impl DrmLeaseHandler for State { fn drm_lease_state(&mut self, node: DrmNode) -> &mut DrmLeaseState { &mut self diff --git a/src/niri.rs b/src/niri.rs index 2d1c80ff..c670f0ba 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -9,7 +9,7 @@ use std::time::{Duration, Instant}; use std::{env, mem, thread}; use _server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as KdeDecorationsMode; -use anyhow::Context; +use anyhow::{ensure, Context}; use calloop::futures::Scheduler; use niri_config::{Config, TrackLayout}; use smithay::backend::allocator::Fourcc; @@ -18,7 +18,9 @@ use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRen use smithay::backend::renderer::element::surface::{ render_elements_from_surface_tree, WaylandSurfaceRenderElement, }; -use smithay::backend::renderer::element::utils::{select_dmabuf_feedback, RelocateRenderElement}; +use smithay::backend::renderer::element::utils::{ + select_dmabuf_feedback, Relocate, RelocateRenderElement, +}; use smithay::backend::renderer::element::{ default_primary_scanout_output_compare, AsRenderElements, Id, Kind, PrimaryScanoutOutput, RenderElementStates, @@ -97,9 +99,10 @@ use crate::input::{apply_libinput_settings, TabletData}; use crate::ipc::server::IpcServer; use crate::layout::{Layout, MonitorRenderElement}; use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState}; +use crate::protocols::screencopy::{Screencopy, ScreencopyManagerState}; use crate::pw_utils::{Cast, PipeWire}; use crate::render_helpers::renderer::NiriRenderer; -use crate::render_helpers::{render_to_texture, render_to_vec}; +use crate::render_helpers::{render_to_shm, render_to_texture, render_to_vec}; use crate::ui::config_error_notification::ConfigErrorNotification; use crate::ui::exit_confirm_dialog::ExitConfirmDialog; use crate::ui::hotkey_overlay::HotkeyOverlay; @@ -154,6 +157,7 @@ pub struct Niri { pub layer_shell_state: WlrLayerShellState, pub session_lock_state: SessionLockManagerState, pub foreign_toplevel_state: ForeignToplevelManagerState, + pub screencopy_state: ScreencopyManagerState, pub shm_state: ShmState, pub output_manager_state: OutputManagerState, pub dmabuf_state: DmabufState, @@ -910,6 +914,9 @@ impl Niri { ForeignToplevelManagerState::new::<State, _>(&display_handle, |client| { !client.get_data::<ClientState>().unwrap().restricted }); + let screencopy_state = ScreencopyManagerState::new::<State, _>(&display_handle, |client| { + !client.get_data::<ClientState>().unwrap().restricted + }); let mut seat: Seat<State> = seat_state.new_wl_seat(&display_handle, backend.seat_name()); seat.add_keyboard( @@ -1030,6 +1037,7 @@ impl Niri { layer_shell_state, session_lock_state, foreign_toplevel_state, + screencopy_state, text_input_state, input_method_state, virtual_keyboard_state, @@ -2166,13 +2174,11 @@ impl Niri { // to err on the safe side. self.send_frame_callbacks(output); - // Render and send to PipeWire screencast streams. - #[cfg(feature = "xdp-gnome-screencast")] - { - backend.with_primary_renderer(|renderer| { - self.render_for_screen_cast(renderer, output, target_presentation_time); - }); - } + backend.with_primary_renderer(|renderer| { + // Render and send to PipeWire screencast streams. + #[cfg(feature = "xdp-gnome-screencast")] + self.render_for_screen_cast(renderer, output, target_presentation_time); + }); } pub fn update_primary_scanout_output( @@ -2586,7 +2592,9 @@ impl Niri { .get_or_insert_with(|| self.render::<GlesRenderer>(renderer, output, true)); let elements = elements.iter().rev(); - if let Err(err) = render_to_dmabuf(renderer, dmabuf, size, scale, elements) { + if let Err(err) = + render_to_dmabuf(renderer, dmabuf, size, scale, Transform::Normal, elements) + { warn!("error rendering to dmabuf: {err:?}"); continue; } @@ -2606,6 +2614,42 @@ impl Niri { } } + pub fn render_for_screencopy( + &mut self, + backend: &mut Backend, + screencopy: Screencopy, + ) -> anyhow::Result<()> { + let output = screencopy.output().clone(); + ensure!(self.output_state.contains_key(&output), "output is missing"); + + backend + .with_primary_renderer(move |renderer| { + let elements = self + .render(renderer, &output, screencopy.overlay_cursor()) + .into_iter() + .rev(); + + let region_loc = screencopy.region_loc(); + let elements = elements.map(|element| { + RelocateRenderElement::from_element( + element, + region_loc.upscale(-1), + Relocate::Relative, + ) + }); + + let scale = output.current_scale().fractional_scale().into(); + let transform = output.current_transform(); + render_to_shm(renderer, screencopy.buffer(), scale, transform, elements) + .context("error rendering to screencopy shm buffer: {err:?}")?; + + screencopy.submit(false); + + Ok(()) + }) + .context("primary renderer is missing")? + } + #[cfg(feature = "xdp-gnome-screencast")] fn stop_cast(&mut self, session_id: usize) { let _span = tracy_client::span!("Niri::stop_cast"); @@ -2665,7 +2709,14 @@ impl Niri { let elements = self.render::<GlesRenderer>(renderer, &output, true); let elements = elements.iter().rev(); - let res = render_to_texture(renderer, size, scale, Fourcc::Abgr8888, elements); + let res = render_to_texture( + renderer, + size, + scale, + Transform::Normal, + Fourcc::Abgr8888, + elements, + ); let screenshot = match res { Ok((texture, _)) => texture, Err(err) => { @@ -2695,7 +2746,14 @@ impl Niri { let scale = Scale::from(output.current_scale().fractional_scale()); let elements = self.render::<GlesRenderer>(renderer, output, true); let elements = elements.iter().rev(); - let pixels = render_to_vec(renderer, size, scale, Fourcc::Abgr8888, elements)?; + let pixels = render_to_vec( + renderer, + size, + scale, + Transform::Normal, + Fourcc::Abgr8888, + elements, + )?; self.save_screenshot(size, pixels) .context("error saving screenshot") @@ -2721,7 +2779,14 @@ impl Niri { 1., ); let elements = elements.iter().rev(); - let pixels = render_to_vec(renderer, size, scale, Fourcc::Abgr8888, elements)?; + let pixels = render_to_vec( + renderer, + size, + scale, + Transform::Normal, + Fourcc::Abgr8888, + elements, + )?; self.save_screenshot(size, pixels) .context("error saving screenshot") @@ -2824,6 +2889,7 @@ impl Niri { renderer, size, Scale::from(f64::from(output_scale)), + Transform::Normal, Fourcc::Abgr8888, elements, )?; diff --git a/src/protocols/mod.rs b/src/protocols/mod.rs index 06d80157..1e3ef42f 100644 --- a/src/protocols/mod.rs +++ b/src/protocols/mod.rs @@ -1 +1,2 @@ pub mod foreign_toplevel; +pub mod screencopy; diff --git a/src/protocols/screencopy.rs b/src/protocols/screencopy.rs new file mode 100644 index 00000000..808be4cb --- /dev/null +++ b/src/protocols/screencopy.rs @@ -0,0 +1,386 @@ +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::time::UNIX_EPOCH; + +use smithay::output::Output; +use smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::zwlr_screencopy_frame_v1::{ + Flags, ZwlrScreencopyFrameV1, +}; +use smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1; +use smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::{ + zwlr_screencopy_frame_v1, zwlr_screencopy_manager_v1, +}; +use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer; +use smithay::reexports::wayland_server::protocol::wl_shm; +use smithay::reexports::wayland_server::{ + Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, +}; +use smithay::utils::{Physical, Point, Rectangle, Size}; +use smithay::wayland::shm; + +// We do not support copy_with_damage() semantics yet. +const VERSION: u32 = 1; + +pub struct ScreencopyManagerState; + +pub struct ScreencopyManagerGlobalData { + filter: Box<dyn for<'c> Fn(&'c Client) -> bool + Send + Sync>, +} + +impl ScreencopyManagerState { + pub fn new<D, F>(display: &DisplayHandle, filter: F) -> Self + where + D: GlobalDispatch<ZwlrScreencopyManagerV1, ScreencopyManagerGlobalData>, + D: Dispatch<ZwlrScreencopyManagerV1, ()>, + D: Dispatch<ZwlrScreencopyFrameV1, ScreencopyFrameState>, + D: ScreencopyHandler, + D: 'static, + F: for<'c> Fn(&'c Client) -> bool + Send + Sync + 'static, + { + let global_data = ScreencopyManagerGlobalData { + filter: Box::new(filter), + }; + display.create_global::<D, ZwlrScreencopyManagerV1, _>(VERSION, global_data); + + Self + } +} + +impl<D> GlobalDispatch<ZwlrScreencopyManagerV1, ScreencopyManagerGlobalData, D> + for ScreencopyManagerState +where + D: GlobalDispatch<ZwlrScreencopyManagerV1, ScreencopyManagerGlobalData>, + D: Dispatch<ZwlrScreencopyManagerV1, ()>, + D: Dispatch<ZwlrScreencopyFrameV1, ScreencopyFrameState>, + D: ScreencopyHandler, + D: 'static, +{ + fn bind( + _state: &mut D, + _display: &DisplayHandle, + _client: &Client, + manager: New<ZwlrScreencopyManagerV1>, + _manager_state: &ScreencopyManagerGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + data_init.init(manager, ()); + } + + fn can_view(client: Client, global_data: &ScreencopyManagerGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +impl<D> Dispatch<ZwlrScreencopyManagerV1, (), D> for ScreencopyManagerState +where + D: GlobalDispatch<ZwlrScreencopyManagerV1, ScreencopyManagerGlobalData>, + D: Dispatch<ZwlrScreencopyManagerV1, ()>, + D: Dispatch<ZwlrScreencopyFrameV1, ScreencopyFrameState>, + D: ScreencopyHandler, + D: 'static, +{ + fn request( + _state: &mut D, + _client: &Client, + _manager: &ZwlrScreencopyManagerV1, + request: zwlr_screencopy_manager_v1::Request, + _data: &(), + _display: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + let (frame, overlay_cursor, buffer_size, region_loc, output) = match request { + zwlr_screencopy_manager_v1::Request::CaptureOutput { + frame, + overlay_cursor, + output, + } => { + let output = Output::from_resource(&output).unwrap(); + let buffer_size = output.current_mode().unwrap().size; + let region_loc = Point::from((0, 0)); + + (frame, overlay_cursor, buffer_size, region_loc, output) + } + zwlr_screencopy_manager_v1::Request::CaptureOutputRegion { + frame, + overlay_cursor, + x, + y, + width, + height, + output, + } => { + if width <= 0 || height <= 0 { + trace!("screencopy client requested invalid sized region"); + let frame = data_init.init(frame, ScreencopyFrameState::Failed); + frame.failed(); + return; + } + + let output = Output::from_resource(&output).unwrap(); + let output_transform = output.current_transform(); + let output_physical_size = + output_transform.transform_size(output.current_mode().unwrap().size); + let output_rect = Rectangle::from_loc_and_size((0, 0), output_physical_size); + + let rect = Rectangle::from_loc_and_size((x, y), (width, height)); + + let output_scale = output.current_scale().integer_scale(); + let physical_rect = rect.to_physical(output_scale); + + // Clamp captured region to the output. + let Some(clamped_rect) = physical_rect.intersection(output_rect) else { + trace!("screencopy client requested region outside of output"); + let frame = data_init.init(frame, ScreencopyFrameState::Failed); + frame.failed(); + return; + }; + + let untransformed_rect = output_transform + .invert() + .transform_rect_in(clamped_rect, &output_physical_size); + + ( + frame, + overlay_cursor, + untransformed_rect.size, + clamped_rect.loc, + output, + ) + } + zwlr_screencopy_manager_v1::Request::Destroy => return, + _ => unreachable!(), + }; + + // Create the frame. + let overlay_cursor = overlay_cursor != 0; + let info = ScreencopyFrameInfo { + output, + overlay_cursor, + buffer_size, + region_loc, + }; + let frame = data_init.init( + frame, + ScreencopyFrameState::Pending { + info, + copied: Arc::new(AtomicBool::new(false)), + }, + ); + + // Send desired SHM buffer parameters. + frame.buffer( + wl_shm::Format::Argb8888, + buffer_size.w as u32, + buffer_size.h as u32, + buffer_size.w as u32 * 4, + ); + + // if manager.version() >= 3 { + // // Send desired DMA buffer parameters. + // frame.linux_dmabuf( + // Fourcc::Argb8888 as u32, + // buffer_size.w as u32, + // buffer_size.h as u32, + // ); + // + // // Notify client that all supported buffers were enumerated. + // frame.buffer_done(); + // } + } +} + +/// Handler trait for wlr-screencopy. +pub trait ScreencopyHandler { + /// Handle new screencopy request. + fn frame(&mut self, frame: Screencopy); +} + +#[allow(missing_docs)] +#[macro_export] +macro_rules! delegate_screencopy { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1: $crate::protocols::screencopy::ScreencopyManagerGlobalData + ] => $crate::protocols::screencopy::ScreencopyManagerState); + + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1: () + ] => $crate::protocols::screencopy::ScreencopyManagerState); + + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1: $crate::protocols::screencopy::ScreencopyFrameState + ] => $crate::protocols::screencopy::ScreencopyManagerState); + }; +} + +#[derive(Clone)] +pub struct ScreencopyFrameInfo { + output: Output, + buffer_size: Size<i32, Physical>, + region_loc: Point<i32, Physical>, + overlay_cursor: bool, +} + +pub enum ScreencopyFrameState { + Failed, + Pending { + info: ScreencopyFrameInfo, + copied: Arc<AtomicBool>, + }, +} + +impl<D> Dispatch<ZwlrScreencopyFrameV1, ScreencopyFrameState, D> for ScreencopyManagerState +where + D: Dispatch<ZwlrScreencopyFrameV1, ScreencopyFrameState>, + D: ScreencopyHandler, + D: 'static, +{ + fn request( + state: &mut D, + _client: &Client, + frame: &ZwlrScreencopyFrameV1, + request: zwlr_screencopy_frame_v1::Request, + data: &ScreencopyFrameState, + _display: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + if matches!(request, zwlr_screencopy_frame_v1::Request::Destroy) { + return; + } + + let (info, copied) = match data { + ScreencopyFrameState::Failed => return, + ScreencopyFrameState::Pending { info, copied } => (info, copied), + }; + + if copied.load(Ordering::SeqCst) { + frame.post_error( + zwlr_screencopy_frame_v1::Error::AlreadyUsed, + "copy was already requested", + ); + return; + } + + let (buffer, with_damage) = match request { + zwlr_screencopy_frame_v1::Request::Copy { buffer } => (buffer, false), + // zwlr_screencopy_frame_v1::Request::CopyWithDamage { buffer } => (buffer, true), + _ => unreachable!(), + }; + + if !shm::with_buffer_contents(&buffer, |_buf, shm_len, buffer_data| { + buffer_data.format == wl_shm::Format::Argb8888 + && buffer_data.stride == info.buffer_size.w * 4 + && buffer_data.height == info.buffer_size.h + && shm_len as i32 == buffer_data.stride * buffer_data.height + }) + .unwrap_or(false) + { + frame.post_error( + zwlr_screencopy_frame_v1::Error::InvalidBuffer, + "invalid buffer", + ); + return; + } + + copied.store(true, Ordering::SeqCst); + + state.frame(Screencopy { + with_damage, + buffer, + frame: frame.clone(), + info: info.clone(), + submitted: false, + }); + } +} + +/// Screencopy frame. +pub struct Screencopy { + info: ScreencopyFrameInfo, + frame: ZwlrScreencopyFrameV1, + #[allow(unused)] + with_damage: bool, + buffer: WlBuffer, + submitted: bool, +} + +impl Drop for Screencopy { + fn drop(&mut self) { + if !self.submitted { + self.frame.failed(); + } + } +} + +impl Screencopy { + /// Get the target buffer to copy to. + pub fn buffer(&self) -> &WlBuffer { + &self.buffer + } + + pub fn region_loc(&self) -> Point<i32, Physical> { + self.info.region_loc + } + + pub fn buffer_size(&self) -> Size<i32, Physical> { + self.info.buffer_size + } + + pub fn output(&self) -> &Output { + &self.info.output + } + + pub fn overlay_cursor(&self) -> bool { + self.info.overlay_cursor + } + + // pub fn damage(&mut self, damage: &[Rectangle<i32, Physical>]) { + // assert!(self.with_damage); + // + // for Rectangle { loc, size } in damage { + // self.frame + // .damage(loc.x as u32, loc.y as u32, size.w as u32, size.h as u32); + // } + // } + + /// Submit the copied content. + pub fn submit(mut self, y_invert: bool) { + // Notify client that buffer is ordinary. + self.frame.flags(if y_invert { + Flags::YInvert + } else { + Flags::empty() + }); + + // Notify client about successful copy. + let time = UNIX_EPOCH.elapsed().unwrap(); + let tv_sec_hi = (time.as_secs() >> 32) as u32; + let tv_sec_lo = (time.as_secs() & 0xFFFFFFFF) as u32; + let tv_nsec = time.subsec_nanos(); + self.frame.ready(tv_sec_hi, tv_sec_lo, tv_nsec); + + // Mark frame as submitted to ensure destructor isn't run. + self.submitted = true; + } + + // pub fn submit_after_sync<T>( + // self, + // y_invert: bool, + // sync_point: Option<OwnedFd>, + // event_loop: &LoopHandle<'_, T>, + // ) { + // match sync_point { + // None => self.submit(y_invert), + // Some(sync_fd) => { + // let source = Generic::new(sync_fd, Interest::READ, Mode::OneShot); + // let mut screencopy = Some(self); + // event_loop + // .insert_source(source, move |_, _, _| { + // screencopy.take().unwrap().submit(y_invert); + // Ok(PostAction::Remove) + // }) + // .unwrap(); + // } + // } + // } +} diff --git a/src/render_helpers/mod.rs b/src/render_helpers/mod.rs index 7d7ea9c1..02ef2853 100644 --- a/src/render_helpers/mod.rs +++ b/src/render_helpers/mod.rs @@ -1,10 +1,15 @@ -use anyhow::Context; +use std::ptr; + +use anyhow::{ensure, Context}; use smithay::backend::allocator::Fourcc; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::{GlesMapping, GlesRenderer, GlesTexture}; use smithay::backend::renderer::sync::SyncPoint; -use smithay::backend::renderer::{Bind, ExportMem, Frame, Offscreen, Renderer}; +use smithay::backend::renderer::{buffer_dimensions, Bind, ExportMem, Frame, Offscreen, Renderer}; +use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer; +use smithay::reexports::wayland_server::protocol::wl_shm; use smithay::utils::{Physical, Rectangle, Scale, Size, Transform}; +use smithay::wayland::shm; pub mod gradient; pub mod offscreen; @@ -18,12 +23,12 @@ pub fn render_to_texture( renderer: &mut GlesRenderer, size: Size<i32, Physical>, scale: Scale<f64>, + transform: Transform, fourcc: Fourcc, elements: impl Iterator<Item = impl RenderElement<GlesRenderer>>, ) -> anyhow::Result<(GlesTexture, SyncPoint)> { let _span = tracy_client::span!(); - let output_rect = Rectangle::from_loc_and_size((0, 0), size); let buffer_size = size.to_logical(1).to_buffer(1, Transform::Normal); let texture: GlesTexture = renderer @@ -34,27 +39,7 @@ pub fn render_to_texture( .bind(texture.clone()) .context("error binding texture")?; - let mut frame = renderer - .render(size, Transform::Normal) - .context("error starting frame")?; - - frame - .clear([0., 0., 0., 0.], &[output_rect]) - .context("error clearing")?; - - for element in elements { - let src = element.src(); - let dst = element.geometry(scale); - - if let Some(mut damage) = output_rect.intersection(dst) { - damage.loc -= dst.loc; - element - .draw(&mut frame, src, dst, &[damage]) - .context("error drawing element")?; - } - } - - let sync_point = frame.finish().context("error finishing frame")?; + let sync_point = render_elements(renderer, size, scale, transform, elements)?; Ok((texture, sync_point)) } @@ -62,12 +47,13 @@ pub fn render_and_download( renderer: &mut GlesRenderer, size: Size<i32, Physical>, scale: Scale<f64>, + transform: Transform, fourcc: Fourcc, elements: impl Iterator<Item = impl RenderElement<GlesRenderer>>, ) -> anyhow::Result<GlesMapping> { let _span = tracy_client::span!(); - let (_, sync_point) = render_to_texture(renderer, size, scale, fourcc, elements)?; + let (_, sync_point) = render_to_texture(renderer, size, scale, transform, fourcc, elements)?; sync_point.wait(); let buffer_size = size.to_logical(1).to_buffer(1, Transform::Normal); @@ -81,13 +67,14 @@ pub fn render_to_vec( renderer: &mut GlesRenderer, size: Size<i32, Physical>, scale: Scale<f64>, + transform: Transform, fourcc: Fourcc, elements: impl Iterator<Item = impl RenderElement<GlesRenderer>>, ) -> anyhow::Result<Vec<u8>> { let _span = tracy_client::span!(); - let mapping = - render_and_download(renderer, size, scale, fourcc, elements).context("error rendering")?; + let mapping = render_and_download(renderer, size, scale, transform, fourcc, elements) + .context("error rendering")?; let copy = renderer .map_texture(&mapping) .context("error mapping texture")?; @@ -100,15 +87,66 @@ pub fn render_to_dmabuf( dmabuf: smithay::backend::allocator::dmabuf::Dmabuf, size: Size<i32, Physical>, scale: Scale<f64>, + transform: Transform, + elements: impl Iterator<Item = impl RenderElement<GlesRenderer>>, +) -> anyhow::Result<SyncPoint> { + let _span = tracy_client::span!(); + renderer.bind(dmabuf).context("error binding texture")?; + render_elements(renderer, size, scale, transform, elements) +} + +pub fn render_to_shm( + renderer: &mut GlesRenderer, + buffer: &WlBuffer, + scale: Scale<f64>, + transform: Transform, elements: impl Iterator<Item = impl RenderElement<GlesRenderer>>, ) -> anyhow::Result<()> { let _span = tracy_client::span!(); - let output_rect = Rectangle::from_loc_and_size((0, 0), size); + let buffer_size = buffer_dimensions(buffer).context("error getting buffer dimensions")?; + let size = buffer_size.to_logical(1, Transform::Normal).to_physical(1); + + let mapping = + render_and_download(renderer, size, scale, transform, Fourcc::Argb8888, elements)?; + let bytes = renderer + .map_texture(&mapping) + .context("error mapping texture")?; + + shm::with_buffer_contents_mut(buffer, |shm_buffer, shm_len, buffer_data| { + ensure!( + // The buffer prefers pixels in little endian ... + buffer_data.format == wl_shm::Format::Argb8888 + && buffer_data.stride == size.w * 4 + && buffer_data.height == size.h + && shm_len as i32 == buffer_data.stride * buffer_data.height, + "invalid buffer format or size" + ); + + ensure!(bytes.len() == shm_len, "mapped buffer has wrong length"); + + unsafe { + let _span = tracy_client::span!("copy_nonoverlapping"); + ptr::copy_nonoverlapping(bytes.as_ptr(), shm_buffer.cast(), shm_len); + } + + Ok(()) + }) + .context("expected shm buffer, but didn't get one")? +} + +fn render_elements( + renderer: &mut GlesRenderer, + size: Size<i32, Physical>, + scale: Scale<f64>, + transform: Transform, + elements: impl Iterator<Item = impl RenderElement<GlesRenderer>>, +) -> anyhow::Result<SyncPoint> { + let transform = transform.invert(); + let output_rect = Rectangle::from_loc_and_size((0, 0), transform.transform_size(size)); - renderer.bind(dmabuf).context("error binding texture")?; let mut frame = renderer - .render(size, Transform::Normal) + .render(size, transform) .context("error starting frame")?; frame @@ -127,7 +165,5 @@ pub fn render_to_dmabuf( } } - let _sync_point = frame.finish().context("error finishing frame")?; - - Ok(()) + frame.finish().context("error finishing frame") } diff --git a/src/render_helpers/offscreen.rs b/src/render_helpers/offscreen.rs index 69b0be84..72c2471b 100644 --- a/src/render_helpers/offscreen.rs +++ b/src/render_helpers/offscreen.rs @@ -54,6 +54,7 @@ impl OffscreenRenderElement { renderer, geo.size, Scale::from(scale as f64), + Transform::Normal, Fourcc::Abgr8888, elements, ) { |
