kms/surface: Split off part of redraw into a send_screencopy_result

This should not change behavior in any way.
This commit is contained in:
Ian Douglas Scott 2025-06-05 13:40:47 -07:00 committed by Victoria Brekenfeld
parent e94377f4de
commit 39b1ac2955

View file

@ -22,10 +22,18 @@ use anyhow::{Context, Result};
use calloop::channel::Channel;
use smithay::{
backend::{
allocator::{format::FormatSet, gbm::GbmAllocator, Fourcc},
allocator::{
format::FormatSet,
gbm::{GbmAllocator, GbmBuffer},
Fourcc,
},
drm::{
compositor::{BlitFrameResultError, FrameError, FrameFlags, PrimaryPlaneElement},
compositor::{
BlitFrameResultError, FrameError, FrameFlags, PrimaryPlaneElement,
RenderFrameResult,
},
exporter::gbm::GbmFramebufferExporter,
gbm::GbmFramebuffer,
output::DrmOutput,
DrmDeviceFd, DrmEventMetadata, DrmEventTime, DrmNode, VrrSupport,
},
@ -223,6 +231,14 @@ pub enum SurfaceCommand {
RenderStates(RenderElementStates),
}
#[derive(Debug, Default)]
struct PrePostprocessData {
states: Option<RenderElementStates>,
texture: Option<GlesTexture>,
cursor_texture: Option<GlesTexture>,
cursor_geometry: Option<Rectangle<i32, Physical>>,
}
impl Surface {
pub fn new(
output: &Output,
@ -1107,14 +1123,6 @@ impl SurfaceThreadState {
|| !self.screen_filter.is_noop()
});
#[derive(Debug, Default)]
struct PrePostprocessData {
states: Option<RenderElementStates>,
texture: Option<GlesTexture>,
cursor_texture: Option<GlesTexture>,
cursor_geometry: Option<Rectangle<i32, Physical>>,
}
let mut pre_postprocess_data = PrePostprocessData::default();
let res = if let Some(source_output) = source_output {
@ -1454,257 +1462,18 @@ impl SurfaceThreadState {
};
}
for (session, frame, res) in frames {
let damage = match res {
Ok((damage, _)) => damage,
Err(err) => {
tracing::warn!(?err, "Failed to screencopy");
session
.user_data()
.get::<SessionData>()
.unwrap()
.lock()
.unwrap()
.reset();
frame.fail(FailureReason::Unknown);
continue;
}
};
let mut sync = SyncPoint::default();
let mut dmabuf_clone;
let mut render_buffer;
let buffer = frame.buffer();
let mut shm_buffer = false;
let mut fb = if let Ok(dmabuf) = get_dmabuf(&buffer) {
dmabuf_clone = dmabuf.clone();
Some(renderer
.bind(&mut dmabuf_clone)
.map_err(RenderError::<<GlMultiRenderer as RendererSuper>::Error>::Rendering)?)
} else {
shm_buffer = true;
let size = buffer_dimensions(&buffer).ok_or(RenderError::<
<GlMultiRenderer as RendererSuper>::Error,
>::Rendering(
MultiError::ImportFailed,
))?;
let format =
with_buffer_contents(&buffer, |_, _, data| shm_format_to_fourcc(data.format))
.map_err(|_| OutputNoMode)? // eh, we have to do some error
.expect("We should be able to convert all hardcoded shm screencopy formats");
if pre_postprocess_data
.texture
.as_ref()
.is_some_and(|tex| tex.format() == Some(format))
&& (session.draw_cursor() == false
|| pre_postprocess_data.cursor_texture.is_none())
{
None
} else {
render_buffer =
Offscreen::<GlesRenderbuffer>::create_buffer(
&mut renderer,
format,
size,
)
.map_err(RenderError::<<GlMultiRenderer as RendererSuper>::Error>::Rendering)?;
Some(renderer
.bind(&mut render_buffer)
.map_err(RenderError::<<GlMultiRenderer as RendererSuper>::Error>::Rendering)?)
}
};
if let Some(ref damage) = damage {
let (output_size, output_scale, output_transform) = (
self.output.current_mode().ok_or(OutputNoMode)?.size,
self.output.current_scale().fractional_scale(),
self.output.current_transform(),
);
let filter = (!session.draw_cursor())
.then(|| {
elements.iter().filter_map(|elem| {
if let CosmicElement::Cursor(_) = elem {
Some(elem.id().clone())
} else {
None
}
})
})
.into_iter()
.flatten();
// If the screen is rotated, we must convert damage to match output.
let adjusted = damage
.iter()
.copied()
.map(|mut d| {
d.size = d
.size
.to_logical(1)
.to_buffer(1, output_transform)
.to_logical(1, Transform::Normal)
.to_physical(1);
d
})
.collect::<Vec<_>>();
if let Some(tex) = pre_postprocess_data.texture.as_mut() {
let mut tex_fb = renderer.bind(tex).map_err(RenderError::<<GlMultiRenderer as RendererSuper>::Error>::Rendering)?;
if let Some(fb) = fb.as_mut() {
for rect in adjusted.iter().copied() {
renderer
.blit(
&mut tex_fb,
fb,
rect,
rect,
TextureFilter::Linear,
)
.map_err(
RenderError::<
<GlMultiRenderer as RendererSuper>::Error,
>::Rendering,
)?;
}
if let Some(cursor_geometry) = pre_postprocess_data
.cursor_geometry
.as_ref()
.filter(|_| session.draw_cursor())
{
let cursor_damage = adjusted
.iter()
.filter_map(|rect| {
cursor_geometry.intersection(*rect)
})
.map(|rect| {
Rectangle::new(
rect.loc - cursor_geometry.loc,
rect.size,
)
})
.collect::<Vec<_>>();
let mut frame = renderer
.render(fb, output_size, output_transform)
.map_err(
RenderError::<
<GlMultiRenderer as RendererSuper>::Error,
>::Rendering,
)?;
frame
.as_mut()
.render_texture_from_to(
pre_postprocess_data
.cursor_texture
.as_ref()
.unwrap(),
Rectangle::new(
Point::from((0., 0.)),
cursor_geometry
.size
.to_logical(1)
.to_buffer(1, Transform::Normal)
.to_f64(),
),
*cursor_geometry,
&cursor_damage,
&[*cursor_geometry],
Transform::Normal,
1.0,
)
.map_err(GlMultiError::Render)
.map_err(
RenderError::<
<GlMultiRenderer as RendererSuper>::Error,
>::Rendering,
)?;
let sync = frame.finish().map_err(
RenderError::<
<GlMultiRenderer as RendererSuper>::Error,
>::Rendering,
)?;
renderer.wait(&sync).map_err(
RenderError::<
<GlMultiRenderer as RendererSuper>::Error,
>::Rendering,
)?;
}
} else {
fb = Some(tex_fb);
}
} else {
match frame_result
.blit_frame_result(
output_size,
output_transform,
output_scale,
&mut renderer,
fb.as_mut().unwrap(),
adjusted,
filter,
)
.map_err(|err| match err {
BlitFrameResultError::Rendering(err) => RenderError::<
<GlMultiRenderer as RendererSuper>::Error,
>::Rendering(
err
),
BlitFrameResultError::Export(_) => RenderError::<
<GlMultiRenderer as RendererSuper>::Error,
>::Rendering(
MultiError::DeviceMissing,
),
}) {
Ok(new_sync) => {
sync = new_sync;
}
Err(err) => {
tracing::warn!(?err, "Failed to screencopy");
session
.user_data()
.get::<SessionData>()
.unwrap()
.lock()
.unwrap()
.reset();
frame.fail(FailureReason::Unknown);
continue;
}
};
};
}
let transform = self.output.current_transform();
match submit_buffer(
frame,
let now = self.clock.now();
for frame in frames {
send_screencopy_result(
&mut renderer,
shm_buffer.then_some(fb.as_mut().unwrap()),
transform,
damage.as_deref(),
sync,
) {
Ok(Some((frame, damage))) => {
if frame_result.is_empty {
frame.success(transform, damage, self.clock.now());
} else {
let _ = tx.send((frame, damage));
}
}
Ok(None) => {}
Err(err) => {
session
.user_data()
.get::<SessionData>()
.unwrap()
.lock()
.unwrap()
.reset();
tracing::warn!(?err, "Failed to screencopy");
}
}
&self.output,
&mut pre_postprocess_data,
&tx,
&frame_result,
&elements,
frame,
now.into(),
)?;
}
if self.mirroring.is_none() {
@ -1941,3 +1710,239 @@ fn get_surface_dmabuf_feedback(
primary_scanout_feedback,
}
}
fn send_screencopy_result<'a>(
renderer: &mut GlMultiRenderer<'a>,
output: &Output,
pre_postprocess_data: &mut PrePostprocessData,
tx: &std::sync::mpsc::Sender<(ScreencopyFrame, Vec<Rectangle<i32, BufferCoords>>)>,
frame_result: &RenderFrameResult<GbmBuffer, GbmFramebuffer, CosmicElement<GlMultiRenderer<'a>>>,
elements: &[CosmicElement<GlMultiRenderer>],
(session, frame, res): (
ScreencopySessionRef,
ScreencopyFrame,
Result<(Option<Vec<Rectangle<i32, Physical>>>, RenderElementStates), OutputNoMode>,
),
presentation_time: Duration,
) -> Result<()> {
let damage = match res {
Ok((damage, _)) => damage,
Err(err) => {
tracing::warn!(?err, "Failed to screencopy");
session
.user_data()
.get::<SessionData>()
.unwrap()
.lock()
.unwrap()
.reset();
frame.fail(FailureReason::Unknown);
return Ok(());
}
};
let mut sync = SyncPoint::default();
let mut dmabuf_clone;
let mut render_buffer;
let buffer = frame.buffer();
let mut shm_buffer = false;
let mut fb = if let Ok(dmabuf) = get_dmabuf(&buffer) {
dmabuf_clone = dmabuf.clone();
Some(
renderer
.bind(&mut dmabuf_clone)
.map_err(RenderError::<<GlMultiRenderer as RendererSuper>::Error>::Rendering)?,
)
} else {
shm_buffer = true;
let size =
buffer_dimensions(&buffer).ok_or(RenderError::<
<GlMultiRenderer as RendererSuper>::Error,
>::Rendering(MultiError::ImportFailed))?;
let format = with_buffer_contents(&buffer, |_, _, data| shm_format_to_fourcc(data.format))
.map_err(|_| OutputNoMode)? // eh, we have to do some error
.expect("We should be able to convert all hardcoded shm screencopy formats");
if pre_postprocess_data
.texture
.as_ref()
.is_some_and(|tex| tex.format() == Some(format))
&& (session.draw_cursor() == false || pre_postprocess_data.cursor_texture.is_none())
{
None
} else {
render_buffer = Offscreen::<GlesRenderbuffer>::create_buffer(renderer, format, size)
.map_err(RenderError::<<GlMultiRenderer as RendererSuper>::Error>::Rendering)?;
Some(
renderer
.bind(&mut render_buffer)
.map_err(RenderError::<<GlMultiRenderer as RendererSuper>::Error>::Rendering)?,
)
}
};
if let Some(ref damage) = damage {
let (output_size, output_scale, output_transform) = (
output.current_mode().ok_or(OutputNoMode)?.size,
output.current_scale().fractional_scale(),
output.current_transform(),
);
let filter = (!session.draw_cursor())
.then(|| {
elements.iter().filter_map(|elem| {
if let CosmicElement::Cursor(_) = elem {
Some(elem.id().clone())
} else {
None
}
})
})
.into_iter()
.flatten();
// If the screen is rotated, we must convert damage to match output.
let adjusted = damage
.iter()
.copied()
.map(|mut d| {
d.size = d
.size
.to_logical(1)
.to_buffer(1, output_transform)
.to_logical(1, Transform::Normal)
.to_physical(1);
d
})
.collect::<Vec<_>>();
if let Some(tex) = pre_postprocess_data.texture.as_mut() {
let mut tex_fb = renderer
.bind(tex)
.map_err(RenderError::<<GlMultiRenderer as RendererSuper>::Error>::Rendering)?;
if let Some(fb) = fb.as_mut() {
for rect in adjusted.iter().copied() {
renderer
.blit(&mut tex_fb, fb, rect, rect, TextureFilter::Linear)
.map_err(
RenderError::<<GlMultiRenderer as RendererSuper>::Error>::Rendering,
)?;
}
if let Some(cursor_geometry) = pre_postprocess_data
.cursor_geometry
.as_ref()
.filter(|_| session.draw_cursor())
{
let cursor_damage = adjusted
.iter()
.filter_map(|rect| cursor_geometry.intersection(*rect))
.map(|rect| Rectangle::new(rect.loc - cursor_geometry.loc, rect.size))
.collect::<Vec<_>>();
let mut frame = renderer.render(fb, output_size, output_transform).map_err(
RenderError::<<GlMultiRenderer as RendererSuper>::Error>::Rendering,
)?;
frame
.as_mut()
.render_texture_from_to(
pre_postprocess_data.cursor_texture.as_ref().unwrap(),
Rectangle::new(
Point::from((0., 0.)),
cursor_geometry
.size
.to_logical(1)
.to_buffer(1, Transform::Normal)
.to_f64(),
),
*cursor_geometry,
&cursor_damage,
&[*cursor_geometry],
Transform::Normal,
1.0,
)
.map_err(GlMultiError::Render)
.map_err(
RenderError::<<GlMultiRenderer as RendererSuper>::Error>::Rendering,
)?;
let sync = frame.finish().map_err(
RenderError::<<GlMultiRenderer as RendererSuper>::Error>::Rendering,
)?;
renderer.wait(&sync).map_err(
RenderError::<<GlMultiRenderer as RendererSuper>::Error>::Rendering,
)?;
}
} else {
fb = Some(tex_fb);
}
} else {
match frame_result
.blit_frame_result(
output_size,
output_transform,
output_scale,
renderer,
fb.as_mut().unwrap(),
adjusted,
filter,
)
.map_err(|err| match err {
BlitFrameResultError::Rendering(err) => {
RenderError::<<GlMultiRenderer as RendererSuper>::Error>::Rendering(err)
}
BlitFrameResultError::Export(_) => {
RenderError::<<GlMultiRenderer as RendererSuper>::Error>::Rendering(
MultiError::DeviceMissing,
)
}
}) {
Ok(new_sync) => {
sync = new_sync;
}
Err(err) => {
tracing::warn!(?err, "Failed to screencopy");
session
.user_data()
.get::<SessionData>()
.unwrap()
.lock()
.unwrap()
.reset();
frame.fail(FailureReason::Unknown);
return Ok(());
}
};
};
}
let transform = output.current_transform();
match submit_buffer(
frame,
renderer,
shm_buffer.then_some(fb.as_mut().unwrap()),
transform,
damage.as_deref(),
sync,
) {
Ok(Some((frame, damage))) => {
if frame_result.is_empty {
frame.success(transform, damage, presentation_time);
} else {
let _ = tx.send((frame, damage));
}
}
Ok(None) => {}
Err(err) => {
session
.user_data()
.get::<SessionData>()
.unwrap()
.lock()
.unwrap()
.reset();
tracing::warn!(?err, "Failed to screencopy");
}
}
Ok(())
}