mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-26 21:58:10 +01:00
Finish winit screencopy
This commit is contained in:
parent
d18d3e4b17
commit
97511b001a
4 changed files with 455 additions and 446 deletions
|
@ -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}"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!(),
|
||||
|
|
|
@ -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?
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue