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::{
backend::render::{
element::{CosmicElement, DamageElement},
init_shaders, output_elements, CursorMode, GlMultiRenderer, PostprocessOutputConfig,
PostprocessShader, PostprocessState, CLEAR_COLOR,
init_shaders, output_elements, CursorMode, GlMultiError, GlMultiRenderer,
PostprocessOutputConfig, PostprocessShader, PostprocessState, CLEAR_COLOR,
},
config::{AdaptiveSync, ScreenFilter},
shell::Shell,
@ -25,6 +25,7 @@ use smithay::{
allocator::{
format::FormatSet,
gbm::{GbmAllocator, GbmDevice},
Fourcc,
},
drm::{
compositor::{BlitFrameResultError, FrameError, FrameFlags, PrimaryPlaneElement},
@ -37,15 +38,21 @@ use smithay::{
damage::Error as RenderError,
element::{
texture::TextureRenderElement,
utils::{constrain_render_elements, ConstrainAlign, ConstrainScaleBehavior},
utils::{
constrain_render_elements, ConstrainAlign, ConstrainScaleBehavior, Relocate,
RelocateRenderElement,
},
Element, Kind, RenderElementStates,
},
gles::{element::TextureShaderElement, GlesRenderbuffer, GlesRenderer, Uniform},
gles::{
element::TextureShaderElement, GlesRenderbuffer, GlesRenderer, GlesTexture, Uniform,
},
glow::GlowRenderer,
multigpu::{Error as MultiError, GpuManager},
sync::SyncPoint,
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,
@ -63,7 +70,7 @@ use smithay::{
},
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::{
dmabuf::{get_dmabuf, DmabufFeedbackBuilder},
presentation::Refresh,
@ -936,6 +943,7 @@ impl SurfaceThreadState {
// we can't use the elements after `compositor.render_frame`,
// so let's collect everything we need for screencopy now
let mut has_cursor_mode_none = false;
let frames: Vec<(
ScreencopySession,
ScreencopyFrame,
@ -993,6 +1001,10 @@ impl SurfaceThreadState {
elements.truncate(old_len);
}
if !session.draw_cursor() {
has_cursor_mode_none = true;
}
let res = res.map(|(a, b)| (a.cloned(), b));
std::mem::drop(damage_tracking);
(session, frame, res)
@ -1011,8 +1023,16 @@ impl SurfaceThreadState {
|| !self.screen_filter.is_noop()
});
let mut pre_postprocess_states = None;
let mut pre_postprocess_texture = None;
#[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 {
let offscreen_output_config =
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
.texture
.render()
.draw::<_, <GlMultiRenderer as RendererSuper>::Error>(|tex| {
if self.mirroring.is_none() {
pre_postprocess_texture = Some(tex.clone());
pre_postprocess_data.texture = Some(tex.clone());
}
let mut fb = renderer.bind(tex)?;
@ -1060,7 +1172,11 @@ impl SurfaceThreadState {
};
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)?;
@ -1081,26 +1197,73 @@ impl SurfaceThreadState {
})
.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();
let postprocess_texture_shader = Borrow::<GlesRenderer>::borrow(renderer.as_mut())
let postprocess_texture_shader = Borrow::<GlesRenderer>::borrow(renderer.as_ref())
.egl_context()
.user_data()
.get::<PostprocessShader>()
.expect("OffscreenShader should be available through `init_shaders`");
let texture_geometry =
texture_elem.geometry(self.output.current_scale().fractional_scale().into());
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,
postprocess_texture_shader.0.clone(),
vec![
@ -1113,23 +1276,24 @@ impl SurfaceThreadState {
.unwrap_or(0.),
),
],
);
));
constrain_render_elements(
std::iter::once(texture_elem),
elements.into_iter().flatten(),
(0, 0),
Rectangle::from_size(
self.output
.geometry()
.size
.as_logical()
.to_f64()
.to_physical(self.output.current_scale().fractional_scale())
.to_i32_round(),
.to_physical_precise_round(
self.output.current_scale().fractional_scale(),
),
),
texture_geometry,
Rectangle::new(Point::from((0, 0)), postprocess_state.output_config.size),
ConstrainScaleBehavior::Fit,
ConstrainAlign::CENTER,
1.0,
postprocess_state.output_config.fractional_scale,
)
.map(CosmicElement::Postprocess)
.collect::<Vec<_>>()
@ -1141,7 +1305,7 @@ impl SurfaceThreadState {
compositor.render_frame(
&mut renderer,
&elements,
[0.0, 0.0, 0.0, 1.0],
[0.0, 0.0, 0.0, 0.0],
self.frame_flags.union(additional_frame_flags),
)
} else {
@ -1222,9 +1386,12 @@ impl SurfaceThreadState {
.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_texture
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 {
@ -1262,21 +1429,25 @@ impl SurfaceThreadState {
.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
});
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_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)?;
if let Some(fb) = fb.as_mut() {
for rect in adjusted {
for rect in adjusted.iter().copied() {
renderer
.blit(
&mut tex_fb,
@ -1291,6 +1462,68 @@ impl SurfaceThreadState {
>::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);
}
@ -1369,7 +1602,7 @@ impl SurfaceThreadState {
if self.mirroring.is_none() {
// 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);
}

View file

@ -63,7 +63,7 @@ use smithay::{
},
},
input::Seat,
output::{Output, OutputNoMode},
output::{Output, OutputModeSource, OutputNoMode},
utils::{
IsAlive, Logical, Monotonic, Physical, Point, Rectangle, Scale, Size, Time, Transform,
},
@ -1003,6 +1003,8 @@ where
pub struct PostprocessState {
pub texture: TextureRenderBuffer<GlesTexture>,
pub damage_tracker: OutputDamageTracker,
pub cursor_texture: Option<TextureRenderBuffer<GlesTexture>>,
pub cursor_damage_tracker: Option<OutputDamageTracker>,
pub output_config: PostprocessOutputConfig,
}
@ -1032,9 +1034,54 @@ impl PostprocessState {
Ok(PostprocessState {
texture: texture_buffer,
damage_tracker,
cursor_texture: None,
cursor_damage_tracker: None,
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)]