1
0
Fork 0
forked from wry/wry

cmm: enable using the display primaries in SDR mode

This commit is contained in:
Julian Orth 2025-12-04 17:08:05 +01:00
parent 2b7b3b5310
commit 67760e270e
19 changed files with 259 additions and 21 deletions

View file

@ -2,11 +2,14 @@ use {
crate::{
backend::{BackendColorSpace, BackendEotfs},
cli::GlobalArgs,
cmm::cmm_primaries::Primaries,
format::{Format, XRGB8888},
ifs::wl_output::BlendSpace,
scale::Scale,
tools::tool_client::{Handle, ToolClient, with_tool_client},
utils::{errorfmt::ErrorFmt, transform_ext::TransformExt},
utils::{
debug_fn::debug_fn, errorfmt::ErrorFmt, ordered_float::F64, transform_ext::TransformExt,
},
wire::{JayRandrId, jay_compositor, jay_randr},
},
clap::{
@ -167,6 +170,30 @@ pub enum OutputCommand {
Brightness(BrightnessArgs),
/// Change the blend space.
BlendSpace(BlendSpaceArgs),
/// Change whether the display primaries are used.
UseNativeGamut(UseNativeGamutArgs),
}
#[derive(Args, Debug, Clone)]
pub struct UseNativeGamutArgs {
/// Configures whether the display primaries are used.
///
/// By default, Jay pretends that the display uses sRGB primaries. This is also how
/// most other systems behave. In reality, most displays use a much larger gamut. For
/// example, they advertise that they support 95% of the DCI-P3 gamut. If the display
/// is interpreting colors in their native gamut, then colors will appear more
/// saturated than their specification.
///
/// If this is set to `true`, Jay assumes that the display uses the primaries
/// advertised in its EDID. This might produce more accurate colors while also
/// allowing color-managed applications to use the full gamut of the display.
///
/// This setting has no effect when the display is explicitly operating in a wide
/// color space.
///
/// The default is `false`.
#[arg(action = clap::ArgAction::Set)]
pub use_native_gamut: bool,
}
#[derive(ValueEnum, Debug, Clone)]
@ -499,6 +526,8 @@ struct Output {
pub brightness_range: Option<(f64, f64)>,
pub brightness: Option<f64>,
pub blend_space: Option<String>,
pub native_gamut: Option<Primaries>,
pub use_native_gamut: bool,
}
#[derive(Copy, Clone, Debug)]
@ -789,6 +818,19 @@ impl Randr {
blend_space: &a.blend_space,
});
}
OutputCommand::UseNativeGamut(a) => {
self.handle_error(randr, move |msg| {
eprintln!(
"Could not change whether the compositor uses the native gamut: {}",
msg,
);
});
tc.send(jay_randr::SetUseNativeGamut {
self_id: randr,
output: &args.output,
use_native_gamut: a.use_native_gamut as _,
});
}
}
tc.round_trip().await;
}
@ -1024,6 +1066,25 @@ impl Randr {
if let Some(bs) = &o.blend_space {
println!(" blend space: {bs}");
}
if let Some(p) = &o.native_gamut {
println!(
" native gamut:{}",
debug_fn(|f| {
if o.use_native_gamut {
f.write_str(" (used for default color space)")?;
}
Ok(())
}),
);
println!(
" red: {:.6} {:.6} green: {:.6} {:.6}",
p.r.0.0, p.r.1.0, p.g.0.0, p.g.1.0
);
println!(
" blue: {:.6} {:.6} white: {:.6} {:.6}",
p.b.0.0, p.b.1.0, p.wp.0.0, p.wp.1.0
);
}
if o.modes.is_not_empty() && modes {
println!(" modes:");
for mode in &o.modes {
@ -1204,6 +1265,24 @@ impl Randr {
let output = c.output.as_mut().unwrap();
output.blend_space = Some(msg.blend_space.to_string());
});
jay_randr::NativeGamut::handle(tc, randr, data.clone(), |data, msg| {
let mut data = data.borrow_mut();
let c = data.connectors.last_mut().unwrap();
let output = c.output.as_mut().unwrap();
let primaries = Primaries {
r: (F64(msg.r_x), F64(msg.r_y)),
g: (F64(msg.g_x), F64(msg.g_y)),
b: (F64(msg.b_x), F64(msg.b_y)),
wp: (F64(msg.w_x), F64(msg.w_y)),
};
output.native_gamut = Some(primaries);
});
jay_randr::UseNativeGamut::handle(tc, randr, data.clone(), |data, _| {
let mut data = data.borrow_mut();
let c = data.connectors.last_mut().unwrap();
let output = c.output.as_mut().unwrap();
output.use_native_gamut = true;
});
tc.round_trip().await;
data.borrow_mut().clone()
}

View file

@ -33,7 +33,7 @@ use {
HeadManagers, HeadState, jay_head_manager_session_v1::handle_jay_head_manager_done,
},
jay_screencast::{perform_screencast_realloc, perform_toplevel_screencasts},
wl_output::{BlendSpace, OutputId, PersistentOutputState, WlOutputGlobal},
wl_output::{OutputId, PersistentOutputState, WlOutputGlobal},
wl_seat::handle_position_hint_requests,
wl_surface::{
NoneSurfaceExt, xdg_surface::handle_xdg_surface_configure_events,
@ -629,16 +629,7 @@ fn create_dummy_output(state: &Rc<State>) {
model: "jay-dummy-output".to_string(),
serial_number: "".to_string(),
});
let persistent_state = Rc::new(PersistentOutputState {
transform: Default::default(),
scale: Default::default(),
pos: Default::default(),
vrr_mode: Cell::new(VrrMode::NEVER),
vrr_cursor_hz: Default::default(),
tearing_mode: Cell::new(&TearingMode::Never),
brightness: Cell::new(None),
blend_space: Cell::new(BlendSpace::Srgb),
});
let persistent_state = Rc::new(PersistentOutputState::default());
let mode = backend::Mode {
width: 0,
height: 0,

View file

@ -1353,6 +1353,16 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_connector_set_use_native_gamut(
&self,
connector: Connector,
use_native_gamut: bool,
) -> Result<(), CphError> {
let connector = self.get_output_node(connector)?;
connector.set_use_native_gamut(use_native_gamut);
Ok(())
}
fn handle_set_float_above_fullscreen(&self, above: bool) {
self.state.float_above_fullscreen.set(above);
for seat in self.state.globals.seats.lock().values() {
@ -3316,6 +3326,12 @@ impl ConfigProxyHandler {
ClientMessage::SeatEnableUnicodeInput { seat } => self
.handle_seat_enable_unicode_input(seat)
.wrn("seat_enable_unicode_input")?,
ClientMessage::ConnectorSetUseNativeGamut {
connector,
use_native_gamut,
} => self
.handle_connector_set_use_native_gamut(connector, use_native_gamut)
.wrn("connector_set_use_native_gamut")?,
}
Ok(())
}

View file

@ -79,7 +79,7 @@ impl Global for JayCompositorGlobal {
}
fn version(&self) -> u32 {
22
23
}
fn required_caps(&self) -> ClientCaps {

View file

@ -36,6 +36,7 @@ const FLIP_MARGIN_SINCE: Version = Version(10);
const COLORIMETRY_SINCE: Version = Version(15);
const BRIGHTNESS_SINCE: Version = Version(16);
const BLEND_SPACE_SINCE: Version = Version(21);
const NATIVE_GAMUT_SINCE: Version = Version(23);
impl JayRandr {
pub fn new(id: JayRandrId, client: &Rc<Client>, version: Version) -> Self {
@ -215,6 +216,23 @@ impl JayRandr {
blend_space: node.global.persistent.blend_space.get().name(),
});
}
if self.version >= NATIVE_GAMUT_SINCE {
let p = &node.global.primaries;
self.client.event(NativeGamut {
self_id: self.id,
r_x: p.r.0.0,
r_y: p.r.1.0,
g_x: p.g.0.0,
g_y: p.g.1.0,
b_x: p.b.0.0,
b_y: p.b.1.0,
w_x: p.wp.0.0,
w_y: p.wp.1.0,
});
if node.global.persistent.use_native_gamut.get() {
self.client.event(UseNativeGamut { self_id: self.id });
}
}
}
fn send_error(&self, msg: &str) {
@ -551,6 +569,18 @@ impl JayRandrRequestHandler for JayRandr {
c.set_blend_space(space);
Ok(())
}
fn set_use_native_gamut(
&self,
req: SetUseNativeGamut<'_>,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
let Some(c) = self.get_output_node(req.output) else {
return Ok(());
};
c.set_use_native_gamut(req.use_native_gamut != 0);
Ok(())
}
}
object_base! {

View file

@ -140,6 +140,7 @@ pub struct PersistentOutputState {
pub tearing_mode: Cell<&'static TearingMode>,
pub brightness: Cell<Option<f64>>,
pub blend_space: Cell<BlendSpace>,
pub use_native_gamut: Cell<bool>,
}
impl Default for PersistentOutputState {
@ -153,6 +154,7 @@ impl Default for PersistentOutputState {
tearing_mode: Cell::new(&TearingMode::Never),
brightness: Default::default(),
blend_space: Cell::new(BlendSpace::Srgb),
use_native_gamut: Cell::new(false),
}
}
}
@ -384,18 +386,25 @@ impl WlOutputGlobal {
let target_primaries;
match self.bcs.get() {
BackendColorSpace::Default => {
named_primaries = NamedPrimaries::Srgb;
primaries = named_primaries.primaries();
if self.persistent.use_native_gamut.get()
&& self.primaries != NamedPrimaries::Srgb.primaries()
{
named_primaries = None;
primaries = self.primaries;
} else {
named_primaries = Some(NamedPrimaries::Srgb);
primaries = NamedPrimaries::Srgb.primaries();
}
target_primaries = primaries;
}
BackendColorSpace::Bt2020 => {
named_primaries = NamedPrimaries::Bt2020;
primaries = named_primaries.primaries();
named_primaries = Some(NamedPrimaries::Bt2020);
primaries = NamedPrimaries::Bt2020.primaries();
target_primaries = self.primaries;
}
}
let cd = self.state.color_manager.get_description(
Some(named_primaries),
named_primaries,
primaries,
luminance,
tf,

View file

@ -184,6 +184,7 @@ impl ConnectorHandler {
tearing_mode: Cell::new(self.state.default_tearing_mode.get()),
brightness: Cell::new(None),
blend_space: Cell::new(BlendSpace::Srgb),
use_native_gamut: Cell::new(false),
});
self.state
.persistent_output_states

View file

@ -335,7 +335,7 @@ impl ToolClient {
self_id: s.registry,
name: s.jay_compositor.0,
interface: JayCompositor.name(),
version: s.jay_compositor.1.min(22),
version: s.jay_compositor.1.min(23),
id: id.into(),
});
self.jay_compositor.set(Some(id));

View file

@ -1005,6 +1005,17 @@ impl OutputNode {
}
}
pub fn set_use_native_gamut(&self, use_native_gamut: bool) {
let old = self
.global
.persistent
.use_native_gamut
.replace(use_native_gamut);
if old != use_native_gamut {
self.update_color_description();
}
}
pub fn set_blend_space(&self, blend_space: BlendSpace) {
let old = self.global.persistent.blend_space.replace(blend_space);
if old != blend_space {