From 18335c6758974b2f0f69ba8537ffad64c17f42f2 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 19 Mar 2025 14:15:47 +0100 Subject: [PATCH] kms: Refactor out postprocessing helpers --- src/backend/kms/surface/mod.rs | 129 ++++++---------------- src/backend/render/element.rs | 35 +++--- src/backend/render/mod.rs | 100 ++++++++++++++++- src/backend/render/shaders/offscreen.frag | 90 +++++++++++++++ 4 files changed, 236 insertions(+), 118 deletions(-) create mode 100644 src/backend/render/shaders/offscreen.frag diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs index 2705150a..335ecfa7 100644 --- a/src/backend/kms/surface/mod.rs +++ b/src/backend/kms/surface/mod.rs @@ -3,7 +3,8 @@ use crate::{ backend::render::{ element::{CosmicElement, DamageElement}, - init_shaders, output_elements, CursorMode, GlMultiRenderer, CLEAR_COLOR, + init_shaders, output_elements, CursorMode, GlMultiRenderer, PostprocessOutputConfig, + PostprocessShader, PostprocessState, CLEAR_COLOR, }, config::AdaptiveSync, shell::Shell, @@ -24,7 +25,6 @@ use smithay::{ allocator::{ format::FormatSet, gbm::{GbmAllocator, GbmDevice}, - Fourcc, }, drm::{ compositor::{BlitFrameResultError, FrameError, FrameFlags, PrimaryPlaneElement}, @@ -34,9 +34,9 @@ use smithay::{ egl::EGLContext, renderer::{ buffer_dimensions, - damage::{Error as RenderError, OutputDamageTracker}, + damage::Error as RenderError, element::{ - texture::{TextureRenderBuffer, TextureRenderElement}, + texture::TextureRenderElement, utils::{constrain_render_elements, ConstrainAlign, ConstrainScaleBehavior}, Element, Kind, RenderElementStates, }, @@ -63,7 +63,7 @@ use smithay::{ }, wayland_server::protocol::wl_surface::WlSurface, }, - utils::{Buffer as BufferCoords, Clock, Monotonic, Physical, Rectangle, Size, Transform}, + utils::{Buffer as BufferCoords, Clock, Monotonic, Physical, Rectangle, Transform}, wayland::{ dmabuf::{get_dmabuf, DmabufFeedbackBuilder}, presentation::Refresh, @@ -129,7 +129,7 @@ pub struct SurfaceThreadState { output: Output, mirroring: Option, - mirroring_textures: HashMap, + postprocess_textures: HashMap, shell: Arc>, @@ -140,76 +140,6 @@ pub struct SurfaceThreadState { egui: EguiState, } -#[derive(Debug, PartialEq)] -struct MirroringOutputConfig { - size: Size, - fractional_scale: f64, -} - -impl MirroringOutputConfig { - fn for_output_untransformed(output: &Output) -> Self { - Self { - // Apply inverse of output transform to mode size to get correct size - // for an untransformed render. - size: output.current_transform().invert().transform_size( - output - .current_mode() - .map(|mode| mode.size) - .unwrap_or_default(), - ), - fractional_scale: output.current_scale().fractional_scale(), - } - } - - fn for_output(output: &Output) -> Self { - Self { - size: output - .current_mode() - .map(|mode| mode.size) - .unwrap_or_default(), - fractional_scale: output.current_scale().fractional_scale(), - } - } -} - -#[derive(Debug)] -struct MirroringState { - texture: TextureRenderBuffer, - damage_tracker: OutputDamageTracker, - output_config: MirroringOutputConfig, -} - -impl MirroringState { - fn new_with_renderer( - renderer: &mut GlMultiRenderer, - format: Fourcc, - output_config: MirroringOutputConfig, - ) -> Result { - let size = output_config.size; - let buffer_size = size.to_logical(1).to_buffer(1, Transform::Normal); - let opaque_regions = vec![Rectangle::from_size(buffer_size)]; - - let texture = Offscreen::::create_buffer(renderer, format, buffer_size)?; - let texture_buffer = TextureRenderBuffer::from_texture( - renderer, - texture, - 1, - Transform::Normal, - Some(opaque_regions), - ); - - // Don't use `from_output` to avoid applying output transform - let damage_tracker = - OutputDamageTracker::new(size, output_config.fractional_scale, Transform::Normal); - - Ok(MirroringState { - texture: texture_buffer, - damage_tracker, - output_config, - }) - } -} - pub type GbmDrmOutput = DrmOutput< GbmAllocator, GbmDevice, @@ -560,7 +490,7 @@ fn surface_thread( output, mirroring: None, - mirroring_textures: HashMap::new(), + postprocess_textures: HashMap::new(), shell, loop_handle: event_loop.handle(), @@ -1056,40 +986,45 @@ impl SurfaceThreadState { }).unwrap_or_default(); // actual rendering - let res = if let Some(mirrored_output) = self.mirroring.as_ref().filter(|mirrored_output| { - MirroringOutputConfig::for_output_untransformed(mirrored_output) - != MirroringOutputConfig::for_output(&self.output) - }) { - let mirrored_output_config = - MirroringOutputConfig::for_output_untransformed(mirrored_output); - let mirroring_state = match self.mirroring_textures.entry(self.target_node) { + let source_output = self + .mirroring + .as_ref() + .filter(|output| { + PostprocessOutputConfig::for_output_untransformed(output) + != PostprocessOutputConfig::for_output(&self.output) + }); + + let res = if let Some(source_output) = source_output { + let offscreen_output_config = + PostprocessOutputConfig::for_output_untransformed(source_output); + let postprocess_state = match self.postprocess_textures.entry(self.target_node) { hash_map::Entry::Occupied(occupied) => { - let mirroring_state = occupied.into_mut(); + let postprocess_state = occupied.into_mut(); // If output config is different, re-create offscreen state - if mirroring_state.output_config != mirrored_output_config { - *mirroring_state = MirroringState::new_with_renderer( + if postprocess_state.output_config != offscreen_output_config { + *postprocess_state = PostprocessState::new_with_renderer( &mut renderer, compositor.format(), - mirrored_output_config, + offscreen_output_config, )? } - mirroring_state + postprocess_state } hash_map::Entry::Vacant(vacant) => { - vacant.insert(MirroringState::new_with_renderer( + vacant.insert(PostprocessState::new_with_renderer( &mut renderer, compositor.format(), - mirrored_output_config, + offscreen_output_config, )?) } }; - mirroring_state + postprocess_state .texture .render() .draw::<_, ::Error>(|tex| { let mut fb = renderer.bind(tex)?; - let res = match mirroring_state.damage_tracker.render_output( + let res = match postprocess_state.damage_tracker.render_output( &mut renderer, &mut fb, 1, @@ -1103,7 +1038,7 @@ impl SurfaceThreadState { renderer.wait(&res.sync)?; std::mem::drop(fb); - let transform = mirrored_output.current_transform(); + let transform = source_output.current_transform(); let area = tex.size().to_logical(1, transform); Ok(res @@ -1120,7 +1055,7 @@ impl SurfaceThreadState { let texture_elem = TextureRenderElement::from_texture_render_buffer( (0., 0.), - &mirroring_state.texture, + &postprocess_state.texture, Some(1.0), None, None, @@ -1145,7 +1080,7 @@ impl SurfaceThreadState { ConstrainAlign::CENTER, 1.0, ) - .map(CosmicElement::Mirror) + .map(CosmicElement::Postprocess) .collect::>(); renderer = self.api.single_renderer(&self.target_node).unwrap(); @@ -1448,7 +1383,7 @@ impl SurfaceThreadState { fn update_mirroring(&mut self, mirroring_output: Option) { self.mirroring = mirroring_output; - self.mirroring_textures.clear(); + self.postprocess_textures.clear(); } fn send_frame_callbacks(&mut self) { diff --git a/src/backend/render/element.rs b/src/backend/render/element.rs index bf5d37b4..426ba2ef 100644 --- a/src/backend/render/element.rs +++ b/src/backend/render/element.rs @@ -1,15 +1,16 @@ use crate::shell::{CosmicMappedRenderElement, WorkspaceRenderElement}; +#[cfg(feature = "debug")] +use smithay::backend::renderer::{element::texture::TextureRenderElement, gles::GlesTexture}; use smithay::{ backend::renderer::{ element::{ memory::MemoryRenderBufferRenderElement, surface::WaylandSurfaceRenderElement, - texture::TextureRenderElement, utils::{CropRenderElement, Relocate, RelocateRenderElement, RescaleRenderElement}, Element, Id, Kind, RenderElement, UnderlyingStorage, }, - gles::{GlesError, GlesTexture}, + gles::{element::TextureShaderElement, GlesError}, glow::{GlowFrame, GlowRenderer}, utils::{CommitCounter, DamageSet, OpaqueRegions}, ImportAll, ImportMem, Renderer, @@ -32,10 +33,8 @@ where Dnd(WaylandSurfaceRenderElement), MoveGrab(RescaleRenderElement>), AdditionalDamage(DamageElement), - Mirror( - CropRenderElement< - RelocateRenderElement>>, - >, + Postprocess( + CropRenderElement>>, ), Zoom(MemoryRenderBufferRenderElement), #[cfg(feature = "debug")] @@ -55,7 +54,7 @@ where CosmicElement::Dnd(elem) => elem.id(), CosmicElement::MoveGrab(elem) => elem.id(), CosmicElement::AdditionalDamage(elem) => elem.id(), - CosmicElement::Mirror(elem) => elem.id(), + CosmicElement::Postprocess(elem) => elem.id(), CosmicElement::Zoom(elem) => elem.id(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.id(), @@ -69,7 +68,7 @@ where CosmicElement::Dnd(elem) => elem.current_commit(), CosmicElement::MoveGrab(elem) => elem.current_commit(), CosmicElement::AdditionalDamage(elem) => elem.current_commit(), - CosmicElement::Mirror(elem) => elem.current_commit(), + CosmicElement::Postprocess(elem) => elem.current_commit(), CosmicElement::Zoom(elem) => elem.current_commit(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.current_commit(), @@ -83,7 +82,7 @@ where CosmicElement::Dnd(elem) => elem.src(), CosmicElement::MoveGrab(elem) => elem.src(), CosmicElement::AdditionalDamage(elem) => elem.src(), - CosmicElement::Mirror(elem) => elem.src(), + CosmicElement::Postprocess(elem) => elem.src(), CosmicElement::Zoom(elem) => elem.src(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.src(), @@ -97,7 +96,7 @@ where CosmicElement::Dnd(elem) => elem.geometry(scale), CosmicElement::MoveGrab(elem) => elem.geometry(scale), CosmicElement::AdditionalDamage(elem) => elem.geometry(scale), - CosmicElement::Mirror(elem) => elem.geometry(scale), + CosmicElement::Postprocess(elem) => elem.geometry(scale), CosmicElement::Zoom(elem) => elem.geometry(scale), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.geometry(scale), @@ -111,7 +110,7 @@ where CosmicElement::Dnd(elem) => elem.location(scale), CosmicElement::MoveGrab(elem) => elem.location(scale), CosmicElement::AdditionalDamage(elem) => elem.location(scale), - CosmicElement::Mirror(elem) => elem.location(scale), + CosmicElement::Postprocess(elem) => elem.location(scale), CosmicElement::Zoom(elem) => elem.location(scale), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.location(scale), @@ -125,7 +124,7 @@ where CosmicElement::Dnd(elem) => elem.transform(), CosmicElement::MoveGrab(elem) => elem.transform(), CosmicElement::AdditionalDamage(elem) => elem.transform(), - CosmicElement::Mirror(elem) => elem.transform(), + CosmicElement::Postprocess(elem) => elem.transform(), CosmicElement::Zoom(elem) => elem.transform(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.transform(), @@ -143,7 +142,7 @@ where CosmicElement::Dnd(elem) => elem.damage_since(scale, commit), CosmicElement::MoveGrab(elem) => elem.damage_since(scale, commit), CosmicElement::AdditionalDamage(elem) => elem.damage_since(scale, commit), - CosmicElement::Mirror(elem) => elem.damage_since(scale, commit), + CosmicElement::Postprocess(elem) => elem.damage_since(scale, commit), CosmicElement::Zoom(elem) => elem.damage_since(scale, commit), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.damage_since(scale, commit), @@ -157,7 +156,7 @@ where CosmicElement::Dnd(elem) => elem.opaque_regions(scale), CosmicElement::MoveGrab(elem) => elem.opaque_regions(scale), CosmicElement::AdditionalDamage(elem) => elem.opaque_regions(scale), - CosmicElement::Mirror(elem) => elem.opaque_regions(scale), + CosmicElement::Postprocess(elem) => elem.opaque_regions(scale), CosmicElement::Zoom(elem) => elem.opaque_regions(scale), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.opaque_regions(scale), @@ -171,7 +170,7 @@ where CosmicElement::Dnd(elem) => elem.alpha(), CosmicElement::MoveGrab(elem) => elem.alpha(), CosmicElement::AdditionalDamage(elem) => elem.alpha(), - CosmicElement::Mirror(elem) => elem.alpha(), + CosmicElement::Postprocess(elem) => elem.alpha(), CosmicElement::Zoom(elem) => elem.alpha(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.alpha(), @@ -185,7 +184,7 @@ where CosmicElement::Dnd(elem) => elem.kind(), CosmicElement::MoveGrab(elem) => elem.kind(), CosmicElement::AdditionalDamage(elem) => elem.kind(), - CosmicElement::Mirror(elem) => elem.kind(), + CosmicElement::Postprocess(elem) => elem.kind(), CosmicElement::Zoom(elem) => elem.kind(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.kind(), @@ -216,7 +215,7 @@ where CosmicElement::AdditionalDamage(elem) => { RenderElement::::draw(elem, frame, src, dst, damage, opaque_regions) } - CosmicElement::Mirror(elem) => { + CosmicElement::Postprocess(elem) => { let glow_frame = R::glow_frame_mut(frame); RenderElement::::draw( elem, @@ -252,7 +251,7 @@ where CosmicElement::Dnd(elem) => elem.underlying_storage(renderer), CosmicElement::MoveGrab(elem) => elem.underlying_storage(renderer), CosmicElement::AdditionalDamage(elem) => elem.underlying_storage(renderer), - CosmicElement::Mirror(elem) => { + CosmicElement::Postprocess(elem) => { let glow_renderer = renderer.glow_renderer_mut(); elem.underlying_storage(glow_renderer) } diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 2468eca8..62fb8620 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -13,6 +13,7 @@ use std::{ use crate::debug::fps_ui; use crate::{ backend::{kms::render::gles::GbmGlowBackend, render::element::DamageElement}, + config::ScreenFilter, shell::{ element::CosmicMappedKey, focus::{render_input_order, target::WindowGroup, Stage}, @@ -36,7 +37,7 @@ use cosmic::Theme; use element::FromGlesError; use smithay::{ backend::{ - allocator::dmabuf::Dmabuf, + allocator::{dmabuf::Dmabuf, Fourcc}, drm::{DrmDeviceFd, DrmNode}, renderer::{ damage::{Error as RenderError, OutputDamageTracker, RenderOutputResult}, @@ -52,13 +53,15 @@ use smithay::{ glow::GlowRenderer, multigpu::{Error as MultiError, MultiFrame, MultiRenderer}, sync::SyncPoint, - Bind, Blit, Color32F, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, + Bind, Blit, Color32F, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, Texture, TextureFilter, }, }, input::Seat, output::{Output, OutputNoMode}, - utils::{IsAlive, Logical, Monotonic, Point, Rectangle, Scale, Time, Transform}, + utils::{ + IsAlive, Logical, Monotonic, Physical, Point, Rectangle, Scale, Size, Time, Transform, + }, wayland::{dmabuf::get_dmabuf, session_lock::LockSurface}, }; @@ -105,6 +108,7 @@ impl<'a> AsMut for RendererRef<'a> { pub static CLEAR_COLOR: Color32F = Color32F::new(0.153, 0.161, 0.165, 1.0); pub static OUTLINE_SHADER: &str = include_str!("./shaders/rounded_outline.frag"); pub static RECTANGLE_SHADER: &str = include_str!("./shaders/rounded_rectangle.frag"); +pub static POSTPROCESS_SHADER: &str = include_str!("./shaders/offscreen.frag"); pub static GROUP_COLOR: [f32; 3] = [0.788, 0.788, 0.788]; pub static ACTIVE_GROUP_COLOR: [f32; 3] = [0.58, 0.922, 0.922]; @@ -346,11 +350,14 @@ impl BackdropShader { } } +pub struct PostprocessShader(pub GlesTexProgram); + pub fn init_shaders(renderer: &mut GlesRenderer) -> Result<(), GlesError> { { let egl_context = renderer.egl_context(); if egl_context.user_data().get::().is_some() && egl_context.user_data().get::().is_some() + && egl_context.user_data().get::().is_some() { return Ok(()); } @@ -371,6 +378,13 @@ pub fn init_shaders(renderer: &mut GlesRenderer) -> Result<(), GlesError> { UniformName::new("radius", UniformType::_1f), ], )?; + let postprocess_shader = renderer.compile_custom_texture_shader( + POSTPROCESS_SHADER, + &[ + UniformName::new("invert", UniformType::_1f), + UniformName::new("color_mode", UniformType::_1f), + ], + )?; let egl_context = renderer.egl_context(); egl_context @@ -379,6 +393,9 @@ pub fn init_shaders(renderer: &mut GlesRenderer) -> Result<(), GlesError> { egl_context .user_data() .insert_if_missing(|| BackdropShader(rectangle_shader)); + egl_context + .user_data() + .insert_if_missing(|| PostprocessShader(postprocess_shader)); Ok(()) } @@ -976,6 +993,83 @@ where } } +// Used for mirroring and postprocessing +#[derive(Debug)] +pub struct PostprocessState { + pub texture: TextureRenderBuffer, + pub damage_tracker: OutputDamageTracker, + pub output_config: PostprocessOutputConfig, +} + +impl PostprocessState { + pub fn new_with_renderer>( + renderer: &mut R, + format: Fourcc, + output_config: PostprocessOutputConfig, + ) -> Result { + let size = output_config.size; + let buffer_size = size.to_logical(1).to_buffer(1, Transform::Normal); + let opaque_regions = vec![Rectangle::from_size(buffer_size)]; + + let texture = Offscreen::::create_buffer(renderer, format, buffer_size)?; + let texture_buffer = TextureRenderBuffer::from_texture( + renderer, + texture, + 1, + Transform::Normal, + Some(opaque_regions), + ); + + // Don't use `from_output` to avoid applying output transform + let damage_tracker = + OutputDamageTracker::new(size, output_config.fractional_scale, Transform::Normal); + + Ok(PostprocessState { + texture: texture_buffer, + damage_tracker, + output_config, + }) + } +} + +#[derive(Debug, PartialEq)] +pub struct PostprocessOutputConfig { + pub size: Size, + pub fractional_scale: f64, +} + +impl PostprocessOutputConfig { + pub fn for_output_untransformed(output: &Output) -> Self { + Self { + // Apply inverse of output transform to mode size to get correct size + // for an untransformed render. + size: output.current_transform().invert().transform_size( + output + .current_mode() + .map(|mode| mode.size) + .unwrap_or_default(), + ), + fractional_scale: output.current_scale().fractional_scale(), + } + } + + pub fn for_output(output: &Output) -> Self { + Self { + size: output + .current_mode() + .map(|mode| mode.size) + .unwrap_or_default(), + fractional_scale: output.current_scale().fractional_scale(), + } + } +} + +#[derive(Debug, Default)] +pub struct ScreenFilterStorage { + pub filter: ScreenFilter, + pub state: Option, +} + #[profiling::function] pub fn render_output<'d, R, OffTarget>( gpu: Option<&DrmNode>, diff --git a/src/backend/render/shaders/offscreen.frag b/src/backend/render/shaders/offscreen.frag new file mode 100644 index 00000000..6b791081 --- /dev/null +++ b/src/backend/render/shaders/offscreen.frag @@ -0,0 +1,90 @@ +#version 100 + +//_DEFINES_ + +#if defined(EXTERNAL) +#extension GL_OES_EGL_image_external : require +#endif + +precision mediump float; +#if defined(EXTERNAL) +uniform samplerExternalOES tex; +#else +uniform sampler2D tex; +#endif + +uniform float alpha; +varying vec2 v_coords; + +#if defined(DEBUG_FLAGS) +uniform float tint; +#endif + +uniform float invert; +uniform float color_mode; + +void main() { + vec4 color = texture2D(tex, v_coords); + +#if defined(NO_ALPHA) + color = vec4(color.rgb, 1.0) * alpha; +#else + color = color * alpha; +#endif + + // un-multiply + color.rgb /= color.a; + + // First invert then filter + + if (invert == 1.0) { + color.rgb = 1.0 - color.rgb; + } + + if (color_mode == 1.0) { // greyscale + float value = (color.r + color.g + color.b) / 3.0; + color = vec4(value, value, value, color.a); + } else if (color_mode >= 2.0) { + float L = (17.8824 * color.r) + (43.5161 * color.g) + (4.11935 * color.b); + float M = (3.45565 * color.r) + (27.1554 * color.g) + (3.86714 * color.b); + float S = (0.0299566 * color.r) + (0.184309 * color.g) + (1.46709 * color.b); + + float l, m, s; + if (color_mode == 2.0) { // Protanopia + l = 0.0 * L + 2.02344 * M + -2.52581 * S; + m = 0.0 * L + 1.0 * M + 0.0 * S; + s = 0.0 * L + 0.0 * M + 1.0 * S; + } else if (color_mode == 3.0) { // Deuteranopia + l = 1.0 * L + 0.0 * M + 0.0 * S; + m = 0.494207 * L + 0.0 * M + 1.24827 * S; + s = 0.0 * L + 0.0 * M + 1.0 * S; + } else if (color_mode == 4.0) { // Tritanopia + l = 1.0 * L + 0.0 * M + 0.0 * S; + m = 0.0 * L + 1.0 * M + 0.0 * S; + s = -0.395913 * L + 0.801109 * M + 0.0 * S; + } else { + // unknown + l = L; + m = M; + s = S; + } + + vec3 error; + error.r = (0.0809444479 * l) + (-0.130504409 * m) + (0.116721066 * s); + error.g = (-0.0102485335 * l) + (0.0540193266 * m) + (-0.113614708 * s); + error.b = (-0.000365296938 * l) + (-0.00412161469 * m) + (0.693511405 * s); + + vec3 diff = color.rgb - error; + vec3 correction; + correction.r = 0.0; + correction.g = (diff.r * 0.7) + (diff.g * 1.0); + correction.b = (diff.r * 0.7) + (diff.b * 1.0); + + color.rgb += correction; + } + + // re-multiply + color.rgb *= color.a; + + gl_FragColor = color; +} \ No newline at end of file