From 69ca5d92e78ffbae7bace89f4f873484e9458ac5 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 21 Feb 2026 14:24:38 +0100 Subject: [PATCH] wl_surface: handle alpha modes --- src/backends/metal/present.rs | 8 ++++++-- src/cursor.rs | 5 ++++- src/gfx_api.rs | 6 ++++++ src/ifs/wl_surface.rs | 19 ++++++++++++++++-- .../wp_color_representation_surface_v1.rs | 20 ++++++++++++------- src/ifs/wp_color_representation_manager_v1.rs | 17 ++++++++++++++-- src/portal/ptr_gui.rs | 5 ++++- src/renderer.rs | 15 ++++++++------ src/renderer/renderer_base.rs | 6 ++++-- src/state.rs | 6 ++++-- 10 files changed, 82 insertions(+), 25 deletions(-) diff --git a/src/backends/metal/present.rs b/src/backends/metal/present.rs index a4edf006..810531be 100644 --- a/src/backends/metal/present.rs +++ b/src/backends/metal/present.rs @@ -9,8 +9,8 @@ use { }, cmm::cmm_description::ColorDescription, gfx_api::{ - AcquireSync, BufferResv, GfxApiOpt, GfxRenderPass, GfxTexture, ReleaseSync, SyncFile, - create_render_pass, + AcquireSync, AlphaMode, BufferResv, GfxApiOpt, GfxRenderPass, GfxTexture, ReleaseSync, + SyncFile, create_render_pass, }, ifs::wl_output::BlendSpace, rect::Region, @@ -668,6 +668,10 @@ impl MetalConnector { } return None; }; + if ct.alpha_mode != AlphaMode::PremultipliedElectrical { + // Direct scanout requires premultiplied electrical alpha. + return None; + } if !ct.cd.embeds_into(cd) { // Direct scanout requires embeddable color descriptions. return None; diff --git a/src/cursor.rs b/src/cursor.rs index 65c1ee39..efcd7339 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -3,7 +3,7 @@ use { async_engine::AsyncEngine, fixed::Fixed, format::ARGB8888, - gfx_api::{AcquireSync, GfxContext, GfxError, GfxTexture, ReleaseSync}, + gfx_api::{AcquireSync, AlphaMode, GfxContext, GfxError, GfxTexture, ReleaseSync}, rect::Rect, renderer::Renderer, scale::Scale, @@ -398,6 +398,7 @@ fn render_img(image: &InstantiatedCursorImage, renderer: &mut Renderer, x: Fixed ReleaseSync::None, false, renderer.state.color_manager.srgb_gamma22(), + AlphaMode::PremultipliedElectrical, ); } } @@ -423,6 +424,7 @@ impl Cursor for StaticCursor { ReleaseSync::None, false, renderer.state.color_manager.srgb_gamma22(), + AlphaMode::PremultipliedElectrical, ); } } @@ -466,6 +468,7 @@ impl Cursor for AnimatedCursor { ReleaseSync::None, false, renderer.state.color_manager.srgb_gamma22(), + AlphaMode::PremultipliedElectrical, ); } } diff --git a/src/gfx_api.rs b/src/gfx_api.rs index c622af3f..cb4f6643 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -219,6 +219,7 @@ pub struct CopyTexture { pub alpha: Option, pub opaque: bool, pub cd: Rc, + pub alpha_mode: AlphaMode, } #[derive(Clone, Debug, PartialEq)] @@ -443,6 +444,7 @@ impl dyn GfxFramebuffer { release_sync, false, texture_cd, + AlphaMode::PremultipliedElectrical, ); let clear = self.format().has_alpha.then_some(&Color::TRANSPARENT); self.render( @@ -825,6 +827,10 @@ pub trait GfxContext: Debug { false } + fn supports_alpha_modes(&self) -> bool { + false + } + fn supports_invalid_modifier(&self) -> bool { false } diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 4ef51ed3..4477d61c 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -30,7 +30,7 @@ use { drm_feedback::DrmFeedback, fixed::Fixed, gfx_api::{ - AsyncShmGfxTexture, BufferResv, BufferResvUser, GfxError, GfxStagingBuffer, + AlphaMode, AsyncShmGfxTexture, BufferResv, BufferResvUser, GfxError, GfxStagingBuffer, ReleaseSync, SampleRect, SyncFile, }, ifs::{ @@ -353,6 +353,7 @@ pub struct WlSurface { CopyHashMap>, color_description: CloneCell>>, color_representation_surface: CloneCell>>, + alpha_mode: Cell, } impl Debug for WlSurface { @@ -490,6 +491,7 @@ struct PendingState { tray_item_ack_serial: Option, color_description: Option>>, serial: Option, + alpha_mode: Option, } struct AttachedSubsurfaceState { @@ -544,6 +546,7 @@ impl PendingState { opt!(tray_item_ack_serial); opt!(color_description); opt!(serial); + opt!(alpha_mode); { let (dx1, dy1) = self.offset; let (dx2, dy2) = mem::take(&mut next.offset); @@ -708,6 +711,7 @@ impl WlSurface { color_management_feedback: Default::default(), color_description: Default::default(), color_representation_surface: Default::default(), + alpha_mode: Default::default(), } } @@ -1202,6 +1206,12 @@ impl WlSurface { color_description_changed = true; self.color_description.set(desc); } + let mut alpha_mode_changed = false; + if let Some(alpha_mode) = pending.alpha_mode.take() + && self.alpha_mode.replace(alpha_mode) != alpha_mode + { + alpha_mode_changed = true; + } let mut alpha_changed = false; if let Some(alpha) = pending.alpha_multiplier.take() { alpha_changed = true; @@ -1213,7 +1223,8 @@ impl WlSurface { || buffer_transform_changed || viewport_changed || alpha_changed - || color_description_changed; + || color_description_changed + || alpha_mode_changed; let mut buffer_changed = false; let mut old_raw_size = None; let (mut dx, mut dy) = mem::take(&mut pending.offset); @@ -1725,6 +1736,10 @@ impl WlSurface { self.alpha.get() } + pub fn alpha_mode(&self) -> AlphaMode { + self.alpha_mode.get() + } + pub fn opaque(&self) -> bool { self.is_opaque.get() } diff --git a/src/ifs/wl_surface/wp_color_representation_surface_v1.rs b/src/ifs/wl_surface/wp_color_representation_surface_v1.rs index 87300e28..f4173fb7 100644 --- a/src/ifs/wl_surface/wp_color_representation_surface_v1.rs +++ b/src/ifs/wl_surface/wp_color_representation_surface_v1.rs @@ -1,6 +1,7 @@ use { crate::{ client::{Client, ClientError}, + gfx_api::AlphaMode, ifs::wl_surface::WlSurface, leaks::Tracker, object::{Object, Version}, @@ -22,12 +23,11 @@ pub struct WpColorRepresentationSurfaceV1 { pub version: Version, pub tracker: Tracker, pub surface: Rc, + pub supports_alpha_modes: bool, } pub const AM_PREMULTIPLIED_ELECTRICAL: u32 = 0; -#[expect(dead_code)] pub const AM_PREMULTIPLIED_OPTICAL: u32 = 1; -#[expect(dead_code)] pub const AM_STRAIGHT: u32 = 2; impl WpColorRepresentationSurfaceV1 { @@ -47,16 +47,22 @@ impl WpColorRepresentationSurfaceV1RequestHandler for WpColorRepresentationSurfa fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { self.surface.color_representation_surface.take(); + self.surface.pending.borrow_mut().alpha_mode = Some(Default::default()); self.client.remove_obj(self)?; Ok(()) } fn set_alpha_mode(&self, req: SetAlphaMode, _slf: &Rc) -> Result<(), Self::Error> { - if req.alpha_mode != AM_PREMULTIPLIED_ELECTRICAL { - return Err(WpColorRepresentationSurfaceV1Error::UnsupportedAlphaMode( - req.alpha_mode, - )); - } + let sam = self.supports_alpha_modes; + let alpha_mode = match req.alpha_mode { + AM_PREMULTIPLIED_ELECTRICAL => AlphaMode::PremultipliedElectrical, + AM_PREMULTIPLIED_OPTICAL if sam => AlphaMode::PremultipliedOptical, + AM_STRAIGHT if sam => AlphaMode::Straight, + n => { + return Err(WpColorRepresentationSurfaceV1Error::UnsupportedAlphaMode(n)); + } + }; + self.surface.pending.borrow_mut().alpha_mode = Some(alpha_mode); Ok(()) } diff --git a/src/ifs/wp_color_representation_manager_v1.rs b/src/ifs/wp_color_representation_manager_v1.rs index 9e43c8eb..9ebc7c15 100644 --- a/src/ifs/wp_color_representation_manager_v1.rs +++ b/src/ifs/wp_color_representation_manager_v1.rs @@ -3,8 +3,8 @@ use { client::{Client, ClientError}, globals::{Global, GlobalName}, ifs::wl_surface::wp_color_representation_surface_v1::{ - AM_PREMULTIPLIED_ELECTRICAL, WpColorRepresentationSurfaceV1, - WpColorRepresentationSurfaceV1Error, + AM_PREMULTIPLIED_ELECTRICAL, AM_PREMULTIPLIED_OPTICAL, AM_STRAIGHT, + WpColorRepresentationSurfaceV1, WpColorRepresentationSurfaceV1Error, }, leaks::Tracker, object::{Object, Version}, @@ -35,11 +35,18 @@ impl WpColorRepresentationManagerV1Global { client: &Rc, version: Version, ) -> Result<(), WpColorRepresentationManagerV1Error> { + let mut supports_alpha_modes = false; + if let Some(ctx) = client.state.render_ctx.get() + && ctx.supports_alpha_modes() + { + supports_alpha_modes = true; + } let obj = Rc::new(WpColorRepresentationManagerV1 { id, client: client.clone(), tracker: Default::default(), version, + supports_alpha_modes, }); track!(client, obj); client.add_client_obj(&obj)?; @@ -53,11 +60,16 @@ pub struct WpColorRepresentationManagerV1 { pub client: Rc, pub version: Version, pub tracker: Tracker, + pub supports_alpha_modes: bool, } impl WpColorRepresentationManagerV1 { fn send_capabilities(&self) { self.send_supported_alpha_mode(AM_PREMULTIPLIED_ELECTRICAL); + if self.supports_alpha_modes { + self.send_supported_alpha_mode(AM_PREMULTIPLIED_OPTICAL); + self.send_supported_alpha_mode(AM_STRAIGHT); + } self.send_done(); } @@ -89,6 +101,7 @@ impl WpColorRepresentationManagerV1RequestHandler for WpColorRepresentationManag version: self.version, tracker: Default::default(), surface: surface.clone(), + supports_alpha_modes: self.supports_alpha_modes, }); track!(self.client, obj); self.client.add_client_obj(&obj)?; diff --git a/src/portal/ptr_gui.rs b/src/portal/ptr_gui.rs index d508c22c..7f01fc4e 100644 --- a/src/portal/ptr_gui.rs +++ b/src/portal/ptr_gui.rs @@ -7,7 +7,8 @@ use { fixed::Fixed, format::ARGB8888, gfx_api::{ - AcquireSync, GfxContext, GfxFramebuffer, GfxTexture, ReleaseSync, needs_render_usage, + AcquireSync, AlphaMode, GfxContext, GfxFramebuffer, GfxTexture, ReleaseSync, + needs_render_usage, }, ifs::zwlr_layer_shell_v1::OVERLAY, portal::{ @@ -230,6 +231,7 @@ impl GuiElement for Button { ReleaseSync::None, false, srgb_srgb, + AlphaMode::PremultipliedElectrical, ); } } @@ -332,6 +334,7 @@ impl GuiElement for Label { ReleaseSync::None, false, color_manager.srgb_gamma22(), + AlphaMode::PremultipliedElectrical, ); } } diff --git a/src/renderer.rs b/src/renderer.rs index 2530ef03..74d56b2e 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -145,6 +145,7 @@ impl Renderer<'_> { ReleaseSync::None, false, self.state.color_manager.srgb_gamma22(), + AlphaMode::PremultipliedElectrical, ); } x += bar_rect.x1() - non_exclusive_rect_rel.x1(); @@ -167,6 +168,7 @@ impl Renderer<'_> { ReleaseSync::None, false, srgb_srgb, + AlphaMode::PremultipliedElectrical, ); } for item in output.tray_items.iter() { @@ -253,6 +255,7 @@ impl Renderer<'_> { ReleaseSync::None, false, self.state.color_manager.srgb_gamma22(), + AlphaMode::PremultipliedElectrical, ); } self.render_tl_aux(placeholder.tl_data(), bounds, true); @@ -304,6 +307,7 @@ impl Renderer<'_> { ReleaseSync::None, false, srgb_srgb, + AlphaMode::PremultipliedElectrical, ); } } @@ -466,6 +470,7 @@ impl Renderer<'_> { ) { let alpha = surface.alpha(); let cd = surface.color_description(); + let alpha_mode = surface.alpha_mode(); if let Some(tex) = buffer.buffer.get_texture(surface) { let mut opaque = surface.opaque(); if !opaque && tex.format().has_alpha { @@ -485,6 +490,7 @@ impl Renderer<'_> { buffer.release_sync, opaque, &cd, + alpha_mode, ); } else if let Some(color) = &buffer.buffer.color { if let Some(rect) = Rect::new_sized(x, y, tsize.0, tsize.1) { @@ -494,12 +500,7 @@ impl Renderer<'_> { }; if !rect.is_empty() { let color = Color::from_u32( - cd.eotf, - AlphaMode::PremultipliedElectrical, - color[0], - color[1], - color[2], - color[3], + cd.eotf, alpha_mode, color[0], color[1], color[2], color[3], ); self.base.sync(); self.base @@ -586,6 +587,7 @@ impl Renderer<'_> { ReleaseSync::None, false, srgb_srgb, + AlphaMode::PremultipliedElectrical, ); } x1 += th; @@ -608,6 +610,7 @@ impl Renderer<'_> { ReleaseSync::None, false, srgb_srgb, + AlphaMode::PremultipliedElectrical, ); } let body = Rect::new_sized_saturating( diff --git a/src/renderer/renderer_base.rs b/src/renderer/renderer_base.rs index 962c18db..ab84c6a9 100644 --- a/src/renderer/renderer_base.rs +++ b/src/renderer/renderer_base.rs @@ -2,8 +2,8 @@ use { crate::{ cmm::cmm_description::{ColorDescription, LinearColorDescription}, gfx_api::{ - AcquireSync, BufferResv, CopyTexture, FillRect, FramebufferRect, GfxApiOpt, GfxTexture, - ReleaseSync, SampleRect, + AcquireSync, AlphaMode, BufferResv, CopyTexture, FillRect, FramebufferRect, GfxApiOpt, + GfxTexture, ReleaseSync, SampleRect, }, rect::Rect, scale::Scale, @@ -181,6 +181,7 @@ impl RendererBase<'_> { release_sync: ReleaseSync, opaque: bool, cd: &Rc, + alpha_mode: AlphaMode, ) { // log::info!("rendering texture {:?}", std::ptr::from_ref(&**texture) as *const u8); // log::info!("{:?}", backtrace::Backtrace::new()); @@ -228,6 +229,7 @@ impl RendererBase<'_> { release_sync, opaque, cd: cd.clone(), + alpha_mode, })); } diff --git a/src/state.rs b/src/state.rs index 667d8fd9..fd18d498 100644 --- a/src/state.rs +++ b/src/state.rs @@ -31,8 +31,9 @@ use { forker::ForkerProxy, format::Format, gfx_api::{ - AcquireSync, BufferResv, GfxBlendBuffer, GfxContext, GfxError, GfxFramebuffer, - GfxTexture, PendingShmTransfer, ReleaseSync, STAGING_DOWNLOAD, SampleRect, SyncFile, + AcquireSync, AlphaMode, BufferResv, GfxBlendBuffer, GfxContext, GfxError, + GfxFramebuffer, GfxTexture, PendingShmTransfer, ReleaseSync, STAGING_DOWNLOAD, + SampleRect, SyncFile, }, gfx_apis::create_gfx_context, globals::{Globals, GlobalsError, RemovableWaylandGlobal, WaylandGlobal}, @@ -1272,6 +1273,7 @@ impl State { release_sync, false, src_cd, + AlphaMode::PremultipliedElectrical, ); if render_hardware_cursors && let Some(cursor_user_group) = self.cursor_user_group_hardware_cursor.get()