diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 24642b4c..f7eed6c0 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -28,7 +28,7 @@ use { theme::{Color, colors::Colorable, sized::Resizable}, timer::Timer, video::{ - ColorSpace, Connector, DrmDevice, Format, GfxApi, Mode, TearingMode, TransferFunction, + BlendSpace, ColorSpace, Connector, DrmDevice, Eotf, Format, GfxApi, Mode, TearingMode, Transform, VrrMode, connector_type::{CON_UNKNOWN, ConnectorType}, }, @@ -1042,16 +1042,18 @@ impl ConfigClient { self.send(&ClientMessage::ConnectorSetFormat { connector, format }); } - pub fn connector_set_colors( - &self, - connector: Connector, - color_space: ColorSpace, - transfer_function: TransferFunction, - ) { + pub fn connector_set_colors(&self, connector: Connector, color_space: ColorSpace, eotf: Eotf) { self.send(&ClientMessage::ConnectorSetColors { connector, color_space, - transfer_function, + eotf, + }); + } + + pub fn connector_set_blend_space(&self, connector: Connector, blend_space: BlendSpace) { + self.send(&ClientMessage::ConnectorSetBlendSpace { + connector, + blend_space, }); } diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index bb2d56d3..aabd1343 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -12,7 +12,7 @@ use { theme::{Color, colors::Colorable, sized::Resizable}, timer::Timer, video::{ - ColorSpace, Connector, DrmDevice, Format, GfxApi, TearingMode, TransferFunction, + BlendSpace, ColorSpace, Connector, DrmDevice, Eotf, Format, GfxApi, TearingMode, Transform, VrrMode, connector_type::ConnectorType, }, window::{ContentType, TileState, Window, WindowMatcher, WindowType}, @@ -555,7 +555,7 @@ pub enum ClientMessage<'a> { ConnectorSetColors { connector: Connector, color_space: ColorSpace, - transfer_function: TransferFunction, + eotf: Eotf, }, ConnectorSetBrightness { connector: Connector, @@ -764,6 +764,10 @@ pub enum ClientMessage<'a> { SetWorkspaceDisplayOrder { order: WorkspaceDisplayOrder, }, + ConnectorSetBlendSpace { + connector: Connector, + blend_space: BlendSpace, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/video.rs b/jay-config/src/video.rs index 92ff75ad..56b8a271 100644 --- a/jay-config/src/video.rs +++ b/jay-config/src/video.rs @@ -273,26 +273,33 @@ impl Connector { get!().connector_set_format(self, format); } - /// Sets the color space and transfer function of the connector. + /// Sets the color space and EOTF of the connector. /// /// By default, the default values are used which usually means sRGB color space with - /// sRGB transfer function. + /// gamma22 EOTF. /// /// If the output supports it, HDR10 can be enabled by setting the color space to - /// BT.2020 and the transfer function to PQ. + /// BT.2020 and the EOTF to PQ. /// /// Note that some displays might ignore incompatible settings. - pub fn set_colors(self, color_space: ColorSpace, transfer_function: TransferFunction) { - get!().connector_set_colors(self, color_space, transfer_function); + pub fn set_colors(self, color_space: ColorSpace, eotf: Eotf) { + get!().connector_set_colors(self, color_space, eotf); + } + + /// Sets the space in which blending is performed for this output. + /// + /// The default is [`BlendSpace::SRGB`] + pub fn set_blend_space(self, blend_space: BlendSpace) { + get!().connector_set_blend_space(self, blend_space); } /// Sets the brightness of the output. /// /// By default or when `brightness` is `None`, the brightness depends on the - /// transfer function: + /// EOTF: /// - /// - [`TransferFunction::DEFAULT`]: The maximum brightness of the output. - /// - [`TransferFunction::PQ`]: 203 cd/m^2. + /// - [`Eotf::DEFAULT`]: The maximum brightness of the output. + /// - [`Eotf::PQ`]: 203 cd/m^2. /// /// This should only be used with the PQ transfer function. If the default transfer /// function is used, you should instead calibrate the hardware directly. @@ -718,13 +725,29 @@ impl ColorSpace { pub const BT2020: Self = Self(1); } -/// A transfer function. +/// An electro-optical transfer function (EOTF). #[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub struct TransferFunction(pub u32); +pub struct Eotf(pub u32); -impl TransferFunction { - /// The default transfer function (usually sRGB). +#[deprecated = "use the Eotf type instead"] +pub type TransferFunction = Eotf; + +impl Eotf { + /// The default EOTF (usually gamma22). pub const DEFAULT: Self = Self(0); - /// The PQ transfer function. + /// The PQ EOTF. pub const PQ: Self = Self(1); } + +/// A space in which color blending is performed. +#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct BlendSpace(pub u32); + +impl BlendSpace { + /// The sRGB blend space with sRGB primaries and gamma22 transfer function. This is + /// the classic desktop blend space. + pub const SRGB: Self = Self(0); + /// The linear blend space performs blending in linear space, which is more physically + /// correct but leads to much lighter output when blending light and dark colors. + pub const LINEAR: Self = Self(1); +} diff --git a/src/backend.rs b/src/backend.rs index 7c2e18ae..b8f15cf7 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -106,7 +106,7 @@ pub struct MonitorInfo { pub non_desktop: bool, pub non_desktop_effective: bool, pub vrr_capable: bool, - pub transfer_functions: Vec, + pub eotfs: Vec, pub color_spaces: Vec, pub primaries: Primaries, pub luminance: Option, @@ -540,7 +540,7 @@ pub trait BackendDrmLessee { } #[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Linearize)] -pub enum BackendTransferFunction { +pub enum BackendEotfs { #[default] Default, Pq, @@ -560,18 +560,18 @@ pub struct BackendLuminance { pub max_fall: f64, } -impl BackendTransferFunction { +impl BackendEotfs { pub fn to_drm(self) -> u8 { match self { - BackendTransferFunction::Default => HDMI_EOTF_TRADITIONAL_GAMMA_SDR, - BackendTransferFunction::Pq => HDMI_EOTF_SMPTE_ST2084, + BackendEotfs::Default => HDMI_EOTF_TRADITIONAL_GAMMA_SDR, + BackendEotfs::Pq => HDMI_EOTF_SMPTE_ST2084, } } pub const fn name(self) -> &'static str { match self { - BackendTransferFunction::Default => "default", - BackendTransferFunction::Pq => "pq", + BackendEotfs::Default => "default", + BackendEotfs::Pq => "pq", } } } @@ -609,5 +609,5 @@ pub struct BackendConnectorState { pub tearing: bool, pub format: &'static Format, pub color_space: BackendColorSpace, - pub transfer_function: BackendTransferFunction, + pub eotf: BackendEotfs, } diff --git a/src/backend/transaction.rs b/src/backend/transaction.rs index dca5ac1a..b5ac531c 100644 --- a/src/backend/transaction.rs +++ b/src/backend/transaction.rs @@ -1,8 +1,8 @@ use { crate::{ backend::{ - BackendColorSpace, BackendConnectorState, BackendTransferFunction, Connector, - ConnectorId, ConnectorKernelId, Mode, + BackendColorSpace, BackendConnectorState, BackendEotfs, Connector, ConnectorId, + ConnectorKernelId, Mode, }, backends::metal::MetalError, state::State, @@ -112,8 +112,8 @@ pub enum BackendConnectorTransactionError { TearingNotSupported(ConnectorKernelId), #[error("Connector {} does not support color space {:?}", .0, .1)] ColorSpaceNotSupported(ConnectorKernelId, BackendColorSpace), - #[error("Connector {} does not support transfer function {:?}", .0, .1)] - TransferFunctionNotSupported(ConnectorKernelId, BackendTransferFunction), + #[error("Connector {} does not support EOTF {:?}", .0, .1)] + EotfNotSupported(ConnectorKernelId, BackendEotfs), #[error("Could not create an hdr metadata blob")] CreateHdrMetadataBlob(#[source] DrmError), #[error("Could not create a mode blob")] diff --git a/src/backends/metal/present.rs b/src/backends/metal/present.rs index d4c42ac8..d6e162a7 100644 --- a/src/backends/metal/present.rs +++ b/src/backends/metal/present.rs @@ -13,6 +13,7 @@ use { AcquireSync, BufferResv, GfxApiOpt, GfxRenderPass, GfxTexture, ReleaseSync, SyncFile, create_render_pass, }, + ifs::wl_output::BlendSpace, rect::Region, theme::Color, time::Time, @@ -202,6 +203,10 @@ impl MetalConnector { let cd = node.global.color_description.get(); let linear_cd = node.global.linear_color_description.get(); + let blend_cd = match node.global.persistent.blend_space.get() { + BlendSpace::Linear => &linear_cd, + BlendSpace::Srgb => self.state.color_manager.srgb_gamma22(), + }; if self.has_damage.get() > 0 || self.cursor_damage.get() { node.schedule.commit_cursor(); @@ -218,7 +223,7 @@ impl MetalConnector { let mut present_fb = None; let mut direct_scanout_id = None; if let Some(latched) = &latched { - let fb = self.prepare_present_fb(&cd, &linear_cd, buffer, &plane, latched, true)?; + let fb = self.prepare_present_fb(&cd, blend_cd, buffer, &plane, latched, true)?; direct_scanout_id = fb.direct_scanout_data.as_ref().map(|d| d.dma_buf_id); present_fb = Some(fb); } @@ -247,7 +252,7 @@ impl MetalConnector { { let fb = self.prepare_present_fb( &cd, - &linear_cd, + blend_cd, buffer, &plane, latched.as_ref().unwrap(), @@ -621,6 +626,7 @@ impl MetalConnector { &self, pass: &GfxRenderPass, plane: &Rc, + blend_cd: &Rc, cd: &Rc, ) -> Option { let ct = 'ct: { @@ -642,6 +648,10 @@ impl MetalConnector { // Direct scanout requires embeddable color descriptions. return None; } + if !ct.opaque && !ct.cd.embeds_into(blend_cd) { + // Blending changes the appearance of translucent buffers. + return None; + } if ct.alpha.is_some() { // Direct scanout with alpha factor is not supported. return None; @@ -796,7 +806,7 @@ impl MetalConnector { fn prepare_present_fb( &self, cd: &Rc, - linear_cd: &Rc, + blend_cd: &Rc, buffer: &RenderBuffer, plane: &Rc, latched: &Latched, @@ -813,7 +823,7 @@ impl MetalConnector { && self.dev.is_render_device(); let mut direct_scanout_data = None; if try_direct_scanout { - direct_scanout_data = self.prepare_direct_scanout(&latched.pass, plane, cd); + direct_scanout_data = self.prepare_direct_scanout(&latched.pass, plane, blend_cd, cd); } let direct_scanout_active = direct_scanout_data.is_some(); if self.direct_scanout_active.replace(direct_scanout_active) != direct_scanout_active { @@ -837,7 +847,7 @@ impl MetalConnector { &latched.pass, &latched.damage, buffer.blend_buffer.as_ref(), - linear_cd, + blend_cd, ) .map_err(MetalError::RenderFrame)?; sync_file = buffer.copy_to_dev(cd, sf)?; diff --git a/src/backends/metal/transaction.rs b/src/backends/metal/transaction.rs index a43a288e..47fb624e 100644 --- a/src/backends/metal/transaction.rs +++ b/src/backends/metal/transaction.rs @@ -2,8 +2,7 @@ use { crate::{ allocator::BufferObject, backend::{ - BackendColorSpace, BackendConnectorState, BackendTransferFunction, Connector, - ConnectorEvent, + BackendColorSpace, BackendConnectorState, BackendEotfs, Connector, ConnectorEvent, transaction::{ BackendAppliedConnectorTransaction, BackendConnectorTransaction, BackendConnectorTransactionError, BackendPreparedConnectorTransaction, @@ -669,16 +668,14 @@ impl MetalDeviceTransaction { } } } - match state.transfer_function { - BackendTransferFunction::Default => {} - BackendTransferFunction::Pq => { + match state.eotf { + BackendEotfs::Default => {} + BackendEotfs::Pq => { if !dd.supports_pq { - return Err( - BackendConnectorTransactionError::TransferFunctionNotSupported( - connector.obj.kernel_id(), - state.transfer_function, - ), - ); + return Err(BackendConnectorTransactionError::EotfNotSupported( + connector.obj.kernel_id(), + state.eotf, + )); } } } @@ -686,12 +683,10 @@ impl MetalDeviceTransaction { *cs = state.color_space.to_drm(); } if dd.hdr_metadata.is_some() { - let new = if state.transfer_function == BackendTransferFunction::Default { + let new = if state.eotf == BackendEotfs::Default { None } else { - Some(hdr_output_metadata::from_eotf( - state.transfer_function.to_drm(), - )) + Some(hdr_output_metadata::from_eotf(state.eotf.to_drm())) }; if connector.new.hdr_metadata != new { if let Some(new) = &new { diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 1401c29b..faa4da72 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -4,10 +4,10 @@ use { async_engine::{Phase, SpawnedFuture}, backend::{ BackendColorSpace, BackendConnectorState, BackendDrmDevice, BackendDrmLease, - BackendDrmLessee, BackendEvent, BackendLuminance, BackendTransferFunction, - CONCAP_CONNECTOR, CONCAP_MODE_SETTING, CONCAP_PHYSICAL_DISPLAY, Connector, - ConnectorCaps, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, - HardwareCursor, HardwareCursorUpdate, Mode, MonitorInfo, + BackendDrmLessee, BackendEotfs, BackendEvent, BackendLuminance, CONCAP_CONNECTOR, + CONCAP_MODE_SETTING, CONCAP_PHYSICAL_DISPLAY, Connector, ConnectorCaps, ConnectorEvent, + ConnectorId, ConnectorKernelId, DrmDeviceId, HardwareCursor, HardwareCursorUpdate, + Mode, MonitorInfo, transaction::{ BackendConnectorTransaction, BackendConnectorTransactionError, BackendConnectorTransactionType, BackendConnectorTransactionTypeDyn, @@ -1092,7 +1092,7 @@ fn create_connector( backend: backend.clone(), connector_id: backend.state.connector_ids.next(), buffers: Default::default(), - color_description: CloneCell::new(backend.state.color_manager.srgb_srgb().clone()), + color_description: CloneCell::new(backend.state.color_manager.srgb_gamma22().clone()), lease: Cell::new(None), buffers_idle: Cell::new(true), crtc_idle: Cell::new(true), @@ -1315,7 +1315,7 @@ fn create_connector_display_data( tearing: false, format: XRGB8888, color_space: Default::default(), - transfer_function: Default::default(), + eotf: Default::default(), }), }); dev.backend @@ -1341,13 +1341,13 @@ fn create_connector_display_data( Err(_) => false, }; { - let viable = match desired_state.transfer_function { - BackendTransferFunction::Default => true, - BackendTransferFunction::Pq => supports_pq, + let viable = match desired_state.eotf { + BackendEotfs::Default => true, + BackendEotfs::Pq => supports_pq, }; if !viable { - log::warn!("Discarding previously desired transfer function"); - desired_state.transfer_function = BackendTransferFunction::Default; + log::warn!("Discarding previously desired EOTF"); + desired_state.eotf = BackendEotfs::Default; } } { @@ -1900,9 +1900,9 @@ impl MetalBackend { modes.push(mode); } } - let mut transfer_functions = vec![]; + let mut eotfs = vec![]; if dd.supports_pq { - transfer_functions.push(BackendTransferFunction::Pq); + eotfs.push(BackendEotfs::Pq); } let mut color_spaces = vec![]; if dd.supports_bt2020 { @@ -1918,7 +1918,7 @@ impl MetalBackend { non_desktop: dd.non_desktop, non_desktop_effective: dd.non_desktop_effective, vrr_capable: dd.vrr_capable, - transfer_functions, + eotfs, color_spaces, primaries: dd.primaries, luminance: dd.luminance, @@ -2684,7 +2684,7 @@ impl MetalBackend { .clear( AcquireSync::Unnecessary, ReleaseSync::None, - self.state.color_manager.srgb_srgb(), + self.state.color_manager.srgb_gamma22(), ) .map_err(MetalError::Clear)?; let (dev_tex, render_tex, render_fb, render_bo) = if dev.id == render_ctx.dev_id { @@ -2742,7 +2742,7 @@ impl MetalBackend { .clear( AcquireSync::Unnecessary, ReleaseSync::None, - self.state.color_manager.srgb_srgb(), + self.state.color_manager.srgb_gamma22(), ) .map_err(MetalError::Clear)?; let render_tex = match render_img.to_texture() { diff --git a/src/backends/x.rs b/src/backends/x.rs index 29e4969d..e2f5838a 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -491,7 +491,7 @@ impl XBackend { tearing: false, format: FORMAT, color_space: Default::default(), - transfer_function: Default::default(), + eotf: Default::default(), }; let output = Rc::new(XOutput { id: self.state.connector_ids.next(), @@ -599,7 +599,7 @@ impl XBackend { non_desktop: false, non_desktop_effective: false, vrr_capable: false, - transfer_functions: vec![], + eotfs: vec![], color_spaces: vec![], primaries: Primaries::SRGB, luminance: None, @@ -774,7 +774,7 @@ impl XBackend { let res = self.state.present_output( &node, &image.fb.get(), - self.state.color_manager.srgb_srgb(), + self.state.color_manager.srgb_gamma22(), AcquireSync::Implicit, ReleaseSync::Implicit, &image.tex.get(), diff --git a/src/cli/damage_tracking.rs b/src/cli/damage_tracking.rs index 9532ff1b..70b8d432 100644 --- a/src/cli/damage_tracking.rs +++ b/src/cli/damage_tracking.rs @@ -1,7 +1,7 @@ use { crate::{ cli::{GlobalArgs, color::parse_color, duration::parse_duration}, - cmm::cmm_transfer_function::TransferFunction, + cmm::cmm_eotf::Eotf, tools::tool_client::{ToolClient, with_tool_client}, wire::jay_damage_tracking::{SetVisualizerColor, SetVisualizerDecay, SetVisualizerEnabled}, }, @@ -86,7 +86,7 @@ impl DamageTracking { } DamageTrackingCmd::SetColor(c) => { let color = parse_color(&c.color); - let [r, g, b, a] = color.to_array(TransferFunction::Srgb); + let [r, g, b, a] = color.to_array(Eotf::Gamma22); tc.send(SetVisualizerColor { self_id: dt, r, diff --git a/src/cli/randr.rs b/src/cli/randr.rs index 09449dbd..cec9887a 100644 --- a/src/cli/randr.rs +++ b/src/cli/randr.rs @@ -1,8 +1,9 @@ use { crate::{ - backend::{BackendColorSpace, BackendTransferFunction}, + backend::{BackendColorSpace, BackendEotfs}, cli::GlobalArgs, format::{Format, XRGB8888}, + ifs::wl_output::BlendSpace, scale::Scale, tools::tool_client::{Handle, ToolClient, with_tool_client}, utils::{errorfmt::ErrorFmt, transform_ext::TransformExt}, @@ -164,6 +165,8 @@ pub enum OutputCommand { Colors(ColorsSettings), /// Change the output brightness. Brightness(BrightnessArgs), + /// Change the blend space. + BlendSpace(BlendSpaceArgs), } #[derive(ValueEnum, Debug, Clone)] @@ -333,14 +336,14 @@ pub struct ColorsSettings { #[derive(Subcommand, Debug, Clone)] pub enum ColorsCommand { - /// Sets the color space and transfer function of the output. + /// Sets the color space and EOTF of the output. Set { /// The name of the color space. #[clap(value_parser = PossibleValuesParser::new(color_space_possible_values()))] color_space: String, - /// The name of the transfer function. - #[clap(value_parser = PossibleValuesParser::new(transfer_function_possible_values()))] - transfer_function: String, + /// The name of the EOTF. + #[clap(value_parser = PossibleValuesParser::new(eotf_possible_values()))] + eotf: String, }, } @@ -357,13 +360,13 @@ fn color_space_possible_values() -> Vec { res } -fn transfer_function_possible_values() -> Vec { +fn eotf_possible_values() -> Vec { let mut res = vec![]; - for cs in BackendTransferFunction::variants() { - use BackendTransferFunction::*; + for cs in BackendEotfs::variants() { + use BackendEotfs::*; let help = match cs { - Default => "The default transfer function (usually sRGB)", - Pq => "The PQ transfer function", + Default => "The default EOTF (usually gamma22)", + Pq => "The PQ EOTF", }; res.push(PossibleValue::new(cs.name()).help(help)); } @@ -375,12 +378,12 @@ pub struct BrightnessArgs { /// The brightness of standard white in cd/m^2 or `default` to use the default /// brightness. /// - /// The default brightness depends on the transfer function: + /// The default brightness depends on the EOTF: /// /// - default: the maximum display brightness /// - PQ: 203 cd/m^2. /// - /// When using the default transfer function, you likely want to set this to `default` + /// When using the default EOTF, you likely want to set this to `default` /// and adjust the display hardware brightness setting instead. /// /// This has no effect unless the vulkan renderer is used. @@ -407,6 +410,26 @@ fn parse_brightness(s: &str) -> Result { .map_err(|_| ParseBrightnessError) } +#[derive(Args, Debug, Clone)] +pub struct BlendSpaceArgs { + /// The space to blend translucent surfaces in. + #[clap(value_parser = PossibleValuesParser::new(blend_space_possible_values()))] + blend_space: String, +} + +fn blend_space_possible_values() -> Vec { + let mut res = vec![]; + for bs in BlendSpace::variants() { + use BlendSpace::*; + let help = match bs { + Linear => "Linear space, more accurate but brighter", + Srgb => "sRGB space, the classic desktop blend space", + }; + res.push(PossibleValue::new(bs.name()).help(help)); + } + res +} + pub fn main(global: GlobalArgs, args: RandrArgs) { with_tool_client(global.log_level.into(), |tc| async move { let idle = Rc::new(Randr { tc: tc.clone() }); @@ -462,10 +485,11 @@ struct Output { pub flip_margin_ns: Option, pub supported_color_spaces: Vec, pub current_color_space: Option, - pub supported_transfer_functions: Vec, - pub current_transfer_function: Option, + pub supported_eotfs: Vec, + pub current_eotf: Option, pub brightness_range: Option<(f64, f64)>, pub brightness: Option, + pub blend_space: Option, } #[derive(Copy, Clone, Debug)] @@ -713,15 +737,12 @@ impl Randr { eprintln!("Could not change the colors: {}", msg); }); match a.command { - ColorsCommand::Set { - color_space, - transfer_function, - } => { + ColorsCommand::Set { color_space, eotf } => { tc.send(jay_randr::SetColors { self_id: randr, output: &args.output, color_space: &color_space, - transfer_function: &transfer_function, + eotf: &eotf, }); } } @@ -746,6 +767,16 @@ impl Randr { } } } + OutputCommand::BlendSpace(a) => { + self.handle_error(randr, move |msg| { + eprintln!("Could not set the blend space: {}", msg); + }); + tc.send(jay_randr::SetBlendSpace { + self_id: randr, + output: &args.output, + blend_space: &a.blend_space, + }); + } } tc.round_trip().await; } @@ -957,19 +988,17 @@ impl Randr { handle_cs("default"); o.supported_color_spaces.iter().for_each(|cs| handle_cs(cs)); } - if o.supported_transfer_functions.is_not_empty() { - println!(" transfer functions:"); + if o.supported_eotfs.is_not_empty() { + println!(" eotfs:"); let handle_tf = |tf: &str| { - let current = match Some(tf) == o.current_transfer_function.as_deref() { + let current = match Some(tf) == o.current_eotf.as_deref() { false => "", true => " (current)", }; println!(" {tf}{current}"); }; handle_tf("default"); - o.supported_transfer_functions - .iter() - .for_each(|tf| handle_tf(tf)); + o.supported_eotfs.iter().for_each(|tf| handle_tf(tf)); } if let Some((min, max)) = o.brightness_range { println!(" min brightness: {:>10.4} cd/m^2", min); @@ -980,6 +1009,9 @@ impl Randr { if let Some(lux) = o.brightness { println!(" brightness: {:>10.4} cd/m^2", lux); } + if let Some(bs) = &o.blend_space { + println!(" blend space: {bs}"); + } if o.modes.is_not_empty() && modes { println!(" modes:"); for mode in &o.modes { @@ -1130,19 +1162,17 @@ impl Randr { let output = c.output.as_mut().unwrap(); output.current_color_space = Some(msg.color_space.to_string()); }); - jay_randr::SupportedTransferFunction::handle(tc, randr, data.clone(), |data, msg| { + jay_randr::SupportedEotf::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(); - output - .supported_transfer_functions - .push(msg.transfer_function.to_string()); + output.supported_eotfs.push(msg.eotf.to_string()); }); - jay_randr::CurrentTransferFunction::handle(tc, randr, data.clone(), |data, msg| { + jay_randr::CurrentEotf::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(); - output.current_transfer_function = Some(msg.transfer_function.to_string()); + output.current_eotf = Some(msg.eotf.to_string()); }); jay_randr::BrightnessRange::handle(tc, randr, data.clone(), |data, msg| { let mut data = data.borrow_mut(); @@ -1156,6 +1186,12 @@ impl Randr { let output = c.output.as_mut().unwrap(); output.brightness = Some(msg.lux); }); + jay_randr::BlendSpace::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(); + output.blend_space = Some(msg.blend_space.to_string()); + }); tc.round_trip().await; data.borrow_mut().clone() } diff --git a/src/cmm.rs b/src/cmm.rs index dac260c1..6e4700de 100644 --- a/src/cmm.rs +++ b/src/cmm.rs @@ -1,8 +1,8 @@ pub mod cmm_description; +pub mod cmm_eotf; pub mod cmm_luminance; pub mod cmm_manager; pub mod cmm_primaries; #[cfg(test)] mod cmm_tests; -pub mod cmm_transfer_function; pub mod cmm_transform; diff --git a/src/cmm/cmm_description.rs b/src/cmm/cmm_description.rs index abb015d3..9e406938 100644 --- a/src/cmm/cmm_description.rs +++ b/src/cmm/cmm_description.rs @@ -1,10 +1,10 @@ use { crate::{ cmm::{ + cmm_eotf::Eotf, cmm_luminance::{Luminance, TargetLuminance, white_balance}, cmm_manager::Shared, cmm_primaries::{NamedPrimaries, Primaries}, - cmm_transfer_function::TransferFunction, cmm_transform::{ColorMatrix, Local, Xyz, bradford_adjustment}, }, utils::ordered_float::F64, @@ -34,7 +34,7 @@ pub struct ColorDescription { pub id: ColorDescriptionId, pub linear: Rc, pub named_primaries: Option, - pub transfer_function: TransferFunction, + pub eotf: Eotf, pub(super) shared: Rc, } @@ -66,8 +66,7 @@ impl LinearColorDescription { impl ColorDescription { pub fn embeds_into(&self, target: &Self) -> bool { - self.transfer_function == target.transfer_function - && self.linear.embeds_into(&target.linear) + self.eotf == target.eotf && self.linear.embeds_into(&target.linear) } } diff --git a/src/cmm/cmm_transfer_function.rs b/src/cmm/cmm_eotf.rs similarity index 79% rename from src/cmm/cmm_transfer_function.rs rename to src/cmm/cmm_eotf.rs index b7c15e0e..b8980cca 100644 --- a/src/cmm/cmm_transfer_function.rs +++ b/src/cmm/cmm_eotf.rs @@ -1,15 +1,13 @@ use linearize::Linearize; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Linearize)] -pub enum TransferFunction { - Srgb, +pub enum Eotf { Linear, St2084Pq, Bt1886, Gamma22, Gamma28, St240, - ExtSrgb, Log100, Log316, St428, diff --git a/src/cmm/cmm_manager.rs b/src/cmm/cmm_manager.rs index c4e36e11..f73e2c12 100644 --- a/src/cmm/cmm_manager.rs +++ b/src/cmm/cmm_manager.rs @@ -5,9 +5,9 @@ use { ColorDescription, ColorDescriptionIds, LinearColorDescription, LinearColorDescriptionId, LinearColorDescriptionIds, }, + cmm_eotf::Eotf, cmm_luminance::{Luminance, TargetLuminance}, cmm_primaries::{NamedPrimaries, Primaries}, - cmm_transfer_function::TransferFunction, }, utils::{copyhashmap::CopyHashMap, numcell::NumCell, ordered_float::F64}, }, @@ -19,7 +19,7 @@ pub struct ColorManager { linear_descriptions: CopyHashMap>, complete_descriptions: CopyHashMap>, shared: Rc, - srgb_srgb: Rc, + srgb_gamma22: Rc, srgb_linear: Rc, windows_scrgb: Rc, } @@ -45,7 +45,7 @@ struct LinearDescriptionKey { struct CompleteDescriptionKey { linear: LinearColorDescriptionId, named_primaries: Option, - transfer_function: TransferFunction, + eotf: Eotf, } impl ColorManager { @@ -55,7 +55,7 @@ impl ColorManager { let complete_descriptions = CopyHashMap::default(); let shared = Rc::new(Shared::default()); let _ = shared.complete_ids.next(); - let srgb_srgb = get_description( + let srgb_gamma22 = get_description( &shared, &linear_descriptions, &complete_descriptions, @@ -63,7 +63,7 @@ impl ColorManager { Some(NamedPrimaries::Srgb), Primaries::SRGB, Luminance::SRGB, - TransferFunction::Srgb, + Eotf::Gamma22, Primaries::SRGB, Luminance::SRGB.to_target(), None, @@ -71,10 +71,10 @@ impl ColorManager { ); let srgb_linear = get_description2( &shared, - &srgb_srgb.linear, + &srgb_gamma22.linear, &complete_descriptions, Some(NamedPrimaries::Srgb), - TransferFunction::Linear, + Eotf::Linear, ); let windows_scrgb = get_description( &shared, @@ -84,7 +84,7 @@ impl ColorManager { Some(NamedPrimaries::Srgb), Primaries::SRGB, Luminance::WINDOWS_SCRGB, - TransferFunction::Linear, + Eotf::Linear, Primaries::BT2020, Luminance::ST2084_PQ.to_target(), None, @@ -95,14 +95,14 @@ impl ColorManager { linear_descriptions, complete_descriptions, shared, - srgb_srgb, + srgb_gamma22, srgb_linear, windows_scrgb, }) } - pub fn srgb_srgb(&self) -> &Rc { - &self.srgb_srgb + pub fn srgb_gamma22(&self) -> &Rc { + &self.srgb_gamma22 } pub fn srgb_linear(&self) -> &Rc { @@ -118,7 +118,7 @@ impl ColorManager { named_primaries: Option, primaries: Primaries, luminance: Luminance, - transfer_function: TransferFunction, + eotf: Eotf, target_primaries: Primaries, target_luminance: TargetLuminance, max_cll: Option, @@ -132,7 +132,7 @@ impl ColorManager { named_primaries, primaries, luminance, - transfer_function, + eotf, target_primaries, target_luminance, max_cll, @@ -143,14 +143,14 @@ impl ColorManager { pub fn get_with_tf( self: &Rc, cd: &Rc, - transfer_function: TransferFunction, + eotf: Eotf, ) -> Rc { get_description2( &self.shared, &cd.linear, &self.complete_descriptions, cd.named_primaries, - transfer_function, + eotf, ) } } @@ -163,7 +163,7 @@ fn get_description( named_primaries: Option, primaries: Primaries, luminance: Luminance, - transfer_function: TransferFunction, + eotf: Eotf, target_primaries: Primaries, target_luminance: TargetLuminance, max_cll: Option, @@ -189,13 +189,7 @@ fn get_description( }; if let Some(d) = linear_descriptions.get(&key) { if let Some(d) = d.upgrade() { - return get_description2( - shared, - &d, - complete_descriptions, - named_primaries, - transfer_function, - ); + return get_description2(shared, &d, complete_descriptions, named_primaries, eotf); } shared.dead_linear.fetch_sub(1); } @@ -216,13 +210,13 @@ fn get_description( let key = CompleteDescriptionKey { linear: d.id, named_primaries, - transfer_function, + eotf, }; let d = Rc::new(ColorDescription { id: shared.complete_ids.next(), linear: d, named_primaries, - transfer_function, + eotf, shared: shared.clone(), }); complete_descriptions.set(key, Rc::downgrade(&d)); @@ -234,12 +228,12 @@ fn get_description2( ld: &Rc, complete_descriptions: &CopyHashMap>, named_primaries: Option, - transfer_function: TransferFunction, + eotf: Eotf, ) -> Rc { let key = CompleteDescriptionKey { linear: ld.id, named_primaries, - transfer_function, + eotf, }; if let Some(d) = complete_descriptions.get(&key) { if let Some(d) = d.upgrade() { @@ -251,7 +245,7 @@ fn get_description2( id: shared.complete_ids.next(), linear: ld.clone(), named_primaries, - transfer_function, + eotf, shared: shared.clone(), }); complete_descriptions.set(key, Rc::downgrade(&d)); diff --git a/src/cmm/cmm_tests.rs b/src/cmm/cmm_tests.rs index 5bc11101..c2f71805 100644 --- a/src/cmm/cmm_tests.rs +++ b/src/cmm/cmm_tests.rs @@ -135,8 +135,8 @@ mod matrices { mod transforms { use crate::cmm::{ - cmm_luminance::Luminance, cmm_manager::ColorManager, cmm_primaries::Primaries, - cmm_transfer_function::TransferFunction, + cmm_eotf::Eotf, cmm_luminance::Luminance, cmm_manager::ColorManager, + cmm_primaries::Primaries, }; fn check(p1: Primaries, p2: Primaries, expected: [[f64; 4]; 3]) { @@ -146,7 +146,7 @@ mod transforms { None, p, Luminance::SRGB, - TransferFunction::Linear, + Eotf::Linear, p, Luminance::SRGB.to_target(), None, diff --git a/src/cmm/cmm_transform.rs b/src/cmm/cmm_transform.rs index 898f060a..5b8a8537 100644 --- a/src/cmm/cmm_transform.rs +++ b/src/cmm/cmm_transform.rs @@ -1,6 +1,6 @@ use { crate::{ - cmm::{cmm_primaries::Primaries, cmm_transfer_function::TransferFunction}, + cmm::{cmm_eotf::Eotf, cmm_primaries::Primaries}, theme::Color, utils::{debug_fn::debug_fn, ordered_float::F64}, }, @@ -131,7 +131,7 @@ impl Mul for ColorMatrix { type Output = Color; fn mul(self, rhs: Color) -> Self::Output { - let mut rgba = rhs.to_array(TransferFunction::Linear); + let mut rgba = rhs.to_array(Eotf::Linear); let a = rgba[3]; if a < 1.0 && a > 0.0 { for c in &mut rgba[..3] { @@ -139,7 +139,7 @@ impl Mul for ColorMatrix { } } let [r, g, b] = self * [rgba[0] as f64, rgba[1] as f64, rgba[2] as f64]; - let mut color = Color::new(TransferFunction::Linear, r as f32, g as f32, b as f32); + let mut color = Color::new(Eotf::Linear, r as f32, g as f32, b as f32); if a < 1.0 { color = color * a; } diff --git a/src/compositor.rs b/src/compositor.rs index c6dce04d..f8e36ced 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -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::{OutputId, PersistentOutputState, WlOutputGlobal}, + wl_output::{BlendSpace, OutputId, PersistentOutputState, WlOutputGlobal}, wl_seat::handle_position_hint_requests, wl_surface::{ NoneSurfaceExt, xdg_surface::handle_xdg_surface_configure_events, @@ -636,6 +636,7 @@ fn create_dummy_output(state: &Rc) { vrr_cursor_hz: Default::default(), tearing_mode: Cell::new(&TearingMode::Never), brightness: Cell::new(None), + blend_space: Cell::new(BlendSpace::Srgb), }); let mode = backend::Mode { width: 0, @@ -652,7 +653,7 @@ fn create_dummy_output(state: &Rc) { tearing: false, format: XRGB8888, color_space: Default::default(), - transfer_function: Default::default(), + eotf: Default::default(), }; let id = state.connector_ids.next(); let connector = Rc::new(DummyOutput { id }) as Rc; @@ -680,7 +681,7 @@ fn create_dummy_output(state: &Rc) { tearing_mode: TearingMode::Never.to_config(), format: XRGB8888, color_space: backend_state.color_space, - transfer_function: backend_state.transfer_function, + eotf: backend_state.eotf, supported_formats: Default::default(), brightness: None, }; diff --git a/src/config/handler.rs b/src/config/handler.rs index ba7e624e..8b464863 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -2,12 +2,12 @@ use { crate::{ async_engine::SpawnedFuture, backend::{ - self, BackendColorSpace, BackendTransferFunction, ConnectorId, DrmDeviceId, + self, BackendColorSpace, BackendEotfs, ConnectorId, DrmDeviceId, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId, transaction::BackendConnectorTransactionError, }, client::{Client, ClientId}, - cmm::cmm_transfer_function::TransferFunction, + cmm::cmm_eotf::Eotf, compositor::MAX_EXTENTS, config::ConfigProxy, criteria::{ @@ -17,6 +17,7 @@ use { }, format::config_formats, ifs::{ + wl_output::BlendSpace, wl_seat::{SeatId, WlSeatGlobal}, wp_content_type_v1::ContentTypeExt, }, @@ -69,9 +70,9 @@ use { theme::{colors::Colorable, sized::Resizable}, timer::Timer as JayTimer, video::{ - ColorSpace, Connector, DrmDevice, Format as ConfigFormat, GfxApi, - TearingMode as ConfigTearingMode, TransferFunction as ConfigTransferFunction, - Transform, VrrMode as ConfigVrrMode, + BlendSpace as ConfigBlendSpace, ColorSpace, Connector, DrmDevice, Eotf as ConfigEotf, + Format as ConfigFormat, GfxApi, TearingMode as ConfigTearingMode, Transform, + VrrMode as ConfigVrrMode, }, window::{TileState, Window, WindowMatcher}, workspace::WorkspaceDisplayOrder, @@ -1285,28 +1286,43 @@ impl ConfigProxyHandler { &self, connector: Connector, color_space: ColorSpace, - transfer_function: ConfigTransferFunction, + eotf: ConfigEotf, ) -> Result<(), CphError> { let bcs = match color_space { ColorSpace::DEFAULT => BackendColorSpace::Default, ColorSpace::BT2020 => BackendColorSpace::Bt2020, _ => return Err(CphError::UnknownColorSpace(color_space)), }; - let btf = match transfer_function { - ConfigTransferFunction::DEFAULT => BackendTransferFunction::Default, - ConfigTransferFunction::PQ => BackendTransferFunction::Pq, - _ => return Err(CphError::UnknownTransferFunction(transfer_function)), + let btf = match eotf { + ConfigEotf::DEFAULT => BackendEotfs::Default, + ConfigEotf::PQ => BackendEotfs::Pq, + _ => return Err(CphError::UnknownEotf(eotf)), }; let connector = self.get_connector(connector)?; connector .modify_state(&self.state, |s| { s.color_space = bcs; - s.transfer_function = btf; + s.eotf = btf; }) .map_err(CphError::ModifyConnectorState)?; Ok(()) } + fn handle_connector_set_blend_space( + &self, + connector: Connector, + blend_space: ConfigBlendSpace, + ) -> Result<(), CphError> { + let blend_space = match blend_space { + ConfigBlendSpace::SRGB => BlendSpace::Srgb, + ConfigBlendSpace::LINEAR => BlendSpace::Linear, + _ => return Err(CphError::UnknownBlendSpace(blend_space)), + }; + let connector = self.get_output_node(connector)?; + connector.set_blend_space(blend_space); + Ok(()) + } + fn handle_connector_set_brightness( &self, connector: Connector, @@ -2365,7 +2381,7 @@ impl ConfigProxyHandler { fn handle_get_color(&self, colorable: Colorable) -> Result<(), CphError> { let color = self.get_color(colorable)?.get(); - let [r, g, b, a] = color.to_array(TransferFunction::Srgb); + let [r, g, b, a] = color.to_array(Eotf::Gamma22); let color = jay_config::theme::Color::new_f32_premultiplied(r, g, b, a); self.respond(Response::GetColor { color }); Ok(()) @@ -2930,9 +2946,9 @@ impl ConfigProxyHandler { ClientMessage::ConnectorSetColors { connector, color_space, - transfer_function, + eotf, } => self - .handle_connector_set_colors(connector, color_space, transfer_function) + .handle_connector_set_colors(connector, color_space, eotf) .wrn("connector_set_colors")?, ClientMessage::ConnectorSetBrightness { connector, @@ -3118,6 +3134,12 @@ impl ConfigProxyHandler { ClientMessage::SeatCopyMark { seat, src, dst } => self .handle_seat_copy_mark(seat, src, dst) .wrn("seat_copy_mark")?, + ClientMessage::ConnectorSetBlendSpace { + connector, + blend_space, + } => self + .handle_connector_set_blend_space(connector, blend_space) + .wrn("connector_set_blend_space")?, } Ok(()) } @@ -3211,8 +3233,8 @@ enum CphError { UnknownXScalingMode(XScalingMode), #[error("Unknown color space {0:?}")] UnknownColorSpace(ColorSpace), - #[error("Unknown transfer function {0:?}")] - UnknownTransferFunction(ConfigTransferFunction), + #[error("Unknown EOTF {0:?}")] + UnknownEotf(ConfigEotf), #[error("Client {0:?} does not exist")] ClientDoesNotExist(ConfigClient), #[error("Window {0:?} does not exist")] @@ -3227,6 +3249,8 @@ enum CphError { WindowMatcherDoesNotExist(WindowMatcher), #[error("Could not modify the connector state")] ModifyConnectorState(#[source] BackendConnectorTransactionError), + #[error("Unknown blend space {0:?}")] + UnknownBlendSpace(ConfigBlendSpace), } trait WithRequestName { diff --git a/src/cursor.rs b/src/cursor.rs index 86e128da..583632cb 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -397,7 +397,7 @@ fn render_img(image: &InstantiatedCursorImage, renderer: &mut Renderer, x: Fixed AcquireSync::None, ReleaseSync::None, false, - renderer.state.color_manager.srgb_srgb(), + renderer.state.color_manager.srgb_gamma22(), ); } } @@ -422,7 +422,7 @@ impl Cursor for StaticCursor { AcquireSync::None, ReleaseSync::None, false, - renderer.state.color_manager.srgb_srgb(), + renderer.state.color_manager.srgb_gamma22(), ); } } @@ -465,7 +465,7 @@ impl Cursor for AnimatedCursor { AcquireSync::None, ReleaseSync::None, false, - renderer.state.color_manager.srgb_srgb(), + renderer.state.color_manager.srgb_gamma22(), ); } } diff --git a/src/damage.rs b/src/damage.rs index 5abd1de2..eee43afd 100644 --- a/src/damage.rs +++ b/src/damage.rs @@ -165,7 +165,7 @@ impl DamageVisualizer { let dy = -cursor_rect.y1(); let decay_millis = decay.as_millis() as u64 as f32; renderer.ops.push(GfxApiOpt::Sync); - let srgb = &self.color_manager.srgb_srgb().linear; + let srgb = &self.color_manager.srgb_gamma22().linear; for entry in entries.iter().rev() { let region = Region::new(entry.rect); let region = region.subtract_cow(&used); diff --git a/src/gfx_api.rs b/src/gfx_api.rs index e76e0c76..3472c41c 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -895,7 +895,7 @@ pub fn create_render_pass( return GfxRenderPass { ops: vec![], clear: Some(Color::SOLID_BLACK), - clear_cd: state.color_manager.srgb_srgb().linear.clone(), + clear_cd: state.color_manager.srgb_gamma22().linear.clone(), }; } let mut ops = vec![]; @@ -962,7 +962,7 @@ pub fn create_render_pass( GfxRenderPass { ops, clear: Some(c), - clear_cd: state.color_manager.srgb_srgb().linear.clone(), + clear_cd: state.color_manager.srgb_gamma22().linear.clone(), } } diff --git a/src/gfx_apis/gl.rs b/src/gfx_apis/gl.rs index 283ee59c..a17eacf8 100644 --- a/src/gfx_apis/gl.rs +++ b/src/gfx_apis/gl.rs @@ -67,7 +67,7 @@ macro_rules! dynload { use { crate::{ - cmm::cmm_transfer_function::TransferFunction, + cmm::cmm_eotf::Eotf, gfx_api::{ AcquireSync, CopyTexture, FramebufferRect, GfxApiOpt, GfxContext, GfxError, GfxTexture, ReleaseSync, SyncFile, @@ -309,7 +309,7 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) -> Option { } fn fill_boxes3(ctx: &GlRenderContext, boxes: &[[f32; 2]], color: &Color) { - let [r, g, b, a] = color.to_array(TransferFunction::Srgb); + let [r, g, b, a] = color.to_array(Eotf::Gamma22); let gles = ctx.ctx.dpy.gles; unsafe { (gles.glUseProgram)(ctx.fill_prog.prog); diff --git a/src/gfx_apis/gl/renderer/framebuffer.rs b/src/gfx_apis/gl/renderer/framebuffer.rs index 92b293f4..7df3bee8 100644 --- a/src/gfx_apis/gl/renderer/framebuffer.rs +++ b/src/gfx_apis/gl/renderer/framebuffer.rs @@ -2,7 +2,7 @@ use { crate::{ cmm::{ cmm_description::{ColorDescription, LinearColorDescription}, - cmm_transfer_function::TransferFunction, + cmm_eotf::Eotf, }, format::Format, gfx_api::{ @@ -82,7 +82,7 @@ impl Framebuffer { (gles.glBindFramebuffer)(GL_FRAMEBUFFER, self.gl.fbo); (gles.glViewport)(0, 0, self.gl.width, self.gl.height); if let Some(c) = clear { - let [r, g, b, a] = c.to_array(TransferFunction::Srgb); + let [r, g, b, a] = c.to_array(Eotf::Gamma22); (gles.glClearColor)(r, g, b, a); (gles.glClear)(GL_COLOR_BUFFER_BIT); } diff --git a/src/gfx_apis/vulkan.rs b/src/gfx_apis/vulkan.rs index c8295659..c54aa949 100644 --- a/src/gfx_apis/vulkan.rs +++ b/src/gfx_apis/vulkan.rs @@ -6,6 +6,7 @@ mod command; mod descriptor; mod descriptor_buffer; mod device; +mod eotfs; mod fence; mod format; mod image; @@ -18,7 +19,6 @@ mod shaders; mod shm_image; mod staging; mod transfer; -mod transfer_functions; use { crate::{ diff --git a/src/gfx_apis/vulkan/descriptor.rs b/src/gfx_apis/vulkan/descriptor.rs index 7d020944..87e093ff 100644 --- a/src/gfx_apis/vulkan/descriptor.rs +++ b/src/gfx_apis/vulkan/descriptor.rs @@ -120,33 +120,6 @@ impl VulkanDevice { })) } - pub(super) fn create_out_descriptor_set_layout( - self: &Rc, - db: &descriptor_buffer::Device, - ) -> Result, VulkanError> { - let binding = DescriptorSetLayoutBinding::default() - .stage_flags(ShaderStageFlags::FRAGMENT) - .descriptor_count(1) - .descriptor_type(DescriptorType::SAMPLED_IMAGE); - let create_info = DescriptorSetLayoutCreateInfo::default() - .bindings(slice::from_ref(&binding)) - .flags(DescriptorSetLayoutCreateFlags::DESCRIPTOR_BUFFER_EXT); - let layout = unsafe { self.device.create_descriptor_set_layout(&create_info, None) }; - let layout = layout.map_err(VulkanError::CreateDescriptorSetLayout)?; - let size = self.get_descriptor_set_size(db, layout); - let mut offsets = ArrayVec::new(); - unsafe { - offsets.push(db.get_descriptor_set_layout_binding_offset(layout, 0)); - } - Ok(Rc::new(VulkanDescriptorSetLayout { - device: self.clone(), - layout, - size, - offsets, - _sampler: None, - })) - } - fn get_descriptor_set_size( &self, db: &descriptor_buffer::Device, diff --git a/src/gfx_apis/vulkan/eotfs.rs b/src/gfx_apis/vulkan/eotfs.rs new file mode 100644 index 00000000..db5a6da4 --- /dev/null +++ b/src/gfx_apis/vulkan/eotfs.rs @@ -0,0 +1,31 @@ +use crate::cmm::cmm_eotf::Eotf; + +pub const EOTF_LINEAR: u32 = 1; +pub const EOTF_ST2084_PQ: u32 = 2; +pub const EOTF_GAMMA24: u32 = 3; +pub const EOTF_GAMMA22: u32 = 4; +pub const EOTF_GAMMA28: u32 = 5; +pub const EOTF_ST240: u32 = 6; +pub const EOTF_LOG100: u32 = 8; +pub const EOTF_LOG316: u32 = 9; +pub const EOTF_ST428: u32 = 10; + +pub trait EotfExt: Sized { + fn to_vulkan(self) -> u32; +} + +impl EotfExt for Eotf { + fn to_vulkan(self) -> u32 { + match self { + Eotf::Linear => EOTF_LINEAR, + Eotf::St2084Pq => EOTF_ST2084_PQ, + Eotf::Bt1886 => EOTF_GAMMA24, + Eotf::Gamma22 => EOTF_GAMMA22, + Eotf::Gamma28 => EOTF_GAMMA28, + Eotf::St240 => EOTF_ST240, + Eotf::Log100 => EOTF_LOG100, + Eotf::Log316 => EOTF_LOG316, + Eotf::St428 => EOTF_ST428, + } + } +} diff --git a/src/gfx_apis/vulkan/pipeline.rs b/src/gfx_apis/vulkan/pipeline.rs index d0fc3073..564e4a55 100644 --- a/src/gfx_apis/vulkan/pipeline.rs +++ b/src/gfx_apis/vulkan/pipeline.rs @@ -40,7 +40,7 @@ pub(super) struct PipelineCreateInfo { pub(super) src_has_alpha: bool, pub(super) has_alpha_mult: bool, pub(super) eotf: u32, - pub(super) oetf: u32, + pub(super) inv_eotf: u32, pub(super) descriptor_set_layouts: ArrayVec, 2>, pub(super) has_color_management_data: bool, } @@ -91,7 +91,7 @@ impl VulkanDevice { frag_spec_entry(&(info.src_has_alpha as u32).to_ne_bytes()); frag_spec_entry(&(info.has_alpha_mult as u32).to_ne_bytes()); frag_spec_entry(&info.eotf.to_ne_bytes()); - frag_spec_entry(&info.oetf.to_ne_bytes()); + frag_spec_entry(&info.inv_eotf.to_ne_bytes()); frag_spec_entry(&(info.has_color_management_data as u32).to_ne_bytes()); let frag_spec = SpecializationInfo::default() .map_entries(&frag_spec_entries) diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index fd461e7c..9df7d71d 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -3,7 +3,7 @@ use { async_engine::{AsyncEngine, SpawnedFuture}, cmm::{ cmm_description::{ColorDescription, LinearColorDescription, LinearColorDescriptionId}, - cmm_transfer_function::TransferFunction, + cmm_eotf::Eotf, cmm_transform::ColorMatrix, }, cpu_worker::PendingJob, @@ -19,6 +19,7 @@ use { descriptor::VulkanDescriptorSetLayout, descriptor_buffer::VulkanDescriptorBufferWriter, device::VulkanDevice, + eotfs::{EOTF_LINEAR, EotfExt}, fence::VulkanFence, image::{QueueFamily, QueueState, QueueTransfer, VulkanImage, VulkanImageMemory}, pipeline::{PipelineCreateInfo, VulkanPipeline}, @@ -27,10 +28,9 @@ use { shaders::{ FILL_FRAG, FILL_VERT, FillPushConstants, LEGACY_FILL_FRAG, LEGACY_FILL_VERT, LEGACY_TEX_FRAG, LEGACY_TEX_VERT, LegacyFillPushConstants, LegacyTexPushConstants, - OUT_FRAG, OUT_VERT, OutPushConstants, TEX_FRAG, TEX_VERT, TexColorManagementData, - TexPushConstants, TexVertex, VulkanShader, + OUT_FRAG, OUT_VERT, OutColorManagementData, OutPushConstants, TEX_FRAG, TEX_VERT, + TexColorManagementData, TexPushConstants, TexVertex, VulkanShader, }, - transfer_functions::{TF_LINEAR, TransferFunctionExt}, }, io_uring::IoUring, rect::{Rect, Region}, @@ -78,10 +78,8 @@ pub struct VulkanRenderer { pub(super) formats: Rc>, pub(super) device: Rc, pub(super) fill_pipelines: CopyHashMap, - pub(super) tex_pipelines: - StaticMap>>, - pub(super) out_pipelines: - StaticMap>>, + pub(super) tex_pipelines: StaticMap>>, + pub(super) out_pipelines: StaticMap>>, pub(super) gfx_command_buffers: CachedCommandBuffers, pub(super) transfer_command_buffers: Option, pub(super) wait_semaphores: Stack>, @@ -181,6 +179,7 @@ pub(super) struct Memory { uniform_buffer_writer: GenericBufferWriter, uniform_buffer_descriptor_cache: Option>, blend_buffer_descriptor_buffer_offset: DeviceAddress, + blend_buffer_color_management_data_address: Option, } type Point = [[f32; 2]; 4]; @@ -247,20 +246,20 @@ type FillPipelines = Rc>>; struct TexPipelineKey { tex_copy_type: TexCopyType, tex_source_type: TexSourceType, - eotf: TransferFunction, + eotf: Eotf, has_color_management_data: bool, } pub(super) struct TexPipelines { format: vk::Format, - oetf: TransferFunction, + eotf: Eotf, pipelines: CopyHashMap>, } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub(super) struct OutPipelineKey { format: vk::Format, - eotf: TransferFunction, + eotf: Eotf, } impl VulkanDevice { @@ -300,7 +299,7 @@ impl VulkanDevice { let out_descriptor_set_layout = self .descriptor_buffer .as_ref() - .map(|db| self.create_out_descriptor_set_layout(db)) + .map(|_| self.create_tex_resource_descriptor_set_layout()) .transpose()?; let gfx_command_buffers = self.create_command_pool(self.graphics_queue_idx)?; let transfer_command_buffers = self @@ -417,8 +416,8 @@ impl VulkanRenderer { src_has_alpha, has_alpha_mult: false, // all transformations are applied in the compositor - eotf: TF_LINEAR, - oetf: TF_LINEAR, + eotf: EOTF_LINEAR, + inv_eotf: EOTF_LINEAR, descriptor_set_layouts: Default::default(), has_color_management_data: false, }; @@ -437,13 +436,13 @@ impl VulkanRenderer { format: vk::Format, target_cd: &ColorDescription, ) -> Rc { - let pipelines = &self.tex_pipelines[target_cd.transfer_function]; + let pipelines = &self.tex_pipelines[target_cd.eotf]; match pipelines.get(&format) { Some(pl) => pl, _ => { let pl = Rc::new(TexPipelines { format, - oetf: target_cd.transfer_function, + eotf: target_cd.eotf, pipelines: Default::default(), }); pipelines.set(format, pl.clone()); @@ -463,7 +462,7 @@ impl VulkanRenderer { let key = TexPipelineKey { tex_copy_type, tex_source_type, - eotf: tex_cd.transfer_function, + eotf: tex_cd.eotf, has_color_management_data, }; if let Some(pl) = pipelines.pipelines.get(&key) { @@ -490,7 +489,7 @@ impl VulkanRenderer { src_has_alpha, has_alpha_mult, eotf: key.eotf.to_vulkan(), - oetf: pipelines.oetf.to_vulkan(), + inv_eotf: pipelines.eotf.to_vulkan(), descriptor_set_layouts: self.tex_descriptor_set_layouts.clone(), has_color_management_data, }; @@ -504,12 +503,13 @@ impl VulkanRenderer { format: vk::Format, bb_cd: &ColorDescription, fb_cd: &ColorDescription, + has_color_management_data: bool, ) -> Result, VulkanError> { let key = OutPipelineKey { format, - eotf: bb_cd.transfer_function, + eotf: bb_cd.eotf, }; - let pipelines = &self.out_pipelines[fb_cd.transfer_function]; + let pipelines = &self.out_pipelines[fb_cd.eotf]; if let Some(pl) = pipelines.get(&key) { return Ok(pl); } @@ -525,9 +525,9 @@ impl VulkanRenderer { src_has_alpha: true, has_alpha_mult: false, eotf: key.eotf.to_vulkan(), - oetf: fb_cd.transfer_function.to_vulkan(), + inv_eotf: fb_cd.eotf.to_vulkan(), descriptor_set_layouts, - has_color_management_data: false, + has_color_management_data, })?; pipelines.set(key, out.clone()); Ok(out) @@ -568,6 +568,20 @@ impl VulkanRenderer { memory.blend_buffer_descriptor_buffer_offset = resource_writer.next_offset(); let mut writer = resource_writer.add_set(layout); writer.write(layout.offsets[0], &bb.sampled_image_descriptor); + if let Some(addr) = memory.blend_buffer_color_management_data_address { + let uniform_buffer = DescriptorAddressInfoEXT::default() + .address(addr) + .range(size_of::() as _); + let info = DescriptorGetInfoEXT::default() + .ty(DescriptorType::UNIFORM_BUFFER) + .data(DescriptorDataEXT { + p_uniform_buffer: &uniform_buffer, + }); + unsafe { + db.get_descriptor(&info, uniform_buffer_descriptor_cache); + } + writer.write(layout.offsets[1], uniform_buffer_descriptor_cache); + } } let tex_descriptor_set_layout = &self.tex_descriptor_set_layouts[1]; for pass in RenderPass::variants() { @@ -725,7 +739,7 @@ impl VulkanRenderer { RenderPass::BlendBuffer => blend_cd, RenderPass::FrameBuffer => fb_cd, }; - let tf = target_cd.transfer_function; + let tf = target_cd.eotf; let color = memory .color_transforms .apply_to_color(&fr.cd, target_cd, fr.color); @@ -818,6 +832,26 @@ impl VulkanRenderer { Ok(()) } + fn create_blend_cm_data( + &self, + bb: Option<&VulkanImage>, + bb_cd: &ColorDescription, + fb_cd: &ColorDescription, + ) { + zone!("create_blend_cm_data"); + let memory = &mut *self.memory.borrow_mut(); + memory.blend_buffer_color_management_data_address = None; + if bb.is_none() { + return; + } + memory.blend_buffer_color_management_data_address = memory.color_transforms.get_offset( + &bb_cd.linear, + fb_cd, + self.device.uniform_buffer_offset_mask, + &mut memory.uniform_buffer_writer, + ); + } + fn create_data_buffer(&self) -> Result<(), VulkanError> { if self.device.descriptor_buffer.is_none() { return Ok(()); @@ -883,6 +917,9 @@ impl VulkanRenderer { } } } + if let Some(addr) = &mut memory.blend_buffer_color_management_data_address { + *addr += buffer.buffer.address; + } memory.used_buffers.push(buffer); Ok(()) } @@ -1047,7 +1084,7 @@ impl VulkanRenderer { .apply_to_color(clear_cd, target_cd, *clear); let clear_value = ClearValue { color: ClearColorValue { - float32: color.to_array(target_cd.transfer_function), + float32: color.to_array(target_cd.eotf), }, }; let use_load_clear = clear_rects.len() == 1 && { @@ -1299,7 +1336,12 @@ impl VulkanRenderer { zone!("blend_buffer_copy"); let memory = &*self.memory.borrow(); let db = self.device.descriptor_buffer.as_ref().unwrap(); - let pipeline = self.get_or_create_out_pipeline(fb.format.vk_format, bb_cd, fb_cd)?; + let pipeline = self.get_or_create_out_pipeline( + fb.format.vk_format, + bb_cd, + fb_cd, + memory.blend_buffer_color_management_data_address.is_some(), + )?; let push = OutPushConstants { vertices: memory.out_address, }; @@ -1852,7 +1894,21 @@ impl VulkanRenderer { Ok(()) } - fn elide_blend_buffer(&self, blend_buffer: &mut Option>) { + fn elide_blend_buffer1( + &self, + blend_buffer: &mut Option>, + bb_cd: &ColorDescription, + fb_cd: &ColorDescription, + ) { + if blend_buffer.is_none() { + return; + } + if bb_cd.embeds_into(fb_cd) { + *blend_buffer = None; + } + } + + fn elide_blend_buffer2(&self, blend_buffer: &mut Option>) { if blend_buffer.is_none() { return; } @@ -1876,11 +1932,13 @@ impl VulkanRenderer { bb_cd: &Rc, ) -> Result<(), VulkanError> { self.check_defunct()?; + self.elide_blend_buffer1(&mut blend_buffer, bb_cd, fb_cd); self.create_regions(fb, opts, clear, region, blend_buffer.as_deref())?; - self.elide_blend_buffer(&mut blend_buffer); + self.elide_blend_buffer2(&mut blend_buffer); let bb = blend_buffer.as_deref(); let buf = self.gfx_command_buffers.allocate()?; self.convert_ops(opts, bb_cd, fb_cd)?; + self.create_blend_cm_data(bb, bb_cd, fb_cd); self.create_data_buffer()?; self.create_uniform_buffer()?; self.collect_memory(); diff --git a/src/gfx_apis/vulkan/shaders.rs b/src/gfx_apis/vulkan/shaders.rs index be199c8a..74ef9031 100644 --- a/src/gfx_apis/vulkan/shaders.rs +++ b/src/gfx_apis/vulkan/shaders.rs @@ -69,6 +69,14 @@ pub struct TexColorManagementData { unsafe impl Packed for TexColorManagementData {} +#[derive(Copy, Clone, Debug)] +#[repr(C, align(16))] +pub struct OutColorManagementData { + pub matrix: [[f32; 4]; 4], +} + +unsafe impl Packed for OutColorManagementData {} + #[derive(Copy, Clone, Debug)] #[repr(C)] pub struct LegacyTexPushConstants { diff --git a/src/gfx_apis/vulkan/shaders/eotfs.glsl b/src/gfx_apis/vulkan/shaders/eotfs.glsl new file mode 100644 index 00000000..049a0444 --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/eotfs.glsl @@ -0,0 +1,113 @@ +#ifndef EOTFS_GLSL +#define EOTFS_GLSL + +#include "frag_spec_const.glsl" + +#define TF_LINEAR 1 +#define TF_ST2084_PQ 2 +#define TF_GAMMA24 3 +#define TF_GAMMA22 4 +#define TF_GAMMA28 5 +#define TF_ST240 6 +#define TF_LOG100 8 +#define TF_LOG316 9 +#define TF_ST428 10 + +vec3 eotf_st2084_pq(vec3 c) { + c = clamp(c, 0.0, 1.0); + vec3 cp = pow(c, vec3(1.0 / 78.84375)); + vec3 num = max(cp - vec3(0.8359375), 0.0); + vec3 den = vec3(18.8515625) - vec3(18.6875) * cp; + return pow(num / den, vec3(1.0 / 0.1593017578125)); +} + +vec3 inv_eotf_st2084_pq(vec3 c) { + c = clamp(c, 0.0, 1.0); + vec3 num = vec3(0.8359375) + vec3(18.8515625) * pow(c, vec3(0.1593017578125)); + vec3 den = vec3(1.0) + vec3(18.6875) * pow(c, vec3(0.1593017578125)); + return pow(num / den, vec3(78.84375)); +} + +vec3 eotf_st240(vec3 c) { + return mix( + c * vec3(1.0 / 4.0), + pow((c + vec3(0.1115)) * vec3(1.0 / 1.1115), vec3(1.0 / 0.45)), + greaterThanEqual(c, vec3(0.0913)) + ); +} + +vec3 inv_eotf_st240(vec3 c) { + return mix( + vec3(4.0) * c, + vec3(1.1115) * pow(c, vec3(0.45)) - vec3(0.1115), + greaterThanEqual(c, vec3(0.0228)) + ); +} + +vec3 eotf_log100(vec3 c) { + return pow(vec3(10), vec3(2.0) * (c - vec3(1.0))); +} + +vec3 inv_eotf_log100(vec3 c) { + c = clamp(c, 0.0, 1.0); + return mix( + vec3(0.0), + vec3(1.0) + log2(c) / vec3(log2(10)) / vec3(2.0), + greaterThanEqual(c, vec3(0.01)) + ); +} + +vec3 eotf_log316(vec3 c) { + return pow(vec3(10), vec3(2.5) * (c - vec3(1.0))); +} + +vec3 inv_eotf_log316(vec3 c) { + c = clamp(c, 0.0, 1.0); + return mix( + vec3(0.0), + vec3(1.0) + log2(c) / vec3(log2(10)) / vec3(2.5), + greaterThanEqual(c, vec3(sqrt(10) / 1000.0)) + ); +} + +vec3 eotf_st428(vec3 c) { + c = max(c, 0.0); + return pow(c, vec3(2.6)) * vec3(52.37 / 48.0); +} + +vec3 inv_eotf_st428(vec3 c) { + c = max(c, 0.0); + return pow(vec3(48.0) * c / vec3(52.37), vec3(1.0 / 2.6)); +} + +vec3 apply_eotf(vec3 c) { + switch (eotf) { + case TF_LINEAR: return c; + case TF_ST2084_PQ: return eotf_st2084_pq(c); + case TF_GAMMA24: return sign(c) * pow(abs(c), vec3(2.4)); + case TF_GAMMA22: return sign(c) * pow(abs(c), vec3(2.2)); + case TF_GAMMA28: return sign(c) * pow(abs(c), vec3(2.8)); + case TF_ST240: return eotf_st240(c); + case TF_LOG100: return eotf_log100(c); + case TF_LOG316: return eotf_log316(c); + case TF_ST428: return eotf_st428(c); + default: return c; + } +} + +vec3 apply_inv_eotf(vec3 c) { + switch (inv_eotf) { + case TF_LINEAR: return c; + case TF_ST2084_PQ: return inv_eotf_st2084_pq(c); + case TF_GAMMA24: return sign(c) * pow(abs(c), vec3(1.0 / 2.4)); + case TF_GAMMA22: return sign(c) * pow(abs(c), vec3(1.0 / 2.2)); + case TF_GAMMA28: return sign(c) * pow(abs(c), vec3(1.0 / 2.8)); + case TF_ST240: return inv_eotf_st240(c); + case TF_LOG100: return inv_eotf_log100(c); + case TF_LOG316: return inv_eotf_log316(c); + case TF_ST428: return inv_eotf_st428(c); + default: return c; + } +} + +#endif diff --git a/src/gfx_apis/vulkan/shaders/frag_spec_const.glsl b/src/gfx_apis/vulkan/shaders/frag_spec_const.glsl index 9bbc6c7b..cf7f8a7c 100644 --- a/src/gfx_apis/vulkan/shaders/frag_spec_const.glsl +++ b/src/gfx_apis/vulkan/shaders/frag_spec_const.glsl @@ -4,7 +4,7 @@ layout(constant_id = 0) const bool src_has_alpha = false; layout(constant_id = 1) const bool has_alpha_multiplier = false; layout(constant_id = 2) const uint eotf = 0; -layout(constant_id = 3) const uint oetf = 0; +layout(constant_id = 3) const uint inv_eotf = 0; layout(constant_id = 4) const bool has_matrix = false; #endif diff --git a/src/gfx_apis/vulkan/shaders/out.frag b/src/gfx_apis/vulkan/shaders/out.frag index c832dee7..d5df53e0 100644 --- a/src/gfx_apis/vulkan/shaders/out.frag +++ b/src/gfx_apis/vulkan/shaders/out.frag @@ -1,20 +1,30 @@ #version 450 + #extension GL_EXT_samplerless_texture_functions : require +#extension GL_EXT_scalar_block_layout : require #include "frag_spec_const.glsl" -#include "transfer_functions.glsl" +#include "eotfs.glsl" #include "out.common.glsl" layout(set = 0, binding = 0) uniform texture2D in_color; +layout(set = 0, binding = 1, row_major, std430) uniform ColorManagementData { + mat4x4 matrix; +} cm_data; layout(location = 0) out vec4 out_color; void main() { vec4 c = texelFetch(in_color, ivec2(gl_FragCoord.xy), 0); - if (eotf != oetf) { - c.rgb /= mix(c.a, 1.0, c.a == 0.0); - c.rgb = apply_eotf(c.rgb); - c.rgb = apply_oetf(c.rgb); - c.rgb *= c.a; + if (eotf != inv_eotf || has_matrix) { + vec3 rgb = c.rgb; + rgb /= mix(c.a, 1.0, c.a == 0.0); + rgb = apply_eotf(rgb); + if (has_matrix) { + rgb = (cm_data.matrix * vec4(rgb, 1.0)).rgb; + } + rgb = apply_inv_eotf(rgb); + rgb *= c.a; + c.rgb = rgb; } out_color = c; } diff --git a/src/gfx_apis/vulkan/shaders/tex.frag b/src/gfx_apis/vulkan/shaders/tex.frag index d7a6a978..6e365a3c 100644 --- a/src/gfx_apis/vulkan/shaders/tex.frag +++ b/src/gfx_apis/vulkan/shaders/tex.frag @@ -3,7 +3,7 @@ #extension GL_EXT_scalar_block_layout : require #include "frag_spec_const.glsl" -#include "transfer_functions.glsl" +#include "eotfs.glsl" #include "tex.common.glsl" layout(set = 0, binding = 0) uniform sampler sam; @@ -16,7 +16,7 @@ layout(location = 0) out vec4 out_color; void main() { vec4 c = textureLod(sampler2D(tex, sam), tex_pos, 0); - if (eotf != oetf || has_matrix) { + if (eotf != inv_eotf || has_matrix) { vec3 rgb = c.rgb; if (src_has_alpha) { rgb /= mix(c.a, 1.0, c.a == 0.0); @@ -25,7 +25,7 @@ void main() { if (has_matrix) { rgb = (cm_data.matrix * vec4(rgb, 1.0)).rgb; } - rgb = apply_oetf(rgb); + rgb = apply_inv_eotf(rgb); if (src_has_alpha) { rgb *= c.a; } diff --git a/src/gfx_apis/vulkan/shaders/transfer_functions.glsl b/src/gfx_apis/vulkan/shaders/transfer_functions.glsl deleted file mode 100644 index 7f9937e3..00000000 --- a/src/gfx_apis/vulkan/shaders/transfer_functions.glsl +++ /dev/null @@ -1,177 +0,0 @@ -#ifndef TRANSFER_FUNCTIONS_GLSL -#define TRANSFER_FUNCTIONS_GLSL - -#include "frag_spec_const.glsl" - -#define TF_SRGB 0 -#define TF_LINEAR 1 -#define TF_ST2084_PQ 2 -#define TF_BT1886 3 -#define TF_GAMMA22 4 -#define TF_GAMMA28 5 -#define TF_ST240 6 -#define TF_EXT_SRGB 7 -#define TF_LOG100 8 -#define TF_LOG316 9 -#define TF_ST428 10 - -vec3 eotf_srgb(vec3 c) { - return mix( - c * vec3(1.0 / 12.92), - pow((c + vec3(0.055)) / vec3(1.055), vec3(2.4)), - greaterThan(c, vec3(0.04045)) - ); -} - -vec3 oetf_srgb(vec3 c) { - c = clamp(c, 0.0, 1.0); - return mix( - c * vec3(12.92), - vec3(1.055) * pow(c, vec3(1/2.4)) - vec3(0.055), - greaterThan(c, vec3(0.0031308)) - ); -} - -vec3 eotf_ext_srgb(vec3 c) { - return mix( - -pow((c - vec3(0.055)) / vec3(-1.055), vec3(2.4)), - mix( - c * vec3(1.0 / 12.92), - pow((c + vec3(0.055)) / vec3(1.055), vec3(2.4)), - greaterThan(c, vec3(0.04045)) - ), - greaterThan(c, vec3(-0.04045)) - ); -} - -vec3 oetf_ext_srgb(vec3 c) { - c = clamp(c, -0.6038, 7.5913); - return mix( - vec3(-1.055) * pow(-c, vec3(1/2.4)) + vec3(0.055), - mix( - c * vec3(12.92), - vec3(1.055) * pow(c, vec3(1/2.4)) - vec3(0.055), - greaterThan(c, vec3(0.0031308)) - ), - greaterThan(c, vec3(-0.0031308)) - ); -} - -vec3 eotf_st2084_pq(vec3 c) { - c = clamp(c, 0.0, 1.0); - vec3 cp = pow(c, vec3(1.0 / 78.84375)); - vec3 num = max(cp - vec3(0.8359375), 0.0); - vec3 den = vec3(18.8515625) - vec3(18.6875) * cp; - return pow(num / den, vec3(1.0 / 0.1593017578125)); -} - -vec3 oetf_st2084_pq(vec3 c) { - c = clamp(c, 0.0, 1.0); - vec3 num = vec3(0.8359375) + vec3(18.8515625) * pow(c, vec3(0.1593017578125)); - vec3 den = vec3(1.0) + vec3(18.6875) * pow(c, vec3(0.1593017578125)); - return pow(num / den, vec3(78.84375)); -} - -vec3 eotf_bt1886(vec3 c) { - return mix( - c * vec3(1.0 / 4.5), - pow((c + vec3(0.099)) * vec3(1.0 / 1.099), vec3(1.0 / 0.45)), - greaterThanEqual(c, vec3(0.081)) - ); -} - -vec3 oetf_bt1886(vec3 c) { - return mix( - vec3(4.5) * c, - vec3(1.099) * pow(c, vec3(0.45)) - vec3(0.099), - greaterThanEqual(c, vec3(0.018)) - ); -} - -vec3 eotf_st240(vec3 c) { - return mix( - c * vec3(1.0 / 4.0), - pow((c + vec3(0.1115)) * vec3(1.0 / 1.1115), vec3(1.0 / 0.45)), - greaterThanEqual(c, vec3(0.0913)) - ); -} - -vec3 oetf_st240(vec3 c) { - return mix( - vec3(4.0) * c, - vec3(1.1115) * pow(c, vec3(0.45)) - vec3(0.1115), - greaterThanEqual(c, vec3(0.0228)) - ); -} - -vec3 eotf_log100(vec3 c) { - return pow(vec3(10), vec3(2.0) * (c - vec3(1.0))); -} - -vec3 oetf_log100(vec3 c) { - c = clamp(c, 0.0, 1.0); - return mix( - vec3(0.0), - vec3(1.0) + log2(c) / vec3(log2(10)) / vec3(2.0), - greaterThanEqual(c, vec3(0.01)) - ); -} - -vec3 eotf_log316(vec3 c) { - return pow(vec3(10), vec3(2.5) * (c - vec3(1.0))); -} - -vec3 oetf_log316(vec3 c) { - c = clamp(c, 0.0, 1.0); - return mix( - vec3(0.0), - vec3(1.0) + log2(c) / vec3(log2(10)) / vec3(2.5), - greaterThanEqual(c, vec3(sqrt(10) / 1000.0)) - ); -} - -vec3 eotf_st428(vec3 c) { - c = max(c, 0.0); - return pow(c, vec3(2.6)) * vec3(52.37 / 48.0); -} - -vec3 oetf_st428(vec3 c) { - c = max(c, 0.0); - return pow(vec3(48.0) * c / vec3(52.37), vec3(1.0 / 2.6)); -} - -vec3 apply_eotf(vec3 c) { - switch (eotf) { - case TF_SRGB: return eotf_srgb(c); - case TF_LINEAR: return c; - case TF_ST2084_PQ: return eotf_st2084_pq(c); - case TF_BT1886: return eotf_bt1886(c); - case TF_GAMMA22: return pow(max(c, 0.0), vec3(2.2)); - case TF_GAMMA28: return pow(max(c, 0.0), vec3(2.8)); - case TF_ST240: return eotf_st240(c); - case TF_EXT_SRGB: return eotf_ext_srgb(c); - case TF_LOG100: return eotf_log100(c); - case TF_LOG316: return eotf_log316(c); - case TF_ST428: return eotf_st428(c); - default: return c; - } -} - -vec3 apply_oetf(vec3 c) { - switch (oetf) { - case TF_SRGB: return oetf_srgb(c); - case TF_LINEAR: return c; - case TF_ST2084_PQ: return oetf_st2084_pq(c); - case TF_BT1886: return oetf_bt1886(c); - case TF_GAMMA22: return pow(max(c, 0.0), vec3(1.0 / 2.2)); - case TF_GAMMA28: return pow(max(c, 0.0), vec3(1.0 / 2.8)); - case TF_ST240: return oetf_st240(c); - case TF_EXT_SRGB: return oetf_ext_srgb(c); - case TF_LOG100: return oetf_log100(c); - case TF_LOG316: return oetf_log316(c); - case TF_ST428: return oetf_st428(c); - default: return c; - } -} - -#endif diff --git a/src/gfx_apis/vulkan/transfer_functions.rs b/src/gfx_apis/vulkan/transfer_functions.rs deleted file mode 100644 index c1354d67..00000000 --- a/src/gfx_apis/vulkan/transfer_functions.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::cmm::cmm_transfer_function::TransferFunction; - -pub const TF_SRGB: u32 = 0; -pub const TF_LINEAR: u32 = 1; -pub const TF_ST2084_PQ: u32 = 2; -pub const TF_BT1887: u32 = 3; -pub const TF_GAMMA22: u32 = 4; -pub const TF_GAMMA28: u32 = 5; -pub const TF_ST240: u32 = 6; -pub const TF_EXT_SRGB: u32 = 7; -pub const TF_LOG100: u32 = 8; -pub const TF_LOG316: u32 = 9; -pub const TF_ST428: u32 = 10; - -pub trait TransferFunctionExt: Sized { - fn to_vulkan(self) -> u32; -} - -impl TransferFunctionExt for TransferFunction { - fn to_vulkan(self) -> u32 { - match self { - TransferFunction::Srgb => TF_SRGB, - TransferFunction::Linear => TF_LINEAR, - TransferFunction::St2084Pq => TF_ST2084_PQ, - TransferFunction::Bt1886 => TF_BT1887, - TransferFunction::Gamma22 => TF_GAMMA22, - TransferFunction::Gamma28 => TF_GAMMA28, - TransferFunction::St240 => TF_ST240, - TransferFunction::ExtSrgb => TF_EXT_SRGB, - TransferFunction::Log100 => TF_LOG100, - TransferFunction::Log316 => TF_LOG316, - TransferFunction::St428 => TF_ST428, - } - } -} diff --git a/src/icons.rs b/src/icons.rs index ba916498..35e20922 100644 --- a/src/icons.rs +++ b/src/icons.rs @@ -2,7 +2,7 @@ use { crate::{ - cmm::cmm_transfer_function::TransferFunction, + cmm::cmm_eotf::Eotf, format::ARGB8888, gfx_api::{GfxContext, GfxError, GfxTexture}, scale::Scale, @@ -221,7 +221,7 @@ impl PathBuilderExt for PathBuilder { impl From for Color { fn from(v: crate::theme::Color) -> Self { - let [r, g, b, a] = v.to_array(TransferFunction::Srgb); + let [r, g, b, a] = v.to_array(Eotf::Gamma22); let mut c = Self::TRANSPARENT; c.set_red(r / a); c.set_green(g / a); @@ -242,7 +242,7 @@ fn calculate_accents(srgb: crate::theme::Color) -> [Color; 2] { } fn srgb_to_lab(srgb: crate::theme::Color) -> [f32; 4] { - let [mut r, mut g, mut b, alpha] = srgb.to_array(TransferFunction::Srgb); + let [mut r, mut g, mut b, alpha] = srgb.to_array(Eotf::Gamma22); if alpha < 1.0 { r /= alpha; g /= alpha; diff --git a/src/ifs/color_management/wp_image_description_creator_params_v1.rs b/src/ifs/color_management/wp_image_description_creator_params_v1.rs index 22356594..29750626 100644 --- a/src/ifs/color_management/wp_image_description_creator_params_v1.rs +++ b/src/ifs/color_management/wp_image_description_creator_params_v1.rs @@ -2,9 +2,9 @@ use { crate::{ client::{Client, ClientError}, cmm::{ + cmm_eotf::Eotf, cmm_luminance::{Luminance, TargetLuminance}, cmm_primaries::{NamedPrimaries, Primaries}, - cmm_transfer_function::TransferFunction, }, ifs::color_management::{ MIN_LUM_MUL_INV, PRIMARIES_MUL_INV, @@ -40,7 +40,7 @@ pub struct WpImageDescriptionCreatorParamsV1 { pub client: Rc, pub version: Version, pub tracker: Tracker, - pub tf: Cell>, + pub tf: Cell>, pub primaries: Cell, Primaries)>>, pub luminance: Cell>, pub mastering_primaries: Cell>, @@ -53,19 +53,19 @@ impl WpImageDescriptionCreatorParamsV1RequestHandler for WpImageDescriptionCreat type Error = WpImageDescriptionCreatorParamsV1Error; fn create(&self, req: Create, _slf: &Rc) -> Result<(), Self::Error> { - let Some(transfer_function) = self.tf.get() else { + let Some(eotf) = self.tf.get() else { return Err(WpImageDescriptionCreatorParamsV1Error::TfNotSet); }; let Some((named_primaries, primaries)) = self.primaries.get() else { return Err(WpImageDescriptionCreatorParamsV1Error::PrimariesNotSet); }; - let default_luminance = match transfer_function { - TransferFunction::Bt1886 => Luminance::BT1886, - TransferFunction::St2084Pq => Luminance::ST2084_PQ, + let default_luminance = match eotf { + Eotf::Bt1886 => Luminance::BT1886, + Eotf::St2084Pq => Luminance::ST2084_PQ, _ => Luminance::SRGB, }; let mut luminance = self.luminance.get().unwrap_or(default_luminance); - if transfer_function == TransferFunction::St2084Pq { + if eotf == Eotf::St2084Pq { luminance.max.0 = luminance.min.0 + 10_000.0; } if luminance.max.0 <= luminance.min.0 || luminance.white.0 <= luminance.min.0 { @@ -80,7 +80,7 @@ impl WpImageDescriptionCreatorParamsV1RequestHandler for WpImageDescriptionCreat named_primaries, primaries, luminance, - transfer_function, + eotf, target_primaries, target_luminance, self.max_cll.get(), @@ -102,17 +102,17 @@ impl WpImageDescriptionCreatorParamsV1RequestHandler for WpImageDescriptionCreat fn set_tf_named(&self, req: SetTfNamed, _slf: &Rc) -> Result<(), Self::Error> { let tf = match req.tf { - TRANSFER_FUNCTION_BT1886 => TransferFunction::Bt1886, - TRANSFER_FUNCTION_GAMMA22 => TransferFunction::Gamma22, - TRANSFER_FUNCTION_GAMMA28 => TransferFunction::Gamma28, - TRANSFER_FUNCTION_ST240 => TransferFunction::St240, - TRANSFER_FUNCTION_EXT_LINEAR => TransferFunction::Linear, - TRANSFER_FUNCTION_LOG_100 => TransferFunction::Log100, - TRANSFER_FUNCTION_LOG_316 => TransferFunction::Log316, - TRANSFER_FUNCTION_SRGB => TransferFunction::Srgb, - TRANSFER_FUNCTION_EXT_SRGB => TransferFunction::ExtSrgb, - TRANSFER_FUNCTION_ST2084_PQ => TransferFunction::St2084Pq, - TRANSFER_FUNCTION_ST428 => TransferFunction::St428, + TRANSFER_FUNCTION_BT1886 => Eotf::Bt1886, + TRANSFER_FUNCTION_GAMMA22 => Eotf::Gamma22, + TRANSFER_FUNCTION_GAMMA28 => Eotf::Gamma28, + TRANSFER_FUNCTION_ST240 => Eotf::St240, + TRANSFER_FUNCTION_EXT_LINEAR => Eotf::Linear, + TRANSFER_FUNCTION_LOG_100 => Eotf::Log100, + TRANSFER_FUNCTION_LOG_316 => Eotf::Log316, + TRANSFER_FUNCTION_SRGB => Eotf::Gamma22, + TRANSFER_FUNCTION_EXT_SRGB => Eotf::Gamma22, + TRANSFER_FUNCTION_ST2084_PQ => Eotf::St2084Pq, + TRANSFER_FUNCTION_ST428 => Eotf::St428, _ => { return Err(WpImageDescriptionCreatorParamsV1Error::UnsupportedTf( req.tf, @@ -261,9 +261,9 @@ pub enum WpImageDescriptionCreatorParamsV1Error { UnsupportedPrimaries(u32), #[error("set_tf_power is not supported")] SetTfPowerNotSupported, - #[error("{} is not a supported named transfer function", .0)] + #[error("{} is not a supported named EOTF", .0)] UnsupportedTf(u32), - #[error("The transfer function has already been set")] + #[error("The EOTF has already been set")] TfAlreadySet, #[error("The primaries have already been set")] PrimariesAlreadySet, @@ -271,7 +271,7 @@ pub enum WpImageDescriptionCreatorParamsV1Error { LuminancesAlreadySet, #[error("The minimum luminance is too low")] MinLuminanceTooLow, - #[error("The transfer function was not set")] + #[error("The EOTF was not set")] TfNotSet, #[error("The primaries were not set")] PrimariesNotSet, diff --git a/src/ifs/color_management/wp_image_description_info_v1.rs b/src/ifs/color_management/wp_image_description_info_v1.rs index 352f7a90..53f63a4c 100644 --- a/src/ifs/color_management/wp_image_description_info_v1.rs +++ b/src/ifs/color_management/wp_image_description_info_v1.rs @@ -1,18 +1,14 @@ use { crate::{ client::Client, - cmm::{ - cmm_description::ColorDescription, cmm_primaries::NamedPrimaries, - cmm_transfer_function::TransferFunction, - }, + cmm::{cmm_description::ColorDescription, cmm_eotf::Eotf, cmm_primaries::NamedPrimaries}, ifs::color_management::{ MIN_LUM_MUL, PRIMARIES_ADOBE_RGB, PRIMARIES_BT2020, PRIMARIES_CIE1931_XYZ, PRIMARIES_DCI_P3, PRIMARIES_DISPLAY_P3, PRIMARIES_GENERIC_FILM, PRIMARIES_MUL, - PRIMARIES_NTSC, PRIMARIES_PAL, PRIMARIES_PAL_M, TRANSFER_FUNCTION_BT1886, - TRANSFER_FUNCTION_EXT_LINEAR, TRANSFER_FUNCTION_EXT_SRGB, TRANSFER_FUNCTION_GAMMA22, + PRIMARIES_NTSC, PRIMARIES_PAL, PRIMARIES_PAL_M, PRIMARIES_SRGB, + TRANSFER_FUNCTION_BT1886, TRANSFER_FUNCTION_EXT_LINEAR, TRANSFER_FUNCTION_GAMMA22, TRANSFER_FUNCTION_GAMMA28, TRANSFER_FUNCTION_LOG_100, TRANSFER_FUNCTION_LOG_316, TRANSFER_FUNCTION_ST240, TRANSFER_FUNCTION_ST428, TRANSFER_FUNCTION_ST2084_PQ, - consts::{PRIMARIES_SRGB, TRANSFER_FUNCTION_SRGB}, }, leaks::Tracker, object::{Object, Version}, @@ -32,18 +28,16 @@ pub struct WpImageDescriptionInfoV1 { impl WpImageDescriptionInfoV1 { pub fn send_description(&self, d: &ColorDescription) { - let tf = match d.transfer_function { - TransferFunction::Srgb => TRANSFER_FUNCTION_SRGB, - TransferFunction::Linear => TRANSFER_FUNCTION_EXT_LINEAR, - TransferFunction::St2084Pq => TRANSFER_FUNCTION_ST2084_PQ, - TransferFunction::Bt1886 => TRANSFER_FUNCTION_BT1886, - TransferFunction::Gamma22 => TRANSFER_FUNCTION_GAMMA22, - TransferFunction::Gamma28 => TRANSFER_FUNCTION_GAMMA28, - TransferFunction::St240 => TRANSFER_FUNCTION_ST240, - TransferFunction::ExtSrgb => TRANSFER_FUNCTION_EXT_SRGB, - TransferFunction::Log100 => TRANSFER_FUNCTION_LOG_100, - TransferFunction::Log316 => TRANSFER_FUNCTION_LOG_316, - TransferFunction::St428 => TRANSFER_FUNCTION_ST428, + let tf = match d.eotf { + Eotf::Linear => TRANSFER_FUNCTION_EXT_LINEAR, + Eotf::St2084Pq => TRANSFER_FUNCTION_ST2084_PQ, + Eotf::Bt1886 => TRANSFER_FUNCTION_BT1886, + Eotf::Gamma22 => TRANSFER_FUNCTION_GAMMA22, + Eotf::Gamma28 => TRANSFER_FUNCTION_GAMMA28, + Eotf::St240 => TRANSFER_FUNCTION_ST240, + Eotf::Log100 => TRANSFER_FUNCTION_LOG_100, + Eotf::Log316 => TRANSFER_FUNCTION_LOG_316, + Eotf::St428 => TRANSFER_FUNCTION_ST428, }; self.send_primaries(&d.linear.primaries); if let Some(n) = d.named_primaries { diff --git a/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs b/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs index c19ece8f..79452e38 100644 --- a/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs +++ b/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs @@ -217,7 +217,7 @@ impl ExtImageCopyCaptureFrameV1 { aq, re, jay_config::video::Transform::None, - self.client.state.color_manager.srgb_srgb(), + self.client.state.color_manager.srgb_gamma22(), on.global.pos.get(), render_hardware_cursors, x_off, @@ -235,7 +235,7 @@ impl ExtImageCopyCaptureFrameV1 { fb.render_node( aq, re, - self.client.state.color_manager.srgb_srgb(), + self.client.state.color_manager.srgb_gamma22(), node, &self.client.state, Some(node.node_absolute_position()), diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index d8b54b6a..d7435cb0 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -1,7 +1,7 @@ use { crate::{ backend::{ - BackendColorSpace, BackendTransferFunction, ConnectorId, Mode, MonitorInfo, + BackendColorSpace, BackendEotfs, ConnectorId, Mode, MonitorInfo, transaction::BackendConnectorTransactionError, }, client::ClientId, @@ -90,7 +90,7 @@ pub struct HeadState { pub tearing_mode: TearingMode, pub format: &'static Format, pub color_space: BackendColorSpace, - pub transfer_function: BackendTransferFunction, + pub eotf: BackendEotfs, pub supported_formats: RcEq>, pub brightness: Option, } @@ -132,7 +132,7 @@ enum HeadOp { SetVrrMode(VrrMode), SetTearingMode(TearingMode), SetFormat(&'static Format), - SetTransferFunction(BackendTransferFunction), + SetEotf(BackendEotfs), SetColorSpace(BackendColorSpace), SetBrightness(Option), } @@ -491,14 +491,10 @@ impl HeadManagers { } } - pub fn handle_colors_change( - &self, - color_space: BackendColorSpace, - transfer_function: BackendTransferFunction, - ) { + pub fn handle_colors_change(&self, color_space: BackendColorSpace, eotf: BackendEotfs) { let state = &mut *self.state.borrow_mut(); state.color_space = color_space; - state.transfer_function = transfer_function; + state.eotf = eotf; for head in self.managers.lock().values() { skip_in_transaction!(head); if let Some(ext) = &head.ext.drm_color_space_info_v1 { diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_brightness_info_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_brightness_info_v1.rs index d1438f4b..34411b22 100644 --- a/src/ifs/head_management/jay_head_ext/jay_head_ext_brightness_info_v1.rs +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_brightness_info_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - backend::BackendTransferFunction, + backend::BackendEotfs, cmm::cmm_luminance::Luminance, ifs::head_management::HeadState, wire::{ @@ -27,7 +27,7 @@ impl HeadName { } fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { - if shared.transfer_function != tran.transfer_function { + if shared.eotf != tran.eotf { self.send_implied_default_brightness(shared); } if shared.brightness != tran.brightness { @@ -36,14 +36,14 @@ impl HeadName { } pub(in super::super) fn send_implied_default_brightness(&self, shared: &HeadState) { - let lux = match shared.transfer_function { - BackendTransferFunction::Default => shared + let lux = match shared.eotf { + BackendEotfs::Default => shared .monitor_info .as_ref() .and_then(|m| m.luminance.as_ref()) .map(|l| l.max) .unwrap_or(Luminance::SRGB.white.0), - BackendTransferFunction::Pq => Luminance::ST2084_PQ.white.0, + BackendEotfs::Pq => Luminance::ST2084_PQ.white.0, }; self.client.event(ImpliedDefaultBrightness { self_id: self.id, diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_drm_color_space_info_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_drm_color_space_info_v1.rs index 0b57ee44..e1c4e19c 100644 --- a/src/ifs/head_management/jay_head_ext/jay_head_ext_drm_color_space_info_v1.rs +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_drm_color_space_info_v1.rs @@ -32,9 +32,7 @@ impl HeadName { } fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { - if (shared.color_space, shared.transfer_function) - != (tran.color_space, tran.transfer_function) - { + if (shared.color_space, shared.eotf) != (tran.color_space, tran.eotf) { self.send_state(shared); } } @@ -42,7 +40,7 @@ impl HeadName { pub(in super::super) fn send_state(&self, state: &HeadState) { self.client.event(HdmiEotf { self_id: self.id, - eotf: state.transfer_function.to_drm() as u32, + eotf: state.eotf.to_drm() as u32, }); self.client.event(Colorimetry { self_id: self.id, diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_drm_color_space_setter_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_drm_color_space_setter_v1.rs index d6f7f486..13b0fd98 100644 --- a/src/ifs/head_management/jay_head_ext/jay_head_ext_drm_color_space_setter_v1.rs +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_drm_color_space_setter_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - backend::{BackendColorSpace, BackendTransferFunction}, + backend::{BackendColorSpace, BackendEotfs}, ifs::head_management::{HeadOp, HeadState}, video::drm::{ DRM_MODE_COLORIMETRY_BT2020_RGB, DRM_MODE_COLORIMETRY_DEFAULT, HDMI_EOTF_SMPTE_ST2084, @@ -41,7 +41,7 @@ impl HeadName { return; }; self.send_supported_eotf(HDMI_EOTF_TRADITIONAL_GAMMA_SDR); - for tf in &mi.transfer_functions { + for tf in &mi.eotfs { self.send_supported_eotf(tf.to_drm()); } self.send_supported_colorimetry(DRM_MODE_COLORIMETRY_DEFAULT); @@ -80,20 +80,20 @@ impl JayHeadExtDrmColorSpaceSetterV1RequestHandler for HeadName { const DEFAULT: u32 = HDMI_EOTF_TRADITIONAL_GAMMA_SDR as u32; const PQ: u32 = HDMI_EOTF_SMPTE_ST2084 as u32; let eotf = match req.eotf { - DEFAULT => BackendTransferFunction::Default, - PQ => BackendTransferFunction::Pq, + DEFAULT => BackendEotfs::Default, + PQ => BackendEotfs::Pq, _ => return Err(ErrorName::UnknownEotf(req.eotf)), }; - if eotf != BackendTransferFunction::Default { + if eotf != BackendEotfs::Default { let state = &*self.common.transaction_state.borrow(); let Some(mi) = &state.monitor_info else { return Err(ErrorName::UnsupportedEotf(req.eotf)); }; - if mi.transfer_functions.not_contains(&eotf) { + if mi.eotfs.not_contains(&eotf) { return Err(ErrorName::UnsupportedEotf(req.eotf)); } } - self.common.push_op(HeadOp::SetTransferFunction(eotf))?; + self.common.push_op(HeadOp::SetEotf(eotf))?; Ok(()) } diff --git a/src/ifs/head_management/jay_head_manager_session_v1.rs b/src/ifs/head_management/jay_head_manager_session_v1.rs index 05394ad2..9efe6ede 100644 --- a/src/ifs/head_management/jay_head_manager_session_v1.rs +++ b/src/ifs/head_management/jay_head_manager_session_v1.rs @@ -265,7 +265,7 @@ impl JayHeadManagerSessionV1 { new.non_desktop_override = desired.override_non_desktop; new.format = desired.format; new.color_space = desired.color_space; - new.transfer_function = desired.transfer_function; + new.eotf = desired.eotf; if old == new { continue; } @@ -447,8 +447,8 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { state.format = f; to_send |= FORMAT_INFO; } - HeadOp::SetTransferFunction(e) => { - state.transfer_function = e; + HeadOp::SetEotf(e) => { + state.eotf = e; to_send |= DRM_COLOR_SPACE_INFO; to_send |= BRIGHTNESS_INFO; } diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index 927be203..05aa3064 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -79,7 +79,7 @@ impl Global for JayCompositorGlobal { } fn version(&self) -> u32 { - 20 + 21 } fn required_caps(&self) -> ClientCaps { diff --git a/src/ifs/jay_damage_tracking.rs b/src/ifs/jay_damage_tracking.rs index 126a2c10..7d58ed59 100644 --- a/src/ifs/jay_damage_tracking.rs +++ b/src/ifs/jay_damage_tracking.rs @@ -1,7 +1,7 @@ use { crate::{ client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientError}, - cmm::cmm_transfer_function::TransferFunction, + cmm::cmm_eotf::Eotf, globals::{Global, GlobalName}, leaks::Tracker, object::{Object, Version}, @@ -97,7 +97,7 @@ impl JayDamageTrackingRequestHandler for JayDamageTracking { req: SetVisualizerColor, _slf: &Rc, ) -> Result<(), Self::Error> { - let color = Color::new(TransferFunction::Srgb, req.r, req.g, req.b) * req.a; + let color = Color::new(Eotf::Gamma22, req.r, req.g, req.b) * req.a; self.client.state.damage_visualizer.set_color(color); Ok(()) } diff --git a/src/ifs/jay_randr.rs b/src/ifs/jay_randr.rs index 29e7ea2f..89b7fe99 100644 --- a/src/ifs/jay_randr.rs +++ b/src/ifs/jay_randr.rs @@ -1,9 +1,10 @@ use { crate::{ - backend::{self, BackendColorSpace, BackendTransferFunction}, + backend::{self, BackendColorSpace, BackendEotfs}, client::{Client, ClientError}, compositor::MAX_EXTENTS, format::named_formats, + ifs::wl_output, leaks::Tracker, object::{Object, Version}, scale::Scale, @@ -34,6 +35,7 @@ const FORMAT_SINCE: Version = Version(8); 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); impl JayRandr { pub fn new(id: JayRandrId, client: &Rc, version: Version) -> Self { @@ -170,15 +172,15 @@ impl JayRandr { }); } if self.version >= COLORIMETRY_SINCE { - for tf in &node.global.transfer_functions { - self.client.event(SupportedTransferFunction { + for eotf in &node.global.eotfs { + self.client.event(SupportedEotf { self_id: self.id, - transfer_function: tf.name(), + eotf: eotf.name(), }); } - self.client.event(CurrentTransferFunction { + self.client.event(CurrentEotf { self_id: self.id, - transfer_function: node.global.btf.get().name(), + eotf: node.global.btf.get().name(), }); for cs in &node.global.color_spaces { self.client.event(SupportedColorSpace { @@ -207,6 +209,12 @@ impl JayRandr { }); } } + if self.version >= BLEND_SPACE_SINCE { + self.client.event(BlendSpace { + self_id: self.id, + blend_space: node.global.persistent.blend_space.get().name(), + }); + } } fn send_error(&self, msg: &str) { @@ -484,21 +492,19 @@ impl JayRandrRequestHandler for JayRandr { )); }; let tf = 'tf: { - for tf in BackendTransferFunction::variants() { - if tf.name() == req.transfer_function { + for tf in BackendEotfs::variants() { + if tf.name() == req.eotf { break 'tf tf; } } - return Err(JayRandrError::UnknownTransferFunction( - req.transfer_function.to_string(), - )); + return Err(JayRandrError::UnknownEotf(req.eotf.to_string())); }; let Some(c) = self.get_connector(req.output) else { return Ok(()); }; let res = c.modify_state(&self.state, |s| { s.color_space = cs; - s.transfer_function = tf; + s.eotf = tf; }); if let Err(e) = res { self.send_error(&format!( @@ -528,6 +534,23 @@ impl JayRandrRequestHandler for JayRandr { c.set_brightness(None); Ok(()) } + + fn set_blend_space(&self, req: SetBlendSpace<'_>, _slf: &Rc) -> Result<(), Self::Error> { + let space = 'space: { + for space in wl_output::BlendSpace::variants() { + if space.name() == req.blend_space { + break 'space space; + } + } + self.send_error(&format!("Unknown blend space: {}", req.blend_space)); + return Ok(()); + }; + let Some(c) = self.get_output_node(req.output) else { + return Ok(()); + }; + c.set_blend_space(space); + Ok(()) + } } object_base! { @@ -551,7 +574,7 @@ pub enum JayRandrError { UnknownFormat(String), #[error("Unknown color space {0}")] UnknownColorSpace(String), - #[error("Unknown transfer function {0}")] - UnknownTransferFunction(String), + #[error("Unknown EOTF {0}")] + UnknownEotf(String), } efrom!(JayRandrError, ClientError); diff --git a/src/ifs/jay_screencast.rs b/src/ifs/jay_screencast.rs index 3a3c0779..0b392b40 100644 --- a/src/ifs/jay_screencast.rs +++ b/src/ifs/jay_screencast.rs @@ -194,7 +194,7 @@ impl JayScreencast { let res = buffer.fb.render_node( AcquireSync::Implicit, ReleaseSync::Implicit, - self.client.state.color_manager.srgb_srgb(), + self.client.state.color_manager.srgb_gamma22(), &*tl, &self.client.state, Some(tl.node_absolute_position()), @@ -341,7 +341,7 @@ impl JayScreencast { AcquireSync::Implicit, ReleaseSync::Implicit, Transform::None, - self.client.state.color_manager.srgb_srgb(), + self.client.state.color_manager.srgb_gamma22(), on.global.pos.get(), render_hardware_cursors, x_off, diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index 9872a970..f0dcd5b4 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -2,13 +2,13 @@ mod removed_output; use { crate::{ - backend::{self, BackendColorSpace, BackendLuminance, BackendTransferFunction}, + backend::{self, BackendColorSpace, BackendEotfs, BackendLuminance}, client::{Client, ClientError, ClientId}, cmm::{ cmm_description::ColorDescription, + cmm_eotf::Eotf, cmm_luminance::Luminance, cmm_primaries::{NamedPrimaries, Primaries}, - cmm_transfer_function::TransferFunction, }, damage::DamageMatrix, format::{Format, XRGB8888}, @@ -30,6 +30,7 @@ use { }, ahash::AHashMap, jay_config::video::Transform, + linearize::Linearize, std::{ cell::{Cell, RefCell}, collections::hash_map::Entry, @@ -76,7 +77,7 @@ pub struct WlOutputGlobal { pub format: Cell<&'static Format>, pub width_mm: i32, pub height_mm: i32, - pub transfer_functions: Vec, + pub eotfs: Vec, pub color_spaces: Vec, pub primaries: Primaries, pub luminance: Option, @@ -86,7 +87,7 @@ pub struct WlOutputGlobal { pub persistent: Rc, pub opt: Rc, pub damage_matrix: Cell, - pub btf: Cell, + pub btf: Cell, pub bcs: Cell, pub color_description: CloneCell>, pub linear_color_description: CloneCell>, @@ -115,6 +116,21 @@ impl OutputGlobalOpt { } } +#[derive(Copy, Clone, Debug, Eq, PartialEq, Linearize)] +pub enum BlendSpace { + Linear, + Srgb, +} + +impl BlendSpace { + pub fn name(self) -> &'static str { + match self { + BlendSpace::Linear => "linear", + BlendSpace::Srgb => "srgb", + } + } +} + pub struct PersistentOutputState { pub transform: Cell, pub scale: Cell, @@ -123,6 +139,7 @@ pub struct PersistentOutputState { pub vrr_cursor_hz: Cell>, pub tearing_mode: Cell<&'static TearingMode>, pub brightness: Cell>, + pub blend_space: Cell, } impl Default for PersistentOutputState { @@ -135,6 +152,7 @@ impl Default for PersistentOutputState { vrr_cursor_hz: Default::default(), tearing_mode: Cell::new(&TearingMode::Never), brightness: Default::default(), + blend_space: Cell::new(BlendSpace::Srgb), } } } @@ -179,7 +197,7 @@ impl WlOutputGlobal { height_mm: i32, output_id: &Rc, persistent_state: &Rc, - transfer_functions: Vec, + eotfs: Vec, color_spaces: Vec, primaries: Primaries, luminance: Option, @@ -205,7 +223,7 @@ impl WlOutputGlobal { format: Cell::new(XRGB8888), width_mm, height_mm, - transfer_functions, + eotfs, color_spaces, primaries, luminance, @@ -215,9 +233,9 @@ impl WlOutputGlobal { persistent: persistent_state.clone(), opt: Default::default(), damage_matrix: Default::default(), - btf: Cell::new(connector_state.transfer_function), + btf: Cell::new(connector_state.eotf), bcs: Cell::new(connector_state.color_space), - color_description: CloneCell::new(state.color_manager.srgb_srgb().clone()), + color_description: CloneCell::new(state.color_manager.srgb_gamma22().clone()), linear_color_description: CloneCell::new(state.color_manager.srgb_linear().clone()), color_description_listeners: Default::default(), }; @@ -345,7 +363,7 @@ impl WlOutputGlobal { pub fn update_color_description(&self) -> bool { let mut luminance = Luminance::SRGB; let tf = match self.btf.get() { - BackendTransferFunction::Default => { + BackendEotfs::Default => { if let Some(brightness) = self.persistent.brightness.get() { let output_max = match self.luminance { None => 80.0, @@ -353,14 +371,14 @@ impl WlOutputGlobal { }; luminance.white.0 = luminance.max.0 * brightness / output_max; } - TransferFunction::Srgb + Eotf::Gamma22 } - BackendTransferFunction::Pq => { + BackendEotfs::Pq => { luminance = Luminance::ST2084_PQ; if let Some(brightness) = self.persistent.brightness.get() { luminance.white.0 = brightness; } - TransferFunction::St2084Pq + Eotf::St2084Pq } }; let mut target_luminance = luminance.to_target(); @@ -386,10 +404,7 @@ impl WlOutputGlobal { max_cll, max_fall, ); - let cd_linear = self - .state - .color_manager - .get_with_tf(&cd, TransferFunction::Linear); + let cd_linear = self.state.color_manager.get_with_tf(&cd, Eotf::Linear); self.linear_color_description.set(cd_linear.clone()); self.color_description.set(cd.clone()).id != cd.id } diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index be07d0ca..282a7696 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -1723,7 +1723,7 @@ impl WlSurface { pub fn color_description(&self) -> Rc { match self.color_description.get() { Some(cd) => cd, - None => self.client.state.color_manager.srgb_srgb().clone(), + None => self.client.state.color_manager.srgb_gamma22().clone(), } } diff --git a/src/it/test_backend.rs b/src/it/test_backend.rs index 3bbbb3d7..cf0d2e0b 100644 --- a/src/it/test_backend.rs +++ b/src/it/test_backend.rs @@ -138,7 +138,7 @@ impl TestBackend { non_desktop: false, non_desktop_effective: false, vrr_capable: false, - transfer_functions: vec![], + eotfs: vec![], color_spaces: vec![], primaries: Primaries::SRGB, luminance: None, @@ -152,7 +152,7 @@ impl TestBackend { tearing: false, format: XRGB8888, color_space: Default::default(), - transfer_function: Default::default(), + eotf: Default::default(), }, }; Self { diff --git a/src/it/test_ifs/test_single_pixel_buffer_manager.rs b/src/it/test_ifs/test_single_pixel_buffer_manager.rs index 4685279c..33afb24d 100644 --- a/src/it/test_ifs/test_single_pixel_buffer_manager.rs +++ b/src/it/test_ifs/test_single_pixel_buffer_manager.rs @@ -1,6 +1,6 @@ use { crate::{ - cmm::cmm_transfer_function::TransferFunction, + cmm::cmm_eotf::Eotf, it::{ test_error::TestResult, test_ifs::test_buffer::TestBuffer, test_object::TestObject, test_transport::TestTransport, @@ -32,7 +32,7 @@ impl TestSinglePixelBufferManager { destroyed: Cell::new(false), }); let map = |c: f32| (c as f64 * u32::MAX as f64) as u32; - let [r, g, b, a] = color.to_array(TransferFunction::Srgb); + let [r, g, b, a] = color.to_array(Eotf::Gamma22); self.tran.send(CreateU32RgbaBuffer { self_id: self.id, id: obj.id, diff --git a/src/it/tests/t0034_workspace_restoration.rs b/src/it/tests/t0034_workspace_restoration.rs index 8fa6907b..e7de4789 100644 --- a/src/it/tests/t0034_workspace_restoration.rs +++ b/src/it/tests/t0034_workspace_restoration.rs @@ -51,7 +51,7 @@ async fn test(run: Rc) -> TestResult { non_desktop: false, non_desktop_effective: false, vrr_capable: false, - transfer_functions: vec![], + eotfs: vec![], color_spaces: vec![], primaries: Primaries::SRGB, luminance: None, @@ -65,7 +65,7 @@ async fn test(run: Rc) -> TestResult { tearing: false, format: XRGB8888, color_space: Default::default(), - transfer_function: Default::default(), + eotf: Default::default(), }, }; run.backend diff --git a/src/it/tests/t0039_alpha_modifier/screenshot_2.qoi b/src/it/tests/t0039_alpha_modifier/screenshot_2.qoi index 5c908f15..9874e2f5 100644 Binary files a/src/it/tests/t0039_alpha_modifier/screenshot_2.qoi and b/src/it/tests/t0039_alpha_modifier/screenshot_2.qoi differ diff --git a/src/it/tests/t0042_toplevel_select/screenshot_1.qoi b/src/it/tests/t0042_toplevel_select/screenshot_1.qoi index 81e7340e..6423ef6d 100644 Binary files a/src/it/tests/t0042_toplevel_select/screenshot_1.qoi and b/src/it/tests/t0042_toplevel_select/screenshot_1.qoi differ diff --git a/src/it/tests/t0042_toplevel_select/screenshot_2.qoi b/src/it/tests/t0042_toplevel_select/screenshot_2.qoi index e25c77ab..823fd750 100644 Binary files a/src/it/tests/t0042_toplevel_select/screenshot_2.qoi and b/src/it/tests/t0042_toplevel_select/screenshot_2.qoi differ diff --git a/src/it/tests/t0042_toplevel_select/screenshot_3.qoi b/src/it/tests/t0042_toplevel_select/screenshot_3.qoi index e25c77ab..823fd750 100644 Binary files a/src/it/tests/t0042_toplevel_select/screenshot_3.qoi and b/src/it/tests/t0042_toplevel_select/screenshot_3.qoi differ diff --git a/src/portal/ptl_text.rs b/src/portal/ptl_text.rs index 5e8da166..188112ba 100644 --- a/src/portal/ptl_text.rs +++ b/src/portal/ptl_text.rs @@ -1,6 +1,6 @@ use { crate::{ - cmm::cmm_transfer_function::TransferFunction, + cmm::cmm_eotf::Eotf, format::ARGB8888, gfx_api::{GfxContext, GfxTexture}, pango::{ @@ -79,7 +79,7 @@ pub fn render( let data = create_data(font, width, height, scale)?; data.layout.set_text(text); let font_height = data.layout.pixel_size().1; - let [r, g, b, a] = color.to_array(TransferFunction::Srgb); + let [r, g, b, a] = color.to_array(Eotf::Gamma22); data.cctx.set_operator(CAIRO_OPERATOR_SOURCE); data.cctx.set_source_rgba(r as _, g as _, b as _, a as _); let y = y.unwrap_or((height - font_height) / 2); diff --git a/src/portal/ptr_gui.rs b/src/portal/ptr_gui.rs index edc3865c..d508c22c 100644 --- a/src/portal/ptr_gui.rs +++ b/src/portal/ptr_gui.rs @@ -192,7 +192,7 @@ impl GuiElement for Button { } fn render_at(&self, color_manager: &ColorManager, r: &mut RendererBase, x1: f32, y1: f32) { - let srgb_srgb = color_manager.srgb_srgb(); + let srgb_srgb = color_manager.srgb_gamma22(); let srgb = &srgb_srgb.linear; let x2 = x1 + self.data.width.get(); let y2 = y1 + self.data.height.get(); @@ -331,7 +331,7 @@ impl GuiElement for Label { AcquireSync::None, ReleaseSync::None, false, - color_manager.srgb_srgb(), + color_manager.srgb_gamma22(), ); } } @@ -644,10 +644,10 @@ impl WindowData { let res = buf.fb.render_custom( AcquireSync::Implicit, ReleaseSync::Implicit, - self.dpy.state.color_manager.srgb_srgb(), + self.dpy.state.color_manager.srgb_gamma22(), self.scale.get(), Some(&Color::from_gray_srgb(0)), - &self.dpy.state.color_manager.srgb_srgb().linear, + &self.dpy.state.color_manager.srgb_gamma22().linear, None, self.dpy.state.color_manager.srgb_linear(), &mut |r| { diff --git a/src/renderer.rs b/src/renderer.rs index bce200f9..fc6cb4c5 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -79,7 +79,7 @@ impl Renderer<'_> { } let theme = &self.state.theme; let th = theme.sizes.title_height.get(); - let srgb_srgb = self.state.color_manager.srgb_srgb(); + let srgb_srgb = self.state.color_manager.srgb_gamma22(); let srgb = &srgb_srgb.linear; if let Some(fs) = &fullscreen { fs.node_render(self, x, y, None); @@ -135,7 +135,7 @@ impl Renderer<'_> { AcquireSync::None, ReleaseSync::None, false, - self.state.color_manager.srgb_srgb(), + self.state.color_manager.srgb_gamma22(), ); } if let Some(status) = &rd.status @@ -219,7 +219,7 @@ impl Renderer<'_> { self.base.fill_boxes( std::slice::from_ref(&pos.at_point(x, y)), &Color::from_srgba_straight(20, 20, 20, 255), - &self.state.color_manager.srgb_srgb().linear, + &self.state.color_manager.srgb_gamma22().linear, ); if let Some(tex) = placeholder.textures.borrow().get(&self.base.scale) && let Some(texture) = tex.texture() @@ -240,7 +240,7 @@ impl Renderer<'_> { AcquireSync::None, ReleaseSync::None, false, - self.state.color_manager.srgb_srgb(), + self.state.color_manager.srgb_gamma22(), ); } self.render_tl_aux(placeholder.tl_data(), bounds, true); @@ -248,7 +248,7 @@ impl Renderer<'_> { pub fn render_container(&mut self, container: &ContainerNode, x: i32, y: i32) { { - let srgb_srgb = self.state.color_manager.srgb_srgb(); + let srgb_srgb = self.state.color_manager.srgb_gamma22(); let srgb = &srgb_srgb.linear; let rd = container.render_data.borrow_mut(); let c = self.state.theme.colors.unfocused_title_background.get(); @@ -367,7 +367,7 @@ impl Renderer<'_> { slice::from_ref(bounds), &color, None, - &self.state.color_manager.srgb_srgb().linear, + &self.state.color_manager.srgb_gamma22().linear, ); } @@ -377,7 +377,7 @@ impl Renderer<'_> { self.base.fill_boxes( slice::from_ref(rect), &color, - &self.state.color_manager.srgb_srgb().linear, + &self.state.color_manager.srgb_gamma22().linear, ); } @@ -482,11 +482,7 @@ impl Renderer<'_> { }; if !rect.is_empty() { let color = Color::from_u32_premultiplied( - cd.transfer_function, - color[0], - color[1], - color[2], - color[3], + cd.eotf, color[0], color[1], color[2], color[3], ); self.base.ops.push(GfxApiOpt::Sync); self.base @@ -522,7 +518,7 @@ impl Renderer<'_> { Rect::new_sized(x + pos.width() - bw, y + bw, bw, pos.height() - bw).unwrap(), Rect::new_sized(x + bw, y + pos.height() - bw, pos.width() - 2 * bw, bw).unwrap(), ]; - let srgb_srgb = self.state.color_manager.srgb_srgb(); + let srgb_srgb = self.state.color_manager.srgb_gamma22(); let srgb = &srgb_srgb.linear; self.base.fill_boxes(&borders, &bc, srgb); let title = [Rect::new_sized(x + bw, y + bw, pos.width() - 2 * bw, th).unwrap()]; diff --git a/src/screenshoter.rs b/src/screenshoter.rs index b89356c1..0585f098 100644 --- a/src/screenshoter.rs +++ b/src/screenshoter.rs @@ -79,7 +79,7 @@ pub fn take_screenshot( fb.render_node( AcquireSync::Unnecessary, ReleaseSync::Implicit, - state.color_manager.srgb_srgb(), + state.color_manager.srgb_gamma22(), state.root.deref(), state, Some(state.root.extents.get()), diff --git a/src/state.rs b/src/state.rs index e8a7850b..56cc29f8 100644 --- a/src/state.rs +++ b/src/state.rs @@ -476,9 +476,9 @@ impl ConnectorData { if old.format != s.format { self.head_managers.handle_format_change(s.format); } - if (old.color_space, old.transfer_function) != (s.color_space, s.transfer_function) { + if (old.color_space, old.eotf) != (s.color_space, s.eotf) { self.head_managers - .handle_colors_change(s.color_space, s.transfer_function); + .handle_colors_change(s.color_space, s.eotf); } if old.mode != s.mode { self.head_managers.handle_mode_change(s.mode); @@ -1290,7 +1290,7 @@ impl State { AcquireSync::Unnecessary, ReleaseSync::None, transform, - self.color_manager.srgb_srgb(), + self.color_manager.srgb_gamma22(), position, true, x_off - capture.rect.x1(), diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 2ca0f8d8..79d707eb 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -9,7 +9,7 @@ use { ifs::{ head_management::{HeadManagers, HeadState}, jay_tray_v1::JayTrayV1Global, - wl_output::{PersistentOutputState, WlOutputGlobal}, + wl_output::{BlendSpace, PersistentOutputState, WlOutputGlobal}, }, output_schedule::OutputSchedule, state::{ConnectorData, OutputData, State}, @@ -41,7 +41,7 @@ pub fn handle(state: &Rc, connector: &Rc) { tearing: false, format: XRGB8888, color_space: Default::default(), - transfer_function: Default::default(), + eotf: Default::default(), }; let id = connector.id(); let name = Rc::new(connector.kernel_id().to_string()); @@ -67,7 +67,7 @@ pub fn handle(state: &Rc, connector: &Rc) { tearing_mode: Default::default(), format: backend_state.format, color_space: backend_state.color_space, - transfer_function: backend_state.transfer_function, + eotf: backend_state.eotf, supported_formats: Default::default(), brightness: None, }; @@ -183,6 +183,7 @@ impl ConnectorHandler { vrr_cursor_hz: Cell::new(self.state.default_vrr_cursor_hz.get()), tearing_mode: Cell::new(self.state.default_tearing_mode.get()), brightness: Cell::new(None), + blend_space: Cell::new(BlendSpace::Srgb), }); self.state .persistent_output_states @@ -199,7 +200,7 @@ impl ConnectorHandler { info.height_mm, &output_id, &desired_state, - info.transfer_functions.clone(), + info.eotfs.clone(), info.color_spaces.clone(), info.primaries, info.luminance, diff --git a/src/text.rs b/src/text.rs index c9d99f82..70a8b38a 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1,6 +1,6 @@ use { crate::{ - cmm::cmm_transfer_function::TransferFunction, + cmm::cmm_eotf::Eotf, cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker, PendingJob}, format::ARGB8888, gfx_api::{ @@ -188,7 +188,7 @@ fn render( data.layout.set_text(text); } let font_height = data.layout.pixel_size().1; - let [r, g, b, a] = color.to_array(TransferFunction::Srgb); + let [r, g, b, a] = color.to_array(Eotf::Gamma22); data.cctx.set_operator(CAIRO_OPERATOR_SOURCE); data.cctx.set_source_rgba(r as _, g as _, b as _, a as _); let y = y.unwrap_or((height - font_height) / 2); diff --git a/src/theme.rs b/src/theme.rs index 3fba81b1..5cf3fa27 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,7 +1,7 @@ #![expect(clippy::excessive_precision)] use { - crate::{cmm::cmm_transfer_function::TransferFunction, utils::clonecell::CloneCell}, + crate::{cmm::cmm_eotf::Eotf, utils::clonecell::CloneCell}, num_traits::Float, std::{cell::Cell, cmp::Ordering, ops::Mul, sync::Arc}, }; @@ -68,14 +68,7 @@ impl Color { a: 1.0, }; - pub fn new(transfer_function: TransferFunction, mut r: f32, mut g: f32, mut b: f32) -> Self { - fn srgb(c: f32) -> f32 { - if c <= 0.04045 { - c / 12.92 - } else { - ((c + 0.055) / 1.055).powf(2.4) - } - } + pub fn new(eotf: Eotf, mut r: f32, mut g: f32, mut b: f32) -> Self { #[inline(always)] fn linear(c: f32) -> f32 { c @@ -86,23 +79,6 @@ impl Color { let den = 18.8515625 - 18.6875 * cp; (num / den).powf(1.0 / 0.1593017578125) } - fn ext_srgb(c: f32) -> f32 { - let c = c.clamp(-0.6038, 7.5913); - if c <= -0.0031308 { - -1.055 * (-c).powf(1.0 / 2.4) + 0.055 - } else if c <= 0.0031308 { - c * 12.92 - } else { - 1.055 * c.powf(1.0 / 2.4) - 0.055 - } - } - fn bt1886(c: f32) -> f32 { - if c < 0.081 { - c / 4.5 - } else { - ((c + 0.099) / 1.099).powf(1.0 / 0.45) - } - } fn st240(c: f32) -> f32 { if c < 0.0913 { c / 4.0 @@ -120,10 +96,13 @@ impl Color { c.powf(2.6) * 52.37 / 48.0 } fn gamma22(c: f32) -> f32 { - c.powf(2.2) + c.signum() * c.abs().powf(2.2) + } + fn gamma24(c: f32) -> f32 { + c.signum() * c.abs().powf(2.4) } fn gamma28(c: f32) -> f32 { - c.powf(2.8) + c.signum() * c.abs().powf(2.8) } macro_rules! convert { ($tf:ident) => {{ @@ -132,30 +111,22 @@ impl Color { b = $tf(b); }}; } - match transfer_function { - TransferFunction::Srgb => convert!(srgb), - TransferFunction::Linear => convert!(linear), - TransferFunction::St2084Pq => convert!(st2084_pq), - TransferFunction::Bt1886 => convert!(bt1886), - TransferFunction::Gamma22 => convert!(gamma22), - TransferFunction::Gamma28 => convert!(gamma28), - TransferFunction::St240 => convert!(st240), - TransferFunction::ExtSrgb => convert!(ext_srgb), - TransferFunction::Log100 => convert!(log100), - TransferFunction::Log316 => convert!(log316), - TransferFunction::St428 => convert!(st428), + match eotf { + Eotf::Linear => convert!(linear), + Eotf::St2084Pq => convert!(st2084_pq), + Eotf::Bt1886 => convert!(gamma24), + Eotf::Gamma22 => convert!(gamma22), + Eotf::Gamma28 => convert!(gamma28), + Eotf::St240 => convert!(st240), + Eotf::Log100 => convert!(log100), + Eotf::Log316 => convert!(log316), + Eotf::St428 => convert!(st428), } Self { r, g, b, a: 1.0 } } - pub fn new_premultiplied( - transfer_function: TransferFunction, - mut r: f32, - mut g: f32, - mut b: f32, - a: f32, - ) -> Self { - if transfer_function == TransferFunction::Linear { + pub fn new_premultiplied(eotf: Eotf, mut r: f32, mut g: f32, mut b: f32, a: f32) -> Self { + if eotf == Eotf::Linear { return Self { r, g, b, a }; } if a < 1.0 && a > 0.0 { @@ -163,7 +134,7 @@ impl Color { *c /= a; } } - let mut c = Self::new(transfer_function, r, g, b); + let mut c = Self::new(eotf, r, g, b); if a < 1.0 { c = c * a; } @@ -179,40 +150,22 @@ impl Color { } pub fn from_srgb(r: u8, g: u8, b: u8) -> Self { - Self::new(TransferFunction::Srgb, to_f32(r), to_f32(g), to_f32(b)) + Self::new(Eotf::Gamma22, to_f32(r), to_f32(g), to_f32(b)) } pub fn from_srgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self { - Self::new_premultiplied( - TransferFunction::Srgb, - to_f32(r), - to_f32(g), - to_f32(b), - to_f32(a), - ) + Self::new_premultiplied(Eotf::Gamma22, to_f32(r), to_f32(g), to_f32(b), to_f32(a)) } - pub fn from_u32_premultiplied( - transfer_function: TransferFunction, - r: u32, - g: u32, - b: u32, - a: u32, - ) -> Self { + pub fn from_u32_premultiplied(eotf: Eotf, r: u32, g: u32, b: u32, a: u32) -> Self { fn to_f32(c: u32) -> f32 { ((c as f64) / (u32::MAX as f64)) as f32 } - Self::new_premultiplied( - transfer_function, - to_f32(r), - to_f32(g), - to_f32(b), - to_f32(a), - ) + Self::new_premultiplied(eotf, to_f32(r), to_f32(g), to_f32(b), to_f32(a)) } pub fn from_srgba_straight(r: u8, g: u8, b: u8, a: u8) -> Self { - let mut c = Self::new(TransferFunction::Srgb, to_f32(r), to_f32(g), to_f32(b)); + let mut c = Self::new(Eotf::Gamma22, to_f32(r), to_f32(g), to_f32(b)); if a < 255 { c = c * to_f32(a); } @@ -220,23 +173,16 @@ impl Color { } pub fn to_srgba_premultiplied(self) -> [u8; 4] { - let [r, g, b, a] = self.to_array(TransferFunction::Srgb); + let [r, g, b, a] = self.to_array(Eotf::Gamma22); [to_u8(r), to_u8(g), to_u8(b), to_u8(a)] } - pub fn to_array(self, transfer_function: TransferFunction) -> [f32; 4] { - self.to_array2(transfer_function, None) + pub fn to_array(self, eotf: Eotf) -> [f32; 4] { + self.to_array2(eotf, None) } - pub fn to_array2(self, transfer_function: TransferFunction, alpha: Option) -> [f32; 4] { + pub fn to_array2(self, eotf: Eotf, alpha: Option) -> [f32; 4] { let mut res = [self.r, self.g, self.b, self.a]; - fn srgb(c: f32) -> f32 { - if c <= 0.0031308 { - c * 12.92 - } else { - 1.055 * c.powf(1.0 / 2.4) - 0.055 - } - } fn linear(c: f32) -> f32 { c } @@ -246,22 +192,6 @@ impl Color { let den = 1.0 + 18.6875 * c.powf(0.1593017578125); (num / den).powf(78.84375) } - fn ext_srgb(c: f32) -> f32 { - if c < -0.04045 { - -((c - 0.055) / -1.055).powf(2.4) - } else if c < 0.04045 { - c / 12.92 - } else { - ((c + 0.055) / 1.055).powf(2.4) - } - } - fn bt1886(c: f32) -> f32 { - if c < 0.018 { - 4.5 * c - } else { - 1.099 * c.powf(0.45) - 0.099 - } - } fn st240(c: f32) -> f32 { if c < 0.0228 { 4.0 * c @@ -285,10 +215,13 @@ impl Color { (48.0 * c / 52.37).powf(1.0 / 2.6) } fn gamma22(c: f32) -> f32 { - c.powf(1.0 / 2.2) + c.signum() * c.abs().powf(1.0 / 2.2) + } + fn gamma24(c: f32) -> f32 { + c.signum() * c.abs().powf(1.0 / 2.4) } fn gamma28(c: f32) -> f32 { - c.powf(1.0 / 2.8) + c.signum() * c.abs().powf(1.0 / 2.8) } macro_rules! convert { ($tf:ident) => {{ @@ -297,24 +230,22 @@ impl Color { } }}; } - if transfer_function != TransferFunction::Linear { + if eotf != Eotf::Linear { if self.a < 1.0 && self.a > 0.0 { for c in &mut res[..3] { *c /= self.a; } } - match transfer_function { - TransferFunction::Srgb => convert!(srgb), - TransferFunction::Linear => convert!(linear), - TransferFunction::St2084Pq => convert!(st2084_pq), - TransferFunction::Bt1886 => convert!(bt1886), - TransferFunction::Gamma22 => convert!(gamma22), - TransferFunction::Gamma28 => convert!(gamma28), - TransferFunction::St240 => convert!(st240), - TransferFunction::ExtSrgb => convert!(ext_srgb), - TransferFunction::Log100 => convert!(log100), - TransferFunction::Log316 => convert!(log316), - TransferFunction::St428 => convert!(st428), + match eotf { + Eotf::Linear => convert!(linear), + Eotf::St2084Pq => convert!(st2084_pq), + Eotf::Bt1886 => convert!(gamma24), + Eotf::Gamma22 => convert!(gamma22), + Eotf::Gamma28 => convert!(gamma28), + Eotf::St240 => convert!(st240), + Eotf::Log100 => convert!(log100), + Eotf::Log316 => convert!(log316), + Eotf::St428 => convert!(st428), } if self.a < 1.0 { for c in &mut res[..3] { @@ -343,7 +274,7 @@ impl Color { impl From for Color { fn from(f: jay_config::theme::Color) -> Self { let [r, g, b, a] = f.to_f32_premultiplied(); - Self::new_premultiplied(TransferFunction::Srgb, r, g, b, a) + Self::new_premultiplied(Eotf::Gamma22, r, g, b, a) } } diff --git a/src/tools/tool_client.rs b/src/tools/tool_client.rs index 0014d743..82f013ba 100644 --- a/src/tools/tool_client.rs +++ b/src/tools/tool_client.rs @@ -335,7 +335,7 @@ impl ToolClient { self_id: s.registry, name: s.jay_compositor.0, interface: JayCompositor.name(), - version: s.jay_compositor.1.min(20), + version: s.jay_compositor.1.min(21), id: id.into(), }); self.jay_compositor.set(Some(id)); diff --git a/src/tree/output.rs b/src/tree/output.rs index c0f49100..1df18b71 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -1,8 +1,7 @@ use { crate::{ backend::{ - BackendColorSpace, BackendConnectorState, BackendTransferFunction, HardwareCursor, - KeyState, Mode, + BackendColorSpace, BackendConnectorState, BackendEotfs, HardwareCursor, KeyState, Mode, }, client::ClientId, cmm::cmm_description::ColorDescription, @@ -14,7 +13,7 @@ use { jay_output::JayOutput, jay_screencast::JayScreencast, wl_buffer::WlBufferStorage, - wl_output::WlOutputGlobal, + wl_output::{BlendSpace, WlOutputGlobal}, wl_seat::{ BTN_LEFT, NodeSeatState, SeatId, WlSeatGlobal, collect_kb_foci2, tablet::{TabletTool, TabletToolChanges, TabletToolId}, @@ -415,7 +414,7 @@ impl OutputNode { AcquireSync::Implicit, ReleaseSync::Implicit, self.global.persistent.transform.get(), - self.state.color_manager.srgb_srgb(), + self.state.color_manager.srgb_gamma22(), self.global.pos.get(), render_hardware_cursors, x_off - capture.rect.x1(), @@ -928,7 +927,7 @@ impl OutputNode { } pub fn update_state(self: &Rc, old: BackendConnectorState, state: BackendConnectorState) { - self.update_btf_and_bcs(state.transfer_function, state.color_space); + self.update_btf_and_bcs(state.eotf, state.color_space); if old.vrr != state.vrr { self.schedule.set_vrr_enabled(state.vrr); } @@ -938,7 +937,7 @@ impl OutputNode { self.global.format.set(state.format); } - fn update_btf_and_bcs(&self, btf: BackendTransferFunction, bcs: BackendColorSpace) { + fn update_btf_and_bcs(&self, btf: BackendEotfs, bcs: BackendColorSpace) { let old_btf = self.global.btf.replace(btf); let old_bcs = self.global.bcs.replace(bcs); if (old_btf, old_bcs) == (btf, bcs) { @@ -972,6 +971,12 @@ impl OutputNode { } } + pub fn set_blend_space(&self, blend_space: BlendSpace) { + let old = self.global.persistent.blend_space.replace(blend_space); + if old != blend_space { + self.state.damage(self.global.position()); + } + } fn find_stacked_at( &self, stack: &LinkedList>, diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index bbdc25e0..9aaca933 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -33,7 +33,7 @@ use { logging::LogLevel, status::MessageFormat, theme::Color, - video::{ColorSpace, Format, GfxApi, TearingMode, TransferFunction, Transform, VrrMode}, + video::{BlendSpace, ColorSpace, Eotf, Format, GfxApi, TearingMode, Transform, VrrMode}, window::{ContentType, TileState, WindowType}, workspace::WorkspaceDisplayOrder, xwayland::XScalingMode, @@ -347,8 +347,9 @@ pub struct Output { pub tearing: Option, pub format: Option, pub color_space: Option, - pub transfer_function: Option, + pub eotf: Option, pub brightness: Option>, + pub blend_space: Option, } #[derive(Debug, Clone)] diff --git a/toml-config/src/config/parsers/output.rs b/toml-config/src/config/parsers/output.rs index d0baef16..b39cc4f8 100644 --- a/toml-config/src/config/parsers/output.rs +++ b/toml-config/src/config/parsers/output.rs @@ -19,7 +19,7 @@ use { }, }, indexmap::IndexMap, - jay_config::video::{ColorSpace, TransferFunction, Transform}, + jay_config::video::{BlendSpace, ColorSpace, Eotf, Transform}, thiserror::Error, }; @@ -51,7 +51,7 @@ impl Parser for OutputParser<'_> { let mut ext = Extractor::new(self.cx, span, table); let ( (name, match_val, x, y, scale, transform, mode, vrr_val, tearing_val, format_val), - (color_space, transfer_function, brightness_val), + (color_space, eotf, brightness_val, blend_space), ) = ext.extract(( ( opt(str("name")), @@ -69,6 +69,7 @@ impl Parser for OutputParser<'_> { recover(opt(str("color-space"))), recover(opt(str("transfer-function"))), opt(val("brightness")), + recover(opt(str("blend-space"))), ), ))?; let transform = match transform { @@ -103,17 +104,13 @@ impl Parser for OutputParser<'_> { } }, }; - let transfer_function = match transfer_function { + let eotf = match eotf { None => None, Some(tf) => match tf.value { - "default" => Some(TransferFunction::DEFAULT), - "pq" => Some(TransferFunction::PQ), + "default" => Some(Eotf::DEFAULT), + "pq" => Some(Eotf::PQ), _ => { - log::warn!( - "Unknown transfer function {}: {}", - tf.value, - self.cx.error3(tf.span) - ); + log::warn!("Unknown EOTF {}: {}", tf.value, self.cx.error3(tf.span)); None } }, @@ -181,6 +178,21 @@ impl Parser for OutputParser<'_> { } } } + let blend_space = match blend_space { + None => None, + Some(bs) => match bs.value { + "linear" => Some(BlendSpace::LINEAR), + "srgb" => Some(BlendSpace::SRGB), + _ => { + log::warn!( + "Unknown blend space {}: {}", + bs.value, + self.cx.error3(bs.span) + ); + None + } + }, + }; Ok(Output { name: name.despan().map(|v| v.to_string()), match_: match_val.parse_map(&mut OutputMatchParser(self.cx))?, @@ -193,8 +205,9 @@ impl Parser for OutputParser<'_> { tearing, format, color_space, - transfer_function, + eotf, brightness, + blend_space, }) } } diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index 8c4b6413..cec684db 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -44,7 +44,7 @@ use { theme::{reset_colors, reset_font, reset_sizes, set_font}, toggle_float_above_fullscreen, toggle_show_bar, video::{ - ColorSpace, Connector, DrmDevice, TransferFunction, connectors, drm_devices, + ColorSpace, Connector, DrmDevice, Eotf, connectors, drm_devices, on_connector_connected, on_connector_disconnected, on_graphics_initialized, on_new_connector, on_new_drm_device, set_direct_scanout_enabled, set_gfx_api, set_tearing_mode, set_vrr_cursor_hz, set_vrr_mode, @@ -769,14 +769,17 @@ impl Output { if let Some(format) = self.format { c.set_format(format); } - if self.color_space.is_some() || self.transfer_function.is_some() { + if self.color_space.is_some() || self.eotf.is_some() { let cs = self.color_space.unwrap_or(ColorSpace::DEFAULT); - let tf = self.transfer_function.unwrap_or(TransferFunction::DEFAULT); + let tf = self.eotf.unwrap_or(Eotf::DEFAULT); c.set_colors(cs, tf); } if let Some(brightness) = self.brightness { c.set_brightness(brightness); } + if let Some(bs) = self.blend_space { + c.set_blend_space(bs); + } } } diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index 10f6704a..174b9f84 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -572,6 +572,14 @@ } ] }, + "BlendSpace": { + "type": "string", + "description": "A color blend space.\n", + "enum": [ + "srgb", + "linear" + ] + }, "Brightness": { "description": "The brightness setting of an output.\n", "anyOf": [ @@ -1141,6 +1149,14 @@ } ] }, + "Eotf": { + "type": "string", + "description": "The EOTF of an output.\n", + "enum": [ + "default", + "pq" + ] + }, "Exec": { "description": "Describes how to execute a program.\n\n- Example 1:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = \"alacritty\" }\n ```\n\n- Example 2:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = [\"notify-send\", \"hello world\"] }\n ```\n\n- Example 3:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = { prog = \"notify-send\", args = [\"hello world\"], env.WAYLAND_DISPLAY = \"2\" } }\n ```\n", "anyOf": [ @@ -1641,12 +1657,16 @@ "$ref": "#/$defs/ColorSpace" }, "transfer-function": { - "description": "The transfer function of the output.\n", - "$ref": "#/$defs/TransferFunction" + "description": "The EOTF of the output.\n", + "$ref": "#/$defs/Eotf" }, "brightness": { "description": "The brightness of the output.\n\nThis setting has no effect unless the vulkan renderer is used.\n", "$ref": "#/$defs/Brightness" + }, + "blend-space": { + "description": "The blend space of the output.\n\nThe default is `srgb`.\n", + "$ref": "#/$defs/BlendSpace" } }, "required": [ @@ -1902,14 +1922,6 @@ "floating" ] }, - "TransferFunction": { - "type": "string", - "description": "The transfer function of an output.\n", - "enum": [ - "default", - "pq" - ] - }, "Transform": { "type": "string", "description": "An output transformation.", diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index 92b162bf..4f6893ea 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -797,6 +797,25 @@ This table is a tagged union. The variant is determined by the `type` field. It The value of this field should be a string. + +### `BlendSpace` + +A color blend space. + +Values of this type should be strings. + +The string should have one of the following values: + +- `srgb`: + + The sRGB blend space. This is the classic desktop blend space. + +- `linear`: + + Linear color space. This is the physically correct blend space. + + + ### `Brightness` @@ -814,7 +833,7 @@ The string should have one of the following values: The default brightness setting. - The behavior depends on the transfer function: + The behavior depends on the EOTF: - `default`: The maximum brightness of the output. - `PQ`: 203 cd/m^2 @@ -2303,6 +2322,25 @@ The table has the following fields: The numbers should be integers. + +### `Eotf` + +The EOTF of an output. + +Values of this type should be strings. + +The string should have one of the following values: + +- `default`: + + The default EOTF (usually gamma22). + +- `pq`: + + The PQ EOTF. + + + ### `Exec` @@ -3517,9 +3555,9 @@ The table has the following fields: - `transfer-function` (optional): - The transfer function of the output. + The EOTF of the output. - The value of this field should be a [TransferFunction](#types-TransferFunction). + The value of this field should be a [Eotf](#types-Eotf). - `brightness` (optional): @@ -3529,6 +3567,14 @@ The table has the following fields: The value of this field should be a [Brightness](#types-Brightness). +- `blend-space` (optional): + + The blend space of the output. + + The default is `srgb`. + + The value of this field should be a [BlendSpace](#types-BlendSpace). + ### `OutputMatch` @@ -4215,25 +4261,6 @@ The string should have one of the following values: - -### `TransferFunction` - -The transfer function of an output. - -Values of this type should be strings. - -The string should have one of the following values: - -- `default`: - - The default transfer function (usually sRGB). - -- `pq`: - - The PQ transfer function. - - - ### `Transform` diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index 558badb9..b9239cca 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -1956,10 +1956,10 @@ Output: description: | The color space of the output. transfer-function: - ref: TransferFunction + ref: Eotf required: false description: | - The transfer function of the output. + The EOTF of the output. brightness: ref: Brightness required: false @@ -1967,6 +1967,13 @@ Output: The brightness of the output. This setting has no effect unless the vulkan renderer is used. + blend-space: + ref: BlendSpace + required: false + description: | + The blend space of the output. + + The default is `srgb`. Transform: @@ -3293,15 +3300,15 @@ ColorSpace: description: The BT.2020 color space. -TransferFunction: +Eotf: description: | - The transfer function of an output. + The EOTF of an output. kind: string values: - value: default - description: The default transfer function (usually sRGB). + description: The default EOTF (usually gamma22). - value: pq - description: The PQ transfer function. + description: The PQ EOTF. Brightness: @@ -3317,7 +3324,7 @@ Brightness: description: | The default brightness setting. - The behavior depends on the transfer function: + The behavior depends on the EOTF: - `default`: The maximum brightness of the output. - `PQ`: 203 cd/m^2 @@ -4029,3 +4036,14 @@ WorkspaceDisplayOrder: description: Workspaces are not sorted and can be manually dragged. - value: sorted description: Workspaces are sorted alphabetically and cannot be manually dragged. + + +BlendSpace: + kind: string + description: | + A color blend space. + values: + - value: srgb + description: The sRGB blend space. This is the classic desktop blend space. + - value: linear + description: Linear color space. This is the physically correct blend space. diff --git a/wire/jay_randr.txt b/wire/jay_randr.txt index a318047d..5450dfaf 100644 --- a/wire/jay_randr.txt +++ b/wire/jay_randr.txt @@ -83,7 +83,7 @@ request set_flip_margin (since = 10) { request set_colors (since = 15) { output: str, color_space: str, - transfer_function: str, + eotf: str, } request set_brightness (since = 16) { @@ -95,6 +95,11 @@ request unset_brightness (since = 16) { output: str, } +request set_blend_space (since = 21) { + output: str, + blend_space: str, +} + # events event global { @@ -184,12 +189,12 @@ event current_color_space (since = 15) { color_space: str, } -event supported_transfer_function (since = 15) { - transfer_function: str, +event supported_eotf (since = 15) { + eotf: str, } -event current_transfer_function (since = 15) { - transfer_function: str, +event current_eotf (since = 15) { + eotf: str, } event brightness_range (since = 16) { @@ -201,3 +206,7 @@ event brightness_range (since = 16) { event brightness (since = 16) { lux: pod(f64), } + +event blend_space (since = 21) { + blend_space: str, +}