diff --git a/src/backend/kms/device.rs b/src/backend/kms/device.rs index 694e7e75..3409a954 100644 --- a/src/backend/kms/device.rs +++ b/src/backend/kms/device.rs @@ -2,7 +2,7 @@ use crate::{ backend::render::{output_elements, CursorMode, GlMultiRenderer, CLEAR_COLOR}, - config::{AdaptiveSync, OutputConfig, OutputState}, + config::{AdaptiveSync, OutputConfig, OutputState, ScreenFilter}, shell::Shell, utils::prelude::*, wayland::protocols::screencopy::Frame as ScreencopyFrame, @@ -272,6 +272,7 @@ impl State { maybe_crtc, (w, 0), &self.common.event_loop_handle, + self.common.config.dynamic_conf.screen_filter().clone(), self.common.shell.clone(), self.common.startup_done.clone(), ) { @@ -365,6 +366,7 @@ impl State { maybe_crtc, (w, 0), &self.common.event_loop_handle, + self.common.config.dynamic_conf.screen_filter().clone(), self.common.shell.clone(), self.common.startup_done.clone(), ) { @@ -516,6 +518,7 @@ impl Device { maybe_crtc: Option, position: (u32, u32), evlh: &LoopHandle<'static, State>, + screen_filter: ScreenFilter, shell: Arc>, startup_done: Arc, ) -> Result<(Output, bool)> { @@ -579,6 +582,7 @@ impl Device { self.dev_node, self.render_node, evlh, + screen_filter, shell, startup_done, ) { diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 199884b1..624ed077 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ - config::{AdaptiveSync, OutputState}, + config::{AdaptiveSync, OutputState, ScreenFilter}, shell::Shell, state::BackendData, utils::prelude::*, @@ -558,6 +558,7 @@ impl KmsState { &mut self, test_only: bool, loop_handle: &LoopHandle<'static, State>, + screen_filter: &ScreenFilter, shell: Arc>, startup_done: Arc, clock: &Clock, @@ -658,6 +659,7 @@ impl KmsState { Some(crtc), (w, 0), loop_handle, + screen_filter.clone(), shell.clone(), startup_done.clone(), )?; @@ -927,4 +929,19 @@ impl KmsState { 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(()) + } } diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs index 335ecfa7..f482faa7 100644 --- a/src/backend/kms/surface/mod.rs +++ b/src/backend/kms/surface/mod.rs @@ -6,7 +6,7 @@ use crate::{ init_shaders, output_elements, CursorMode, GlMultiRenderer, PostprocessOutputConfig, PostprocessShader, PostprocessState, CLEAR_COLOR, }, - config::AdaptiveSync, + config::{AdaptiveSync, ScreenFilter}, shell::Shell, state::SurfaceDmabufFeedback, utils::prelude::*, @@ -40,12 +40,12 @@ use smithay::{ utils::{constrain_render_elements, ConstrainAlign, ConstrainScaleBehavior}, Element, Kind, RenderElementStates, }, - gles::{GlesRenderbuffer, GlesTexture}, + gles::{element::TextureShaderElement, GlesRenderbuffer, GlesRenderer, Uniform}, glow::GlowRenderer, multigpu::{Error as MultiError, GpuManager}, sync::SyncPoint, utils::with_renderer_surface_state, - Bind, ImportDma, Offscreen, Renderer, RendererSuper, Texture, + Bind, Blit, ImportDma, Offscreen, Renderer, RendererSuper, Texture, TextureFilter, }, }, desktop::utils::OutputPresentationFeedback, @@ -74,7 +74,7 @@ use smithay::{ use tracing::{error, trace, warn}; use std::{ - borrow::BorrowMut, + borrow::{Borrow, BorrowMut}, collections::{hash_map, HashMap, HashSet}, mem, sync::{ @@ -129,6 +129,7 @@ pub struct SurfaceThreadState { output: Output, mirroring: Option, + screen_filter: ScreenFilter, postprocess_textures: HashMap, shell: Arc>, @@ -190,6 +191,7 @@ pub enum ThreadCommand { node: DrmNode, }, UpdateMirroring(Option), + UpdateScreenFilter(ScreenFilter), VBlank(Option), ScheduleRender, AdaptiveSyncAvailable(SyncSender>), @@ -214,6 +216,7 @@ impl Surface { dev_node: DrmNode, target_node: DrmNode, evlh: &LoopHandle<'static, State>, + screen_filter: ScreenFilter, shell: Arc>, startup_done: Arc, ) -> Result { @@ -233,6 +236,7 @@ impl Surface { target_node, shell, active_clone, + screen_filter, tx2, rx, startup_done, @@ -354,6 +358,12 @@ impl Surface { .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 { let (tx, rx) = std::sync::mpsc::sync_channel(1); let _ = self @@ -447,6 +457,7 @@ fn surface_thread( target_node: DrmNode, shell: Arc>, active: Arc, + screen_filter: ScreenFilter, thread_sender: Sender, thread_receiver: Channel, startup_done: Arc, @@ -490,6 +501,7 @@ fn surface_thread( output, mirroring: None, + screen_filter, postprocess_textures: HashMap::new(), shell, @@ -528,6 +540,9 @@ fn surface_thread( Event::Msg(ThreadCommand::UpdateMirroring(mirroring_output)) => { state.update_mirroring(mirroring_output); } + Event::Msg(ThreadCommand::UpdateScreenFilter(filter_config)) => { + state.update_screen_filter(filter_config); + } Event::Msg(ThreadCommand::AdaptiveSyncAvailable(result)) => { if let Some(compositor) = state.compositor.as_mut() { let _ = result.send( @@ -989,11 +1004,15 @@ impl SurfaceThreadState { let source_output = self .mirroring .as_ref() + .or((!self.screen_filter.is_noop()).then(|| &self.output)) .filter(|output| { PostprocessOutputConfig::for_output_untransformed(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 offscreen_output_config = PostprocessOutputConfig::for_output_untransformed(source_output); @@ -1023,6 +1042,10 @@ impl SurfaceThreadState { .texture .render() .draw::<_, ::Error>(|tex| { + if self.mirroring.is_none() { + pre_postprocess_texture = Some(tex.clone()); + } + let mut fb = renderer.bind(tex)?; let res = match postprocess_state.damage_tracker.render_output( &mut renderer, @@ -1035,6 +1058,11 @@ impl SurfaceThreadState { Err(RenderError::Rendering(err)) => return Err(err), Err(RenderError::OutputNoMode(_)) => unreachable!(), }; + + if self.mirroring.is_none() { + pre_postprocess_states = Some(res.states); + } + renderer.wait(&res.sync)?; std::mem::drop(fb); @@ -1061,29 +1089,52 @@ impl SurfaceThreadState { None, 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::>(); renderer = self.api.single_renderer(&self.target_node).unwrap(); + + let postprocess_texture_shader = Borrow::::borrow(renderer.as_mut()) + .egl_context() + .user_data() + .get::() + .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::>() + }; + if let Err(err) = compositor.with_compositor(|c| c.use_vrr(vrr)) { warn!("Unable to set adaptive VRR state: {}", err); } @@ -1150,16 +1201,15 @@ impl SurfaceThreadState { }; 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(); - renderer + Some(renderer .bind(&mut dmabuf_clone) - .map_err(RenderError::<::Error>::Rendering)? + .map_err(RenderError::<::Error>::Rendering)?) } else { shm_buffer = true; 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)) .map_err(|_| OutputNoMode)? // eh, we have to do some error .expect("We should be able to convert all hardcoded shm screencopy formats"); - render_buffer = - Offscreen::::create_buffer( - &mut renderer, - format, - size, - ) - .map_err(RenderError::<::Error>::Rendering)?; - renderer - .bind(&mut render_buffer) - .map_err(RenderError::<::Error>::Rendering)? + + if pre_postprocess_texture + .as_ref() + .is_some_and(|tex| tex.format() == Some(format)) + { + None + } else { + render_buffer = + Offscreen::::create_buffer( + &mut renderer, + format, + size, + ) + .map_err(RenderError::<::Error>::Rendering)?; + Some(renderer + .bind(&mut render_buffer) + .map_err(RenderError::<::Error>::Rendering)?) + } }; if let Some(ref damage) = damage { @@ -1214,52 +1272,76 @@ impl SurfaceThreadState { d }); - match frame_result - .blit_frame_result( - output_size, - output_transform, - output_scale, - &mut renderer, - &mut fb, - adjusted, - filter, - ) - .map_err(|err| match err { - BlitFrameResultError::Rendering(err) => RenderError::< - ::Error, - >::Rendering( - err - ), - BlitFrameResultError::Export(_) => RenderError::< - ::Error, - >::Rendering( - MultiError::DeviceMissing, - ), - }) { - Ok(new_sync) => { - sync = new_sync; - } - Err(err) => { - tracing::warn!(?err, "Failed to screencopy"); - session - .user_data() - .get::() - .unwrap() - .lock() - .unwrap() - .reset(); - frame.fail(FailureReason::Unknown); - continue; + if let Some(tex) = pre_postprocess_texture.as_mut() { + let mut tex_fb = renderer.bind(tex).map_err(RenderError::<::Error>::Rendering)?; + + if let Some(fb) = fb.as_mut() { + for rect in adjusted { + renderer + .blit( + &mut tex_fb, + fb, + rect, + rect, + TextureFilter::Linear, + ) + .map_err( + RenderError::< + ::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::< + ::Error, + >::Rendering( + err + ), + BlitFrameResultError::Export(_) => RenderError::< + ::Error, + >::Rendering( + MultiError::DeviceMissing, + ), + }) { + Ok(new_sync) => { + sync = new_sync; + } + Err(err) => { + tracing::warn!(?err, "Failed to screencopy"); + session + .user_data() + .get::() + .unwrap() + .lock() + .unwrap() + .reset(); + frame.fail(FailureReason::Unknown); + continue; + } + }; }; - }; + } let transform = self.output.current_transform(); match submit_buffer( frame, &mut renderer, - shm_buffer.then_some(&mut fb), + shm_buffer.then_some(fb.as_mut().unwrap()), transform, damage.as_deref(), sync, @@ -1286,7 +1368,8 @@ impl SurfaceThreadState { } 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); } @@ -1386,6 +1469,11 @@ impl SurfaceThreadState { 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) { if self.mirroring.is_none() { let _ = self diff --git a/src/state.rs b/src/state.rs index c49a8d9e..fe6e1899 100644 --- a/src/state.rs +++ b/src/state.rs @@ -308,6 +308,7 @@ impl BackendData { &mut self, test_only: bool, loop_handle: &LoopHandle<'static, State>, + screen_filter: &ScreenFilter, shell: Arc>, workspace_state: &mut WorkspaceUpdateGuard<'_, State>, xdg_activation_state: &XdgActivationState, @@ -318,6 +319,7 @@ impl BackendData { BackendData::Kms(ref mut state) => state.apply_config_for_outputs( test_only, loop_handle, + screen_filter, shell.clone(), startup_done, clock, @@ -453,7 +455,12 @@ impl BackendData { } 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"), + } } } diff --git a/src/wayland/handlers/output_configuration.rs b/src/wayland/handlers/output_configuration.rs index 04245736..1f0ad7bb 100644 --- a/src/wayland/handlers/output_configuration.rs +++ b/src/wayland/handlers/output_configuration.rs @@ -136,6 +136,7 @@ impl State { let res = self.backend.apply_config_for_outputs( test_only, &self.common.event_loop_handle, + self.common.config.dynamic_conf.screen_filter(), self.common.shell.clone(), &mut self.common.workspace_state.update(), &self.common.xdg_activation_state, @@ -158,6 +159,7 @@ impl State { if let Err(err) = self.backend.apply_config_for_outputs( false, &self.common.event_loop_handle, + self.common.config.dynamic_conf.screen_filter(), self.common.shell.clone(), &mut self.common.workspace_state.update(), &self.common.xdg_activation_state,