1
0
Fork 0
forked from wry/wry

metal: allow configuring color space and transfer function

This commit is contained in:
Julian Orth 2025-03-11 14:54:35 +01:00
parent 04f280aabe
commit bb56efb968
38 changed files with 1365 additions and 160 deletions

View file

@ -1,7 +1,10 @@
use {
crate::{
client::{Client, ClientError},
ifs::color_management::wp_image_description_v1::WpImageDescriptionV1,
ifs::{
color_management::{CAUSE_NO_OUTPUT, wp_image_description_v1::WpImageDescriptionV1},
wl_output::OutputGlobalOpt,
},
leaks::Tracker,
object::{Object, Version},
wire::{WpColorManagementOutputV1Id, wp_color_management_output_v1::*},
@ -15,20 +18,29 @@ pub struct WpColorManagementOutputV1 {
pub client: Rc<Client>,
pub version: Version,
pub tracker: Tracker<Self>,
pub output: Rc<OutputGlobalOpt>,
}
impl WpColorManagementOutputV1 {
#[expect(dead_code)]
pub fn send_image_description_changed(&self) {
self.client
.event(ImageDescriptionChanged { self_id: self.id });
}
fn detach(&self) {
if let Some(output) = self.output.get() {
output
.color_description_listeners
.remove(&(self.client.id, self.id));
}
}
}
impl WpColorManagementOutputV1RequestHandler for WpColorManagementOutputV1 {
type Error = WpColorManagementOutputV1Error;
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.detach();
self.client.remove_obj(self)?;
Ok(())
}
@ -43,11 +55,15 @@ impl WpColorManagementOutputV1RequestHandler for WpColorManagementOutputV1 {
client: self.client.clone(),
version: self.version,
tracker: Default::default(),
description: self.client.state.color_manager.srgb_srgb().clone(),
description: self.output.get().map(|o| o.color_description.get()),
});
track!(self.client, obj);
self.client.add_client_obj(&obj)?;
obj.send_ready();
if obj.description.is_some() {
obj.send_ready();
} else {
obj.send_failed(CAUSE_NO_OUTPUT, "the output no longer exists");
}
Ok(())
}
}
@ -57,7 +73,11 @@ object_base! {
version = self.version;
}
impl Object for WpColorManagementOutputV1 {}
impl Object for WpColorManagementOutputV1 {
fn break_loops(&self) {
self.detach();
}
}
simple_add_obj!(WpColorManagementOutputV1);

View file

@ -1,7 +1,10 @@
use {
crate::{
client::{Client, ClientError},
ifs::color_management::wp_image_description_v1::WpImageDescriptionV1,
cmm::cmm_description::ColorDescription,
ifs::{
color_management::wp_image_description_v1::WpImageDescriptionV1, wl_surface::WlSurface,
},
leaks::Tracker,
object::{Object, Version},
wire::{
@ -18,6 +21,7 @@ pub struct WpColorManagementSurfaceFeedbackV1 {
pub client: Rc<Client>,
pub version: Version,
pub tracker: Tracker<Self>,
pub surface: Rc<WlSurface>,
}
impl WpColorManagementSurfaceFeedbackV1 {
@ -30,13 +34,20 @@ impl WpColorManagementSurfaceFeedbackV1 {
client: self.client.clone(),
version: self.version,
tracker: Default::default(),
description: self.client.state.color_manager.srgb_srgb().clone(),
description: Some(self.surface.get_output().global.color_description.get()),
});
track!(self.client, obj);
self.client.add_client_obj(&obj)?;
obj.send_ready();
Ok(())
}
pub fn send_preferred_changed(&self, cd: &ColorDescription) {
self.client.event(PreferredChanged {
self_id: self.id,
identity: cd.id.raw(),
});
}
}
impl WpColorManagementSurfaceFeedbackV1RequestHandler for WpColorManagementSurfaceFeedbackV1 {
@ -44,6 +55,7 @@ impl WpColorManagementSurfaceFeedbackV1RequestHandler for WpColorManagementSurfa
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.client.remove_obj(self)?;
self.surface.remove_color_management_feedback(self);
Ok(())
}

View file

@ -147,15 +147,21 @@ impl WpColorManagerV1RequestHandler for WpColorManagerV1 {
}
fn get_output(&self, req: GetOutput, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let _ = self.client.lookup(req.output)?;
let output = self.client.lookup(req.output)?;
let obj = Rc::new(WpColorManagementOutputV1 {
id: req.id,
client: self.client.clone(),
version: self.version,
tracker: Default::default(),
output: output.global.clone(),
});
track!(self.client, obj);
self.client.add_client_obj(&obj)?;
if let Some(global) = output.global.get() {
global
.color_description_listeners
.set((self.client.id, req.id), obj);
}
Ok(())
}
@ -179,15 +185,17 @@ impl WpColorManagerV1RequestHandler for WpColorManagerV1 {
req: GetSurfaceFeedback,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
let _ = self.client.lookup(req.surface)?;
let surface = self.client.lookup(req.surface)?;
let obj = Rc::new(WpColorManagementSurfaceFeedbackV1 {
id: req.id,
client: self.client.clone(),
version: self.version,
tracker: Default::default(),
surface: surface.clone(),
});
track!(self.client, obj);
self.client.add_client_obj(&obj)?;
surface.add_color_management_feedback(&obj);
Ok(())
}
@ -232,7 +240,7 @@ impl WpColorManagerV1RequestHandler for WpColorManagerV1 {
client: self.client.clone(),
version: self.version,
tracker: Default::default(),
description: self.client.state.color_manager.windows_scrgb().clone(),
description: Some(self.client.state.color_manager.windows_scrgb().clone()),
});
track!(self.client, obj);
self.client.add_client_obj(&obj)?;

