Implement wlr-screencopy v1 (#243)

* Implement wlr-screencopy

* Finish the implementation

Lots of changes, mainly to fix transform handling. Turns out, grim
expects transformed buffers and untransforms them by itself using info
from wl_output. This means that render helpers needed to learn how to
actually render transformed buffers.

Also, it meant that y_invert is no longer needed.

Next, moved the rendering to the Screencopy frame handler. Turns out,
copy() is more or less expected to return immediately, whereas
copy_with_damage() is expected to wait until the next VBlank. At least
that's the intent I parse reading the protocol.

Finally, brought the version from 3 down to 1, because
copy_with_damage() will need bigger changes. Grim still works, others
not really, mainly because they bind v3 unnecessarily, even if they
don't use the damage request.

---------

Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
This commit is contained in:
sodiboo 2024-03-08 13:10:55 +01:00 committed by GitHub
parent 1a784e6e66
commit ca22e70cc4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 566 additions and 63 deletions

View File

@ -7,11 +7,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1707685877, "lastModified": 1709610799,
"narHash": "sha256-XoXRS+5whotelr1rHiZle5t5hDg9kpguS5yk8c8qzOc=", "narHash": "sha256-5jfLQx0U9hXbi2skYMGodDJkIgffrjIOgMRjZqms2QE=",
"owner": "ipetkov", "owner": "ipetkov",
"repo": "crane", "repo": "crane",
"rev": "2c653e4478476a52c6aa3ac0495e4dea7449ea0e", "rev": "81c393c776d5379c030607866afef6406ca1be57",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -28,11 +28,11 @@
"rust-analyzer-src": "rust-analyzer-src" "rust-analyzer-src": "rust-analyzer-src"
}, },
"locked": { "locked": {
"lastModified": 1706768574, "lastModified": 1709274179,
"narHash": "sha256-4o6TMpzBHO659EiJTzd/EGQGUDdbgwKwhqf3u6b23U8=", "narHash": "sha256-O6EC6QELBLHzhdzBOJj0chx8AOcd4nDRECIagfT5Nd0=",
"owner": "nix-community", "owner": "nix-community",
"repo": "fenix", "repo": "fenix",
"rev": "668102037129923cd0fc239d864fce71eabdc6a3", "rev": "4be608f4f81d351aacca01b21ffd91028c23cc22",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -47,11 +47,11 @@
"systems": "systems" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1705309234, "lastModified": 1709126324,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", "narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", "rev": "d465f4819400de7c8d874d50b982301f28a84605",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -77,11 +77,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1707619277, "lastModified": 1709386671,
"narHash": "sha256-vKnYD5GMQbNQyyQm4wRlqi+5n0/F1hnvqSQgaBy4BqY=", "narHash": "sha256-VPqfBnIJ+cfa78pd4Y5Cr6sOWVW8GYHRVucxJGmRf8Q=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "f3a93440fbfff8a74350f4791332a19282cc6dc8", "rev": "fa9a51752f1b5de583ad5213eb621be071806663",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -103,11 +103,11 @@
"rust-analyzer-src": { "rust-analyzer-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1706735270, "lastModified": 1709219524,
"narHash": "sha256-IJk+UitcJsxzMQWm9pa1ZbJBriQ4ginXOlPyVq+Cu40=", "narHash": "sha256-8HHRXm4kYQLdUohNDUuCC3Rge7fXrtkjBUf0GERxrkM=",
"owner": "rust-lang", "owner": "rust-lang",
"repo": "rust-analyzer", "repo": "rust-analyzer",
"rev": "42cb1a2bd79af321b0cc503d2960b73f34e2f92b", "rev": "9efa23c4dacee88b93540632eb3d88c5dfebfe17",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -54,12 +54,13 @@ use smithay::{
delegate_text_input_manager, delegate_virtual_keyboard_manager, delegate_text_input_manager, delegate_virtual_keyboard_manager,
}; };
use crate::delegate_foreign_toplevel;
use crate::niri::{ClientState, State}; use crate::niri::{ClientState, State};
use crate::protocols::foreign_toplevel::{ use crate::protocols::foreign_toplevel::{
self, ForeignToplevelHandler, ForeignToplevelManagerState, self, ForeignToplevelHandler, ForeignToplevelManagerState,
}; };
use crate::protocols::screencopy::{Screencopy, ScreencopyHandler};
use crate::utils::output_size; use crate::utils::output_size;
use crate::{delegate_foreign_toplevel, delegate_screencopy};
impl SeatHandler for State { impl SeatHandler for State {
type KeyboardFocus = WlSurface; type KeyboardFocus = WlSurface;
@ -380,6 +381,18 @@ impl ForeignToplevelHandler for State {
} }
delegate_foreign_toplevel!(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 { impl DrmLeaseHandler for State {
fn drm_lease_state(&mut self, node: DrmNode) -> &mut DrmLeaseState { fn drm_lease_state(&mut self, node: DrmNode) -> &mut DrmLeaseState {
&mut self &mut self

View File

@ -9,7 +9,7 @@ use std::time::{Duration, Instant};
use std::{env, mem, thread}; use std::{env, mem, thread};
use _server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as KdeDecorationsMode; 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 calloop::futures::Scheduler;
use niri_config::{Config, TrackLayout}; use niri_config::{Config, TrackLayout};
use smithay::backend::allocator::Fourcc; use smithay::backend::allocator::Fourcc;
@ -18,7 +18,9 @@ use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRen
use smithay::backend::renderer::element::surface::{ use smithay::backend::renderer::element::surface::{
render_elements_from_surface_tree, WaylandSurfaceRenderElement, 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::{ use smithay::backend::renderer::element::{
default_primary_scanout_output_compare, AsRenderElements, Id, Kind, PrimaryScanoutOutput, default_primary_scanout_output_compare, AsRenderElements, Id, Kind, PrimaryScanoutOutput,
RenderElementStates, RenderElementStates,
@ -97,9 +99,10 @@ use crate::input::{apply_libinput_settings, TabletData};
use crate::ipc::server::IpcServer; use crate::ipc::server::IpcServer;
use crate::layout::{Layout, MonitorRenderElement}; use crate::layout::{Layout, MonitorRenderElement};
use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState}; use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState};
use crate::protocols::screencopy::{Screencopy, ScreencopyManagerState};
use crate::pw_utils::{Cast, PipeWire}; use crate::pw_utils::{Cast, PipeWire};
use crate::render_helpers::renderer::NiriRenderer; 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::config_error_notification::ConfigErrorNotification;
use crate::ui::exit_confirm_dialog::ExitConfirmDialog; use crate::ui::exit_confirm_dialog::ExitConfirmDialog;
use crate::ui::hotkey_overlay::HotkeyOverlay; use crate::ui::hotkey_overlay::HotkeyOverlay;
@ -154,6 +157,7 @@ pub struct Niri {
pub layer_shell_state: WlrLayerShellState, pub layer_shell_state: WlrLayerShellState,
pub session_lock_state: SessionLockManagerState, pub session_lock_state: SessionLockManagerState,
pub foreign_toplevel_state: ForeignToplevelManagerState, pub foreign_toplevel_state: ForeignToplevelManagerState,
pub screencopy_state: ScreencopyManagerState,
pub shm_state: ShmState, pub shm_state: ShmState,
pub output_manager_state: OutputManagerState, pub output_manager_state: OutputManagerState,
pub dmabuf_state: DmabufState, pub dmabuf_state: DmabufState,
@ -910,6 +914,9 @@ impl Niri {
ForeignToplevelManagerState::new::<State, _>(&display_handle, |client| { ForeignToplevelManagerState::new::<State, _>(&display_handle, |client| {
!client.get_data::<ClientState>().unwrap().restricted !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()); let mut seat: Seat<State> = seat_state.new_wl_seat(&display_handle, backend.seat_name());
seat.add_keyboard( seat.add_keyboard(
@ -1030,6 +1037,7 @@ impl Niri {
layer_shell_state, layer_shell_state,
session_lock_state, session_lock_state,
foreign_toplevel_state, foreign_toplevel_state,
screencopy_state,
text_input_state, text_input_state,
input_method_state, input_method_state,
virtual_keyboard_state, virtual_keyboard_state,
@ -2166,13 +2174,11 @@ impl Niri {
// to err on the safe side. // to err on the safe side.
self.send_frame_callbacks(output); self.send_frame_callbacks(output);
// Render and send to PipeWire screencast streams. backend.with_primary_renderer(|renderer| {
#[cfg(feature = "xdp-gnome-screencast")] // 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);
self.render_for_screen_cast(renderer, output, target_presentation_time); });
});
}
} }
pub fn update_primary_scanout_output( pub fn update_primary_scanout_output(
@ -2586,7 +2592,9 @@ impl Niri {
.get_or_insert_with(|| self.render::<GlesRenderer>(renderer, output, true)); .get_or_insert_with(|| self.render::<GlesRenderer>(renderer, output, true));
let elements = elements.iter().rev(); 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:?}"); warn!("error rendering to dmabuf: {err:?}");
continue; 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")] #[cfg(feature = "xdp-gnome-screencast")]
fn stop_cast(&mut self, session_id: usize) { fn stop_cast(&mut self, session_id: usize) {
let _span = tracy_client::span!("Niri::stop_cast"); let _span = tracy_client::span!("Niri::stop_cast");
@ -2665,7 +2709,14 @@ impl Niri {
let elements = self.render::<GlesRenderer>(renderer, &output, true); let elements = self.render::<GlesRenderer>(renderer, &output, true);
let elements = elements.iter().rev(); 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 { let screenshot = match res {
Ok((texture, _)) => texture, Ok((texture, _)) => texture,
Err(err) => { Err(err) => {
@ -2695,7 +2746,14 @@ impl Niri {
let scale = Scale::from(output.current_scale().fractional_scale()); let scale = Scale::from(output.current_scale().fractional_scale());
let elements = self.render::<GlesRenderer>(renderer, output, true); let elements = self.render::<GlesRenderer>(renderer, output, true);
let elements = elements.iter().rev(); 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) self.save_screenshot(size, pixels)
.context("error saving screenshot") .context("error saving screenshot")
@ -2721,7 +2779,14 @@ impl Niri {
1., 1.,
); );
let elements = elements.iter().rev(); 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) self.save_screenshot(size, pixels)
.context("error saving screenshot") .context("error saving screenshot")
@ -2824,6 +2889,7 @@ impl Niri {
renderer, renderer,
size, size,
Scale::from(f64::from(output_scale)), Scale::from(f64::from(output_scale)),
Transform::Normal,
Fourcc::Abgr8888, Fourcc::Abgr8888,
elements, elements,
)?; )?;

View File

@ -1 +1,2 @@
pub mod foreign_toplevel; pub mod foreign_toplevel;
pub mod screencopy;

386
src/protocols/screencopy.rs Normal file
View File

@ -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();
// }
// }
// }
}

View File

@ -1,10 +1,15 @@
use anyhow::Context; use std::ptr;
use anyhow::{ensure, Context};
use smithay::backend::allocator::Fourcc; use smithay::backend::allocator::Fourcc;
use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::{GlesMapping, GlesRenderer, GlesTexture}; use smithay::backend::renderer::gles::{GlesMapping, GlesRenderer, GlesTexture};
use smithay::backend::renderer::sync::SyncPoint; 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::utils::{Physical, Rectangle, Scale, Size, Transform};
use smithay::wayland::shm;
pub mod gradient; pub mod gradient;
pub mod offscreen; pub mod offscreen;
@ -18,12 +23,12 @@ pub fn render_to_texture(
renderer: &mut GlesRenderer, renderer: &mut GlesRenderer,
size: Size<i32, Physical>, size: Size<i32, Physical>,
scale: Scale<f64>, scale: Scale<f64>,
transform: Transform,
fourcc: Fourcc, fourcc: Fourcc,
elements: impl Iterator<Item = impl RenderElement<GlesRenderer>>, elements: impl Iterator<Item = impl RenderElement<GlesRenderer>>,
) -> anyhow::Result<(GlesTexture, SyncPoint)> { ) -> anyhow::Result<(GlesTexture, SyncPoint)> {
let _span = tracy_client::span!(); 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 buffer_size = size.to_logical(1).to_buffer(1, Transform::Normal);
let texture: GlesTexture = renderer let texture: GlesTexture = renderer
@ -34,27 +39,7 @@ pub fn render_to_texture(
.bind(texture.clone()) .bind(texture.clone())
.context("error binding texture")?; .context("error binding texture")?;
let mut frame = renderer let sync_point = render_elements(renderer, size, scale, transform, elements)?;
.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")?;
Ok((texture, sync_point)) Ok((texture, sync_point))
} }
@ -62,12 +47,13 @@ pub fn render_and_download(
renderer: &mut GlesRenderer, renderer: &mut GlesRenderer,
size: Size<i32, Physical>, size: Size<i32, Physical>,
scale: Scale<f64>, scale: Scale<f64>,
transform: Transform,
fourcc: Fourcc, fourcc: Fourcc,
elements: impl Iterator<Item = impl RenderElement<GlesRenderer>>, elements: impl Iterator<Item = impl RenderElement<GlesRenderer>>,
) -> anyhow::Result<GlesMapping> { ) -> anyhow::Result<GlesMapping> {
let _span = tracy_client::span!(); 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(); sync_point.wait();
let buffer_size = size.to_logical(1).to_buffer(1, Transform::Normal); let buffer_size = size.to_logical(1).to_buffer(1, Transform::Normal);
@ -81,13 +67,14 @@ pub fn render_to_vec(
renderer: &mut GlesRenderer, renderer: &mut GlesRenderer,
size: Size<i32, Physical>, size: Size<i32, Physical>,
scale: Scale<f64>, scale: Scale<f64>,
transform: Transform,
fourcc: Fourcc, fourcc: Fourcc,
elements: impl Iterator<Item = impl RenderElement<GlesRenderer>>, elements: impl Iterator<Item = impl RenderElement<GlesRenderer>>,
) -> anyhow::Result<Vec<u8>> { ) -> anyhow::Result<Vec<u8>> {
let _span = tracy_client::span!(); let _span = tracy_client::span!();
let mapping = let mapping = render_and_download(renderer, size, scale, transform, fourcc, elements)
render_and_download(renderer, size, scale, fourcc, elements).context("error rendering")?; .context("error rendering")?;
let copy = renderer let copy = renderer
.map_texture(&mapping) .map_texture(&mapping)
.context("error mapping texture")?; .context("error mapping texture")?;
@ -100,15 +87,66 @@ pub fn render_to_dmabuf(
dmabuf: smithay::backend::allocator::dmabuf::Dmabuf, dmabuf: smithay::backend::allocator::dmabuf::Dmabuf,
size: Size<i32, Physical>, size: Size<i32, Physical>,
scale: Scale<f64>, 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>>, elements: impl Iterator<Item = impl RenderElement<GlesRenderer>>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let _span = tracy_client::span!(); 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 let mut frame = renderer
.render(size, Transform::Normal) .render(size, transform)
.context("error starting frame")?; .context("error starting frame")?;
frame frame
@ -127,7 +165,5 @@ pub fn render_to_dmabuf(
} }
} }
let _sync_point = frame.finish().context("error finishing frame")?; frame.finish().context("error finishing frame")
Ok(())
} }

View File

@ -54,6 +54,7 @@ impl OffscreenRenderElement {
renderer, renderer,
geo.size, geo.size,
Scale::from(scale as f64), Scale::from(scale as f64),
Transform::Normal,
Fourcc::Abgr8888, Fourcc::Abgr8888,
elements, elements,
) { ) {