Finish winit screencopy

This commit is contained in:
Ottatop 2024-04-04 00:22:46 -05:00
parent d18d3e4b17
commit 97511b001a
4 changed files with 455 additions and 446 deletions

View file

@ -1449,287 +1449,289 @@ fn handle_pending_screencopy<'a>(
render_frame_result: &UdevRenderFrameResult<'a>,
loop_handle: &LoopHandle<'static, State>,
) {
if let Some(mut screencopy) = output.with_state_mut(|state| state.screencopy.take()) {
assert!(screencopy.output() == output);
let Some(mut screencopy) = output.with_state_mut(|state| state.screencopy.take()) else {
return;
};
assert!(screencopy.output() == output);
let untransformed_output_size = output.current_mode().expect("output no mode").size;
let untransformed_output_size = output.current_mode().expect("output no mode").size;
let scale = smithay::utils::Scale::from(output.current_scale().fractional_scale());
let scale = smithay::utils::Scale::from(output.current_scale().fractional_scale());
if screencopy.with_damage() {
if render_frame_result.is_empty {
output.with_state_mut(|state| state.screencopy.replace(screencopy));
return;
}
// Compute damage
//
// I have no idea if the damage event is supposed to send rects local to the output or to the
// region. Sway does the former, Hyprland the latter. Also, no one actually seems to be using the
// received damage. wf-recorder and wl-mirror have no-op handlers for the damage event.
let damage = match &render_frame_result.primary_element {
PrimaryPlaneElement::Swapchain(element) => {
let swapchain_commit =
&mut surface.screencopy_commit_state.primary_plane_swapchain;
let damage = element.damage.damage_since(Some(*swapchain_commit));
*swapchain_commit = element.damage.current_commit();
damage.map(|dmg| {
dmg.into_iter()
.map(|rect| {
rect.to_logical(1, Transform::Normal, &rect.size)
.to_physical(1)
})
.collect()
})
}
PrimaryPlaneElement::Element(element) => {
// INFO: Is this element guaranteed to be the same size as the
// | output? If not this becomes a
// FIXME: offset the damage by the element's location
//
// also is this even ever reachable?
let element_commit = &mut surface.screencopy_commit_state.primary_plane_element;
let damage = element.damage_since(scale, Some(*element_commit));
*element_commit = element.current_commit();
Some(damage)
}
}
.unwrap_or_else(|| {
// Returning `None` means the previous CommitCounter is too old or damage
// was reset, so damage the whole output
vec![Rectangle::from_loc_and_size(
Point::from((0, 0)),
untransformed_output_size,
)]
});
// INFO: This code is here for if the bug where `blit_frame_result` makes the area around
// | the cursor transparent is fixed/a workaround found.
// let cursor_damage = render_frame_result
// .cursor_element
// .map(|cursor| {
// let damage =
// cursor.damage_since(scale, Some(surface.screencopy_commit_state.cursor));
// new_commit_counters.cursor = cursor.current_commit();
// damage
// })
// .unwrap_or_default();
//
// damage.extend(cursor_damage);
//
// // The primary plane and cursor had no damage but something got rendered,
// // so it must be the cursor moving.
// //
// // We currently have overlay planes disabled, so we don't have to worry about that.
// if damage.is_empty() && !render_frame_result.is_empty {
// if let Some(cursor_elem) = render_frame_result.cursor_element {
// damage.push(cursor_elem.geometry(scale));
// }
// }
// INFO: Protocol states that `copy_with_damage` should wait until there is
// | damage to be copied.
// |.
// | Now, for region screencopies this currently submits the frame if there is
// | *any* damage on the output, not just in the region. I've found that
// | wf-recorder blocks until the last frame is submitted, and if I don't
// | send a submission because its region isn't damaged it will hang.
// | I'm fairly certain Sway is doing a similar thing.
if damage.is_empty() {
output.with_state_mut(|state| state.screencopy.replace(screencopy));
return;
}
screencopy.damage(&damage);
if screencopy.with_damage() {
if render_frame_result.is_empty {
output.with_state_mut(|state| state.screencopy.replace(screencopy));
return;
}
let sync_point = if let Ok(dmabuf) = dmabuf::get_dmabuf(screencopy.buffer()) {
trace!("Dmabuf screencopy");
// Compute damage
//
// I have no idea if the damage event is supposed to send rects local to the output or to the
// region. Sway does the former, Hyprland the latter. Also, no one actually seems to be using the
// received damage. wf-recorder and wl-mirror have no-op handlers for the damage event.
let format_correct =
Some(dmabuf.format().code) == shm_format_to_fourcc(wl_shm::Format::Argb8888);
let width_correct = dmabuf.width() == screencopy.physical_region().size.w as u32;
let height_correct = dmabuf.height() == screencopy.physical_region().size.h as u32;
if !(format_correct && width_correct && height_correct) {
return;
let damage = match &render_frame_result.primary_element {
PrimaryPlaneElement::Swapchain(element) => {
let swapchain_commit = &mut surface.screencopy_commit_state.primary_plane_swapchain;
let damage = element.damage.damage_since(Some(*swapchain_commit));
*swapchain_commit = element.damage.current_commit();
damage.map(|dmg| {
dmg.into_iter()
.map(|rect| {
rect.to_logical(1, Transform::Normal, &rect.size)
.to_physical(1)
})
.collect()
})
}
PrimaryPlaneElement::Element(element) => {
// INFO: Is this element guaranteed to be the same size as the
// | output? If not this becomes a
// FIXME: offset the damage by the element's location
//
// also is this even ever reachable?
let element_commit = &mut surface.screencopy_commit_state.primary_plane_element;
let damage = element.damage_since(scale, Some(*element_commit));
*element_commit = element.current_commit();
Some(damage)
}
}
.unwrap_or_else(|| {
// Returning `None` means the previous CommitCounter is too old or damage
// was reset, so damage the whole output
vec![Rectangle::from_loc_and_size(
Point::from((0, 0)),
untransformed_output_size,
)]
});
(|| -> anyhow::Result<Option<SyncPoint>> {
if screencopy.physical_region()
== Rectangle::from_loc_and_size(Point::from((0, 0)), untransformed_output_size)
{
// Optimization to not have to do an extra blit;
// just blit the whole output
renderer.bind(dmabuf)?;
// INFO: This code is here for if the bug where `blit_frame_result` makes the area around
// | the cursor transparent is fixed/a workaround found.
// let cursor_damage = render_frame_result
// .cursor_element
// .map(|cursor| {
// let damage =
// cursor.damage_since(scale, Some(surface.screencopy_commit_state.cursor));
// new_commit_counters.cursor = cursor.current_commit();
// damage
// })
// .unwrap_or_default();
//
// damage.extend(cursor_damage);
//
// // The primary plane and cursor had no damage but something got rendered,
// // so it must be the cursor moving.
// //
// // We currently have overlay planes disabled, so we don't have to worry about that.
// if damage.is_empty() && !render_frame_result.is_empty {
// if let Some(cursor_elem) = render_frame_result.cursor_element {
// damage.push(cursor_elem.geometry(scale));
// }
// }
Ok(Some(render_frame_result.blit_frame_result(
// INFO: Protocol states that `copy_with_damage` should wait until there is
// | damage to be copied.
// |.
// | Now, for region screencopies this currently submits the frame if there is
// | *any* damage on the output, not just in the region. I've found that
// | wf-recorder blocks until the last frame is submitted, and if I don't
// | send a submission because its region isn't damaged it will hang.
// | I'm fairly certain Sway is doing a similar thing.
if damage.is_empty() {
output.with_state_mut(|state| state.screencopy.replace(screencopy));
return;
}
screencopy.damage(&damage);
}
let sync_point = if let Ok(dmabuf) = dmabuf::get_dmabuf(screencopy.buffer()) {
trace!("Dmabuf screencopy");
let format_correct =
Some(dmabuf.format().code) == shm_format_to_fourcc(wl_shm::Format::Argb8888);
let width_correct = dmabuf.width() == screencopy.physical_region().size.w as u32;
let height_correct = dmabuf.height() == screencopy.physical_region().size.h as u32;
if !(format_correct && width_correct && height_correct) {
return;
}
(|| -> anyhow::Result<Option<SyncPoint>> {
if screencopy.physical_region()
== Rectangle::from_loc_and_size(Point::from((0, 0)), untransformed_output_size)
{
// Optimization to not have to do an extra blit;
// just blit the whole output
renderer.bind(dmabuf)?;
Ok(Some(render_frame_result.blit_frame_result(
screencopy.physical_region().size,
Transform::Normal,
output.current_scale().fractional_scale(),
renderer,
[screencopy.physical_region()],
[],
)?))
} else {
// `RenderFrameResult::blit_frame_result` doesn't expose a way to
// blit from a source rectangle, so blit into another buffer
// then blit from that into the dmabuf.
let output_buffer_size = untransformed_output_size
.to_logical(1)
.to_buffer(1, Transform::Normal);
let offscreen: GlesRenderbuffer = renderer.create_buffer(
smithay::backend::allocator::Fourcc::Abgr8888,
output_buffer_size,
)?;
renderer.bind(offscreen.clone())?;
let sync_point = render_frame_result.blit_frame_result(
untransformed_output_size,
Transform::Normal,
output.current_scale().fractional_scale(),
renderer,
[Rectangle::from_loc_and_size(
Point::from((0, 0)),
untransformed_output_size,
)],
[],
)?;
// ayo are we supposed to wait this here (granted it doesn't do anything
// because it's always ready but I want to be correct here)
//
// renderer.wait(&sync_point)?; // no-op
// INFO: I have literally no idea why but doing
// | a blit_to offscreen -> dmabuf leads to some weird
// | artifacting within the first few frames of a wf-recorder
// | recording, but doing it with the targets reversed
// | is completely fine???? Bruh that essentially runs the same internal
// | code and I don't understand why there's different behavior.
// |.
// | I can see in the code that `blit_to` is missing a `self.unbind()?`
// | call, but adding that back in doesn't fix anything. So strange
renderer.bind(dmabuf)?;
renderer.blit_from(
offscreen,
screencopy.physical_region(),
Rectangle::from_loc_and_size(
Point::from((0, 0)),
screencopy.physical_region().size,
Transform::Normal,
output.current_scale().fractional_scale(),
renderer,
[screencopy.physical_region()],
[],
)?))
} else {
// `RenderFrameResult::blit_frame_result` doesn't expose a way to
// blit from a source rectangle, so blit into another buffer
// then blit from that into the dmabuf.
),
TextureFilter::Linear,
)?;
let output_buffer_size = untransformed_output_size
.to_logical(1)
.to_buffer(1, Transform::Normal);
Ok(Some(sync_point))
}
})()
} else if !matches!(
renderer::buffer_type(screencopy.buffer()),
Some(BufferType::Shm)
) {
Err(anyhow!("not a shm buffer"))
} else {
trace!("Shm screencopy");
let offscreen: GlesRenderbuffer = renderer.create_buffer(
smithay::backend::allocator::Fourcc::Abgr8888,
output_buffer_size,
)?;
let res = smithay::wayland::shm::with_buffer_contents_mut(
&screencopy.buffer().clone(),
|shm_ptr, shm_len, buffer_data| {
// yoinked from Niri (thanks yall)
ensure!(
// The buffer prefers pixels in little endian ...
buffer_data.format == wl_shm::Format::Argb8888
&& buffer_data.stride == screencopy.physical_region().size.w * 4
&& buffer_data.height == screencopy.physical_region().size.h
&& shm_len as i32 == buffer_data.stride * buffer_data.height,
"invalid buffer format or size"
);
renderer.bind(offscreen.clone())?;
let src_buffer_rect = screencopy.physical_region().to_logical(1).to_buffer(
1,
Transform::Normal,
&screencopy.physical_region().size.to_logical(1),
);
let sync_point = render_frame_result.blit_frame_result(
let output_buffer_size = untransformed_output_size
.to_logical(1)
.to_buffer(1, Transform::Normal);
let offscreen: GlesRenderbuffer = renderer.create_buffer(
smithay::backend::allocator::Fourcc::Abgr8888,
output_buffer_size,
)?;
renderer.bind(offscreen)?;
// Blit the entire output to `offscreen`.
// Only the needed region will be copied below
let sync_point = render_frame_result.blit_frame_result(
untransformed_output_size,
Transform::Normal,
output.current_scale().fractional_scale(),
renderer,
[Rectangle::from_loc_and_size(
Point::from((0, 0)),
untransformed_output_size,
Transform::Normal,
output.current_scale().fractional_scale(),
renderer,
[Rectangle::from_loc_and_size(
Point::from((0, 0)),
untransformed_output_size,
)],
[],
)?;
)],
[],
)?;
// ayo are we supposed to wait this here (granted it doesn't do anything
// because it's always ready but I want to be correct here)
//
// renderer.wait(&sync_point)?; // no-op
// Can someone explain to me why it feels like some things are
// arbitrarily `Physical` or `Buffer`
let mapping = renderer.copy_framebuffer(
src_buffer_rect,
smithay::backend::allocator::Fourcc::Argb8888,
)?;
// INFO: I have literally no idea why but doing
// | a blit_to offscreen -> dmabuf leads to some weird
// | artifacting within the first few frames of a wf-recorder
// | recording, but doing it with the targets reversed
// | is completely fine???? Bruh that essentially runs the same internal
// | code and I don't understand why there's different behavior.
// |.
// | I can see in the code that `blit_to` is missing a `self.unbind()?`
// | call, but adding that back in doesn't fix anything. So strange
renderer.bind(dmabuf)?;
let bytes = renderer.map_texture(&mapping)?;
renderer.blit_from(
offscreen,
screencopy.physical_region(),
Rectangle::from_loc_and_size(
Point::from((0, 0)),
screencopy.physical_region().size,
),
TextureFilter::Linear,
)?;
ensure!(bytes.len() == shm_len, "mapped buffer has wrong length");
Ok(Some(sync_point))
// SAFETY: TODO: safety docs
unsafe {
std::ptr::copy_nonoverlapping(bytes.as_ptr(), shm_ptr, shm_len);
}
})()
} else if !matches!(
renderer::buffer_type(screencopy.buffer()),
Some(BufferType::Shm)
) {
Err(anyhow!("not a shm buffer"))
} else {
trace!("Shm screencopy");
let res = smithay::wayland::shm::with_buffer_contents_mut(
&screencopy.buffer().clone(),
|shm_ptr, shm_len, buffer_data| {
// yoinked from Niri (thanks yall)
ensure!(
// The buffer prefers pixels in little endian ...
buffer_data.format == wl_shm::Format::Argb8888
&& buffer_data.stride == screencopy.physical_region().size.w * 4
&& buffer_data.height == screencopy.physical_region().size.h
&& shm_len as i32 == buffer_data.stride * buffer_data.height,
"invalid buffer format or size"
);
Ok(Some(sync_point))
},
);
let src_buffer_rect = screencopy.physical_region().to_logical(1).to_buffer(
1,
Transform::Normal,
&screencopy.physical_region().size.to_logical(1),
);
let output_buffer_size = untransformed_output_size
.to_logical(1)
.to_buffer(1, Transform::Normal);
let offscreen: GlesRenderbuffer = renderer.create_buffer(
smithay::backend::allocator::Fourcc::Abgr8888,
output_buffer_size,
)?;
renderer.bind(offscreen)?;
// Blit the entire output to `offscreen`.
// Only the needed region will be copied below
let sync_point = render_frame_result.blit_frame_result(
untransformed_output_size,
Transform::Normal,
output.current_scale().fractional_scale(),
renderer,
[Rectangle::from_loc_and_size(
Point::from((0, 0)),
untransformed_output_size,
)],
[],
)?;
// Can someone explain to me why it feels like some things are
// arbitrarily `Physical` or `Buffer`
let mapping = renderer.copy_framebuffer(
src_buffer_rect,
smithay::backend::allocator::Fourcc::Argb8888,
)?;
let bytes = renderer.map_texture(&mapping)?;
ensure!(bytes.len() == shm_len, "mapped buffer has wrong length");
// SAFETY: TODO: safety docs
unsafe {
std::ptr::copy_nonoverlapping(bytes.as_ptr(), shm_ptr, shm_len);
}
Ok(Some(sync_point))
},
let Ok(res) = res else {
unreachable!(
"buffer is guaranteed to be shm from above and should be managed by the shm global"
);
let Ok(res) = res else {
unreachable!("buffer is guaranteed to be shm from above and should be managed by the shm global");
};
res
};
match sync_point {
Ok(Some(sync_point)) if !sync_point.is_reached() => {
let Some(sync_fd) = sync_point.export() else {
screencopy.submit(false);
return;
res
};
match sync_point {
Ok(Some(sync_point)) if !sync_point.is_reached() => {
let Some(sync_fd) = sync_point.export() else {
screencopy.submit(false);
return;
};
let mut screencopy = Some(screencopy);
let source = Generic::new(sync_fd, Interest::READ, calloop::Mode::OneShot);
let res = loop_handle.insert_source(source, move |_, _, _| {
let Some(screencopy) = screencopy.take() else {
unreachable!("This source is removed after one run");
};
let mut screencopy = Some(screencopy);
let source = Generic::new(sync_fd, Interest::READ, calloop::Mode::OneShot);
let res = loop_handle.insert_source(source, move |_, _, _| {
let Some(screencopy) = screencopy.take() else {
unreachable!("This source is removed after one run");
};
screencopy.submit(false);
trace!("Submitted screencopy");
Ok(PostAction::Remove)
});
if res.is_err() {
error!("Failed to schedule screencopy submission");
}
screencopy.submit(false);
trace!("Submitted screencopy");
Ok(PostAction::Remove)
});
if res.is_err() {
error!("Failed to schedule screencopy submission");
}
Ok(_) => screencopy.submit(false),
Err(err) => error!("Failed to submit screencopy: {err}"),
}
Ok(_) => screencopy.submit(false),
Err(err) => error!("Failed to submit screencopy: {err}"),
}
}

View file

@ -2,15 +2,16 @@
use std::{ffi::OsString, path::PathBuf, time::Duration};
use anyhow::anyhow;
use anyhow::{anyhow, ensure};
use smithay::{
backend::{
egl::EGLDevice,
renderer::{
self,
damage::{self, OutputDamageTracker},
gles::{GlesRenderer, GlesTexture},
Blit, BufferType, ImportDma, ImportEgl, ImportMemWl, TextureFilter,
self, buffer_type,
damage::{self, OutputDamageTracker, RenderOutputResult},
gles::{GlesRenderbuffer, GlesRenderer, GlesTexture},
Bind, Blit, BufferType, ExportMem, ImportDma, ImportEgl, ImportMemWl, Offscreen,
TextureFilter,
},
winit::{self, WinitEvent, WinitGraphicsBackend},
},
@ -22,10 +23,13 @@ use smithay::{
self,
generic::Generic,
timer::{TimeoutAction, Timer},
EventLoop, Interest, PostAction,
EventLoop, Interest, LoopHandle, PostAction,
},
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback,
wayland_server::{protocol::wl_surface::WlSurface, Display},
wayland_server::{
protocol::{wl_shm, wl_surface::WlSurface},
Display,
},
winit::{
platform::pump_events::PumpStatus,
window::{Icon, WindowBuilder},
@ -34,12 +38,12 @@ use smithay::{
utils::{IsAlive, Point, Rectangle, Transform},
wayland::dmabuf::{self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufState},
};
use tracing::{error, info};
use tracing::{error, trace, warn};
use crate::{
render::{
copy_to_shm_screencopy, generate_render_elements, pointer::PointerElement,
pointer_render_elements, take_presentation_feedback,
generate_render_elements, pointer::PointerElement, pointer_render_elements,
take_presentation_feedback,
},
state::{State, WithState},
};
@ -110,8 +114,6 @@ pub fn setup_winit(
let output = Output::new("Pinnacle window".to_string(), physical_properties);
output.create_global::<State>(&display_handle);
output.change_current_state(
Some(mode),
Some(Transform::Flipped180),
@ -137,11 +139,11 @@ pub fn setup_winit(
Some(dmabuf_default_feedback)
}
Ok(None) => {
tracing::warn!("failed to query render node, dmabuf will use v3");
warn!("failed to query render node, dmabuf will use v3");
None
}
Err(err) => {
tracing::warn!("{}", err);
warn!("{}", err);
None
}
};
@ -189,6 +191,9 @@ pub fn setup_winit(
config_dir,
)?;
// wl-mirror segfaults if it gets a wl-output global before the xdg output manager global
output.create_global::<State>(&display_handle);
state.output_focus_stack.set_focus(output.clone());
let winit = state.backend.winit_mut();
@ -342,92 +347,7 @@ impl State {
match render_res {
Ok(render_output_result) => {
'screencopy: {
// If there is a pending screencopy, copy to it
if let Some(mut screencopy) =
output.with_state_mut(|state| state.screencopy.take())
{
assert!(screencopy.output() == output);
if screencopy.with_damage() {
if let Some(damage) = render_output_result.damage.as_ref() {
let damage = damage
.iter()
.flat_map(|rect| {
rect.intersection(screencopy.physical_region())
})
.collect::<Vec<_>>();
if damage.is_empty() {
output.with_state_mut(|state| {
state.screencopy.replace(screencopy)
});
break 'screencopy;
}
screencopy.damage(&damage);
} else {
output.with_state_mut(|state| state.screencopy.replace(screencopy));
break 'screencopy;
}
}
let sync_fd = if let Ok(dmabuf) = dmabuf::get_dmabuf(screencopy.buffer()) {
info!("Dmabuf screencopy");
winit
.backend
.renderer()
.blit_to(
dmabuf,
screencopy.physical_region(),
Rectangle::from_loc_and_size(
Point::from((0, 0)),
screencopy.physical_region().size,
),
TextureFilter::Nearest,
)
.map(|_| render_output_result.sync.export())
.map_err(|err| anyhow!("{err}"))
} else if !matches!(
renderer::buffer_type(screencopy.buffer()),
Some(BufferType::Shm)
) {
Err(anyhow!("not a shm buffer"))
} else {
info!("Shm screencopy");
let res = copy_to_shm_screencopy(winit.backend.renderer(), &screencopy)
.map(|_| render_output_result.sync.export());
// We must rebind to the underlying EGL surface for buffer swapping
// as it is bound in `copy_to_shm_screencopy` above.
let _ = winit.backend.bind();
res
};
match sync_fd {
Ok(Some(sync_fd)) => {
let mut screencopy = Some(screencopy);
let source =
Generic::new(sync_fd, Interest::READ, calloop::Mode::OneShot);
let res = self.loop_handle.insert_source(source, move |_, _, _| {
let Some(screencopy) = screencopy.take() else {
unreachable!("This source is removed after one run");
};
screencopy.submit(false);
Ok(PostAction::Remove)
});
if res.is_err() {
error!("Failed to schedule screencopy submission");
}
}
Ok(None) => {
screencopy.submit(false);
}
Err(err) => error!("Failed to submit screencopy: {err}"),
}
}
}
winit.handle_pending_screencopy(output, &render_output_result, &self.loop_handle);
let has_rendered = render_output_result.damage.is_some();
if let Some(damage) = render_output_result.damage {
@ -467,8 +387,171 @@ impl State {
}
}
Err(err) => {
tracing::warn!("{}", err);
warn!("{}", err);
}
}
}
}
impl Winit {
fn handle_pending_screencopy(
&mut self,
output: &Output,
render_output_result: &RenderOutputResult,
loop_handle: &LoopHandle<'static, State>,
) {
let Some(mut screencopy) = output.with_state_mut(|state| state.screencopy.take()) else {
return;
};
assert!(screencopy.output() == output);
if screencopy.with_damage() {
match render_output_result.damage.as_ref() {
Some(damage) if !damage.is_empty() => screencopy.damage(damage),
_ => {
output.with_state_mut(|state| state.screencopy.replace(screencopy));
return;
}
}
}
let sync_point = if let Ok(dmabuf) = dmabuf::get_dmabuf(screencopy.buffer()) {
trace!("Dmabuf screencopy");
self.backend
.renderer()
.blit_to(
dmabuf,
screencopy.physical_region(),
Rectangle::from_loc_and_size(
Point::from((0, 0)),
screencopy.physical_region().size,
),
TextureFilter::Nearest,
)
.map(|_| render_output_result.sync.clone())
.map_err(|err| anyhow!("{err}"))
} else if !matches!(
renderer::buffer_type(screencopy.buffer()),
Some(BufferType::Shm)
) {
Err(anyhow!("not a shm buffer"))
} else {
trace!("Shm screencopy");
let sync_point = {
let renderer = self.backend.renderer();
let screencopy = &screencopy;
if !matches!(buffer_type(screencopy.buffer()), Some(BufferType::Shm)) {
warn!("screencopy does not have a shm buffer");
return;
}
let res = smithay::wayland::shm::with_buffer_contents_mut(
&screencopy.buffer().clone(),
|shm_ptr, shm_len, buffer_data| {
// yoinked from Niri (thanks yall)
ensure!(
// The buffer prefers pixels in little endian ...
buffer_data.format == wl_shm::Format::Argb8888
&& buffer_data.stride == screencopy.physical_region().size.w * 4
&& buffer_data.height == screencopy.physical_region().size.h
&& shm_len as i32 == buffer_data.stride * buffer_data.height,
"invalid buffer format or size"
);
let buffer_rect = screencopy.physical_region().to_logical(1).to_buffer(
1,
Transform::Normal,
&screencopy.physical_region().size.to_logical(1),
);
// On winit, we cannot just copy the EGL framebuffer because I get an
// `UnsupportedPixelFormat` error. Therefore we'll blit
// to this buffer and then copy it.
let offscreen: GlesRenderbuffer = renderer.create_buffer(
smithay::backend::allocator::Fourcc::Argb8888,
buffer_rect.size,
)?;
renderer.blit_to(
offscreen.clone(),
screencopy.physical_region(),
Rectangle::from_loc_and_size(
Point::from((0, 0)),
screencopy.physical_region().size,
),
TextureFilter::Nearest,
)?;
renderer.bind(offscreen)?;
let mapping = renderer.copy_framebuffer(
Rectangle::from_loc_and_size(Point::from((0, 0)), buffer_rect.size),
smithay::backend::allocator::Fourcc::Argb8888,
)?;
let bytes = renderer.map_texture(&mapping)?;
ensure!(bytes.len() == shm_len, "mapped buffer has wrong length");
// SAFETY:
// - `bytes.as_ptr()` is valid for reads of size `shm_len` because that was
// checked above and is properly aligned because it
// originated from safe Rust
// - We are assuming `shm_ptr` is valid for writes of `shm_len` and is
// properly aligned
// - Overlapping-ness: TODO:
unsafe {
std::ptr::copy_nonoverlapping(bytes.as_ptr(), shm_ptr, shm_len);
}
Ok(())
},
);
let Ok(res) = res else {
unreachable!(
"buffer is guaranteed to be shm from above and managed by smithay"
);
};
res
}
.map(|_| render_output_result.sync.clone());
// We must rebind to the underlying EGL surface for buffer swapping
// as it is bound to a `GlesRenderbuffer` above.
if let Err(err) = self.backend.bind() {
error!("Failed to rebind EGL surface after screencopy: {err}");
}
sync_point
};
match sync_point {
Ok(sync_point) if !sync_point.is_reached() => {
let Some(sync_fd) = sync_point.export() else {
screencopy.submit(false);
return;
};
let mut screencopy = Some(screencopy);
let source = Generic::new(sync_fd, Interest::READ, calloop::Mode::OneShot);
let res = loop_handle.insert_source(source, move |_, _, _| {
let Some(screencopy) = screencopy.take() else {
unreachable!("This source is removed after one run");
};
screencopy.submit(false);
trace!("Submitted screencopy");
Ok(PostAction::Remove)
});
if res.is_err() {
error!("Failed to schedule screencopy submission");
}
}
Ok(_) => screencopy.submit(false),
Err(err) => error!("Failed to submit screencopy: {err}"),
}
}
}

View file

@ -130,28 +130,31 @@ where
let output = Output::from_resource(&output).expect("no output for resource");
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 output_transformed_physical_size = output_transform
.transform_size(output.current_mode().expect("output no mode").size);
let output_transformed_rect =
Rectangle::from_loc_and_size((0, 0), output_transformed_physical_size);
let rect = Rectangle::from_loc_and_size((x, y), (width, height));
// This is in the transformed space
let screencopy_region = Rectangle::from_loc_and_size((x, y), (width, height));
let output_scale = output.current_scale().fractional_scale();
let physical_rect = rect.to_physical_precise_round(output_scale);
let physical_rect = screencopy_region.to_physical_precise_round(output_scale);
// Clamp captured region to the output.
let Some(clamped_rect) = physical_rect.intersection(output_rect) else {
let Some(clamped_rect) = physical_rect.intersection(output_transformed_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
// Untransform the region to the actual physical rect
let untransformed_region = output_transform
.invert()
.transform_rect_in(clamped_rect, &output_physical_size);
.transform_rect_in(clamped_rect, &output_transformed_physical_size);
(frame, overlay_cursor, untransformed_rect, output)
(frame, overlay_cursor, untransformed_region, output)
}
zwlr_screencopy_manager_v1::Request::Destroy => return,
_ => unreachable!(),

View file

@ -2,18 +2,14 @@
use std::{ops::Deref, sync::Mutex};
use anyhow::ensure;
use smithay::{
backend::renderer::{
buffer_type,
element::{
surface::WaylandSurfaceRenderElement,
utils::{CropRenderElement, RelocateRenderElement, RescaleRenderElement},
AsRenderElements, RenderElementStates, Wrap,
},
gles::GlesRenderbuffer,
Blit, BufferType, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, Texture,
TextureFilter,
ImportAll, ImportMem, Renderer, Texture,
},
desktop::{
layer_map_for_output,
@ -28,16 +24,15 @@ use smithay::{
output::Output,
reexports::{
wayland_protocols::xdg::shell::server::xdg_toplevel,
wayland_server::protocol::{wl_shm, wl_surface::WlSurface},
wayland_server::protocol::wl_surface::WlSurface,
},
render_elements,
utils::{Logical, Physical, Point, Rectangle, Scale, Transform},
utils::{Logical, Physical, Point, Scale},
wayland::{compositor, shell::wlr_layer},
};
use crate::{
backend::Backend,
protocol::screencopy::Screencopy,
state::{State, WithState},
window::WindowElement,
};
@ -405,77 +400,3 @@ impl State {
}
}
}
/// Copy the currently bound framebuffer into the given screencopy shm buffer.
///
/// This will bind the renderer to a [`GlesRenderbuffer`]. You may need to rebind to the previous
/// target afterwards.
pub fn copy_to_shm_screencopy<R>(renderer: &mut R, screencopy: &Screencopy) -> anyhow::Result<()>
where
R: Renderer + Blit<GlesRenderbuffer> + ExportMem + Offscreen<GlesRenderbuffer>,
<R as Renderer>::Error: Send + Sync + 'static,
{
ensure!(
matches!(buffer_type(screencopy.buffer()), Some(BufferType::Shm)),
"screencopy does not have a shm buffer"
);
let res = smithay::wayland::shm::with_buffer_contents_mut(
&screencopy.buffer().clone(),
|shm_ptr, shm_len, buffer_data| {
// yoinked from Niri (thanks yall)
ensure!(
// The buffer prefers pixels in little endian ...
buffer_data.format == wl_shm::Format::Argb8888
&& buffer_data.stride == screencopy.physical_region().size.w * 4
&& buffer_data.height == screencopy.physical_region().size.h
&& shm_len as i32 == buffer_data.stride * buffer_data.height,
"invalid buffer format or size"
);
let buffer_rect = screencopy.physical_region().to_logical(1).to_buffer(
1,
Transform::Normal,
&screencopy.physical_region().size.to_logical(1),
);
// On winit, we cannot just copy the EGL framebuffer because I get an
// `UnsupportedPixelFormat` error. Therefore we'll blit
// to this buffer and then copy it.
let offscreen: GlesRenderbuffer = renderer.create_buffer(
smithay::backend::allocator::Fourcc::Argb8888,
buffer_rect.size,
)?;
renderer.blit_to(
offscreen.clone(),
screencopy.physical_region(),
Rectangle::from_loc_and_size(
Point::from((0, 0)),
screencopy.physical_region().size,
),
TextureFilter::Nearest,
)?;
renderer.bind(offscreen)?;
let mapping = renderer.copy_framebuffer(
Rectangle::from_loc_and_size(Point::from((0, 0)), buffer_rect.size),
smithay::backend::allocator::Fourcc::Argb8888,
)?;
let bytes = renderer.map_texture(&mapping)?;
ensure!(bytes.len() == shm_len, "mapped buffer has wrong length");
// SAFETY: TODO: safety docs
unsafe {
std::ptr::copy_nonoverlapping(bytes.as_ptr(), shm_ptr, shm_len);
}
Ok(())
},
);
res?
}