View file

@ -91,7 +91,7 @@ impl WpImageDescriptionCreatorParamsV1RequestHandler for WpImageDescriptionCreat
client: self.client.clone(),
version: self.version,
tracker: Default::default(),
description,
description: Some(description),
});
track!(self.client, obj);
self.client.add_client_obj(&obj)?;

View file

@ -16,11 +16,10 @@ pub struct WpImageDescriptionV1 {
pub client: Rc<Client>,
pub version: Version,
pub tracker: Tracker<Self>,
pub description: Rc<ColorDescription>,
pub description: Option<Rc<ColorDescription>>,
}
impl WpImageDescriptionV1 {
#[expect(dead_code)]
pub fn send_failed(&self, cause: u32, msg: &str) {
self.client.event(Failed {
self_id: self.id,
@ -32,7 +31,7 @@ impl WpImageDescriptionV1 {
pub fn send_ready(&self) {
self.client.event(Ready {
self_id: self.id,
identity: self.description.id.into(),
identity: self.description.as_ref().unwrap().id.raw(),
});
}
}
@ -46,6 +45,9 @@ impl WpImageDescriptionV1RequestHandler for WpImageDescriptionV1 {
}
fn get_information(&self, req: GetInformation, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(desc) = &self.description else {
return Err(WpImageDescriptionV1Error::NotReady);
};
let obj = Rc::new(WpImageDescriptionInfoV1 {
id: req.information,
client: self.client.clone(),
@ -54,7 +56,7 @@ impl WpImageDescriptionV1RequestHandler for WpImageDescriptionV1 {
});
self.client.add_client_obj(&obj)?;
track!(self.client, obj);
obj.send_description(self.client.state.color_manager.srgb_srgb());
obj.send_description(desc);
self.client.remove_obj(&*obj)?;
Ok(())
}
@ -77,5 +79,7 @@ dedicated_add_obj!(
pub enum WpImageDescriptionV1Error {
#[error(transparent)]
ClientError(Box<ClientError>),
#[error("The description is not ready")]
NotReady,
}
efrom!(WpImageDescriptionV1Error, ClientError);

View file

@ -73,7 +73,7 @@ impl Global for JayCompositorGlobal {
}
fn version(&self) -> u32 {
14
15
}
fn required_caps(&self) -> ClientCaps {

View file

@ -1,6 +1,6 @@
use {
crate::{
backend,
backend::{self, BackendColorSpace, BackendTransferFunction},
client::{Client, ClientError},
compositor::MAX_EXTENTS,
format::named_formats,
@ -15,6 +15,7 @@ use {
jay_config::video::{
GfxApi, TearingMode as ConfigTearingMode, Transform, VrrMode as ConfigVrrMode,
},
linearize::LinearizeExt,
std::rc::Rc,
thiserror::Error,
};
@ -30,6 +31,7 @@ const VRR_CAPABLE_SINCE: Version = Version(2);
const TEARING_SINCE: Version = Version(3);
const FORMAT_SINCE: Version = Version(8);
const FLIP_MARGIN_SINCE: Version = Version(10);
const COLORIMETRY_SINCE: Version = Version(15);
impl JayRandr {
pub fn new(id: JayRandrId, client: &Rc<Client>, version: Version) -> Self {
@ -163,6 +165,28 @@ impl JayRandr {
current: (mode == &current_mode) as _,
});
}
if self.version >= COLORIMETRY_SINCE {
for tf in &node.global.transfer_functions {
self.client.event(SupportedTransferFunction {
self_id: self.id,
transfer_function: tf.name(),
});
}
self.client.event(CurrentTransferFunction {
self_id: self.id,
transfer_function: node.global.btf.get().name(),
});
for cs in &node.global.color_spaces {
self.client.event(SupportedColorSpace {
self_id: self.id,
color_space: cs.name(),
});
}
self.client.event(CurrentColorSpace {
self_id: self.id,
color_space: node.global.bcs.get().name(),
});
}
}
fn send_error(&self, msg: &str) {
@ -412,6 +436,34 @@ impl JayRandrRequestHandler for JayRandr {
dev.dev.set_flip_margin(req.margin_ns);
Ok(())
}
fn set_colors(&self, req: SetColors<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let cs = 'cs: {
for cs in BackendColorSpace::variants() {
if cs.name() == req.color_space {
break 'cs cs;
}
}
return Err(JayRandrError::UnknownColorSpace(
req.color_space.to_string(),
));
};
let tf = 'tf: {
for tf in BackendTransferFunction::variants() {
if tf.name() == req.transfer_function {
break 'tf tf;
}
}
return Err(JayRandrError::UnknownTransferFunction(
req.transfer_function.to_string(),
));
};
let Some(c) = self.get_connector(req.output) else {
return Ok(());
};
c.connector.set_colors(cs, tf);
Ok(())
}
}
object_base! {
@ -433,5 +485,9 @@ pub enum JayRandrError {
UnknownTearingMode(u32),
#[error("Unknown format {0}")]
UnknownFormat(String),
#[error("Unknown color space {0}")]
UnknownColorSpace(String),
#[error("Unknown transfer function {0}")]
UnknownTransferFunction(String),
}
efrom!(JayRandrError, ClientError);

View file

@ -2,22 +2,31 @@ mod removed_output;
use {
crate::{
backend,
backend::{self, BackendColorSpace, BackendLuminance, BackendTransferFunction},
client::{Client, ClientError, ClientId},
cmm::{
cmm_description::ColorDescription,
cmm_luminance::Luminance,
cmm_primaries::{NamedPrimaries, Primaries},
cmm_transfer_function::TransferFunction,
},
damage::DamageMatrix,
format::{Format, XRGB8888},
globals::{Global, GlobalName},
ifs::{wl_surface::WlSurface, zxdg_output_v1::ZxdgOutputV1},
ifs::{
color_management::wp_color_management_output_v1::WpColorManagementOutputV1,
wl_surface::WlSurface, zxdg_output_v1::ZxdgOutputV1,
},
leaks::Tracker,
object::{Object, Version},
rect::Rect,
state::{ConnectorData, State},
tree::{OutputNode, TearingMode, VrrMode, calculate_logical_size},
utils::{
cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, rc_eq::rc_eq,
transform_ext::TransformExt,
cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, ordered_float::F64,
rc_eq::rc_eq, transform_ext::TransformExt,
},
wire::{WlOutputId, ZxdgOutputV1Id, wl_output::*},
wire::{WlOutputId, WpColorManagementOutputV1Id, ZxdgOutputV1Id, wl_output::*},
},
ahash::AHashMap,
jay_config::video::Transform,
@ -67,12 +76,22 @@ pub struct WlOutputGlobal {
pub format: Cell<&'static Format>,
pub width_mm: i32,
pub height_mm: i32,
pub transfer_functions: Vec<BackendTransferFunction>,
pub color_spaces: Vec<BackendColorSpace>,
pub primaries: Primaries,
pub luminance: Option<BackendLuminance>,
pub bindings: RefCell<AHashMap<ClientId, AHashMap<WlOutputId, Rc<WlOutput>>>>,
pub destroyed: Cell<bool>,
pub legacy_scale: Cell<u32>,
pub persistent: Rc<PersistentOutputState>,
pub opt: Rc<OutputGlobalOpt>,
pub damage_matrix: Cell<DamageMatrix>,
pub btf: Cell<BackendTransferFunction>,
pub bcs: Cell<BackendColorSpace>,
pub color_description: CloneCell<Rc<ColorDescription>>,
pub linear_color_description: CloneCell<Rc<ColorDescription>>,
pub color_description_listeners:
CopyHashMap<(ClientId, WpColorManagementOutputV1Id), Rc<WpColorManagementOutputV1>>,
}
#[derive(Default)]
@ -133,6 +152,7 @@ impl WlOutputGlobal {
pub fn clear(&self) {
self.opt.clear();
self.bindings.borrow_mut().clear();
self.color_description_listeners.clear();
}
pub fn new(
@ -145,6 +165,12 @@ impl WlOutputGlobal {
height_mm: i32,
output_id: &Rc<OutputId>,
persistent_state: &Rc<PersistentOutputState>,
transfer_functions: Vec<BackendTransferFunction>,
btf: BackendTransferFunction,
color_spaces: Vec<BackendColorSpace>,
bcs: BackendColorSpace,
primaries: Primaries,
luminance: Option<BackendLuminance>,
) -> Self {
let (x, y) = persistent_state.pos.get();
let scale = persistent_state.scale.get();
@ -166,14 +192,24 @@ impl WlOutputGlobal {
format: Cell::new(XRGB8888),
width_mm,
height_mm,
transfer_functions,
color_spaces,
primaries,
luminance,
bindings: Default::default(),
destroyed: Cell::new(false),
legacy_scale: Cell::new(scale.round_up()),
persistent: persistent_state.clone(),
opt: Default::default(),
damage_matrix: Default::default(),
btf: Cell::new(btf),
bcs: Cell::new(bcs),
color_description: CloneCell::new(state.color_manager.srgb_srgb().clone()),
linear_color_description: CloneCell::new(state.color_manager.srgb_linear().clone()),
color_description_listeners: Default::default(),
};
global.update_damage_matrix();
global.update_color_description();
global
}
@ -292,6 +328,46 @@ impl WlOutputGlobal {
pub fn add_visualizer_damage(&self) {
self.state.damage_visualizer.copy_damage(self);
}
pub fn update_color_description(&self) -> bool {
let mut luminance = Luminance::SRGB;
let tf = match self.btf.get() {
BackendTransferFunction::Default => TransferFunction::Srgb,
BackendTransferFunction::Pq => {
luminance = Luminance::ST2084_PQ;
TransferFunction::St2084Pq
}
};
let mut target_luminance = luminance.to_target();
let mut max_cll = None;
let mut max_fall = None;
if let Some(l) = self.luminance {
target_luminance.min = F64(l.min);
target_luminance.max = F64(l.max);
max_cll = Some(F64(l.max));
max_fall = Some(F64(l.max_fall));
}
let primaries = match self.bcs.get() {
BackendColorSpace::Default => NamedPrimaries::Srgb,
BackendColorSpace::Bt2020 => NamedPrimaries::Bt2020,
};
let cd = self.state.color_manager.get_description(
Some(primaries),
primaries.primaries(),
luminance,
tf,
self.primaries,
target_luminance,
max_cll,
max_fall,
);
let cd_linear = self
.state
.color_manager
.get_with_tf(&cd, TransferFunction::Linear);
self.linear_color_description.set(cd_linear.clone());
self.color_description.set(cd.clone()).id != cd.id
}
}
global_base!(WlOutputGlobal, WlOutput, WlOutputError);

View file

@ -33,6 +33,7 @@ use {
ReleaseSync, SampleRect, SyncFile,
},
ifs::{
color_management::wp_color_management_surface_feedback_v1::WpColorManagementSurfaceFeedbackV1,
wl_buffer::WlBuffer,
wl_callback::WlCallback,
wl_seat::{
@ -89,8 +90,8 @@ use {
drm::sync_obj::{SyncObj, SyncObjPoint},
},
wire::{
WlOutputId, WlSurfaceId, ZwpIdleInhibitorV1Id, ZwpLinuxDmabufFeedbackV1Id,
wl_surface::*,
WlOutputId, WlSurfaceId, WpColorManagementSurfaceFeedbackV1Id, ZwpIdleInhibitorV1Id,
ZwpLinuxDmabufFeedbackV1Id, wl_surface::*,
},
xwayland::XWaylandEvent,
},
@ -201,6 +202,14 @@ impl NodeVisitorBase for SurfaceSendPreferredTransformVisitor {
}
}
pub struct SurfaceSendPreferredColorDescription;
impl NodeVisitorBase for SurfaceSendPreferredColorDescription {
fn visit_surface(&mut self, node: &Rc<WlSurface>) {
node.send_preferred_color_description();
node.node_visit_children(self);
}
}
struct SurfaceBufferExplicitRelease {
sync_obj: Rc<SyncObj>,
point: SyncObjPoint,
@ -336,6 +345,8 @@ pub struct WlSurface {
before_latch_listener: EventListener<dyn BeforeLatchListener>,
is_opaque: Cell<bool>,
color_management_surface: CloneCell<Option<Rc<WpColorManagementSurfaceV1>>>,
color_management_feedback:
CopyHashMap<WpColorManagementSurfaceFeedbackV1Id, Rc<WpColorManagementSurfaceFeedbackV1>>,
color_description: CloneCell<Option<Rc<ColorDescription>>>,
}
@ -678,6 +689,7 @@ impl WlSurface {
before_latch_listener: EventListener::new(slf.clone()),
is_opaque: Cell::new(false),
color_management_surface: Default::default(),
color_management_feedback: Default::default(),
color_description: Default::default(),
}
}
@ -699,7 +711,6 @@ impl WlSurface {
Ok(ext.into_xsurface().unwrap())
}
#[cfg_attr(not(feature = "it"), expect(dead_code))]
pub fn get_output(&self) -> Rc<OutputNode> {
self.output.get()
}
@ -720,6 +731,9 @@ impl WlSurface {
if old.global.persistent.transform.get() != output.global.persistent.transform.get() {
self.send_preferred_buffer_transform();
}
if old.global.color_description.get().id != output.global.color_description.get().id {
self.send_preferred_color_description();
}
let children = self.children.borrow_mut();
if let Some(children) = &*children {
for ss in children.subsurfaces.values() {
@ -1682,6 +1696,24 @@ impl WlSurface {
None => self.client.state.color_manager.srgb_srgb().clone(),
}
}
pub fn add_color_management_feedback(&self, fb: &Rc<WpColorManagementSurfaceFeedbackV1>) {
self.color_management_feedback.set(fb.id, fb.clone());
}
pub fn remove_color_management_feedback(&self, fb: &WpColorManagementSurfaceFeedbackV1) {
self.color_management_feedback.remove(&fb.id);
}
pub fn send_preferred_color_description(&self) {
if self.color_management_feedback.is_empty() {
return;
}
let cd = self.output.get().global.color_description.get();
for fb in self.color_management_feedback.lock().values() {
fb.send_preferred_changed(&cd);
}
}
}
object_base! {
@ -1714,6 +1746,7 @@ impl Object for WlSurface {
self.fifo.take();
self.commit_timer.take();
self.color_management_surface.take();
self.color_management_feedback.clear();
}
}

View file

@ -41,6 +41,7 @@ impl WpColorManagementSurfaceV1RequestHandler for WpColorManagementSurfaceV1 {
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.surface.color_management_surface.take();
self.surface.pending.borrow_mut().color_description = Some(None);
self.client.remove_obj(self)?;
Ok(())
}
@ -56,7 +57,10 @@ impl WpColorManagementSurfaceV1RequestHandler for WpColorManagementSurfaceV1 {
));
}
let desc = self.client.lookup(req.image_description)?;
self.surface.pending.borrow_mut().color_description = Some(Some(desc.description.clone()));
if desc.description.is_none() {
return Err(WpColorManagementSurfaceV1Error::NotReady);
}
self.surface.pending.borrow_mut().color_description = Some(desc.description.clone());
Ok(())
}
@ -87,5 +91,7 @@ pub enum WpColorManagementSurfaceV1Error {
UnsupportedRenderIntent(u32),
#[error("wl_surface already has a color-management extension")]
HasSurface,
#[error("The color description is not ready")]
NotReady,
}
efrom!(WpColorManagementSurfaceV1Error, ClientError);