kms: Support screencopy cursor modes with active filters

This commit is contained in:
Victoria Brekenfeld 2025-03-21 20:04:32 +01:00 committed by Victoria Brekenfeld
parent 7373b3f513
commit 7a8577592d
2 changed files with 326 additions and 46 deletions

View file

@ -3,8 +3,8 @@
use crate::{ use crate::{
backend::render::{ backend::render::{
element::{CosmicElement, DamageElement}, element::{CosmicElement, DamageElement},
init_shaders, output_elements, CursorMode, GlMultiRenderer, PostprocessOutputConfig, init_shaders, output_elements, CursorMode, GlMultiError, GlMultiRenderer,
PostprocessShader, PostprocessState, CLEAR_COLOR, PostprocessOutputConfig, PostprocessShader, PostprocessState, CLEAR_COLOR,
}, },
config::{AdaptiveSync, ScreenFilter}, config::{AdaptiveSync, ScreenFilter},
shell::Shell, shell::Shell,
@ -25,6 +25,7 @@ use smithay::{
allocator::{ allocator::{
format::FormatSet, format::FormatSet,
gbm::{GbmAllocator, GbmDevice}, gbm::{GbmAllocator, GbmDevice},
Fourcc,
}, },
drm::{ drm::{
compositor::{BlitFrameResultError, FrameError, FrameFlags, PrimaryPlaneElement}, compositor::{BlitFrameResultError, FrameError, FrameFlags, PrimaryPlaneElement},
@ -37,15 +38,21 @@ use smithay::{
damage::Error as RenderError, damage::Error as RenderError,
element::{ element::{
texture::TextureRenderElement, texture::TextureRenderElement,
utils::{constrain_render_elements, ConstrainAlign, ConstrainScaleBehavior}, utils::{
constrain_render_elements, ConstrainAlign, ConstrainScaleBehavior, Relocate,
RelocateRenderElement,
},
Element, Kind, RenderElementStates, Element, Kind, RenderElementStates,
}, },
gles::{element::TextureShaderElement, GlesRenderbuffer, GlesRenderer, Uniform}, gles::{
element::TextureShaderElement, GlesRenderbuffer, GlesRenderer, GlesTexture, Uniform,
},
glow::GlowRenderer, glow::GlowRenderer,
multigpu::{Error as MultiError, GpuManager}, multigpu::{Error as MultiError, GpuManager},
sync::SyncPoint, sync::SyncPoint,
utils::with_renderer_surface_state, utils::with_renderer_surface_state,
Bind, Blit, ImportDma, Offscreen, Renderer, RendererSuper, Texture, TextureFilter, Bind, Blit, Frame, ImportDma, Offscreen, Renderer, RendererSuper, Texture,
TextureFilter,
}, },
}, },
desktop::utils::OutputPresentationFeedback, desktop::utils::OutputPresentationFeedback,
@ -63,7 +70,7 @@ use smithay::{
}, },
wayland_server::protocol::wl_surface::WlSurface, wayland_server::protocol::wl_surface::WlSurface,
}, },
utils::{Buffer as BufferCoords, Clock, Monotonic, Physical, Rectangle, Transform}, utils::{Buffer as BufferCoords, Clock, Monotonic, Physical, Point, Rectangle, Transform},
wayland::{ wayland::{
dmabuf::{get_dmabuf, DmabufFeedbackBuilder}, dmabuf::{get_dmabuf, DmabufFeedbackBuilder},
presentation::Refresh, presentation::Refresh,
@ -936,6 +943,7 @@ impl SurfaceThreadState {
// we can't use the elements after `compositor.render_frame`, // we can't use the elements after `compositor.render_frame`,
// so let's collect everything we need for screencopy now // so let's collect everything we need for screencopy now
let mut has_cursor_mode_none = false;
let frames: Vec<( let frames: Vec<(
ScreencopySession, ScreencopySession,
ScreencopyFrame, ScreencopyFrame,
@ -993,6 +1001,10 @@ impl SurfaceThreadState {
elements.truncate(old_len); elements.truncate(old_len);
} }
if !session.draw_cursor() {
has_cursor_mode_none = true;
}
let res = res.map(|(a, b)| (a.cloned(), b)); let res = res.map(|(a, b)| (a.cloned(), b));
std::mem::drop(damage_tracking); std::mem::drop(damage_tracking);
(session, frame, res) (session, frame, res)
@ -1011,8 +1023,16 @@ impl SurfaceThreadState {
|| !self.screen_filter.is_noop() || !self.screen_filter.is_noop()
}); });
let mut pre_postprocess_states = None; #[derive(Debug, Default)]
let mut pre_postprocess_texture = None; 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 { let res = if let Some(source_output) = source_output {
let offscreen_output_config = let offscreen_output_config =
PostprocessOutputConfig::for_output_untransformed(source_output); PostprocessOutputConfig::for_output_untransformed(source_output);
@ -1038,12 +1058,104 @@ impl SurfaceThreadState {
} }
}; };
if has_cursor_mode_none && self.mirroring.is_none() {
// TODO: use `extract_if` once stablized
let cursor_element_count = elements
.iter()
.take_while(|elem| elem.kind() == Kind::Cursor)
.count();
let cursor_elements = elements.drain(..cursor_element_count).collect::<Vec<_>>();
let scale = source_output.current_scale().fractional_scale().into();
let geometry: Option<Rectangle<i32, Physical>> =
cursor_elements.iter().fold(None, |acc, elem| {
let geometry = elem.geometry(scale);
if let Some(acc) = acc {
Some(acc.merge(geometry))
} else {
Some(geometry)
}
});
if let Some(geometry) = geometry {
let cursor_elements = cursor_elements
.into_iter()
.map(|elem| {
RelocateRenderElement::from_element(
elem,
Point::from((-geometry.loc.x, -geometry.loc.y)),
Relocate::Relative,
)
})
.collect::<Vec<_>>();
postprocess_state.track_cursor(
&mut renderer,
Fourcc::Abgr8888,
geometry.size,
scale,
)?;
postprocess_state
.cursor_texture
.as_mut()
.unwrap()
.render()
.draw::<_, <GlMultiRenderer as RendererSuper>::Error>(|tex| {
if self.mirroring.is_none() {
pre_postprocess_data.cursor_geometry = Some(geometry);
pre_postprocess_data.cursor_texture = Some(tex.clone());
}
let mut fb = renderer.bind(tex)?;
let res = match postprocess_state
.cursor_damage_tracker
.as_mut()
.unwrap()
.render_output(
&mut renderer,
&mut fb,
1,
&cursor_elements,
[0.0, 0.0, 0.0, 0.0],
) {
Ok(res) => res,
Err(RenderError::Rendering(err)) => return Err(err),
Err(RenderError::OutputNoMode(_)) => unreachable!(),
};
if self.mirroring.is_none() {
pre_postprocess_data.states = Some(res.states);
}
renderer.wait(&res.sync)?;
std::mem::drop(fb);
let transform = source_output.current_transform();
let area = tex.size().to_logical(1, transform);
Ok(res
.damage
.cloned()
.map(|v| {
v.into_iter()
.map(|r| r.to_logical(1).to_buffer(1, transform, &area))
.collect::<Vec<_>>()
})
.unwrap_or_default())
})
.context("Failed to draw to offscreen render target")?;
}
} else {
postprocess_state.remove_cursor();
}
postprocess_state postprocess_state
.texture .texture
.render() .render()
.draw::<_, <GlMultiRenderer as RendererSuper>::Error>(|tex| { .draw::<_, <GlMultiRenderer as RendererSuper>::Error>(|tex| {
if self.mirroring.is_none() { if self.mirroring.is_none() {
pre_postprocess_texture = Some(tex.clone()); pre_postprocess_data.texture = Some(tex.clone());
} }
let mut fb = renderer.bind(tex)?; let mut fb = renderer.bind(tex)?;
@ -1060,7 +1172,11 @@ impl SurfaceThreadState {
}; };
if self.mirroring.is_none() { if self.mirroring.is_none() {
pre_postprocess_states = Some(res.states); if let Some(states) = pre_postprocess_data.states.as_mut() {
states.states.extend(res.states.states);
} else {
pre_postprocess_data.states = Some(res.states);
}
} }
renderer.wait(&res.sync)?; renderer.wait(&res.sync)?;
@ -1081,26 +1197,73 @@ impl SurfaceThreadState {
}) })
.context("Failed to draw to offscreen render target")?; .context("Failed to draw to offscreen render target")?;
let texture_elem = TextureRenderElement::from_texture_render_buffer(
(0., 0.),
&postprocess_state.texture,
Some(1.0),
None,
None,
Kind::Unspecified,
);
renderer = self.api.single_renderer(&self.target_node).unwrap(); renderer = self.api.single_renderer(&self.target_node).unwrap();
let postprocess_texture_shader = Borrow::<GlesRenderer>::borrow(renderer.as_ref())
let postprocess_texture_shader = Borrow::<GlesRenderer>::borrow(renderer.as_mut())
.egl_context() .egl_context()
.user_data() .user_data()
.get::<PostprocessShader>() .get::<PostprocessShader>()
.expect("OffscreenShader should be available through `init_shaders`"); .expect("OffscreenShader should be available through `init_shaders`");
let texture_geometry =
texture_elem.geometry(self.output.current_scale().fractional_scale().into());
elements = { elements = {
let texture_elem = TextureShaderElement::new( let mut elements: [Option<TextureShaderElement>; 2] = [None, None];
if let Some(cursor_texture) = postprocess_state.cursor_texture.as_ref() {
let cursor_geometry = pre_postprocess_data.cursor_geometry.unwrap();
let texture_elem = TextureRenderElement::from_texture_render_buffer(
cursor_geometry.loc.to_f64(),
cursor_texture,
None,
Some(Rectangle::new(
Point::from((0., 0.)),
cursor_geometry.size.to_logical(1).to_f64(),
)),
Some(
cursor_geometry
.size
.to_f64()
.to_logical(self.output.current_scale().fractional_scale())
.to_i32_round(),
),
Kind::Cursor,
);
elements[0] = Some(TextureShaderElement::new(
texture_elem,
postprocess_texture_shader.0.clone(),
vec![
Uniform::new(
"invert",
if self.screen_filter.inverted { 1. } else { 0. },
),
Uniform::new(
"color_mode",
self.screen_filter
.color_filter
.map(|val| val as u8 as f32)
.unwrap_or(0.),
),
],
));
}
let texture_elem = TextureRenderElement::from_texture_render_buffer(
(0., 0.),
&postprocess_state.texture,
None,
Some(Rectangle::new(
Point::from((0., 0.)),
postprocess_state.output_config.size.to_logical(1).to_f64(),
)),
Some(
postprocess_state
.output_config
.size
.to_f64()
.to_logical(postprocess_state.output_config.fractional_scale)
.to_i32_round(),
),
Kind::Unspecified,
);
elements[1] = Some(TextureShaderElement::new(
texture_elem, texture_elem,
postprocess_texture_shader.0.clone(), postprocess_texture_shader.0.clone(),
vec![ vec![
@ -1113,23 +1276,24 @@ impl SurfaceThreadState {
.unwrap_or(0.), .unwrap_or(0.),
), ),
], ],
); ));
constrain_render_elements( constrain_render_elements(
std::iter::once(texture_elem), elements.into_iter().flatten(),
(0, 0), (0, 0),
Rectangle::from_size( Rectangle::from_size(
self.output self.output
.geometry() .geometry()
.size .size
.as_logical() .as_logical()
.to_f64() .to_physical_precise_round(
.to_physical(self.output.current_scale().fractional_scale()) self.output.current_scale().fractional_scale(),
.to_i32_round(), ),
), ),
texture_geometry, Rectangle::new(Point::from((0, 0)), postprocess_state.output_config.size),
ConstrainScaleBehavior::Fit, ConstrainScaleBehavior::Fit,
ConstrainAlign::CENTER, ConstrainAlign::CENTER,
1.0, postprocess_state.output_config.fractional_scale,
) )
.map(CosmicElement::Postprocess) .map(CosmicElement::Postprocess)
.collect::<Vec<_>>() .collect::<Vec<_>>()
@ -1141,7 +1305,7 @@ impl SurfaceThreadState {
compositor.render_frame( compositor.render_frame(
&mut renderer, &mut renderer,
&elements, &elements,
[0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 0.0],
self.frame_flags.union(additional_frame_flags), self.frame_flags.union(additional_frame_flags),
) )
} else { } else {
@ -1222,9 +1386,12 @@ impl SurfaceThreadState {
.map_err(|_| OutputNoMode)? // eh, we have to do some error .map_err(|_| OutputNoMode)? // eh, we have to do some error
.expect("We should be able to convert all hardcoded shm screencopy formats"); .expect("We should be able to convert all hardcoded shm screencopy formats");
if pre_postprocess_texture if pre_postprocess_data
.texture
.as_ref() .as_ref()
.is_some_and(|tex| tex.format() == Some(format)) .is_some_and(|tex| tex.format() == Some(format))
&& (session.draw_cursor() == false
|| pre_postprocess_data.cursor_texture.is_none())
{ {
None None
} else { } else {
@ -1262,21 +1429,25 @@ impl SurfaceThreadState {
.flatten(); .flatten();
// If the screen is rotated, we must convert damage to match output. // If the screen is rotated, we must convert damage to match output.
let adjusted = damage.iter().copied().map(|mut d| { let adjusted = damage
d.size = d .iter()
.size .copied()
.to_logical(1) .map(|mut d| {
.to_buffer(1, output_transform) d.size = d
.to_logical(1, Transform::Normal) .size
.to_physical(1); .to_logical(1)
d .to_buffer(1, output_transform)
}); .to_logical(1, Transform::Normal)
.to_physical(1);
d
})
.collect::<Vec<_>>();
if let Some(tex) = pre_postprocess_texture.as_mut() { 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)?; let mut tex_fb = renderer.bind(tex).map_err(RenderError::<<GlMultiRenderer as RendererSuper>::Error>::Rendering)?;
if let Some(fb) = fb.as_mut() { if let Some(fb) = fb.as_mut() {
for rect in adjusted { for rect in adjusted.iter().copied() {
renderer renderer
.blit( .blit(
&mut tex_fb, &mut tex_fb,
@ -1291,6 +1462,68 @@ impl SurfaceThreadState {
>::Rendering, >::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 { } else {
fb = Some(tex_fb); fb = Some(tex_fb);
} }
@ -1369,7 +1602,7 @@ impl SurfaceThreadState {
if self.mirroring.is_none() { if self.mirroring.is_none() {
// If postprocessing, use states from first render // If postprocessing, use states from first render
let states = pre_postprocess_states.unwrap_or(frame_result.states); let states = pre_postprocess_data.states.unwrap_or(frame_result.states);
self.send_dmabuf_feedback(states); self.send_dmabuf_feedback(states);
} }

View file

@ -63,7 +63,7 @@ use smithay::{
}, },
}, },
input::Seat, input::Seat,
output::{Output, OutputNoMode}, output::{Output, OutputModeSource, OutputNoMode},
utils::{ utils::{
IsAlive, Logical, Monotonic, Physical, Point, Rectangle, Scale, Size, Time, Transform, IsAlive, Logical, Monotonic, Physical, Point, Rectangle, Scale, Size, Time, Transform,
}, },
@ -1003,6 +1003,8 @@ where
pub struct PostprocessState { pub struct PostprocessState {
pub texture: TextureRenderBuffer<GlesTexture>, pub texture: TextureRenderBuffer<GlesTexture>,
pub damage_tracker: OutputDamageTracker, pub damage_tracker: OutputDamageTracker,
pub cursor_texture: Option<TextureRenderBuffer<GlesTexture>>,
pub cursor_damage_tracker: Option<OutputDamageTracker>,
pub output_config: PostprocessOutputConfig, pub output_config: PostprocessOutputConfig,
} }
@ -1032,9 +1034,54 @@ impl PostprocessState {
Ok(PostprocessState { Ok(PostprocessState {
texture: texture_buffer, texture: texture_buffer,
damage_tracker, damage_tracker,
cursor_texture: None,
cursor_damage_tracker: None,
output_config, output_config,
}) })
} }
pub fn track_cursor<R: Renderer + Offscreen<GlesTexture>>(
&mut self,
renderer: &mut R,
format: Fourcc,
size: Size<i32, Physical>,
scale: Scale<f64>,
) -> Result<(), R::Error> {
let buffer_size = size.to_logical(1).to_buffer(1, Transform::Normal);
if let (Some(tex), Some(tracker)) = (
self.cursor_texture.as_ref(),
self.cursor_damage_tracker.as_ref(),
) {
if tex.format().is_some_and(|f| f == format)
&& tracker.mode()
== &(OutputModeSource::Static {
size,
scale,
transform: Transform::Normal,
})
{
return Ok(());
}
}
let texture = Offscreen::<GlesTexture>::create_buffer(renderer, format, buffer_size)?;
let texture_buffer =
TextureRenderBuffer::from_texture(renderer, texture, 1, Transform::Normal, None);
let damage_tracker = OutputDamageTracker::new(size, scale, Transform::Normal);
self.cursor_texture = Some(texture_buffer);
self.cursor_damage_tracker = Some(damage_tracker);
Ok(())
}
pub fn remove_cursor(&mut self) {
self.cursor_texture.take();
self.cursor_damage_tracker.take();
}
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]