kms: Support screen filters

This commit is contained in:
Victoria Brekenfeld 2025-03-19 14:16:58 +01:00 committed by Victoria Brekenfeld
parent 18335c6758
commit 7929e25966
5 changed files with 198 additions and 80 deletions

View file

@ -2,7 +2,7 @@
use crate::{ use crate::{
backend::render::{output_elements, CursorMode, GlMultiRenderer, CLEAR_COLOR}, backend::render::{output_elements, CursorMode, GlMultiRenderer, CLEAR_COLOR},
config::{AdaptiveSync, OutputConfig, OutputState}, config::{AdaptiveSync, OutputConfig, OutputState, ScreenFilter},
shell::Shell, shell::Shell,
utils::prelude::*, utils::prelude::*,
wayland::protocols::screencopy::Frame as ScreencopyFrame, wayland::protocols::screencopy::Frame as ScreencopyFrame,
@ -272,6 +272,7 @@ impl State {
maybe_crtc, maybe_crtc,
(w, 0), (w, 0),
&self.common.event_loop_handle, &self.common.event_loop_handle,
self.common.config.dynamic_conf.screen_filter().clone(),
self.common.shell.clone(), self.common.shell.clone(),
self.common.startup_done.clone(), self.common.startup_done.clone(),
) { ) {
@ -365,6 +366,7 @@ impl State {
maybe_crtc, maybe_crtc,
(w, 0), (w, 0),
&self.common.event_loop_handle, &self.common.event_loop_handle,
self.common.config.dynamic_conf.screen_filter().clone(),
self.common.shell.clone(), self.common.shell.clone(),
self.common.startup_done.clone(), self.common.startup_done.clone(),
) { ) {
@ -516,6 +518,7 @@ impl Device {
maybe_crtc: Option<crtc::Handle>, maybe_crtc: Option<crtc::Handle>,
position: (u32, u32), position: (u32, u32),
evlh: &LoopHandle<'static, State>, evlh: &LoopHandle<'static, State>,
screen_filter: ScreenFilter,
shell: Arc<RwLock<Shell>>, shell: Arc<RwLock<Shell>>,
startup_done: Arc<AtomicBool>, startup_done: Arc<AtomicBool>,
) -> Result<(Output, bool)> { ) -> Result<(Output, bool)> {
@ -579,6 +582,7 @@ impl Device {
self.dev_node, self.dev_node,
self.render_node, self.render_node,
evlh, evlh,
screen_filter,
shell, shell,
startup_done, startup_done,
) { ) {

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use crate::{ use crate::{
config::{AdaptiveSync, OutputState}, config::{AdaptiveSync, OutputState, ScreenFilter},
shell::Shell, shell::Shell,
state::BackendData, state::BackendData,
utils::prelude::*, utils::prelude::*,
@ -558,6 +558,7 @@ impl KmsState {
&mut self, &mut self,
test_only: bool, test_only: bool,
loop_handle: &LoopHandle<'static, State>, loop_handle: &LoopHandle<'static, State>,
screen_filter: &ScreenFilter,
shell: Arc<RwLock<Shell>>, shell: Arc<RwLock<Shell>>,
startup_done: Arc<AtomicBool>, startup_done: Arc<AtomicBool>,
clock: &Clock<Monotonic>, clock: &Clock<Monotonic>,
@ -658,6 +659,7 @@ impl KmsState {
Some(crtc), Some(crtc),
(w, 0), (w, 0),
loop_handle, loop_handle,
screen_filter.clone(),
shell.clone(), shell.clone(),
startup_done.clone(), startup_done.clone(),
)?; )?;
@ -927,4 +929,19 @@ impl KmsState {
Ok(all_outputs) Ok(all_outputs)
} }
pub fn update_screen_filter(&mut self, screen_filter: &ScreenFilter) -> Result<()> {
for device in self.drm_devices.values_mut() {
for surface in device.surfaces.values_mut() {
surface.set_screen_filter(screen_filter.clone());
}
}
// We don't expect this to fail in a meaningful way.
// The shader is already compiled at this point and we don't rely on any features,
// that might not be available for any filters we currently expose.
//
// But we might conditionally fail here in the future.
Ok(())
}
} }

View file

@ -6,7 +6,7 @@ use crate::{
init_shaders, output_elements, CursorMode, GlMultiRenderer, PostprocessOutputConfig, init_shaders, output_elements, CursorMode, GlMultiRenderer, PostprocessOutputConfig,
PostprocessShader, PostprocessState, CLEAR_COLOR, PostprocessShader, PostprocessState, CLEAR_COLOR,
}, },
config::AdaptiveSync, config::{AdaptiveSync, ScreenFilter},
shell::Shell, shell::Shell,
state::SurfaceDmabufFeedback, state::SurfaceDmabufFeedback,
utils::prelude::*, utils::prelude::*,
@ -40,12 +40,12 @@ use smithay::{
utils::{constrain_render_elements, ConstrainAlign, ConstrainScaleBehavior}, utils::{constrain_render_elements, ConstrainAlign, ConstrainScaleBehavior},
Element, Kind, RenderElementStates, Element, Kind, RenderElementStates,
}, },
gles::{GlesRenderbuffer, GlesTexture}, gles::{element::TextureShaderElement, GlesRenderbuffer, GlesRenderer, 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, ImportDma, Offscreen, Renderer, RendererSuper, Texture, Bind, Blit, ImportDma, Offscreen, Renderer, RendererSuper, Texture, TextureFilter,
}, },
}, },
desktop::utils::OutputPresentationFeedback, desktop::utils::OutputPresentationFeedback,
@ -74,7 +74,7 @@ use smithay::{
use tracing::{error, trace, warn}; use tracing::{error, trace, warn};
use std::{ use std::{
borrow::BorrowMut, borrow::{Borrow, BorrowMut},
collections::{hash_map, HashMap, HashSet}, collections::{hash_map, HashMap, HashSet},
mem, mem,
sync::{ sync::{
@ -129,6 +129,7 @@ pub struct SurfaceThreadState {
output: Output, output: Output,
mirroring: Option<Output>, mirroring: Option<Output>,
screen_filter: ScreenFilter,
postprocess_textures: HashMap<DrmNode, PostprocessState>, postprocess_textures: HashMap<DrmNode, PostprocessState>,
shell: Arc<RwLock<Shell>>, shell: Arc<RwLock<Shell>>,
@ -190,6 +191,7 @@ pub enum ThreadCommand {
node: DrmNode, node: DrmNode,
}, },
UpdateMirroring(Option<Output>), UpdateMirroring(Option<Output>),
UpdateScreenFilter(ScreenFilter),
VBlank(Option<DrmEventMetadata>), VBlank(Option<DrmEventMetadata>),
ScheduleRender, ScheduleRender,
AdaptiveSyncAvailable(SyncSender<Result<VrrSupport>>), AdaptiveSyncAvailable(SyncSender<Result<VrrSupport>>),
@ -214,6 +216,7 @@ impl Surface {
dev_node: DrmNode, dev_node: DrmNode,
target_node: DrmNode, target_node: DrmNode,
evlh: &LoopHandle<'static, State>, evlh: &LoopHandle<'static, State>,
screen_filter: ScreenFilter,
shell: Arc<RwLock<Shell>>, shell: Arc<RwLock<Shell>>,
startup_done: Arc<AtomicBool>, startup_done: Arc<AtomicBool>,
) -> Result<Self> { ) -> Result<Self> {
@ -233,6 +236,7 @@ impl Surface {
target_node, target_node,
shell, shell,
active_clone, active_clone,
screen_filter,
tx2, tx2,
rx, rx,
startup_done, startup_done,
@ -354,6 +358,12 @@ impl Surface {
.send(ThreadCommand::UpdateMirroring(output)); .send(ThreadCommand::UpdateMirroring(output));
} }
pub fn set_screen_filter(&mut self, config: ScreenFilter) {
let _ = self
.thread_command
.send(ThreadCommand::UpdateScreenFilter(config));
}
pub fn adaptive_sync_support(&self) -> Result<VrrSupport> { pub fn adaptive_sync_support(&self) -> Result<VrrSupport> {
let (tx, rx) = std::sync::mpsc::sync_channel(1); let (tx, rx) = std::sync::mpsc::sync_channel(1);
let _ = self let _ = self
@ -447,6 +457,7 @@ fn surface_thread(
target_node: DrmNode, target_node: DrmNode,
shell: Arc<RwLock<Shell>>, shell: Arc<RwLock<Shell>>,
active: Arc<AtomicBool>, active: Arc<AtomicBool>,
screen_filter: ScreenFilter,
thread_sender: Sender<SurfaceCommand>, thread_sender: Sender<SurfaceCommand>,
thread_receiver: Channel<ThreadCommand>, thread_receiver: Channel<ThreadCommand>,
startup_done: Arc<AtomicBool>, startup_done: Arc<AtomicBool>,
@ -490,6 +501,7 @@ fn surface_thread(
output, output,
mirroring: None, mirroring: None,
screen_filter,
postprocess_textures: HashMap::new(), postprocess_textures: HashMap::new(),
shell, shell,
@ -528,6 +540,9 @@ fn surface_thread(
Event::Msg(ThreadCommand::UpdateMirroring(mirroring_output)) => { Event::Msg(ThreadCommand::UpdateMirroring(mirroring_output)) => {
state.update_mirroring(mirroring_output); state.update_mirroring(mirroring_output);
} }
Event::Msg(ThreadCommand::UpdateScreenFilter(filter_config)) => {
state.update_screen_filter(filter_config);
}
Event::Msg(ThreadCommand::AdaptiveSyncAvailable(result)) => { Event::Msg(ThreadCommand::AdaptiveSyncAvailable(result)) => {
if let Some(compositor) = state.compositor.as_mut() { if let Some(compositor) = state.compositor.as_mut() {
let _ = result.send( let _ = result.send(
@ -989,11 +1004,15 @@ impl SurfaceThreadState {
let source_output = self let source_output = self
.mirroring .mirroring
.as_ref() .as_ref()
.or((!self.screen_filter.is_noop()).then(|| &self.output))
.filter(|output| { .filter(|output| {
PostprocessOutputConfig::for_output_untransformed(output) PostprocessOutputConfig::for_output_untransformed(output)
!= PostprocessOutputConfig::for_output(&self.output) != PostprocessOutputConfig::for_output(&self.output)
|| !self.screen_filter.is_noop()
}); });
let mut pre_postprocess_states = None;
let mut pre_postprocess_texture = None;
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);
@ -1023,6 +1042,10 @@ impl SurfaceThreadState {
.texture .texture
.render() .render()
.draw::<_, <GlMultiRenderer as RendererSuper>::Error>(|tex| { .draw::<_, <GlMultiRenderer as RendererSuper>::Error>(|tex| {
if self.mirroring.is_none() {
pre_postprocess_texture = Some(tex.clone());
}
let mut fb = renderer.bind(tex)?; let mut fb = renderer.bind(tex)?;
let res = match postprocess_state.damage_tracker.render_output( let res = match postprocess_state.damage_tracker.render_output(
&mut renderer, &mut renderer,
@ -1035,6 +1058,11 @@ impl SurfaceThreadState {
Err(RenderError::Rendering(err)) => return Err(err), Err(RenderError::Rendering(err)) => return Err(err),
Err(RenderError::OutputNoMode(_)) => unreachable!(), Err(RenderError::OutputNoMode(_)) => unreachable!(),
}; };
if self.mirroring.is_none() {
pre_postprocess_states = Some(res.states);
}
renderer.wait(&res.sync)?; renderer.wait(&res.sync)?;
std::mem::drop(fb); std::mem::drop(fb);
@ -1061,29 +1089,52 @@ impl SurfaceThreadState {
None, None,
Kind::Unspecified, Kind::Unspecified,
); );
let texture_geometry =
texture_elem.geometry(self.output.current_scale().fractional_scale().into());
elements = constrain_render_elements(
std::iter::once(texture_elem),
(0, 0),
Rectangle::from_size(
self.output
.geometry()
.size
.as_logical()
.to_f64()
.to_physical(self.output.current_scale().fractional_scale())
.to_i32_round(),
),
texture_geometry,
ConstrainScaleBehavior::Fit,
ConstrainAlign::CENTER,
1.0,
)
.map(CosmicElement::Postprocess)
.collect::<Vec<_>>();
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_mut())
.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(
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.),
),
],
);
constrain_render_elements(
std::iter::once(texture_elem),
(0, 0),
Rectangle::from_size(
self.output
.geometry()
.size
.as_logical()
.to_f64()
.to_physical(self.output.current_scale().fractional_scale())
.to_i32_round(),
),
texture_geometry,
ConstrainScaleBehavior::Fit,
ConstrainAlign::CENTER,
1.0,
)
.map(CosmicElement::Postprocess)
.collect::<Vec<_>>()
};
if let Err(err) = compositor.with_compositor(|c| c.use_vrr(vrr)) { if let Err(err) = compositor.with_compositor(|c| c.use_vrr(vrr)) {
warn!("Unable to set adaptive VRR state: {}", err); warn!("Unable to set adaptive VRR state: {}", err);
} }
@ -1150,16 +1201,15 @@ impl SurfaceThreadState {
}; };
let mut sync = SyncPoint::default(); let mut sync = SyncPoint::default();
let mut dmabuf_clone; let mut dmabuf_clone;
let mut render_buffer; let mut render_buffer;
let buffer = frame.buffer(); let buffer = frame.buffer();
let mut shm_buffer = false; let mut shm_buffer = false;
let mut fb = if let Ok(dmabuf) = get_dmabuf(&buffer) { let mut fb = if let Ok(dmabuf) = get_dmabuf(&buffer) {
dmabuf_clone = dmabuf.clone(); dmabuf_clone = dmabuf.clone();
renderer Some(renderer
.bind(&mut dmabuf_clone) .bind(&mut dmabuf_clone)
.map_err(RenderError::<<GlMultiRenderer as RendererSuper>::Error>::Rendering)? .map_err(RenderError::<<GlMultiRenderer as RendererSuper>::Error>::Rendering)?)
} else { } else {
shm_buffer = true; shm_buffer = true;
let size = buffer_dimensions(&buffer).ok_or(RenderError::< let size = buffer_dimensions(&buffer).ok_or(RenderError::<
@ -1171,16 +1221,24 @@ impl SurfaceThreadState {
with_buffer_contents(&buffer, |_, _, data| shm_format_to_fourcc(data.format)) with_buffer_contents(&buffer, |_, _, data| shm_format_to_fourcc(data.format))
.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");
render_buffer =
Offscreen::<GlesRenderbuffer>::create_buffer( if pre_postprocess_texture
&mut renderer, .as_ref()
format, .is_some_and(|tex| tex.format() == Some(format))
size, {
) None
.map_err(RenderError::<<GlMultiRenderer as RendererSuper>::Error>::Rendering)?; } else {
renderer render_buffer =
.bind(&mut render_buffer) Offscreen::<GlesRenderbuffer>::create_buffer(
.map_err(RenderError::<<GlMultiRenderer as RendererSuper>::Error>::Rendering)? &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 { if let Some(ref damage) = damage {
@ -1214,52 +1272,76 @@ impl SurfaceThreadState {
d d
}); });
match frame_result if let Some(tex) = pre_postprocess_texture.as_mut() {
.blit_frame_result( let mut tex_fb = renderer.bind(tex).map_err(RenderError::<<GlMultiRenderer as RendererSuper>::Error>::Rendering)?;
output_size,
output_transform, if let Some(fb) = fb.as_mut() {
output_scale, for rect in adjusted {
&mut renderer, renderer
&mut fb, .blit(
adjusted, &mut tex_fb,
filter, fb,
) rect,
.map_err(|err| match err { rect,
BlitFrameResultError::Rendering(err) => RenderError::< TextureFilter::Linear,
<GlMultiRenderer as RendererSuper>::Error, )
>::Rendering( .map_err(
err RenderError::<
), <GlMultiRenderer as RendererSuper>::Error,
BlitFrameResultError::Export(_) => RenderError::< >::Rendering,
<GlMultiRenderer as RendererSuper>::Error, )?;
>::Rendering( }
MultiError::DeviceMissing, } else {
), fb = Some(tex_fb);
}) {
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;
} }
} 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(); let transform = self.output.current_transform();
match submit_buffer( match submit_buffer(
frame, frame,
&mut renderer, &mut renderer,
shm_buffer.then_some(&mut fb), shm_buffer.then_some(fb.as_mut().unwrap()),
transform, transform,
damage.as_deref(), damage.as_deref(),
sync, sync,
@ -1286,7 +1368,8 @@ impl SurfaceThreadState {
} }
if self.mirroring.is_none() { if self.mirroring.is_none() {
let states = frame_result.states; // If postprocessing, use states from first render
let states = pre_postprocess_states.unwrap_or(frame_result.states);
self.send_dmabuf_feedback(states); self.send_dmabuf_feedback(states);
} }
@ -1386,6 +1469,11 @@ impl SurfaceThreadState {
self.postprocess_textures.clear(); self.postprocess_textures.clear();
} }
fn update_screen_filter(&mut self, filter_config: ScreenFilter) {
self.screen_filter = filter_config;
self.postprocess_textures.clear();
}
fn send_frame_callbacks(&mut self) { fn send_frame_callbacks(&mut self) {
if self.mirroring.is_none() { if self.mirroring.is_none() {
let _ = self let _ = self

View file

@ -308,6 +308,7 @@ impl BackendData {
&mut self, &mut self,
test_only: bool, test_only: bool,
loop_handle: &LoopHandle<'static, State>, loop_handle: &LoopHandle<'static, State>,
screen_filter: &ScreenFilter,
shell: Arc<RwLock<Shell>>, shell: Arc<RwLock<Shell>>,
workspace_state: &mut WorkspaceUpdateGuard<'_, State>, workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
xdg_activation_state: &XdgActivationState, xdg_activation_state: &XdgActivationState,
@ -318,6 +319,7 @@ impl BackendData {
BackendData::Kms(ref mut state) => state.apply_config_for_outputs( BackendData::Kms(ref mut state) => state.apply_config_for_outputs(
test_only, test_only,
loop_handle, loop_handle,
screen_filter,
shell.clone(), shell.clone(),
startup_done, startup_done,
clock, clock,
@ -453,7 +455,12 @@ impl BackendData {
} }
pub fn update_screen_filter(&mut self, screen_filter: &ScreenFilter) -> anyhow::Result<()> { pub fn update_screen_filter(&mut self, screen_filter: &ScreenFilter) -> anyhow::Result<()> {
let _ = screen_filter; // TODO match self {
BackendData::Kms(ref mut state) => state.update_screen_filter(screen_filter),
BackendData::Winit(ref mut state) => {}, // TODO
BackendData::X11(ref mut state) => {}, // TODO
_ => unreachable!("No backend set when setting screen filters"),
}
} }
} }

View file

@ -136,6 +136,7 @@ impl State {
let res = self.backend.apply_config_for_outputs( let res = self.backend.apply_config_for_outputs(
test_only, test_only,
&self.common.event_loop_handle, &self.common.event_loop_handle,
self.common.config.dynamic_conf.screen_filter(),
self.common.shell.clone(), self.common.shell.clone(),
&mut self.common.workspace_state.update(), &mut self.common.workspace_state.update(),
&self.common.xdg_activation_state, &self.common.xdg_activation_state,
@ -158,6 +159,7 @@ impl State {
if let Err(err) = self.backend.apply_config_for_outputs( if let Err(err) = self.backend.apply_config_for_outputs(
false, false,
&self.common.event_loop_handle, &self.common.event_loop_handle,
self.common.config.dynamic_conf.screen_filter(),
self.common.shell.clone(), self.common.shell.clone(),
&mut self.common.workspace_state.update(), &mut self.common.workspace_state.update(),
&self.common.xdg_activation_state, &self.common.xdg_activation_state,