mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-26 21:58:10 +01:00
Make dmabuf udev screencopy actually work
Well that wasn't fun. TODO: finish winit screencopy, test all with output transforms
This commit is contained in:
parent
4c41fe1f65
commit
d18d3e4b17
3 changed files with 413 additions and 232 deletions
|
@ -17,7 +17,7 @@ use smithay::{
|
|||
dmabuf::{AnyError, Dmabuf, DmabufAllocator},
|
||||
gbm::{GbmAllocator, GbmBufferFlags, GbmDevice},
|
||||
vulkan::{ImageUsageFlags, VulkanAllocator},
|
||||
Allocator, Fourcc,
|
||||
Allocator, Buffer, Fourcc,
|
||||
},
|
||||
drm::{
|
||||
compositor::{DrmCompositor, PrimaryPlaneElement, RenderFrameResult},
|
||||
|
@ -29,12 +29,15 @@ use smithay::{
|
|||
libinput::{LibinputInputBackend, LibinputSessionInterface},
|
||||
renderer::{
|
||||
self, damage,
|
||||
element::{self, texture::TextureBuffer, Element, RenderElement},
|
||||
element::{
|
||||
self, surface::WaylandSurfaceRenderElement, texture::TextureBuffer, Element,
|
||||
},
|
||||
gles::{GlesRenderbuffer, GlesRenderer},
|
||||
multigpu::{gbm::GbmGlesBackend, GpuManager, MultiRenderer, MultiTexture},
|
||||
sync::SyncPoint,
|
||||
utils::CommitCounter,
|
||||
Bind, BufferType, ExportMem, ImportDma, ImportEgl, ImportMemWl, Offscreen, Renderer,
|
||||
TextureFilter,
|
||||
Bind, Blit, BufferType, ExportMem, ImportDma, ImportEgl, ImportMemWl, Offscreen,
|
||||
Renderer, TextureFilter,
|
||||
},
|
||||
session::{
|
||||
self,
|
||||
|
@ -72,16 +75,22 @@ use smithay::{
|
|||
},
|
||||
},
|
||||
utils::{DeviceFd, IsAlive, Point, Rectangle, Transform},
|
||||
wayland::dmabuf::{self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufState},
|
||||
wayland::{
|
||||
dmabuf::{self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufState},
|
||||
shm::shm_format_to_fourcc,
|
||||
},
|
||||
};
|
||||
use smithay_drm_extras::drm_scanner::{DrmScanEvent, DrmScanner};
|
||||
use tracing::{debug, error, warn};
|
||||
use tracing::{error, info, trace, warn};
|
||||
|
||||
use crate::{
|
||||
backend::Backend,
|
||||
config::ConnectorSavedState,
|
||||
output::OutputName,
|
||||
render::{pointer::PointerElement, pointer_render_elements, take_presentation_feedback},
|
||||
render::{
|
||||
pointer::PointerElement, pointer_render_elements, take_presentation_feedback,
|
||||
OutputRenderElements,
|
||||
},
|
||||
state::{State, SurfaceDmabufFeedback, WithState},
|
||||
};
|
||||
|
||||
|
@ -98,7 +107,6 @@ const SUPPORTED_FORMATS: &[Fourcc] = &[
|
|||
const SUPPORTED_FORMATS_8BIT_ONLY: &[Fourcc] = &[Fourcc::Abgr8888, Fourcc::Argb8888];
|
||||
|
||||
/// A [`MultiRenderer`] that uses the [`GbmGlesBackend`].
|
||||
#[allow(dead_code)] // dunno if i'm gonna need this again
|
||||
type UdevRenderer<'a> = MultiRenderer<
|
||||
'a,
|
||||
'a,
|
||||
|
@ -106,6 +114,13 @@ type UdevRenderer<'a> = MultiRenderer<
|
|||
GbmGlesBackend<GlesRenderer, DrmDeviceFd>,
|
||||
>;
|
||||
|
||||
type UdevRenderFrameResult<'a> = RenderFrameResult<
|
||||
'a,
|
||||
BufferObject<()>,
|
||||
GbmFramebuffer,
|
||||
OutputRenderElements<UdevRenderer<'a>, WaylandSurfaceRenderElement<UdevRenderer<'a>>>,
|
||||
>;
|
||||
|
||||
/// Udev state attached to each [`Output`].
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct UdevOutputData {
|
||||
|
@ -313,9 +328,15 @@ pub fn setup_udev(
|
|||
.find_map(|x| DrmNode::from_path(x).ok())
|
||||
.expect("No GPU!")
|
||||
});
|
||||
tracing::info!("Using {} as primary gpu.", primary_gpu);
|
||||
info!("Using {} as primary gpu.", primary_gpu);
|
||||
|
||||
let gpu_manager = GpuManager::new(GbmGlesBackend::default())?;
|
||||
// let gpu_manager = GpuManager::new(GbmGlesBackend::with_factory(|egl| {
|
||||
// let ctx = EGLContext::new(egl)?;
|
||||
// let mut supported = unsafe { GlesRenderer::supported_capabilities(&ctx) }?;
|
||||
// supported.retain(|cap| cap != &Capability::ColorTransformations);
|
||||
// Ok(unsafe { GlesRenderer::with_capabilities(ctx, supported) }?)
|
||||
// }))?;
|
||||
|
||||
let data = Udev {
|
||||
display_handle: display.handle(),
|
||||
|
@ -415,14 +436,14 @@ pub fn setup_udev(
|
|||
match event {
|
||||
session::Event::PauseSession => {
|
||||
libinput_context.suspend();
|
||||
tracing::info!("pausing session");
|
||||
info!("pausing session");
|
||||
|
||||
for backend in udev.backends.values_mut() {
|
||||
backend.drm.pause();
|
||||
}
|
||||
}
|
||||
session::Event::ActivateSession => {
|
||||
tracing::info!("resuming session");
|
||||
info!("resuming session");
|
||||
|
||||
if let Err(err) = libinput_context.resume() {
|
||||
error!("Failed to resume libinput context: {:?}", err);
|
||||
|
@ -509,7 +530,7 @@ pub fn setup_udev(
|
|||
}
|
||||
|
||||
if udev.allocator.is_none() {
|
||||
tracing::info!("No vulkan allocator found, using GBM.");
|
||||
info!("No vulkan allocator found, using GBM.");
|
||||
let gbm = udev
|
||||
.backends
|
||||
.get(&primary_gpu)
|
||||
|
@ -527,13 +548,13 @@ pub fn setup_udev(
|
|||
|
||||
let mut renderer = udev.gpu_manager.single_renderer(&primary_gpu)?;
|
||||
|
||||
tracing::info!(
|
||||
info!(
|
||||
?primary_gpu,
|
||||
"Trying to initialize EGL Hardware Acceleration",
|
||||
);
|
||||
|
||||
match renderer.bind_wl_display(&display_handle) {
|
||||
Ok(_) => tracing::info!("EGL hardware-acceleration enabled"),
|
||||
Ok(_) => info!("EGL hardware-acceleration enabled"),
|
||||
Err(err) => error!(?err, "Failed to initialize EGL hardware-acceleration"),
|
||||
}
|
||||
|
||||
|
@ -705,8 +726,14 @@ struct RenderSurface {
|
|||
compositor: GbmDrmCompositor,
|
||||
dmabuf_feedback: Option<DrmSurfaceDmabufFeedback>,
|
||||
render_state: RenderState,
|
||||
primary_plane_element_swapchain_commit_counter: CommitCounter,
|
||||
primary_plane_element_element_commit_counter: CommitCounter,
|
||||
screencopy_commit_state: ScreencopyCommitState,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
struct ScreencopyCommitState {
|
||||
primary_plane_swapchain: CommitCounter,
|
||||
primary_plane_element: CommitCounter,
|
||||
_cursor: CommitCounter,
|
||||
}
|
||||
|
||||
impl Drop for RenderSurface {
|
||||
|
@ -728,18 +755,15 @@ type GbmDrmCompositor = DrmCompositor<
|
|||
/// Render a frame with the given elements.
|
||||
///
|
||||
/// This frame needs to be queued for scanout afterwards.
|
||||
fn render_frame<'a, R, E>(
|
||||
fn render_frame<'a>(
|
||||
compositor: &mut GbmDrmCompositor,
|
||||
renderer: &mut R,
|
||||
elements: &'a [E],
|
||||
renderer: &mut UdevRenderer<'a>,
|
||||
elements: &'a [OutputRenderElements<
|
||||
UdevRenderer<'a>,
|
||||
WaylandSurfaceRenderElement<UdevRenderer<'a>>,
|
||||
>],
|
||||
clear_color: [f32; 4],
|
||||
) -> Result<RenderFrameResult<'a, BufferObject<()>, GbmFramebuffer, E>, SwapBuffersError>
|
||||
where
|
||||
R: Renderer + Bind<Dmabuf>,
|
||||
<R as Renderer>::TextureId: 'static,
|
||||
<R as Renderer>::Error: Into<SwapBuffersError>,
|
||||
E: RenderElement<R>,
|
||||
{
|
||||
) -> Result<UdevRenderFrameResult<'a>, SwapBuffersError> {
|
||||
use smithay::backend::drm::compositor::RenderFrameError;
|
||||
|
||||
compositor
|
||||
|
@ -838,7 +862,7 @@ impl State {
|
|||
.dmabuf_render_formats()
|
||||
.clone();
|
||||
|
||||
tracing::info!(
|
||||
info!(
|
||||
?crtc,
|
||||
"Trying to setup connector {:?}-{}",
|
||||
connector.interface(),
|
||||
|
@ -981,8 +1005,7 @@ impl State {
|
|||
compositor,
|
||||
dmabuf_feedback,
|
||||
render_state: RenderState::Idle,
|
||||
primary_plane_element_swapchain_commit_counter: CommitCounter::default(),
|
||||
primary_plane_element_element_commit_counter: CommitCounter::default(),
|
||||
screencopy_commit_state: ScreencopyCommitState::default(),
|
||||
};
|
||||
|
||||
device.surfaces.insert(crtc, surface);
|
||||
|
@ -1305,9 +1328,6 @@ impl State {
|
|||
|
||||
// If there isn't a pending screencopy that doesn't want to overlay the cursor,
|
||||
// render it.
|
||||
//
|
||||
// This will cause the cursor to disappear for a frame if there is one though,
|
||||
// but it shouldn't meaningfully affect anything.
|
||||
match pending_screencopy_with_cursor {
|
||||
Some(include_cursor) => {
|
||||
if include_cursor {
|
||||
|
@ -1315,6 +1335,9 @@ impl State {
|
|||
// | cursor plane causes the cursor to overwrite the pixels underneath it,
|
||||
// | leading to a transparent hole under the cursor.
|
||||
// | To circumvent that, we set the cursor to render on the primary plane instead.
|
||||
// | Unfortunately that means I can't composite the cursor separately from
|
||||
// | the screencopy, meaning if you have an active screencopy recording
|
||||
// | without cursor overlay then the cursor will dim/flicker out/disappear.
|
||||
udev.pointer_element
|
||||
.set_element_kind(element::Kind::Unspecified);
|
||||
let pointer_render_elements = pointer_render_elements(
|
||||
|
@ -1363,188 +1386,13 @@ impl State {
|
|||
element.sync.wait();
|
||||
}
|
||||
|
||||
// Compute damage
|
||||
//
|
||||
// Also, I'm pretty sure that `RenderFrameResult` used to give us the damage, didn't
|
||||
// it? But it was removed in favor of the `is_empty` bool
|
||||
|
||||
let damage = match &render_frame_result.primary_element {
|
||||
PrimaryPlaneElement::Swapchain(element) => {
|
||||
let damage = element
|
||||
.damage
|
||||
.damage_since(Some(surface.primary_plane_element_swapchain_commit_counter));
|
||||
surface.primary_plane_element_swapchain_commit_counter =
|
||||
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) => {
|
||||
let damage = element.damage_since(
|
||||
smithay::utils::Scale::from(output.current_scale().fractional_scale()),
|
||||
Some(surface.primary_plane_element_swapchain_commit_counter),
|
||||
);
|
||||
surface.primary_plane_element_element_commit_counter = element.current_commit();
|
||||
Some(damage)
|
||||
}
|
||||
};
|
||||
|
||||
let mut damage = damage.unwrap_or_else(|| {
|
||||
vec![Rectangle::from_loc_and_size(
|
||||
Point::from((0, 0)),
|
||||
output.current_mode().unwrap().size, // TODO: transform
|
||||
)]
|
||||
});
|
||||
|
||||
// The primary plane had no damage but something got rendered, so it must be the cursor
|
||||
//
|
||||
// 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(smithay::utils::Scale::from(
|
||||
output.current_scale().fractional_scale(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mut screencopy) = output.with_state_mut(|state| state.screencopy.take()) {
|
||||
'screencopy: {
|
||||
assert!(screencopy.output() == output);
|
||||
|
||||
if screencopy.with_damage() {
|
||||
tracing::info!("WITH DAMAGE");
|
||||
if damage.is_empty() {
|
||||
output.with_state_mut(|state| state.screencopy.replace(screencopy));
|
||||
break 'screencopy;
|
||||
}
|
||||
screencopy.damage(&damage);
|
||||
}
|
||||
|
||||
let sync_fd = if let Ok(dmabuf) = dmabuf::get_dmabuf(screencopy.buffer()) {
|
||||
debug!("Dmabuf screencopy");
|
||||
|
||||
renderer.bind(dmabuf).unwrap();
|
||||
|
||||
render_frame_result
|
||||
.blit_frame_result(
|
||||
screencopy.physical_region().size,
|
||||
Transform::Normal,
|
||||
output.current_scale().fractional_scale(),
|
||||
&mut renderer,
|
||||
if screencopy.with_damage() {
|
||||
damage
|
||||
} else {
|
||||
vec![screencopy.physical_region()]
|
||||
},
|
||||
[],
|
||||
)
|
||||
.map(|sync| 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 {
|
||||
debug!("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"
|
||||
);
|
||||
|
||||
let buffer_rect =
|
||||
screencopy.physical_region().to_logical(1).to_buffer(
|
||||
1,
|
||||
Transform::Normal,
|
||||
&screencopy.physical_region().size.to_logical(1),
|
||||
);
|
||||
|
||||
let offscreen: GlesRenderbuffer = renderer.create_buffer(
|
||||
smithay::backend::allocator::Fourcc::Argb8888,
|
||||
buffer_rect.size,
|
||||
)?;
|
||||
|
||||
renderer.bind(offscreen)?;
|
||||
|
||||
let sync_point = render_frame_result.blit_frame_result(
|
||||
screencopy.physical_region().size,
|
||||
Transform::Normal,
|
||||
output.current_scale().fractional_scale(),
|
||||
&mut renderer,
|
||||
if screencopy.with_damage() {
|
||||
damage
|
||||
} else {
|
||||
vec![screencopy.physical_region()]
|
||||
},
|
||||
[],
|
||||
)?;
|
||||
|
||||
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(sync_point.export())
|
||||
},
|
||||
);
|
||||
|
||||
res.unwrap()
|
||||
};
|
||||
|
||||
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}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let time = self.clock.now();
|
||||
handle_pending_screencopy(
|
||||
&mut renderer,
|
||||
output,
|
||||
surface,
|
||||
&render_frame_result,
|
||||
&self.loop_handle,
|
||||
);
|
||||
|
||||
super::post_repaint(
|
||||
output,
|
||||
|
@ -1557,7 +1405,7 @@ impl State {
|
|||
render_feedback: &feedback.render_feedback,
|
||||
scanout_feedback: &feedback.scanout_feedback,
|
||||
}),
|
||||
time.into(),
|
||||
Duration::from(self.clock.now()),
|
||||
&self.cursor_status,
|
||||
);
|
||||
|
||||
|
@ -1566,6 +1414,7 @@ impl State {
|
|||
if rendered {
|
||||
let output_presentation_feedback =
|
||||
take_presentation_feedback(output, &self.space, &render_frame_result.states);
|
||||
|
||||
surface
|
||||
.compositor
|
||||
.queue_frame(Some(output_presentation_feedback))
|
||||
|
@ -1592,3 +1441,295 @@ fn render_surface_for_output<'a>(
|
|||
.get_mut(device_id)
|
||||
.and_then(|device| device.surfaces.get_mut(crtc))
|
||||
}
|
||||
|
||||
fn handle_pending_screencopy<'a>(
|
||||
renderer: &mut UdevRenderer<'a>,
|
||||
output: &Output,
|
||||
surface: &mut RenderSurface,
|
||||
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 untransformed_output_size = output.current_mode().expect("output no mode").size;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
),
|
||||
TextureFilter::Linear,
|
||||
)?;
|
||||
|
||||
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 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 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");
|
||||
};
|
||||
|
||||
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");
|
||||
};
|
||||
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}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ use smithay::{
|
|||
utils::{IsAlive, Point, Rectangle, Transform},
|
||||
wayland::dmabuf::{self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufState},
|
||||
};
|
||||
use tracing::{debug, error};
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::{
|
||||
render::{
|
||||
|
@ -371,7 +371,7 @@ impl State {
|
|||
}
|
||||
|
||||
let sync_fd = if let Ok(dmabuf) = dmabuf::get_dmabuf(screencopy.buffer()) {
|
||||
debug!("Dmabuf screencopy");
|
||||
info!("Dmabuf screencopy");
|
||||
|
||||
winit
|
||||
.backend
|
||||
|
@ -393,7 +393,7 @@ impl State {
|
|||
) {
|
||||
Err(anyhow!("not a shm buffer"))
|
||||
} else {
|
||||
debug!("Shm screencopy");
|
||||
info!("Shm screencopy");
|
||||
|
||||
let res = copy_to_shm_screencopy(winit.backend.renderer(), &screencopy)
|
||||
.map(|_| render_output_result.sync.export());
|
||||
|
|
|
@ -7,6 +7,10 @@ use std::{
|
|||
};
|
||||
|
||||
use smithay::{
|
||||
backend::{
|
||||
allocator::Buffer,
|
||||
renderer::{buffer_type, BufferType},
|
||||
},
|
||||
output::Output,
|
||||
reexports::{
|
||||
wayland_protocols_wlr::screencopy::v1::server::{
|
||||
|
@ -20,7 +24,10 @@ use smithay::{
|
|||
},
|
||||
},
|
||||
utils::{Physical, Point, Rectangle},
|
||||
wayland::shm,
|
||||
wayland::{
|
||||
dmabuf::get_dmabuf,
|
||||
shm::{self, shm_format_to_fourcc},
|
||||
},
|
||||
};
|
||||
use tracing::trace;
|
||||
|
||||
|
@ -262,19 +269,52 @@ where
|
|||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if !shm::with_buffer_contents(&buffer, |_buf, shm_len, buffer_data| {
|
||||
buffer_data.format == wl_shm::Format::Argb8888
|
||||
&& buffer_data.stride == info.physical_region.size.w * 4
|
||||
&& buffer_data.height == info.physical_region.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;
|
||||
match buffer_type(&buffer) {
|
||||
Some(BufferType::Shm) => {
|
||||
if !shm::with_buffer_contents(&buffer, |_buf, shm_len, buffer_data| {
|
||||
buffer_data.format == wl_shm::Format::Argb8888
|
||||
&& buffer_data.stride == info.physical_region.size.w * 4
|
||||
&& buffer_data.height == info.physical_region.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;
|
||||
}
|
||||
}
|
||||
Some(BufferType::Dma) => match get_dmabuf(&buffer) {
|
||||
Ok(dmabuf) => {
|
||||
if !(Some(dmabuf.format().code)
|
||||
== shm_format_to_fourcc(wl_shm::Format::Argb8888)
|
||||
&& dmabuf.width() == info.physical_region.size.w as u32
|
||||
&& dmabuf.height() == info.physical_region.size.h as u32)
|
||||
{
|
||||
frame.post_error(
|
||||
zwlr_screencopy_frame_v1::Error::InvalidBuffer,
|
||||
"invalid buffer",
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
frame.post_error(
|
||||
zwlr_screencopy_frame_v1::Error::InvalidBuffer,
|
||||
err.to_string(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
frame.post_error(
|
||||
zwlr_screencopy_frame_v1::Error::InvalidBuffer,
|
||||
"invalid buffer",
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
copied.store(true, Ordering::SeqCst);
|
||||
|
|
Loading…
Reference in a new issue