1
0
Fork 0
forked from wry/wry

Merge pull request #593 from mahkoh/jorth/color-management-fixes

Various color management fixes
This commit is contained in:
mahkoh 2025-09-05 21:20:06 +02:00 committed by GitHub
commit 0e51b9469b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
75 changed files with 893 additions and 784 deletions

View file

@ -28,7 +28,7 @@ use {
theme::{Color, colors::Colorable, sized::Resizable}, theme::{Color, colors::Colorable, sized::Resizable},
timer::Timer, timer::Timer,
video::{ video::{
ColorSpace, Connector, DrmDevice, Format, GfxApi, Mode, TearingMode, TransferFunction, BlendSpace, ColorSpace, Connector, DrmDevice, Eotf, Format, GfxApi, Mode, TearingMode,
Transform, VrrMode, Transform, VrrMode,
connector_type::{CON_UNKNOWN, ConnectorType}, connector_type::{CON_UNKNOWN, ConnectorType},
}, },
@ -1042,16 +1042,18 @@ impl ConfigClient {
self.send(&ClientMessage::ConnectorSetFormat { connector, format }); self.send(&ClientMessage::ConnectorSetFormat { connector, format });
} }
pub fn connector_set_colors( pub fn connector_set_colors(&self, connector: Connector, color_space: ColorSpace, eotf: Eotf) {
&self,
connector: Connector,
color_space: ColorSpace,
transfer_function: TransferFunction,
) {
self.send(&ClientMessage::ConnectorSetColors { self.send(&ClientMessage::ConnectorSetColors {
connector, connector,
color_space, color_space,
transfer_function, eotf,
});
}
pub fn connector_set_blend_space(&self, connector: Connector, blend_space: BlendSpace) {
self.send(&ClientMessage::ConnectorSetBlendSpace {
connector,
blend_space,
}); });
} }

View file

@ -12,7 +12,7 @@ use {
theme::{Color, colors::Colorable, sized::Resizable}, theme::{Color, colors::Colorable, sized::Resizable},
timer::Timer, timer::Timer,
video::{ video::{
ColorSpace, Connector, DrmDevice, Format, GfxApi, TearingMode, TransferFunction, BlendSpace, ColorSpace, Connector, DrmDevice, Eotf, Format, GfxApi, TearingMode,
Transform, VrrMode, connector_type::ConnectorType, Transform, VrrMode, connector_type::ConnectorType,
}, },
window::{ContentType, TileState, Window, WindowMatcher, WindowType}, window::{ContentType, TileState, Window, WindowMatcher, WindowType},
@ -555,7 +555,7 @@ pub enum ClientMessage<'a> {
ConnectorSetColors { ConnectorSetColors {
connector: Connector, connector: Connector,
color_space: ColorSpace, color_space: ColorSpace,
transfer_function: TransferFunction, eotf: Eotf,
}, },
ConnectorSetBrightness { ConnectorSetBrightness {
connector: Connector, connector: Connector,
@ -764,6 +764,10 @@ pub enum ClientMessage<'a> {
SetWorkspaceDisplayOrder { SetWorkspaceDisplayOrder {
order: WorkspaceDisplayOrder, order: WorkspaceDisplayOrder,
}, },
ConnectorSetBlendSpace {
connector: Connector,
blend_space: BlendSpace,
},
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]

View file

@ -273,26 +273,33 @@ impl Connector {
get!().connector_set_format(self, format); 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 /// 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 /// 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. /// Note that some displays might ignore incompatible settings.
pub fn set_colors(self, color_space: ColorSpace, transfer_function: TransferFunction) { pub fn set_colors(self, color_space: ColorSpace, eotf: Eotf) {
get!().connector_set_colors(self, color_space, transfer_function); 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. /// Sets the brightness of the output.
/// ///
/// By default or when `brightness` is `None`, the brightness depends on the /// By default or when `brightness` is `None`, the brightness depends on the
/// transfer function: /// EOTF:
/// ///
/// - [`TransferFunction::DEFAULT`]: The maximum brightness of the output. /// - [`Eotf::DEFAULT`]: The maximum brightness of the output.
/// - [`TransferFunction::PQ`]: 203 cd/m^2. /// - [`Eotf::PQ`]: 203 cd/m^2.
/// ///
/// This should only be used with the PQ transfer function. If the default transfer /// This should only be used with the PQ transfer function. If the default transfer
/// function is used, you should instead calibrate the hardware directly. /// function is used, you should instead calibrate the hardware directly.
@ -718,13 +725,29 @@ impl ColorSpace {
pub const BT2020: Self = Self(1); pub const BT2020: Self = Self(1);
} }
/// A transfer function. /// An electro-optical transfer function (EOTF).
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)] #[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct TransferFunction(pub u32); pub struct Eotf(pub u32);
impl TransferFunction { #[deprecated = "use the Eotf type instead"]
/// The default transfer function (usually sRGB). pub type TransferFunction = Eotf;
impl Eotf {
/// The default EOTF (usually gamma22).
pub const DEFAULT: Self = Self(0); pub const DEFAULT: Self = Self(0);
/// The PQ transfer function. /// The PQ EOTF.
pub const PQ: Self = Self(1); 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);
}

View file

@ -106,7 +106,7 @@ pub struct MonitorInfo {
pub non_desktop: bool, pub non_desktop: bool,
pub non_desktop_effective: bool, pub non_desktop_effective: bool,
pub vrr_capable: bool, pub vrr_capable: bool,
pub transfer_functions: Vec<BackendTransferFunction>, pub eotfs: Vec<BackendEotfs>,
pub color_spaces: Vec<BackendColorSpace>, pub color_spaces: Vec<BackendColorSpace>,
pub primaries: Primaries, pub primaries: Primaries,
pub luminance: Option<BackendLuminance>, pub luminance: Option<BackendLuminance>,
@ -540,7 +540,7 @@ pub trait BackendDrmLessee {
} }
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Linearize)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Linearize)]
pub enum BackendTransferFunction { pub enum BackendEotfs {
#[default] #[default]
Default, Default,
Pq, Pq,
@ -560,18 +560,18 @@ pub struct BackendLuminance {
pub max_fall: f64, pub max_fall: f64,
} }
impl BackendTransferFunction { impl BackendEotfs {
pub fn to_drm(self) -> u8 { pub fn to_drm(self) -> u8 {
match self { match self {
BackendTransferFunction::Default => HDMI_EOTF_TRADITIONAL_GAMMA_SDR, BackendEotfs::Default => HDMI_EOTF_TRADITIONAL_GAMMA_SDR,
BackendTransferFunction::Pq => HDMI_EOTF_SMPTE_ST2084, BackendEotfs::Pq => HDMI_EOTF_SMPTE_ST2084,
} }
} }
pub const fn name(self) -> &'static str { pub const fn name(self) -> &'static str {
match self { match self {
BackendTransferFunction::Default => "default", BackendEotfs::Default => "default",
BackendTransferFunction::Pq => "pq", BackendEotfs::Pq => "pq",
} }
} }
} }
@ -609,5 +609,5 @@ pub struct BackendConnectorState {
pub tearing: bool, pub tearing: bool,
pub format: &'static Format, pub format: &'static Format,
pub color_space: BackendColorSpace, pub color_space: BackendColorSpace,
pub transfer_function: BackendTransferFunction, pub eotf: BackendEotfs,
} }

View file

@ -1,8 +1,8 @@
use { use {
crate::{ crate::{
backend::{ backend::{
BackendColorSpace, BackendConnectorState, BackendTransferFunction, Connector, BackendColorSpace, BackendConnectorState, BackendEotfs, Connector, ConnectorId,
ConnectorId, ConnectorKernelId, Mode, ConnectorKernelId, Mode,
}, },
backends::metal::MetalError, backends::metal::MetalError,
state::State, state::State,
@ -112,8 +112,8 @@ pub enum BackendConnectorTransactionError {
TearingNotSupported(ConnectorKernelId), TearingNotSupported(ConnectorKernelId),
#[error("Connector {} does not support color space {:?}", .0, .1)] #[error("Connector {} does not support color space {:?}", .0, .1)]
ColorSpaceNotSupported(ConnectorKernelId, BackendColorSpace), ColorSpaceNotSupported(ConnectorKernelId, BackendColorSpace),
#[error("Connector {} does not support transfer function {:?}", .0, .1)] #[error("Connector {} does not support EOTF {:?}", .0, .1)]
TransferFunctionNotSupported(ConnectorKernelId, BackendTransferFunction), EotfNotSupported(ConnectorKernelId, BackendEotfs),
#[error("Could not create an hdr metadata blob")] #[error("Could not create an hdr metadata blob")]
CreateHdrMetadataBlob(#[source] DrmError), CreateHdrMetadataBlob(#[source] DrmError),
#[error("Could not create a mode blob")] #[error("Could not create a mode blob")]

View file

@ -13,6 +13,7 @@ use {
AcquireSync, BufferResv, GfxApiOpt, GfxRenderPass, GfxTexture, ReleaseSync, SyncFile, AcquireSync, BufferResv, GfxApiOpt, GfxRenderPass, GfxTexture, ReleaseSync, SyncFile,
create_render_pass, create_render_pass,
}, },
ifs::wl_output::BlendSpace,
rect::Region, rect::Region,
theme::Color, theme::Color,
time::Time, time::Time,
@ -202,6 +203,10 @@ impl MetalConnector {
let cd = node.global.color_description.get(); let cd = node.global.color_description.get();
let linear_cd = node.global.linear_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() { if self.has_damage.get() > 0 || self.cursor_damage.get() {
node.schedule.commit_cursor(); node.schedule.commit_cursor();
@ -218,7 +223,7 @@ impl MetalConnector {
let mut present_fb = None; let mut present_fb = None;
let mut direct_scanout_id = None; let mut direct_scanout_id = None;
if let Some(latched) = &latched { 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); direct_scanout_id = fb.direct_scanout_data.as_ref().map(|d| d.dma_buf_id);
present_fb = Some(fb); present_fb = Some(fb);
} }
@ -247,7 +252,7 @@ impl MetalConnector {
{ {
let fb = self.prepare_present_fb( let fb = self.prepare_present_fb(
&cd, &cd,
&linear_cd, blend_cd,
buffer, buffer,
&plane, &plane,
latched.as_ref().unwrap(), latched.as_ref().unwrap(),
@ -621,6 +626,7 @@ impl MetalConnector {
&self, &self,
pass: &GfxRenderPass, pass: &GfxRenderPass,
plane: &Rc<MetalPlane>, plane: &Rc<MetalPlane>,
blend_cd: &Rc<ColorDescription>,
cd: &Rc<ColorDescription>, cd: &Rc<ColorDescription>,
) -> Option<DirectScanoutData> { ) -> Option<DirectScanoutData> {
let ct = 'ct: { let ct = 'ct: {
@ -642,6 +648,10 @@ impl MetalConnector {
// Direct scanout requires embeddable color descriptions. // Direct scanout requires embeddable color descriptions.
return None; 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() { if ct.alpha.is_some() {
// Direct scanout with alpha factor is not supported. // Direct scanout with alpha factor is not supported.
return None; return None;
@ -796,7 +806,7 @@ impl MetalConnector {
fn prepare_present_fb( fn prepare_present_fb(
&self, &self,
cd: &Rc<ColorDescription>, cd: &Rc<ColorDescription>,
linear_cd: &Rc<ColorDescription>, blend_cd: &Rc<ColorDescription>,
buffer: &RenderBuffer, buffer: &RenderBuffer,
plane: &Rc<MetalPlane>, plane: &Rc<MetalPlane>,
latched: &Latched, latched: &Latched,
@ -813,7 +823,7 @@ impl MetalConnector {
&& self.dev.is_render_device(); && self.dev.is_render_device();
let mut direct_scanout_data = None; let mut direct_scanout_data = None;
if try_direct_scanout { 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(); let direct_scanout_active = direct_scanout_data.is_some();
if self.direct_scanout_active.replace(direct_scanout_active) != direct_scanout_active { if self.direct_scanout_active.replace(direct_scanout_active) != direct_scanout_active {
@ -837,7 +847,7 @@ impl MetalConnector {
&latched.pass, &latched.pass,
&latched.damage, &latched.damage,
buffer.blend_buffer.as_ref(), buffer.blend_buffer.as_ref(),
linear_cd, blend_cd,
) )
.map_err(MetalError::RenderFrame)?; .map_err(MetalError::RenderFrame)?;
sync_file = buffer.copy_to_dev(cd, sf)?; sync_file = buffer.copy_to_dev(cd, sf)?;

View file

@ -2,8 +2,7 @@ use {
crate::{ crate::{
allocator::BufferObject, allocator::BufferObject,
backend::{ backend::{
BackendColorSpace, BackendConnectorState, BackendTransferFunction, Connector, BackendColorSpace, BackendConnectorState, BackendEotfs, Connector, ConnectorEvent,
ConnectorEvent,
transaction::{ transaction::{
BackendAppliedConnectorTransaction, BackendConnectorTransaction, BackendAppliedConnectorTransaction, BackendConnectorTransaction,
BackendConnectorTransactionError, BackendPreparedConnectorTransaction, BackendConnectorTransactionError, BackendPreparedConnectorTransaction,
@ -669,16 +668,14 @@ impl MetalDeviceTransaction {
} }
} }
} }
match state.transfer_function { match state.eotf {
BackendTransferFunction::Default => {} BackendEotfs::Default => {}
BackendTransferFunction::Pq => { BackendEotfs::Pq => {
if !dd.supports_pq { if !dd.supports_pq {
return Err( return Err(BackendConnectorTransactionError::EotfNotSupported(
BackendConnectorTransactionError::TransferFunctionNotSupported( connector.obj.kernel_id(),
connector.obj.kernel_id(), state.eotf,
state.transfer_function, ));
),
);
} }
} }
} }
@ -686,12 +683,10 @@ impl MetalDeviceTransaction {
*cs = state.color_space.to_drm(); *cs = state.color_space.to_drm();
} }
if dd.hdr_metadata.is_some() { if dd.hdr_metadata.is_some() {
let new = if state.transfer_function == BackendTransferFunction::Default { let new = if state.eotf == BackendEotfs::Default {
None None
} else { } else {
Some(hdr_output_metadata::from_eotf( Some(hdr_output_metadata::from_eotf(state.eotf.to_drm()))
state.transfer_function.to_drm(),
))
}; };
if connector.new.hdr_metadata != new { if connector.new.hdr_metadata != new {
if let Some(new) = &new { if let Some(new) = &new {

View file

@ -4,10 +4,10 @@ use {
async_engine::{Phase, SpawnedFuture}, async_engine::{Phase, SpawnedFuture},
backend::{ backend::{
BackendColorSpace, BackendConnectorState, BackendDrmDevice, BackendDrmLease, BackendColorSpace, BackendConnectorState, BackendDrmDevice, BackendDrmLease,
BackendDrmLessee, BackendEvent, BackendLuminance, BackendTransferFunction, BackendDrmLessee, BackendEotfs, BackendEvent, BackendLuminance, CONCAP_CONNECTOR,
CONCAP_CONNECTOR, CONCAP_MODE_SETTING, CONCAP_PHYSICAL_DISPLAY, Connector, CONCAP_MODE_SETTING, CONCAP_PHYSICAL_DISPLAY, Connector, ConnectorCaps, ConnectorEvent,
ConnectorCaps, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, ConnectorId, ConnectorKernelId, DrmDeviceId, HardwareCursor, HardwareCursorUpdate,
HardwareCursor, HardwareCursorUpdate, Mode, MonitorInfo, Mode, MonitorInfo,
transaction::{ transaction::{
BackendConnectorTransaction, BackendConnectorTransactionError, BackendConnectorTransaction, BackendConnectorTransactionError,
BackendConnectorTransactionType, BackendConnectorTransactionTypeDyn, BackendConnectorTransactionType, BackendConnectorTransactionTypeDyn,
@ -1092,7 +1092,7 @@ fn create_connector(
backend: backend.clone(), backend: backend.clone(),
connector_id: backend.state.connector_ids.next(), connector_id: backend.state.connector_ids.next(),
buffers: Default::default(), 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), lease: Cell::new(None),
buffers_idle: Cell::new(true), buffers_idle: Cell::new(true),
crtc_idle: Cell::new(true), crtc_idle: Cell::new(true),
@ -1315,7 +1315,7 @@ fn create_connector_display_data(
tearing: false, tearing: false,
format: XRGB8888, format: XRGB8888,
color_space: Default::default(), color_space: Default::default(),
transfer_function: Default::default(), eotf: Default::default(),
}), }),
}); });
dev.backend dev.backend
@ -1341,13 +1341,13 @@ fn create_connector_display_data(
Err(_) => false, Err(_) => false,
}; };
{ {
let viable = match desired_state.transfer_function { let viable = match desired_state.eotf {
BackendTransferFunction::Default => true, BackendEotfs::Default => true,
BackendTransferFunction::Pq => supports_pq, BackendEotfs::Pq => supports_pq,
}; };
if !viable { if !viable {
log::warn!("Discarding previously desired transfer function"); log::warn!("Discarding previously desired EOTF");
desired_state.transfer_function = BackendTransferFunction::Default; desired_state.eotf = BackendEotfs::Default;
} }
} }
{ {
@ -1900,9 +1900,9 @@ impl MetalBackend {
modes.push(mode); modes.push(mode);
} }
} }
let mut transfer_functions = vec![]; let mut eotfs = vec![];
if dd.supports_pq { if dd.supports_pq {
transfer_functions.push(BackendTransferFunction::Pq); eotfs.push(BackendEotfs::Pq);
} }
let mut color_spaces = vec![]; let mut color_spaces = vec![];
if dd.supports_bt2020 { if dd.supports_bt2020 {
@ -1918,7 +1918,7 @@ impl MetalBackend {
non_desktop: dd.non_desktop, non_desktop: dd.non_desktop,
non_desktop_effective: dd.non_desktop_effective, non_desktop_effective: dd.non_desktop_effective,
vrr_capable: dd.vrr_capable, vrr_capable: dd.vrr_capable,
transfer_functions, eotfs,
color_spaces, color_spaces,
primaries: dd.primaries, primaries: dd.primaries,
luminance: dd.luminance, luminance: dd.luminance,
@ -2684,7 +2684,7 @@ impl MetalBackend {
.clear( .clear(
AcquireSync::Unnecessary, AcquireSync::Unnecessary,
ReleaseSync::None, ReleaseSync::None,
self.state.color_manager.srgb_srgb(), self.state.color_manager.srgb_gamma22(),
) )
.map_err(MetalError::Clear)?; .map_err(MetalError::Clear)?;
let (dev_tex, render_tex, render_fb, render_bo) = if dev.id == render_ctx.dev_id { let (dev_tex, render_tex, render_fb, render_bo) = if dev.id == render_ctx.dev_id {
@ -2742,7 +2742,7 @@ impl MetalBackend {
.clear( .clear(
AcquireSync::Unnecessary, AcquireSync::Unnecessary,
ReleaseSync::None, ReleaseSync::None,
self.state.color_manager.srgb_srgb(), self.state.color_manager.srgb_gamma22(),
) )
.map_err(MetalError::Clear)?; .map_err(MetalError::Clear)?;
let render_tex = match render_img.to_texture() { let render_tex = match render_img.to_texture() {

View file

@ -491,7 +491,7 @@ impl XBackend {
tearing: false, tearing: false,
format: FORMAT, format: FORMAT,
color_space: Default::default(), color_space: Default::default(),
transfer_function: Default::default(), eotf: Default::default(),
}; };
let output = Rc::new(XOutput { let output = Rc::new(XOutput {
id: self.state.connector_ids.next(), id: self.state.connector_ids.next(),
@ -599,7 +599,7 @@ impl XBackend {
non_desktop: false, non_desktop: false,
non_desktop_effective: false, non_desktop_effective: false,
vrr_capable: false, vrr_capable: false,
transfer_functions: vec![], eotfs: vec![],
color_spaces: vec![], color_spaces: vec![],
primaries: Primaries::SRGB, primaries: Primaries::SRGB,
luminance: None, luminance: None,
@ -774,7 +774,7 @@ impl XBackend {
let res = self.state.present_output( let res = self.state.present_output(
&node, &node,
&image.fb.get(), &image.fb.get(),
self.state.color_manager.srgb_srgb(), self.state.color_manager.srgb_gamma22(),
AcquireSync::Implicit, AcquireSync::Implicit,
ReleaseSync::Implicit, ReleaseSync::Implicit,
&image.tex.get(), &image.tex.get(),

View file

@ -1,7 +1,7 @@
use { use {
crate::{ crate::{
cli::{GlobalArgs, color::parse_color, duration::parse_duration}, cli::{GlobalArgs, color::parse_color, duration::parse_duration},
cmm::cmm_transfer_function::TransferFunction, cmm::cmm_eotf::Eotf,
tools::tool_client::{ToolClient, with_tool_client}, tools::tool_client::{ToolClient, with_tool_client},
wire::jay_damage_tracking::{SetVisualizerColor, SetVisualizerDecay, SetVisualizerEnabled}, wire::jay_damage_tracking::{SetVisualizerColor, SetVisualizerDecay, SetVisualizerEnabled},
}, },
@ -86,7 +86,7 @@ impl DamageTracking {
} }
DamageTrackingCmd::SetColor(c) => { DamageTrackingCmd::SetColor(c) => {
let color = parse_color(&c.color); 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 { tc.send(SetVisualizerColor {
self_id: dt, self_id: dt,
r, r,

View file

@ -1,8 +1,9 @@
use { use {
crate::{ crate::{
backend::{BackendColorSpace, BackendTransferFunction}, backend::{BackendColorSpace, BackendEotfs},
cli::GlobalArgs, cli::GlobalArgs,
format::{Format, XRGB8888}, format::{Format, XRGB8888},
ifs::wl_output::BlendSpace,
scale::Scale, scale::Scale,
tools::tool_client::{Handle, ToolClient, with_tool_client}, tools::tool_client::{Handle, ToolClient, with_tool_client},
utils::{errorfmt::ErrorFmt, transform_ext::TransformExt}, utils::{errorfmt::ErrorFmt, transform_ext::TransformExt},
@ -164,6 +165,8 @@ pub enum OutputCommand {
Colors(ColorsSettings), Colors(ColorsSettings),
/// Change the output brightness. /// Change the output brightness.
Brightness(BrightnessArgs), Brightness(BrightnessArgs),
/// Change the blend space.
BlendSpace(BlendSpaceArgs),
} }
#[derive(ValueEnum, Debug, Clone)] #[derive(ValueEnum, Debug, Clone)]
@ -333,14 +336,14 @@ pub struct ColorsSettings {
#[derive(Subcommand, Debug, Clone)] #[derive(Subcommand, Debug, Clone)]
pub enum ColorsCommand { pub enum ColorsCommand {
/// Sets the color space and transfer function of the output. /// Sets the color space and EOTF of the output.
Set { Set {
/// The name of the color space. /// The name of the color space.
#[clap(value_parser = PossibleValuesParser::new(color_space_possible_values()))] #[clap(value_parser = PossibleValuesParser::new(color_space_possible_values()))]
color_space: String, color_space: String,
/// The name of the transfer function. /// The name of the EOTF.
#[clap(value_parser = PossibleValuesParser::new(transfer_function_possible_values()))] #[clap(value_parser = PossibleValuesParser::new(eotf_possible_values()))]
transfer_function: String, eotf: String,
}, },
} }
@ -357,13 +360,13 @@ fn color_space_possible_values() -> Vec<PossibleValue> {
res res
} }
fn transfer_function_possible_values() -> Vec<PossibleValue> { fn eotf_possible_values() -> Vec<PossibleValue> {
let mut res = vec![]; let mut res = vec![];
for cs in BackendTransferFunction::variants() { for cs in BackendEotfs::variants() {
use BackendTransferFunction::*; use BackendEotfs::*;
let help = match cs { let help = match cs {
Default => "The default transfer function (usually sRGB)", Default => "The default EOTF (usually gamma22)",
Pq => "The PQ transfer function", Pq => "The PQ EOTF",
}; };
res.push(PossibleValue::new(cs.name()).help(help)); 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 /// The brightness of standard white in cd/m^2 or `default` to use the default
/// brightness. /// brightness.
/// ///
/// The default brightness depends on the transfer function: /// The default brightness depends on the EOTF:
/// ///
/// - default: the maximum display brightness /// - default: the maximum display brightness
/// - PQ: 203 cd/m^2. /// - 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. /// and adjust the display hardware brightness setting instead.
/// ///
/// This has no effect unless the vulkan renderer is used. /// This has no effect unless the vulkan renderer is used.
@ -407,6 +410,26 @@ fn parse_brightness(s: &str) -> Result<Brightness, ParseBrightnessError> {
.map_err(|_| ParseBrightnessError) .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<PossibleValue> {
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) { pub fn main(global: GlobalArgs, args: RandrArgs) {
with_tool_client(global.log_level.into(), |tc| async move { with_tool_client(global.log_level.into(), |tc| async move {
let idle = Rc::new(Randr { tc: tc.clone() }); let idle = Rc::new(Randr { tc: tc.clone() });
@ -462,10 +485,11 @@ struct Output {
pub flip_margin_ns: Option<u64>, pub flip_margin_ns: Option<u64>,
pub supported_color_spaces: Vec<String>, pub supported_color_spaces: Vec<String>,
pub current_color_space: Option<String>, pub current_color_space: Option<String>,
pub supported_transfer_functions: Vec<String>, pub supported_eotfs: Vec<String>,
pub current_transfer_function: Option<String>, pub current_eotf: Option<String>,
pub brightness_range: Option<(f64, f64)>, pub brightness_range: Option<(f64, f64)>,
pub brightness: Option<f64>, pub brightness: Option<f64>,
pub blend_space: Option<String>,
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
@ -713,15 +737,12 @@ impl Randr {
eprintln!("Could not change the colors: {}", msg); eprintln!("Could not change the colors: {}", msg);
}); });
match a.command { match a.command {
ColorsCommand::Set { ColorsCommand::Set { color_space, eotf } => {
color_space,
transfer_function,
} => {
tc.send(jay_randr::SetColors { tc.send(jay_randr::SetColors {
self_id: randr, self_id: randr,
output: &args.output, output: &args.output,
color_space: &color_space, 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; tc.round_trip().await;
} }
@ -957,19 +988,17 @@ impl Randr {
handle_cs("default"); handle_cs("default");
o.supported_color_spaces.iter().for_each(|cs| handle_cs(cs)); o.supported_color_spaces.iter().for_each(|cs| handle_cs(cs));
} }
if o.supported_transfer_functions.is_not_empty() { if o.supported_eotfs.is_not_empty() {
println!(" transfer functions:"); println!(" eotfs:");
let handle_tf = |tf: &str| { 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 => "", false => "",
true => " (current)", true => " (current)",
}; };
println!(" {tf}{current}"); println!(" {tf}{current}");
}; };
handle_tf("default"); handle_tf("default");
o.supported_transfer_functions o.supported_eotfs.iter().for_each(|tf| handle_tf(tf));
.iter()
.for_each(|tf| handle_tf(tf));
} }
if let Some((min, max)) = o.brightness_range { if let Some((min, max)) = o.brightness_range {
println!(" min brightness: {:>10.4} cd/m^2", min); println!(" min brightness: {:>10.4} cd/m^2", min);
@ -980,6 +1009,9 @@ impl Randr {
if let Some(lux) = o.brightness { if let Some(lux) = o.brightness {
println!(" brightness: {:>10.4} cd/m^2", lux); 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 { if o.modes.is_not_empty() && modes {
println!(" modes:"); println!(" modes:");
for mode in &o.modes { for mode in &o.modes {
@ -1130,19 +1162,17 @@ impl Randr {
let output = c.output.as_mut().unwrap(); let output = c.output.as_mut().unwrap();
output.current_color_space = Some(msg.color_space.to_string()); 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 mut data = data.borrow_mut();
let c = data.connectors.last_mut().unwrap(); let c = data.connectors.last_mut().unwrap();
let output = c.output.as_mut().unwrap(); let output = c.output.as_mut().unwrap();
output output.supported_eotfs.push(msg.eotf.to_string());
.supported_transfer_functions
.push(msg.transfer_function.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 mut data = data.borrow_mut();
let c = data.connectors.last_mut().unwrap(); let c = data.connectors.last_mut().unwrap();
let output = c.output.as_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| { jay_randr::BrightnessRange::handle(tc, randr, data.clone(), |data, msg| {
let mut data = data.borrow_mut(); let mut data = data.borrow_mut();
@ -1156,6 +1186,12 @@ impl Randr {
let output = c.output.as_mut().unwrap(); let output = c.output.as_mut().unwrap();
output.brightness = Some(msg.lux); 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; tc.round_trip().await;
data.borrow_mut().clone() data.borrow_mut().clone()
} }

View file

@ -1,8 +1,8 @@
pub mod cmm_description; pub mod cmm_description;
pub mod cmm_eotf;
pub mod cmm_luminance; pub mod cmm_luminance;
pub mod cmm_manager; pub mod cmm_manager;
pub mod cmm_primaries; pub mod cmm_primaries;
#[cfg(test)] #[cfg(test)]
mod cmm_tests; mod cmm_tests;
pub mod cmm_transfer_function;
pub mod cmm_transform; pub mod cmm_transform;

View file

@ -1,10 +1,10 @@
use { use {
crate::{ crate::{
cmm::{ cmm::{
cmm_eotf::Eotf,
cmm_luminance::{Luminance, TargetLuminance, white_balance}, cmm_luminance::{Luminance, TargetLuminance, white_balance},
cmm_manager::Shared, cmm_manager::Shared,
cmm_primaries::{NamedPrimaries, Primaries}, cmm_primaries::{NamedPrimaries, Primaries},
cmm_transfer_function::TransferFunction,
cmm_transform::{ColorMatrix, Local, Xyz, bradford_adjustment}, cmm_transform::{ColorMatrix, Local, Xyz, bradford_adjustment},
}, },
utils::ordered_float::F64, utils::ordered_float::F64,
@ -34,7 +34,7 @@ pub struct ColorDescription {
pub id: ColorDescriptionId, pub id: ColorDescriptionId,
pub linear: Rc<LinearColorDescription>, pub linear: Rc<LinearColorDescription>,
pub named_primaries: Option<NamedPrimaries>, pub named_primaries: Option<NamedPrimaries>,
pub transfer_function: TransferFunction, pub eotf: Eotf,
pub(super) shared: Rc<Shared>, pub(super) shared: Rc<Shared>,
} }
@ -66,8 +66,7 @@ impl LinearColorDescription {
impl ColorDescription { impl ColorDescription {
pub fn embeds_into(&self, target: &Self) -> bool { pub fn embeds_into(&self, target: &Self) -> bool {
self.transfer_function == target.transfer_function self.eotf == target.eotf && self.linear.embeds_into(&target.linear)
&& self.linear.embeds_into(&target.linear)
} }
} }

View file

@ -1,15 +1,13 @@
use linearize::Linearize; use linearize::Linearize;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Linearize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Linearize)]
pub enum TransferFunction { pub enum Eotf {
Srgb,
Linear, Linear,
St2084Pq, St2084Pq,
Bt1886, Bt1886,
Gamma22, Gamma22,
Gamma28, Gamma28,
St240, St240,
ExtSrgb,
Log100, Log100,
Log316, Log316,
St428, St428,

View file

@ -5,9 +5,9 @@ use {
ColorDescription, ColorDescriptionIds, LinearColorDescription, ColorDescription, ColorDescriptionIds, LinearColorDescription,
LinearColorDescriptionId, LinearColorDescriptionIds, LinearColorDescriptionId, LinearColorDescriptionIds,
}, },
cmm_eotf::Eotf,
cmm_luminance::{Luminance, TargetLuminance}, cmm_luminance::{Luminance, TargetLuminance},
cmm_primaries::{NamedPrimaries, Primaries}, cmm_primaries::{NamedPrimaries, Primaries},
cmm_transfer_function::TransferFunction,
}, },
utils::{copyhashmap::CopyHashMap, numcell::NumCell, ordered_float::F64}, utils::{copyhashmap::CopyHashMap, numcell::NumCell, ordered_float::F64},
}, },
@ -19,7 +19,7 @@ pub struct ColorManager {
linear_descriptions: CopyHashMap<LinearDescriptionKey, Weak<LinearColorDescription>>, linear_descriptions: CopyHashMap<LinearDescriptionKey, Weak<LinearColorDescription>>,
complete_descriptions: CopyHashMap<CompleteDescriptionKey, Weak<ColorDescription>>, complete_descriptions: CopyHashMap<CompleteDescriptionKey, Weak<ColorDescription>>,
shared: Rc<Shared>, shared: Rc<Shared>,
srgb_srgb: Rc<ColorDescription>, srgb_gamma22: Rc<ColorDescription>,
srgb_linear: Rc<ColorDescription>, srgb_linear: Rc<ColorDescription>,
windows_scrgb: Rc<ColorDescription>, windows_scrgb: Rc<ColorDescription>,
} }
@ -45,7 +45,7 @@ struct LinearDescriptionKey {
struct CompleteDescriptionKey { struct CompleteDescriptionKey {
linear: LinearColorDescriptionId, linear: LinearColorDescriptionId,
named_primaries: Option<NamedPrimaries>, named_primaries: Option<NamedPrimaries>,
transfer_function: TransferFunction, eotf: Eotf,
} }
impl ColorManager { impl ColorManager {
@ -55,7 +55,7 @@ impl ColorManager {
let complete_descriptions = CopyHashMap::default(); let complete_descriptions = CopyHashMap::default();
let shared = Rc::new(Shared::default()); let shared = Rc::new(Shared::default());
let _ = shared.complete_ids.next(); let _ = shared.complete_ids.next();
let srgb_srgb = get_description( let srgb_gamma22 = get_description(
&shared, &shared,
&linear_descriptions, &linear_descriptions,
&complete_descriptions, &complete_descriptions,
@ -63,7 +63,7 @@ impl ColorManager {
Some(NamedPrimaries::Srgb), Some(NamedPrimaries::Srgb),
Primaries::SRGB, Primaries::SRGB,
Luminance::SRGB, Luminance::SRGB,
TransferFunction::Srgb, Eotf::Gamma22,
Primaries::SRGB, Primaries::SRGB,
Luminance::SRGB.to_target(), Luminance::SRGB.to_target(),
None, None,
@ -71,10 +71,10 @@ impl ColorManager {
); );
let srgb_linear = get_description2( let srgb_linear = get_description2(
&shared, &shared,
&srgb_srgb.linear, &srgb_gamma22.linear,
&complete_descriptions, &complete_descriptions,
Some(NamedPrimaries::Srgb), Some(NamedPrimaries::Srgb),
TransferFunction::Linear, Eotf::Linear,
); );
let windows_scrgb = get_description( let windows_scrgb = get_description(
&shared, &shared,
@ -84,7 +84,7 @@ impl ColorManager {
Some(NamedPrimaries::Srgb), Some(NamedPrimaries::Srgb),
Primaries::SRGB, Primaries::SRGB,
Luminance::WINDOWS_SCRGB, Luminance::WINDOWS_SCRGB,
TransferFunction::Linear, Eotf::Linear,
Primaries::BT2020, Primaries::BT2020,
Luminance::ST2084_PQ.to_target(), Luminance::ST2084_PQ.to_target(),
None, None,
@ -95,14 +95,14 @@ impl ColorManager {
linear_descriptions, linear_descriptions,
complete_descriptions, complete_descriptions,
shared, shared,
srgb_srgb, srgb_gamma22,
srgb_linear, srgb_linear,
windows_scrgb, windows_scrgb,
}) })
} }
pub fn srgb_srgb(&self) -> &Rc<ColorDescription> { pub fn srgb_gamma22(&self) -> &Rc<ColorDescription> {
&self.srgb_srgb &self.srgb_gamma22
} }
pub fn srgb_linear(&self) -> &Rc<ColorDescription> { pub fn srgb_linear(&self) -> &Rc<ColorDescription> {
@ -118,7 +118,7 @@ impl ColorManager {
named_primaries: Option<NamedPrimaries>, named_primaries: Option<NamedPrimaries>,
primaries: Primaries, primaries: Primaries,
luminance: Luminance, luminance: Luminance,
transfer_function: TransferFunction, eotf: Eotf,
target_primaries: Primaries, target_primaries: Primaries,
target_luminance: TargetLuminance, target_luminance: TargetLuminance,
max_cll: Option<F64>, max_cll: Option<F64>,
@ -132,7 +132,7 @@ impl ColorManager {
named_primaries, named_primaries,
primaries, primaries,
luminance, luminance,
transfer_function, eotf,
target_primaries, target_primaries,
target_luminance, target_luminance,
max_cll, max_cll,
@ -143,14 +143,14 @@ impl ColorManager {
pub fn get_with_tf( pub fn get_with_tf(
self: &Rc<Self>, self: &Rc<Self>,
cd: &Rc<ColorDescription>, cd: &Rc<ColorDescription>,
transfer_function: TransferFunction, eotf: Eotf,
) -> Rc<ColorDescription> { ) -> Rc<ColorDescription> {
get_description2( get_description2(
&self.shared, &self.shared,
&cd.linear, &cd.linear,
&self.complete_descriptions, &self.complete_descriptions,
cd.named_primaries, cd.named_primaries,
transfer_function, eotf,
) )
} }
} }
@ -163,7 +163,7 @@ fn get_description(
named_primaries: Option<NamedPrimaries>, named_primaries: Option<NamedPrimaries>,
primaries: Primaries, primaries: Primaries,
luminance: Luminance, luminance: Luminance,
transfer_function: TransferFunction, eotf: Eotf,
target_primaries: Primaries, target_primaries: Primaries,
target_luminance: TargetLuminance, target_luminance: TargetLuminance,
max_cll: Option<F64>, max_cll: Option<F64>,
@ -189,13 +189,7 @@ fn get_description(
}; };
if let Some(d) = linear_descriptions.get(&key) { if let Some(d) = linear_descriptions.get(&key) {
if let Some(d) = d.upgrade() { if let Some(d) = d.upgrade() {
return get_description2( return get_description2(shared, &d, complete_descriptions, named_primaries, eotf);
shared,
&d,
complete_descriptions,
named_primaries,
transfer_function,
);
} }
shared.dead_linear.fetch_sub(1); shared.dead_linear.fetch_sub(1);
} }
@ -216,13 +210,13 @@ fn get_description(
let key = CompleteDescriptionKey { let key = CompleteDescriptionKey {
linear: d.id, linear: d.id,
named_primaries, named_primaries,
transfer_function, eotf,
}; };
let d = Rc::new(ColorDescription { let d = Rc::new(ColorDescription {
id: shared.complete_ids.next(), id: shared.complete_ids.next(),
linear: d, linear: d,
named_primaries, named_primaries,
transfer_function, eotf,
shared: shared.clone(), shared: shared.clone(),
}); });
complete_descriptions.set(key, Rc::downgrade(&d)); complete_descriptions.set(key, Rc::downgrade(&d));
@ -234,12 +228,12 @@ fn get_description2(
ld: &Rc<LinearColorDescription>, ld: &Rc<LinearColorDescription>,
complete_descriptions: &CopyHashMap<CompleteDescriptionKey, Weak<ColorDescription>>, complete_descriptions: &CopyHashMap<CompleteDescriptionKey, Weak<ColorDescription>>,
named_primaries: Option<NamedPrimaries>, named_primaries: Option<NamedPrimaries>,
transfer_function: TransferFunction, eotf: Eotf,
) -> Rc<ColorDescription> { ) -> Rc<ColorDescription> {
let key = CompleteDescriptionKey { let key = CompleteDescriptionKey {
linear: ld.id, linear: ld.id,
named_primaries, named_primaries,
transfer_function, eotf,
}; };
if let Some(d) = complete_descriptions.get(&key) { if let Some(d) = complete_descriptions.get(&key) {
if let Some(d) = d.upgrade() { if let Some(d) = d.upgrade() {
@ -251,7 +245,7 @@ fn get_description2(
id: shared.complete_ids.next(), id: shared.complete_ids.next(),
linear: ld.clone(), linear: ld.clone(),
named_primaries, named_primaries,
transfer_function, eotf,
shared: shared.clone(), shared: shared.clone(),
}); });
complete_descriptions.set(key, Rc::downgrade(&d)); complete_descriptions.set(key, Rc::downgrade(&d));

View file

@ -135,8 +135,8 @@ mod matrices {
mod transforms { mod transforms {
use crate::cmm::{ use crate::cmm::{
cmm_luminance::Luminance, cmm_manager::ColorManager, cmm_primaries::Primaries, cmm_eotf::Eotf, cmm_luminance::Luminance, cmm_manager::ColorManager,
cmm_transfer_function::TransferFunction, cmm_primaries::Primaries,
}; };
fn check(p1: Primaries, p2: Primaries, expected: [[f64; 4]; 3]) { fn check(p1: Primaries, p2: Primaries, expected: [[f64; 4]; 3]) {
@ -146,7 +146,7 @@ mod transforms {
None, None,
p, p,
Luminance::SRGB, Luminance::SRGB,
TransferFunction::Linear, Eotf::Linear,
p, p,
Luminance::SRGB.to_target(), Luminance::SRGB.to_target(),
None, None,

View file

@ -1,6 +1,6 @@
use { use {
crate::{ crate::{
cmm::{cmm_primaries::Primaries, cmm_transfer_function::TransferFunction}, cmm::{cmm_eotf::Eotf, cmm_primaries::Primaries},
theme::Color, theme::Color,
utils::{debug_fn::debug_fn, ordered_float::F64}, utils::{debug_fn::debug_fn, ordered_float::F64},
}, },
@ -131,7 +131,7 @@ impl<T, U> Mul<Color> for ColorMatrix<T, U> {
type Output = Color; type Output = Color;
fn mul(self, rhs: Color) -> Self::Output { 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]; let a = rgba[3];
if a < 1.0 && a > 0.0 { if a < 1.0 && a > 0.0 {
for c in &mut rgba[..3] { for c in &mut rgba[..3] {
@ -139,7 +139,7 @@ impl<T, U> Mul<Color> for ColorMatrix<T, U> {
} }
} }
let [r, g, b] = self * [rgba[0] as f64, rgba[1] as f64, rgba[2] as f64]; 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 { if a < 1.0 {
color = color * a; color = color * a;
} }

View file

@ -33,7 +33,7 @@ use {
HeadManagers, HeadState, jay_head_manager_session_v1::handle_jay_head_manager_done, HeadManagers, HeadState, jay_head_manager_session_v1::handle_jay_head_manager_done,
}, },
jay_screencast::{perform_screencast_realloc, perform_toplevel_screencasts}, 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_seat::handle_position_hint_requests,
wl_surface::{ wl_surface::{
NoneSurfaceExt, xdg_surface::handle_xdg_surface_configure_events, NoneSurfaceExt, xdg_surface::handle_xdg_surface_configure_events,
@ -636,6 +636,7 @@ fn create_dummy_output(state: &Rc<State>) {
vrr_cursor_hz: Default::default(), vrr_cursor_hz: Default::default(),
tearing_mode: Cell::new(&TearingMode::Never), tearing_mode: Cell::new(&TearingMode::Never),
brightness: Cell::new(None), brightness: Cell::new(None),
blend_space: Cell::new(BlendSpace::Srgb),
}); });
let mode = backend::Mode { let mode = backend::Mode {
width: 0, width: 0,
@ -652,7 +653,7 @@ fn create_dummy_output(state: &Rc<State>) {
tearing: false, tearing: false,
format: XRGB8888, format: XRGB8888,
color_space: Default::default(), color_space: Default::default(),
transfer_function: Default::default(), eotf: Default::default(),
}; };
let id = state.connector_ids.next(); let id = state.connector_ids.next();
let connector = Rc::new(DummyOutput { id }) as Rc<dyn Connector>; let connector = Rc::new(DummyOutput { id }) as Rc<dyn Connector>;
@ -680,7 +681,7 @@ fn create_dummy_output(state: &Rc<State>) {
tearing_mode: TearingMode::Never.to_config(), tearing_mode: TearingMode::Never.to_config(),
format: XRGB8888, format: XRGB8888,
color_space: backend_state.color_space, color_space: backend_state.color_space,
transfer_function: backend_state.transfer_function, eotf: backend_state.eotf,
supported_formats: Default::default(), supported_formats: Default::default(),
brightness: None, brightness: None,
}; };

View file

@ -2,12 +2,12 @@ use {
crate::{ crate::{
async_engine::SpawnedFuture, async_engine::SpawnedFuture,
backend::{ backend::{
self, BackendColorSpace, BackendTransferFunction, ConnectorId, DrmDeviceId, self, BackendColorSpace, BackendEotfs, ConnectorId, DrmDeviceId,
InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId,
transaction::BackendConnectorTransactionError, transaction::BackendConnectorTransactionError,
}, },
client::{Client, ClientId}, client::{Client, ClientId},
cmm::cmm_transfer_function::TransferFunction, cmm::cmm_eotf::Eotf,
compositor::MAX_EXTENTS, compositor::MAX_EXTENTS,
config::ConfigProxy, config::ConfigProxy,
criteria::{ criteria::{
@ -17,6 +17,7 @@ use {
}, },
format::config_formats, format::config_formats,
ifs::{ ifs::{
wl_output::BlendSpace,
wl_seat::{SeatId, WlSeatGlobal}, wl_seat::{SeatId, WlSeatGlobal},
wp_content_type_v1::ContentTypeExt, wp_content_type_v1::ContentTypeExt,
}, },
@ -69,9 +70,9 @@ use {
theme::{colors::Colorable, sized::Resizable}, theme::{colors::Colorable, sized::Resizable},
timer::Timer as JayTimer, timer::Timer as JayTimer,
video::{ video::{
ColorSpace, Connector, DrmDevice, Format as ConfigFormat, GfxApi, BlendSpace as ConfigBlendSpace, ColorSpace, Connector, DrmDevice, Eotf as ConfigEotf,
TearingMode as ConfigTearingMode, TransferFunction as ConfigTransferFunction, Format as ConfigFormat, GfxApi, TearingMode as ConfigTearingMode, Transform,
Transform, VrrMode as ConfigVrrMode, VrrMode as ConfigVrrMode,
}, },
window::{TileState, Window, WindowMatcher}, window::{TileState, Window, WindowMatcher},
workspace::WorkspaceDisplayOrder, workspace::WorkspaceDisplayOrder,
@ -1285,28 +1286,43 @@ impl ConfigProxyHandler {
&self, &self,
connector: Connector, connector: Connector,
color_space: ColorSpace, color_space: ColorSpace,
transfer_function: ConfigTransferFunction, eotf: ConfigEotf,
) -> Result<(), CphError> { ) -> Result<(), CphError> {
let bcs = match color_space { let bcs = match color_space {
ColorSpace::DEFAULT => BackendColorSpace::Default, ColorSpace::DEFAULT => BackendColorSpace::Default,
ColorSpace::BT2020 => BackendColorSpace::Bt2020, ColorSpace::BT2020 => BackendColorSpace::Bt2020,
_ => return Err(CphError::UnknownColorSpace(color_space)), _ => return Err(CphError::UnknownColorSpace(color_space)),
}; };
let btf = match transfer_function { let btf = match eotf {
ConfigTransferFunction::DEFAULT => BackendTransferFunction::Default, ConfigEotf::DEFAULT => BackendEotfs::Default,
ConfigTransferFunction::PQ => BackendTransferFunction::Pq, ConfigEotf::PQ => BackendEotfs::Pq,
_ => return Err(CphError::UnknownTransferFunction(transfer_function)), _ => return Err(CphError::UnknownEotf(eotf)),
}; };
let connector = self.get_connector(connector)?; let connector = self.get_connector(connector)?;
connector connector
.modify_state(&self.state, |s| { .modify_state(&self.state, |s| {
s.color_space = bcs; s.color_space = bcs;
s.transfer_function = btf; s.eotf = btf;
}) })
.map_err(CphError::ModifyConnectorState)?; .map_err(CphError::ModifyConnectorState)?;
Ok(()) 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( fn handle_connector_set_brightness(
&self, &self,
connector: Connector, connector: Connector,
@ -2365,7 +2381,7 @@ impl ConfigProxyHandler {
fn handle_get_color(&self, colorable: Colorable) -> Result<(), CphError> { fn handle_get_color(&self, colorable: Colorable) -> Result<(), CphError> {
let color = self.get_color(colorable)?.get(); 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); let color = jay_config::theme::Color::new_f32_premultiplied(r, g, b, a);
self.respond(Response::GetColor { color }); self.respond(Response::GetColor { color });
Ok(()) Ok(())
@ -2930,9 +2946,9 @@ impl ConfigProxyHandler {
ClientMessage::ConnectorSetColors { ClientMessage::ConnectorSetColors {
connector, connector,
color_space, color_space,
transfer_function, eotf,
} => self } => self
.handle_connector_set_colors(connector, color_space, transfer_function) .handle_connector_set_colors(connector, color_space, eotf)
.wrn("connector_set_colors")?, .wrn("connector_set_colors")?,
ClientMessage::ConnectorSetBrightness { ClientMessage::ConnectorSetBrightness {
connector, connector,
@ -3118,6 +3134,12 @@ impl ConfigProxyHandler {
ClientMessage::SeatCopyMark { seat, src, dst } => self ClientMessage::SeatCopyMark { seat, src, dst } => self
.handle_seat_copy_mark(seat, src, dst) .handle_seat_copy_mark(seat, src, dst)
.wrn("seat_copy_mark")?, .wrn("seat_copy_mark")?,
ClientMessage::ConnectorSetBlendSpace {
connector,
blend_space,
} => self
.handle_connector_set_blend_space(connector, blend_space)
.wrn("connector_set_blend_space")?,
} }
Ok(()) Ok(())
} }
@ -3211,8 +3233,8 @@ enum CphError {
UnknownXScalingMode(XScalingMode), UnknownXScalingMode(XScalingMode),
#[error("Unknown color space {0:?}")] #[error("Unknown color space {0:?}")]
UnknownColorSpace(ColorSpace), UnknownColorSpace(ColorSpace),
#[error("Unknown transfer function {0:?}")] #[error("Unknown EOTF {0:?}")]
UnknownTransferFunction(ConfigTransferFunction), UnknownEotf(ConfigEotf),
#[error("Client {0:?} does not exist")] #[error("Client {0:?} does not exist")]
ClientDoesNotExist(ConfigClient), ClientDoesNotExist(ConfigClient),
#[error("Window {0:?} does not exist")] #[error("Window {0:?} does not exist")]
@ -3227,6 +3249,8 @@ enum CphError {
WindowMatcherDoesNotExist(WindowMatcher), WindowMatcherDoesNotExist(WindowMatcher),
#[error("Could not modify the connector state")] #[error("Could not modify the connector state")]
ModifyConnectorState(#[source] BackendConnectorTransactionError), ModifyConnectorState(#[source] BackendConnectorTransactionError),
#[error("Unknown blend space {0:?}")]
UnknownBlendSpace(ConfigBlendSpace),
} }
trait WithRequestName { trait WithRequestName {

View file

@ -397,7 +397,7 @@ fn render_img(image: &InstantiatedCursorImage, renderer: &mut Renderer, x: Fixed
AcquireSync::None, AcquireSync::None,
ReleaseSync::None, ReleaseSync::None,
false, false,
renderer.state.color_manager.srgb_srgb(), renderer.state.color_manager.srgb_gamma22(),
); );
} }
} }
@ -422,7 +422,7 @@ impl Cursor for StaticCursor {
AcquireSync::None, AcquireSync::None,
ReleaseSync::None, ReleaseSync::None,
false, false,
renderer.state.color_manager.srgb_srgb(), renderer.state.color_manager.srgb_gamma22(),
); );
} }
} }
@ -465,7 +465,7 @@ impl Cursor for AnimatedCursor {
AcquireSync::None, AcquireSync::None,
ReleaseSync::None, ReleaseSync::None,
false, false,
renderer.state.color_manager.srgb_srgb(), renderer.state.color_manager.srgb_gamma22(),
); );
} }
} }

View file

@ -165,7 +165,7 @@ impl DamageVisualizer {
let dy = -cursor_rect.y1(); let dy = -cursor_rect.y1();
let decay_millis = decay.as_millis() as u64 as f32; let decay_millis = decay.as_millis() as u64 as f32;
renderer.ops.push(GfxApiOpt::Sync); 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() { for entry in entries.iter().rev() {
let region = Region::new(entry.rect); let region = Region::new(entry.rect);
let region = region.subtract_cow(&used); let region = region.subtract_cow(&used);

View file

@ -895,7 +895,7 @@ pub fn create_render_pass(
return GfxRenderPass { return GfxRenderPass {
ops: vec![], ops: vec![],
clear: Some(Color::SOLID_BLACK), 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![]; let mut ops = vec![];
@ -962,7 +962,7 @@ pub fn create_render_pass(
GfxRenderPass { GfxRenderPass {
ops, ops,
clear: Some(c), clear: Some(c),
clear_cd: state.color_manager.srgb_srgb().linear.clone(), clear_cd: state.color_manager.srgb_gamma22().linear.clone(),
} }
} }

View file

@ -67,7 +67,7 @@ macro_rules! dynload {
use { use {
crate::{ crate::{
cmm::cmm_transfer_function::TransferFunction, cmm::cmm_eotf::Eotf,
gfx_api::{ gfx_api::{
AcquireSync, CopyTexture, FramebufferRect, GfxApiOpt, GfxContext, GfxError, GfxTexture, AcquireSync, CopyTexture, FramebufferRect, GfxApiOpt, GfxContext, GfxError, GfxTexture,
ReleaseSync, SyncFile, ReleaseSync, SyncFile,
@ -309,7 +309,7 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) -> Option<SyncFile> {
} }
fn fill_boxes3(ctx: &GlRenderContext, boxes: &[[f32; 2]], color: &Color) { 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; let gles = ctx.ctx.dpy.gles;
unsafe { unsafe {
(gles.glUseProgram)(ctx.fill_prog.prog); (gles.glUseProgram)(ctx.fill_prog.prog);

View file

@ -2,7 +2,7 @@ use {
crate::{ crate::{
cmm::{ cmm::{
cmm_description::{ColorDescription, LinearColorDescription}, cmm_description::{ColorDescription, LinearColorDescription},
cmm_transfer_function::TransferFunction, cmm_eotf::Eotf,
}, },
format::Format, format::Format,
gfx_api::{ gfx_api::{
@ -82,7 +82,7 @@ impl Framebuffer {
(gles.glBindFramebuffer)(GL_FRAMEBUFFER, self.gl.fbo); (gles.glBindFramebuffer)(GL_FRAMEBUFFER, self.gl.fbo);
(gles.glViewport)(0, 0, self.gl.width, self.gl.height); (gles.glViewport)(0, 0, self.gl.width, self.gl.height);
if let Some(c) = clear { 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.glClearColor)(r, g, b, a);
(gles.glClear)(GL_COLOR_BUFFER_BIT); (gles.glClear)(GL_COLOR_BUFFER_BIT);
} }

View file

@ -6,6 +6,7 @@ mod command;
mod descriptor; mod descriptor;
mod descriptor_buffer; mod descriptor_buffer;
mod device; mod device;
mod eotfs;
mod fence; mod fence;
mod format; mod format;
mod image; mod image;
@ -18,7 +19,6 @@ mod shaders;
mod shm_image; mod shm_image;
mod staging; mod staging;
mod transfer; mod transfer;
mod transfer_functions;
use { use {
crate::{ crate::{

View file

@ -120,33 +120,6 @@ impl VulkanDevice {
})) }))
} }
pub(super) fn create_out_descriptor_set_layout(
self: &Rc<Self>,
db: &descriptor_buffer::Device,
) -> Result<Rc<VulkanDescriptorSetLayout>, 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( fn get_descriptor_set_size(
&self, &self,
db: &descriptor_buffer::Device, db: &descriptor_buffer::Device,

View file

@ -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,
}
}
}

View file

@ -40,7 +40,7 @@ pub(super) struct PipelineCreateInfo {
pub(super) src_has_alpha: bool, pub(super) src_has_alpha: bool,
pub(super) has_alpha_mult: bool, pub(super) has_alpha_mult: bool,
pub(super) eotf: u32, pub(super) eotf: u32,
pub(super) oetf: u32, pub(super) inv_eotf: u32,
pub(super) descriptor_set_layouts: ArrayVec<Rc<VulkanDescriptorSetLayout>, 2>, pub(super) descriptor_set_layouts: ArrayVec<Rc<VulkanDescriptorSetLayout>, 2>,
pub(super) has_color_management_data: bool, 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.src_has_alpha as u32).to_ne_bytes());
frag_spec_entry(&(info.has_alpha_mult 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.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()); frag_spec_entry(&(info.has_color_management_data as u32).to_ne_bytes());
let frag_spec = SpecializationInfo::default() let frag_spec = SpecializationInfo::default()
.map_entries(&frag_spec_entries) .map_entries(&frag_spec_entries)

View file

@ -3,7 +3,7 @@ use {
async_engine::{AsyncEngine, SpawnedFuture}, async_engine::{AsyncEngine, SpawnedFuture},
cmm::{ cmm::{
cmm_description::{ColorDescription, LinearColorDescription, LinearColorDescriptionId}, cmm_description::{ColorDescription, LinearColorDescription, LinearColorDescriptionId},
cmm_transfer_function::TransferFunction, cmm_eotf::Eotf,
cmm_transform::ColorMatrix, cmm_transform::ColorMatrix,
}, },
cpu_worker::PendingJob, cpu_worker::PendingJob,
@ -19,6 +19,7 @@ use {
descriptor::VulkanDescriptorSetLayout, descriptor::VulkanDescriptorSetLayout,
descriptor_buffer::VulkanDescriptorBufferWriter, descriptor_buffer::VulkanDescriptorBufferWriter,
device::VulkanDevice, device::VulkanDevice,
eotfs::{EOTF_LINEAR, EotfExt},
fence::VulkanFence, fence::VulkanFence,
image::{QueueFamily, QueueState, QueueTransfer, VulkanImage, VulkanImageMemory}, image::{QueueFamily, QueueState, QueueTransfer, VulkanImage, VulkanImageMemory},
pipeline::{PipelineCreateInfo, VulkanPipeline}, pipeline::{PipelineCreateInfo, VulkanPipeline},
@ -27,10 +28,9 @@ use {
shaders::{ shaders::{
FILL_FRAG, FILL_VERT, FillPushConstants, LEGACY_FILL_FRAG, LEGACY_FILL_VERT, FILL_FRAG, FILL_VERT, FillPushConstants, LEGACY_FILL_FRAG, LEGACY_FILL_VERT,
LEGACY_TEX_FRAG, LEGACY_TEX_VERT, LegacyFillPushConstants, LegacyTexPushConstants, LEGACY_TEX_FRAG, LEGACY_TEX_VERT, LegacyFillPushConstants, LegacyTexPushConstants,
OUT_FRAG, OUT_VERT, OutPushConstants, TEX_FRAG, TEX_VERT, TexColorManagementData, OUT_FRAG, OUT_VERT, OutColorManagementData, OutPushConstants, TEX_FRAG, TEX_VERT,
TexPushConstants, TexVertex, VulkanShader, TexColorManagementData, TexPushConstants, TexVertex, VulkanShader,
}, },
transfer_functions::{TF_LINEAR, TransferFunctionExt},
}, },
io_uring::IoUring, io_uring::IoUring,
rect::{Rect, Region}, rect::{Rect, Region},
@ -78,10 +78,8 @@ pub struct VulkanRenderer {
pub(super) formats: Rc<AHashMap<u32, GfxFormat>>, pub(super) formats: Rc<AHashMap<u32, GfxFormat>>,
pub(super) device: Rc<VulkanDevice>, pub(super) device: Rc<VulkanDevice>,
pub(super) fill_pipelines: CopyHashMap<vk::Format, FillPipelines>, pub(super) fill_pipelines: CopyHashMap<vk::Format, FillPipelines>,
pub(super) tex_pipelines: pub(super) tex_pipelines: StaticMap<Eotf, CopyHashMap<vk::Format, Rc<TexPipelines>>>,
StaticMap<TransferFunction, CopyHashMap<vk::Format, Rc<TexPipelines>>>, pub(super) out_pipelines: StaticMap<Eotf, CopyHashMap<OutPipelineKey, Rc<VulkanPipeline>>>,
pub(super) out_pipelines:
StaticMap<TransferFunction, CopyHashMap<OutPipelineKey, Rc<VulkanPipeline>>>,
pub(super) gfx_command_buffers: CachedCommandBuffers, pub(super) gfx_command_buffers: CachedCommandBuffers,
pub(super) transfer_command_buffers: Option<CachedCommandBuffers>, pub(super) transfer_command_buffers: Option<CachedCommandBuffers>,
pub(super) wait_semaphores: Stack<Rc<VulkanSemaphore>>, pub(super) wait_semaphores: Stack<Rc<VulkanSemaphore>>,
@ -181,6 +179,7 @@ pub(super) struct Memory {
uniform_buffer_writer: GenericBufferWriter, uniform_buffer_writer: GenericBufferWriter,
uniform_buffer_descriptor_cache: Option<Box<[u8]>>, uniform_buffer_descriptor_cache: Option<Box<[u8]>>,
blend_buffer_descriptor_buffer_offset: DeviceAddress, blend_buffer_descriptor_buffer_offset: DeviceAddress,
blend_buffer_color_management_data_address: Option<DeviceSize>,
} }
type Point = [[f32; 2]; 4]; type Point = [[f32; 2]; 4];
@ -247,20 +246,20 @@ type FillPipelines = Rc<StaticMap<TexSourceType, Rc<VulkanPipeline>>>;
struct TexPipelineKey { struct TexPipelineKey {
tex_copy_type: TexCopyType, tex_copy_type: TexCopyType,
tex_source_type: TexSourceType, tex_source_type: TexSourceType,
eotf: TransferFunction, eotf: Eotf,
has_color_management_data: bool, has_color_management_data: bool,
} }
pub(super) struct TexPipelines { pub(super) struct TexPipelines {
format: vk::Format, format: vk::Format,
oetf: TransferFunction, eotf: Eotf,
pipelines: CopyHashMap<TexPipelineKey, Rc<VulkanPipeline>>, pipelines: CopyHashMap<TexPipelineKey, Rc<VulkanPipeline>>,
} }
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub(super) struct OutPipelineKey { pub(super) struct OutPipelineKey {
format: vk::Format, format: vk::Format,
eotf: TransferFunction, eotf: Eotf,
} }
impl VulkanDevice { impl VulkanDevice {
@ -300,7 +299,7 @@ impl VulkanDevice {
let out_descriptor_set_layout = self let out_descriptor_set_layout = self
.descriptor_buffer .descriptor_buffer
.as_ref() .as_ref()
.map(|db| self.create_out_descriptor_set_layout(db)) .map(|_| self.create_tex_resource_descriptor_set_layout())
.transpose()?; .transpose()?;
let gfx_command_buffers = self.create_command_pool(self.graphics_queue_idx)?; let gfx_command_buffers = self.create_command_pool(self.graphics_queue_idx)?;
let transfer_command_buffers = self let transfer_command_buffers = self
@ -417,8 +416,8 @@ impl VulkanRenderer {
src_has_alpha, src_has_alpha,
has_alpha_mult: false, has_alpha_mult: false,
// all transformations are applied in the compositor // all transformations are applied in the compositor
eotf: TF_LINEAR, eotf: EOTF_LINEAR,
oetf: TF_LINEAR, inv_eotf: EOTF_LINEAR,
descriptor_set_layouts: Default::default(), descriptor_set_layouts: Default::default(),
has_color_management_data: false, has_color_management_data: false,
}; };
@ -437,13 +436,13 @@ impl VulkanRenderer {
format: vk::Format, format: vk::Format,
target_cd: &ColorDescription, target_cd: &ColorDescription,
) -> Rc<TexPipelines> { ) -> Rc<TexPipelines> {
let pipelines = &self.tex_pipelines[target_cd.transfer_function]; let pipelines = &self.tex_pipelines[target_cd.eotf];
match pipelines.get(&format) { match pipelines.get(&format) {
Some(pl) => pl, Some(pl) => pl,
_ => { _ => {
let pl = Rc::new(TexPipelines { let pl = Rc::new(TexPipelines {
format, format,
oetf: target_cd.transfer_function, eotf: target_cd.eotf,
pipelines: Default::default(), pipelines: Default::default(),
}); });
pipelines.set(format, pl.clone()); pipelines.set(format, pl.clone());
@ -463,7 +462,7 @@ impl VulkanRenderer {
let key = TexPipelineKey { let key = TexPipelineKey {
tex_copy_type, tex_copy_type,
tex_source_type, tex_source_type,
eotf: tex_cd.transfer_function, eotf: tex_cd.eotf,
has_color_management_data, has_color_management_data,
}; };
if let Some(pl) = pipelines.pipelines.get(&key) { if let Some(pl) = pipelines.pipelines.get(&key) {
@ -490,7 +489,7 @@ impl VulkanRenderer {
src_has_alpha, src_has_alpha,
has_alpha_mult, has_alpha_mult,
eotf: key.eotf.to_vulkan(), 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(), descriptor_set_layouts: self.tex_descriptor_set_layouts.clone(),
has_color_management_data, has_color_management_data,
}; };
@ -504,12 +503,13 @@ impl VulkanRenderer {
format: vk::Format, format: vk::Format,
bb_cd: &ColorDescription, bb_cd: &ColorDescription,
fb_cd: &ColorDescription, fb_cd: &ColorDescription,
has_color_management_data: bool,
) -> Result<Rc<VulkanPipeline>, VulkanError> { ) -> Result<Rc<VulkanPipeline>, VulkanError> {
let key = OutPipelineKey { let key = OutPipelineKey {
format, 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) { if let Some(pl) = pipelines.get(&key) {
return Ok(pl); return Ok(pl);
} }
@ -525,9 +525,9 @@ impl VulkanRenderer {
src_has_alpha: true, src_has_alpha: true,
has_alpha_mult: false, has_alpha_mult: false,
eotf: key.eotf.to_vulkan(), eotf: key.eotf.to_vulkan(),
oetf: fb_cd.transfer_function.to_vulkan(), inv_eotf: fb_cd.eotf.to_vulkan(),
descriptor_set_layouts, descriptor_set_layouts,
has_color_management_data: false, has_color_management_data,
})?; })?;
pipelines.set(key, out.clone()); pipelines.set(key, out.clone());
Ok(out) Ok(out)
@ -568,6 +568,20 @@ impl VulkanRenderer {
memory.blend_buffer_descriptor_buffer_offset = resource_writer.next_offset(); memory.blend_buffer_descriptor_buffer_offset = resource_writer.next_offset();
let mut writer = resource_writer.add_set(layout); let mut writer = resource_writer.add_set(layout);
writer.write(layout.offsets[0], &bb.sampled_image_descriptor); 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::<OutColorManagementData>() 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]; let tex_descriptor_set_layout = &self.tex_descriptor_set_layouts[1];
for pass in RenderPass::variants() { for pass in RenderPass::variants() {
@ -725,7 +739,7 @@ impl VulkanRenderer {
RenderPass::BlendBuffer => blend_cd, RenderPass::BlendBuffer => blend_cd,
RenderPass::FrameBuffer => fb_cd, RenderPass::FrameBuffer => fb_cd,
}; };
let tf = target_cd.transfer_function; let tf = target_cd.eotf;
let color = memory let color = memory
.color_transforms .color_transforms
.apply_to_color(&fr.cd, target_cd, fr.color); .apply_to_color(&fr.cd, target_cd, fr.color);
@ -818,6 +832,26 @@ impl VulkanRenderer {
Ok(()) 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> { fn create_data_buffer(&self) -> Result<(), VulkanError> {
if self.device.descriptor_buffer.is_none() { if self.device.descriptor_buffer.is_none() {
return Ok(()); 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); memory.used_buffers.push(buffer);
Ok(()) Ok(())
} }
@ -1047,7 +1084,7 @@ impl VulkanRenderer {
.apply_to_color(clear_cd, target_cd, *clear); .apply_to_color(clear_cd, target_cd, *clear);
let clear_value = ClearValue { let clear_value = ClearValue {
color: ClearColorValue { 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 && { let use_load_clear = clear_rects.len() == 1 && {
@ -1299,7 +1336,12 @@ impl VulkanRenderer {
zone!("blend_buffer_copy"); zone!("blend_buffer_copy");
let memory = &*self.memory.borrow(); let memory = &*self.memory.borrow();
let db = self.device.descriptor_buffer.as_ref().unwrap(); 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 { let push = OutPushConstants {
vertices: memory.out_address, vertices: memory.out_address,
}; };
@ -1852,7 +1894,21 @@ impl VulkanRenderer {
Ok(()) Ok(())
} }
fn elide_blend_buffer(&self, blend_buffer: &mut Option<Rc<VulkanImage>>) { fn elide_blend_buffer1(
&self,
blend_buffer: &mut Option<Rc<VulkanImage>>,
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<Rc<VulkanImage>>) {
if blend_buffer.is_none() { if blend_buffer.is_none() {
return; return;
} }
@ -1876,11 +1932,13 @@ impl VulkanRenderer {
bb_cd: &Rc<ColorDescription>, bb_cd: &Rc<ColorDescription>,
) -> Result<(), VulkanError> { ) -> Result<(), VulkanError> {
self.check_defunct()?; 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.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 bb = blend_buffer.as_deref();
let buf = self.gfx_command_buffers.allocate()?; let buf = self.gfx_command_buffers.allocate()?;
self.convert_ops(opts, bb_cd, fb_cd)?; self.convert_ops(opts, bb_cd, fb_cd)?;
self.create_blend_cm_data(bb, bb_cd, fb_cd);
self.create_data_buffer()?; self.create_data_buffer()?;
self.create_uniform_buffer()?; self.create_uniform_buffer()?;
self.collect_memory(); self.collect_memory();

View file

@ -69,6 +69,14 @@ pub struct TexColorManagementData {
unsafe impl Packed for 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)] #[derive(Copy, Clone, Debug)]
#[repr(C)] #[repr(C)]
pub struct LegacyTexPushConstants { pub struct LegacyTexPushConstants {

View file

@ -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

View file

@ -4,7 +4,7 @@
layout(constant_id = 0) const bool src_has_alpha = false; layout(constant_id = 0) const bool src_has_alpha = false;
layout(constant_id = 1) const bool has_alpha_multiplier = false; layout(constant_id = 1) const bool has_alpha_multiplier = false;
layout(constant_id = 2) const uint eotf = 0; 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; layout(constant_id = 4) const bool has_matrix = false;
#endif #endif

View file

@ -1,20 +1,30 @@
#version 450 #version 450
#extension GL_EXT_samplerless_texture_functions : require #extension GL_EXT_samplerless_texture_functions : require
#extension GL_EXT_scalar_block_layout : require
#include "frag_spec_const.glsl" #include "frag_spec_const.glsl"
#include "transfer_functions.glsl" #include "eotfs.glsl"
#include "out.common.glsl" #include "out.common.glsl"
layout(set = 0, binding = 0) uniform texture2D in_color; 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; layout(location = 0) out vec4 out_color;
void main() { void main() {
vec4 c = texelFetch(in_color, ivec2(gl_FragCoord.xy), 0); vec4 c = texelFetch(in_color, ivec2(gl_FragCoord.xy), 0);
if (eotf != oetf) { if (eotf != inv_eotf || has_matrix) {
c.rgb /= mix(c.a, 1.0, c.a == 0.0); vec3 rgb = c.rgb;
c.rgb = apply_eotf(c.rgb); rgb /= mix(c.a, 1.0, c.a == 0.0);
c.rgb = apply_oetf(c.rgb); rgb = apply_eotf(rgb);
c.rgb *= c.a; 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; out_color = c;
} }

View file

@ -3,7 +3,7 @@
#extension GL_EXT_scalar_block_layout : require #extension GL_EXT_scalar_block_layout : require
#include "frag_spec_const.glsl" #include "frag_spec_const.glsl"
#include "transfer_functions.glsl" #include "eotfs.glsl"
#include "tex.common.glsl" #include "tex.common.glsl"
layout(set = 0, binding = 0) uniform sampler sam; layout(set = 0, binding = 0) uniform sampler sam;
@ -16,7 +16,7 @@ layout(location = 0) out vec4 out_color;
void main() { void main() {
vec4 c = textureLod(sampler2D(tex, sam), tex_pos, 0); vec4 c = textureLod(sampler2D(tex, sam), tex_pos, 0);
if (eotf != oetf || has_matrix) { if (eotf != inv_eotf || has_matrix) {
vec3 rgb = c.rgb; vec3 rgb = c.rgb;
if (src_has_alpha) { if (src_has_alpha) {
rgb /= mix(c.a, 1.0, c.a == 0.0); rgb /= mix(c.a, 1.0, c.a == 0.0);
@ -25,7 +25,7 @@ void main() {
if (has_matrix) { if (has_matrix) {
rgb = (cm_data.matrix * vec4(rgb, 1.0)).rgb; rgb = (cm_data.matrix * vec4(rgb, 1.0)).rgb;
} }
rgb = apply_oetf(rgb); rgb = apply_inv_eotf(rgb);
if (src_has_alpha) { if (src_has_alpha) {
rgb *= c.a; rgb *= c.a;
} }

View file

@ -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

View file

@ -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,
}
}
}

View file

@ -2,7 +2,7 @@
use { use {
crate::{ crate::{
cmm::cmm_transfer_function::TransferFunction, cmm::cmm_eotf::Eotf,
format::ARGB8888, format::ARGB8888,
gfx_api::{GfxContext, GfxError, GfxTexture}, gfx_api::{GfxContext, GfxError, GfxTexture},
scale::Scale, scale::Scale,
@ -221,7 +221,7 @@ impl PathBuilderExt for PathBuilder {
impl From<crate::theme::Color> for Color { impl From<crate::theme::Color> for Color {
fn from(v: crate::theme::Color) -> Self { 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; let mut c = Self::TRANSPARENT;
c.set_red(r / a); c.set_red(r / a);
c.set_green(g / 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] { 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 { if alpha < 1.0 {
r /= alpha; r /= alpha;
g /= alpha; g /= alpha;

View file

@ -2,9 +2,9 @@ use {
crate::{ crate::{
client::{Client, ClientError}, client::{Client, ClientError},
cmm::{ cmm::{
cmm_eotf::Eotf,
cmm_luminance::{Luminance, TargetLuminance}, cmm_luminance::{Luminance, TargetLuminance},
cmm_primaries::{NamedPrimaries, Primaries}, cmm_primaries::{NamedPrimaries, Primaries},
cmm_transfer_function::TransferFunction,
}, },
ifs::color_management::{ ifs::color_management::{
MIN_LUM_MUL_INV, PRIMARIES_MUL_INV, MIN_LUM_MUL_INV, PRIMARIES_MUL_INV,
@ -40,7 +40,7 @@ pub struct WpImageDescriptionCreatorParamsV1 {
pub client: Rc<Client>, pub client: Rc<Client>,
pub version: Version, pub version: Version,
pub tracker: Tracker<Self>, pub tracker: Tracker<Self>,
pub tf: Cell<Option<TransferFunction>>, pub tf: Cell<Option<Eotf>>,
pub primaries: Cell<Option<(Option<NamedPrimaries>, Primaries)>>, pub primaries: Cell<Option<(Option<NamedPrimaries>, Primaries)>>,
pub luminance: Cell<Option<Luminance>>, pub luminance: Cell<Option<Luminance>>,
pub mastering_primaries: Cell<Option<Primaries>>, pub mastering_primaries: Cell<Option<Primaries>>,
@ -53,19 +53,19 @@ impl WpImageDescriptionCreatorParamsV1RequestHandler for WpImageDescriptionCreat
type Error = WpImageDescriptionCreatorParamsV1Error; type Error = WpImageDescriptionCreatorParamsV1Error;
fn create(&self, req: Create, _slf: &Rc<Self>) -> Result<(), Self::Error> { fn create(&self, req: Create, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(transfer_function) = self.tf.get() else { let Some(eotf) = self.tf.get() else {
return Err(WpImageDescriptionCreatorParamsV1Error::TfNotSet); return Err(WpImageDescriptionCreatorParamsV1Error::TfNotSet);
}; };
let Some((named_primaries, primaries)) = self.primaries.get() else { let Some((named_primaries, primaries)) = self.primaries.get() else {
return Err(WpImageDescriptionCreatorParamsV1Error::PrimariesNotSet); return Err(WpImageDescriptionCreatorParamsV1Error::PrimariesNotSet);
}; };
let default_luminance = match transfer_function { let default_luminance = match eotf {
TransferFunction::Bt1886 => Luminance::BT1886, Eotf::Bt1886 => Luminance::BT1886,
TransferFunction::St2084Pq => Luminance::ST2084_PQ, Eotf::St2084Pq => Luminance::ST2084_PQ,
_ => Luminance::SRGB, _ => Luminance::SRGB,
}; };
let mut luminance = self.luminance.get().unwrap_or(default_luminance); 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; luminance.max.0 = luminance.min.0 + 10_000.0;
} }
if luminance.max.0 <= luminance.min.0 || luminance.white.0 <= luminance.min.0 { if luminance.max.0 <= luminance.min.0 || luminance.white.0 <= luminance.min.0 {
@ -80,7 +80,7 @@ impl WpImageDescriptionCreatorParamsV1RequestHandler for WpImageDescriptionCreat
named_primaries, named_primaries,
primaries, primaries,
luminance, luminance,
transfer_function, eotf,
target_primaries, target_primaries,
target_luminance, target_luminance,
self.max_cll.get(), self.max_cll.get(),
@ -102,17 +102,17 @@ impl WpImageDescriptionCreatorParamsV1RequestHandler for WpImageDescriptionCreat
fn set_tf_named(&self, req: SetTfNamed, _slf: &Rc<Self>) -> Result<(), Self::Error> { fn set_tf_named(&self, req: SetTfNamed, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let tf = match req.tf { let tf = match req.tf {
TRANSFER_FUNCTION_BT1886 => TransferFunction::Bt1886, TRANSFER_FUNCTION_BT1886 => Eotf::Bt1886,
TRANSFER_FUNCTION_GAMMA22 => TransferFunction::Gamma22, TRANSFER_FUNCTION_GAMMA22 => Eotf::Gamma22,
TRANSFER_FUNCTION_GAMMA28 => TransferFunction::Gamma28, TRANSFER_FUNCTION_GAMMA28 => Eotf::Gamma28,
TRANSFER_FUNCTION_ST240 => TransferFunction::St240, TRANSFER_FUNCTION_ST240 => Eotf::St240,
TRANSFER_FUNCTION_EXT_LINEAR => TransferFunction::Linear, TRANSFER_FUNCTION_EXT_LINEAR => Eotf::Linear,
TRANSFER_FUNCTION_LOG_100 => TransferFunction::Log100, TRANSFER_FUNCTION_LOG_100 => Eotf::Log100,
TRANSFER_FUNCTION_LOG_316 => TransferFunction::Log316, TRANSFER_FUNCTION_LOG_316 => Eotf::Log316,
TRANSFER_FUNCTION_SRGB => TransferFunction::Srgb, TRANSFER_FUNCTION_SRGB => Eotf::Gamma22,
TRANSFER_FUNCTION_EXT_SRGB => TransferFunction::ExtSrgb, TRANSFER_FUNCTION_EXT_SRGB => Eotf::Gamma22,
TRANSFER_FUNCTION_ST2084_PQ => TransferFunction::St2084Pq, TRANSFER_FUNCTION_ST2084_PQ => Eotf::St2084Pq,
TRANSFER_FUNCTION_ST428 => TransferFunction::St428, TRANSFER_FUNCTION_ST428 => Eotf::St428,
_ => { _ => {
return Err(WpImageDescriptionCreatorParamsV1Error::UnsupportedTf( return Err(WpImageDescriptionCreatorParamsV1Error::UnsupportedTf(
req.tf, req.tf,
@ -261,9 +261,9 @@ pub enum WpImageDescriptionCreatorParamsV1Error {
UnsupportedPrimaries(u32), UnsupportedPrimaries(u32),
#[error("set_tf_power is not supported")] #[error("set_tf_power is not supported")]
SetTfPowerNotSupported, SetTfPowerNotSupported,
#[error("{} is not a supported named transfer function", .0)] #[error("{} is not a supported named EOTF", .0)]
UnsupportedTf(u32), UnsupportedTf(u32),
#[error("The transfer function has already been set")] #[error("The EOTF has already been set")]
TfAlreadySet, TfAlreadySet,
#[error("The primaries have already been set")] #[error("The primaries have already been set")]
PrimariesAlreadySet, PrimariesAlreadySet,
@ -271,7 +271,7 @@ pub enum WpImageDescriptionCreatorParamsV1Error {
LuminancesAlreadySet, LuminancesAlreadySet,
#[error("The minimum luminance is too low")] #[error("The minimum luminance is too low")]
MinLuminanceTooLow, MinLuminanceTooLow,
#[error("The transfer function was not set")] #[error("The EOTF was not set")]
TfNotSet, TfNotSet,
#[error("The primaries were not set")] #[error("The primaries were not set")]
PrimariesNotSet, PrimariesNotSet,

View file

@ -1,18 +1,14 @@
use { use {
crate::{ crate::{
client::Client, client::Client,
cmm::{ cmm::{cmm_description::ColorDescription, cmm_eotf::Eotf, cmm_primaries::NamedPrimaries},
cmm_description::ColorDescription, cmm_primaries::NamedPrimaries,
cmm_transfer_function::TransferFunction,
},
ifs::color_management::{ ifs::color_management::{
MIN_LUM_MUL, PRIMARIES_ADOBE_RGB, PRIMARIES_BT2020, PRIMARIES_CIE1931_XYZ, MIN_LUM_MUL, PRIMARIES_ADOBE_RGB, PRIMARIES_BT2020, PRIMARIES_CIE1931_XYZ,
PRIMARIES_DCI_P3, PRIMARIES_DISPLAY_P3, PRIMARIES_GENERIC_FILM, PRIMARIES_MUL, PRIMARIES_DCI_P3, PRIMARIES_DISPLAY_P3, PRIMARIES_GENERIC_FILM, PRIMARIES_MUL,
PRIMARIES_NTSC, PRIMARIES_PAL, PRIMARIES_PAL_M, TRANSFER_FUNCTION_BT1886, PRIMARIES_NTSC, PRIMARIES_PAL, PRIMARIES_PAL_M, PRIMARIES_SRGB,
TRANSFER_FUNCTION_EXT_LINEAR, TRANSFER_FUNCTION_EXT_SRGB, TRANSFER_FUNCTION_GAMMA22, TRANSFER_FUNCTION_BT1886, TRANSFER_FUNCTION_EXT_LINEAR, TRANSFER_FUNCTION_GAMMA22,
TRANSFER_FUNCTION_GAMMA28, TRANSFER_FUNCTION_LOG_100, TRANSFER_FUNCTION_LOG_316, TRANSFER_FUNCTION_GAMMA28, TRANSFER_FUNCTION_LOG_100, TRANSFER_FUNCTION_LOG_316,
TRANSFER_FUNCTION_ST240, TRANSFER_FUNCTION_ST428, TRANSFER_FUNCTION_ST2084_PQ, TRANSFER_FUNCTION_ST240, TRANSFER_FUNCTION_ST428, TRANSFER_FUNCTION_ST2084_PQ,
consts::{PRIMARIES_SRGB, TRANSFER_FUNCTION_SRGB},
}, },
leaks::Tracker, leaks::Tracker,
object::{Object, Version}, object::{Object, Version},
@ -32,18 +28,16 @@ pub struct WpImageDescriptionInfoV1 {
impl WpImageDescriptionInfoV1 { impl WpImageDescriptionInfoV1 {
pub fn send_description(&self, d: &ColorDescription) { pub fn send_description(&self, d: &ColorDescription) {
let tf = match d.transfer_function { let tf = match d.eotf {
TransferFunction::Srgb => TRANSFER_FUNCTION_SRGB, Eotf::Linear => TRANSFER_FUNCTION_EXT_LINEAR,
TransferFunction::Linear => TRANSFER_FUNCTION_EXT_LINEAR, Eotf::St2084Pq => TRANSFER_FUNCTION_ST2084_PQ,
TransferFunction::St2084Pq => TRANSFER_FUNCTION_ST2084_PQ, Eotf::Bt1886 => TRANSFER_FUNCTION_BT1886,
TransferFunction::Bt1886 => TRANSFER_FUNCTION_BT1886, Eotf::Gamma22 => TRANSFER_FUNCTION_GAMMA22,
TransferFunction::Gamma22 => TRANSFER_FUNCTION_GAMMA22, Eotf::Gamma28 => TRANSFER_FUNCTION_GAMMA28,
TransferFunction::Gamma28 => TRANSFER_FUNCTION_GAMMA28, Eotf::St240 => TRANSFER_FUNCTION_ST240,
TransferFunction::St240 => TRANSFER_FUNCTION_ST240, Eotf::Log100 => TRANSFER_FUNCTION_LOG_100,
TransferFunction::ExtSrgb => TRANSFER_FUNCTION_EXT_SRGB, Eotf::Log316 => TRANSFER_FUNCTION_LOG_316,
TransferFunction::Log100 => TRANSFER_FUNCTION_LOG_100, Eotf::St428 => TRANSFER_FUNCTION_ST428,
TransferFunction::Log316 => TRANSFER_FUNCTION_LOG_316,
TransferFunction::St428 => TRANSFER_FUNCTION_ST428,
}; };
self.send_primaries(&d.linear.primaries); self.send_primaries(&d.linear.primaries);
if let Some(n) = d.named_primaries { if let Some(n) = d.named_primaries {

View file

@ -217,7 +217,7 @@ impl ExtImageCopyCaptureFrameV1 {
aq, aq,
re, re,
jay_config::video::Transform::None, jay_config::video::Transform::None,
self.client.state.color_manager.srgb_srgb(), self.client.state.color_manager.srgb_gamma22(),
on.global.pos.get(), on.global.pos.get(),
render_hardware_cursors, render_hardware_cursors,
x_off, x_off,
@ -235,7 +235,7 @@ impl ExtImageCopyCaptureFrameV1 {
fb.render_node( fb.render_node(
aq, aq,
re, re,
self.client.state.color_manager.srgb_srgb(), self.client.state.color_manager.srgb_gamma22(),
node, node,
&self.client.state, &self.client.state,
Some(node.node_absolute_position()), Some(node.node_absolute_position()),

View file

@ -1,7 +1,7 @@
use { use {
crate::{ crate::{
backend::{ backend::{
BackendColorSpace, BackendTransferFunction, ConnectorId, Mode, MonitorInfo, BackendColorSpace, BackendEotfs, ConnectorId, Mode, MonitorInfo,
transaction::BackendConnectorTransactionError, transaction::BackendConnectorTransactionError,
}, },
client::ClientId, client::ClientId,
@ -90,7 +90,7 @@ pub struct HeadState {
pub tearing_mode: TearingMode, pub tearing_mode: TearingMode,
pub format: &'static Format, pub format: &'static Format,
pub color_space: BackendColorSpace, pub color_space: BackendColorSpace,
pub transfer_function: BackendTransferFunction, pub eotf: BackendEotfs,
pub supported_formats: RcEq<Vec<&'static Format>>, pub supported_formats: RcEq<Vec<&'static Format>>,
pub brightness: Option<f64>, pub brightness: Option<f64>,
} }
@ -132,7 +132,7 @@ enum HeadOp {
SetVrrMode(VrrMode), SetVrrMode(VrrMode),
SetTearingMode(TearingMode), SetTearingMode(TearingMode),
SetFormat(&'static Format), SetFormat(&'static Format),
SetTransferFunction(BackendTransferFunction), SetEotf(BackendEotfs),
SetColorSpace(BackendColorSpace), SetColorSpace(BackendColorSpace),
SetBrightness(Option<f64>), SetBrightness(Option<f64>),
} }
@ -491,14 +491,10 @@ impl HeadManagers {
} }
} }
pub fn handle_colors_change( pub fn handle_colors_change(&self, color_space: BackendColorSpace, eotf: BackendEotfs) {
&self,
color_space: BackendColorSpace,
transfer_function: BackendTransferFunction,
) {
let state = &mut *self.state.borrow_mut(); let state = &mut *self.state.borrow_mut();
state.color_space = color_space; state.color_space = color_space;
state.transfer_function = transfer_function; state.eotf = eotf;
for head in self.managers.lock().values() { for head in self.managers.lock().values() {
skip_in_transaction!(head); skip_in_transaction!(head);
if let Some(ext) = &head.ext.drm_color_space_info_v1 { if let Some(ext) = &head.ext.drm_color_space_info_v1 {

View file

@ -1,6 +1,6 @@
use { use {
crate::{ crate::{
backend::BackendTransferFunction, backend::BackendEotfs,
cmm::cmm_luminance::Luminance, cmm::cmm_luminance::Luminance,
ifs::head_management::HeadState, ifs::head_management::HeadState,
wire::{ wire::{
@ -27,7 +27,7 @@ impl HeadName {
} }
fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { 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); self.send_implied_default_brightness(shared);
} }
if shared.brightness != tran.brightness { if shared.brightness != tran.brightness {
@ -36,14 +36,14 @@ impl HeadName {
} }
pub(in super::super) fn send_implied_default_brightness(&self, shared: &HeadState) { pub(in super::super) fn send_implied_default_brightness(&self, shared: &HeadState) {
let lux = match shared.transfer_function { let lux = match shared.eotf {
BackendTransferFunction::Default => shared BackendEotfs::Default => shared
.monitor_info .monitor_info
.as_ref() .as_ref()
.and_then(|m| m.luminance.as_ref()) .and_then(|m| m.luminance.as_ref())
.map(|l| l.max) .map(|l| l.max)
.unwrap_or(Luminance::SRGB.white.0), .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.client.event(ImpliedDefaultBrightness {
self_id: self.id, self_id: self.id,

View file

@ -32,9 +32,7 @@ impl HeadName {
} }
fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { fn after_transaction(&self, shared: &HeadState, tran: &HeadState) {
if (shared.color_space, shared.transfer_function) if (shared.color_space, shared.eotf) != (tran.color_space, tran.eotf) {
!= (tran.color_space, tran.transfer_function)
{
self.send_state(shared); self.send_state(shared);
} }
} }
@ -42,7 +40,7 @@ impl HeadName {
pub(in super::super) fn send_state(&self, state: &HeadState) { pub(in super::super) fn send_state(&self, state: &HeadState) {
self.client.event(HdmiEotf { self.client.event(HdmiEotf {
self_id: self.id, self_id: self.id,
eotf: state.transfer_function.to_drm() as u32, eotf: state.eotf.to_drm() as u32,
}); });
self.client.event(Colorimetry { self.client.event(Colorimetry {
self_id: self.id, self_id: self.id,

View file

@ -1,6 +1,6 @@
use { use {
crate::{ crate::{
backend::{BackendColorSpace, BackendTransferFunction}, backend::{BackendColorSpace, BackendEotfs},
ifs::head_management::{HeadOp, HeadState}, ifs::head_management::{HeadOp, HeadState},
video::drm::{ video::drm::{
DRM_MODE_COLORIMETRY_BT2020_RGB, DRM_MODE_COLORIMETRY_DEFAULT, HDMI_EOTF_SMPTE_ST2084, DRM_MODE_COLORIMETRY_BT2020_RGB, DRM_MODE_COLORIMETRY_DEFAULT, HDMI_EOTF_SMPTE_ST2084,
@ -41,7 +41,7 @@ impl HeadName {
return; return;
}; };
self.send_supported_eotf(HDMI_EOTF_TRADITIONAL_GAMMA_SDR); 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_eotf(tf.to_drm());
} }
self.send_supported_colorimetry(DRM_MODE_COLORIMETRY_DEFAULT); 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 DEFAULT: u32 = HDMI_EOTF_TRADITIONAL_GAMMA_SDR as u32;
const PQ: u32 = HDMI_EOTF_SMPTE_ST2084 as u32; const PQ: u32 = HDMI_EOTF_SMPTE_ST2084 as u32;
let eotf = match req.eotf { let eotf = match req.eotf {
DEFAULT => BackendTransferFunction::Default, DEFAULT => BackendEotfs::Default,
PQ => BackendTransferFunction::Pq, PQ => BackendEotfs::Pq,
_ => return Err(ErrorName::UnknownEotf(req.eotf)), _ => return Err(ErrorName::UnknownEotf(req.eotf)),
}; };
if eotf != BackendTransferFunction::Default { if eotf != BackendEotfs::Default {
let state = &*self.common.transaction_state.borrow(); let state = &*self.common.transaction_state.borrow();
let Some(mi) = &state.monitor_info else { let Some(mi) = &state.monitor_info else {
return Err(ErrorName::UnsupportedEotf(req.eotf)); 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)); return Err(ErrorName::UnsupportedEotf(req.eotf));
} }
} }
self.common.push_op(HeadOp::SetTransferFunction(eotf))?; self.common.push_op(HeadOp::SetEotf(eotf))?;
Ok(()) Ok(())
} }

View file

@ -265,7 +265,7 @@ impl JayHeadManagerSessionV1 {
new.non_desktop_override = desired.override_non_desktop; new.non_desktop_override = desired.override_non_desktop;
new.format = desired.format; new.format = desired.format;
new.color_space = desired.color_space; new.color_space = desired.color_space;
new.transfer_function = desired.transfer_function; new.eotf = desired.eotf;
if old == new { if old == new {
continue; continue;
} }
@ -447,8 +447,8 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 {
state.format = f; state.format = f;
to_send |= FORMAT_INFO; to_send |= FORMAT_INFO;
} }
HeadOp::SetTransferFunction(e) => { HeadOp::SetEotf(e) => {
state.transfer_function = e; state.eotf = e;
to_send |= DRM_COLOR_SPACE_INFO; to_send |= DRM_COLOR_SPACE_INFO;
to_send |= BRIGHTNESS_INFO; to_send |= BRIGHTNESS_INFO;
} }

View file

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

View file

@ -1,7 +1,7 @@
use { use {
crate::{ crate::{
client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientError}, client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientError},
cmm::cmm_transfer_function::TransferFunction, cmm::cmm_eotf::Eotf,
globals::{Global, GlobalName}, globals::{Global, GlobalName},
leaks::Tracker, leaks::Tracker,
object::{Object, Version}, object::{Object, Version},
@ -97,7 +97,7 @@ impl JayDamageTrackingRequestHandler for JayDamageTracking {
req: SetVisualizerColor, req: SetVisualizerColor,
_slf: &Rc<Self>, _slf: &Rc<Self>,
) -> Result<(), Self::Error> { ) -> 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); self.client.state.damage_visualizer.set_color(color);
Ok(()) Ok(())
} }

View file

@ -1,9 +1,10 @@
use { use {
crate::{ crate::{
backend::{self, BackendColorSpace, BackendTransferFunction}, backend::{self, BackendColorSpace, BackendEotfs},
client::{Client, ClientError}, client::{Client, ClientError},
compositor::MAX_EXTENTS, compositor::MAX_EXTENTS,
format::named_formats, format::named_formats,
ifs::wl_output,
leaks::Tracker, leaks::Tracker,
object::{Object, Version}, object::{Object, Version},
scale::Scale, scale::Scale,
@ -34,6 +35,7 @@ const FORMAT_SINCE: Version = Version(8);
const FLIP_MARGIN_SINCE: Version = Version(10); const FLIP_MARGIN_SINCE: Version = Version(10);
const COLORIMETRY_SINCE: Version = Version(15); const COLORIMETRY_SINCE: Version = Version(15);
const BRIGHTNESS_SINCE: Version = Version(16); const BRIGHTNESS_SINCE: Version = Version(16);
const BLEND_SPACE_SINCE: Version = Version(21);
impl JayRandr { impl JayRandr {
pub fn new(id: JayRandrId, client: &Rc<Client>, version: Version) -> Self { pub fn new(id: JayRandrId, client: &Rc<Client>, version: Version) -> Self {
@ -170,15 +172,15 @@ impl JayRandr {
}); });
} }
if self.version >= COLORIMETRY_SINCE { if self.version >= COLORIMETRY_SINCE {
for tf in &node.global.transfer_functions { for eotf in &node.global.eotfs {
self.client.event(SupportedTransferFunction { self.client.event(SupportedEotf {
self_id: self.id, self_id: self.id,
transfer_function: tf.name(), eotf: eotf.name(),
}); });
} }
self.client.event(CurrentTransferFunction { self.client.event(CurrentEotf {
self_id: self.id, self_id: self.id,
transfer_function: node.global.btf.get().name(), eotf: node.global.btf.get().name(),
}); });
for cs in &node.global.color_spaces { for cs in &node.global.color_spaces {
self.client.event(SupportedColorSpace { 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) { fn send_error(&self, msg: &str) {
@ -484,21 +492,19 @@ impl JayRandrRequestHandler for JayRandr {
)); ));
}; };
let tf = 'tf: { let tf = 'tf: {
for tf in BackendTransferFunction::variants() { for tf in BackendEotfs::variants() {
if tf.name() == req.transfer_function { if tf.name() == req.eotf {
break 'tf tf; break 'tf tf;
} }
} }
return Err(JayRandrError::UnknownTransferFunction( return Err(JayRandrError::UnknownEotf(req.eotf.to_string()));
req.transfer_function.to_string(),
));
}; };
let Some(c) = self.get_connector(req.output) else { let Some(c) = self.get_connector(req.output) else {
return Ok(()); return Ok(());
}; };
let res = c.modify_state(&self.state, |s| { let res = c.modify_state(&self.state, |s| {
s.color_space = cs; s.color_space = cs;
s.transfer_function = tf; s.eotf = tf;
}); });
if let Err(e) = res { if let Err(e) = res {
self.send_error(&format!( self.send_error(&format!(
@ -528,6 +534,23 @@ impl JayRandrRequestHandler for JayRandr {
c.set_brightness(None); c.set_brightness(None);
Ok(()) Ok(())
} }
fn set_blend_space(&self, req: SetBlendSpace<'_>, _slf: &Rc<Self>) -> 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! { object_base! {
@ -551,7 +574,7 @@ pub enum JayRandrError {
UnknownFormat(String), UnknownFormat(String),
#[error("Unknown color space {0}")] #[error("Unknown color space {0}")]
UnknownColorSpace(String), UnknownColorSpace(String),
#[error("Unknown transfer function {0}")] #[error("Unknown EOTF {0}")]
UnknownTransferFunction(String), UnknownEotf(String),
} }
efrom!(JayRandrError, ClientError); efrom!(JayRandrError, ClientError);

View file

@ -194,7 +194,7 @@ impl JayScreencast {
let res = buffer.fb.render_node( let res = buffer.fb.render_node(
AcquireSync::Implicit, AcquireSync::Implicit,
ReleaseSync::Implicit, ReleaseSync::Implicit,
self.client.state.color_manager.srgb_srgb(), self.client.state.color_manager.srgb_gamma22(),
&*tl, &*tl,
&self.client.state, &self.client.state,
Some(tl.node_absolute_position()), Some(tl.node_absolute_position()),
@ -341,7 +341,7 @@ impl JayScreencast {
AcquireSync::Implicit, AcquireSync::Implicit,
ReleaseSync::Implicit, ReleaseSync::Implicit,
Transform::None, Transform::None,
self.client.state.color_manager.srgb_srgb(), self.client.state.color_manager.srgb_gamma22(),
on.global.pos.get(), on.global.pos.get(),
render_hardware_cursors, render_hardware_cursors,
x_off, x_off,

View file

@ -2,13 +2,13 @@ mod removed_output;
use { use {
crate::{ crate::{
backend::{self, BackendColorSpace, BackendLuminance, BackendTransferFunction}, backend::{self, BackendColorSpace, BackendEotfs, BackendLuminance},
client::{Client, ClientError, ClientId}, client::{Client, ClientError, ClientId},
cmm::{ cmm::{
cmm_description::ColorDescription, cmm_description::ColorDescription,
cmm_eotf::Eotf,
cmm_luminance::Luminance, cmm_luminance::Luminance,
cmm_primaries::{NamedPrimaries, Primaries}, cmm_primaries::{NamedPrimaries, Primaries},
cmm_transfer_function::TransferFunction,
}, },
damage::DamageMatrix, damage::DamageMatrix,
format::{Format, XRGB8888}, format::{Format, XRGB8888},
@ -30,6 +30,7 @@ use {
}, },
ahash::AHashMap, ahash::AHashMap,
jay_config::video::Transform, jay_config::video::Transform,
linearize::Linearize,
std::{ std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
collections::hash_map::Entry, collections::hash_map::Entry,
@ -76,7 +77,7 @@ pub struct WlOutputGlobal {
pub format: Cell<&'static Format>, pub format: Cell<&'static Format>,
pub width_mm: i32, pub width_mm: i32,
pub height_mm: i32, pub height_mm: i32,
pub transfer_functions: Vec<BackendTransferFunction>, pub eotfs: Vec<BackendEotfs>,
pub color_spaces: Vec<BackendColorSpace>, pub color_spaces: Vec<BackendColorSpace>,
pub primaries: Primaries, pub primaries: Primaries,
pub luminance: Option<BackendLuminance>, pub luminance: Option<BackendLuminance>,
@ -86,7 +87,7 @@ pub struct WlOutputGlobal {
pub persistent: Rc<PersistentOutputState>, pub persistent: Rc<PersistentOutputState>,
pub opt: Rc<OutputGlobalOpt>, pub opt: Rc<OutputGlobalOpt>,
pub damage_matrix: Cell<DamageMatrix>, pub damage_matrix: Cell<DamageMatrix>,
pub btf: Cell<BackendTransferFunction>, pub btf: Cell<BackendEotfs>,
pub bcs: Cell<BackendColorSpace>, pub bcs: Cell<BackendColorSpace>,
pub color_description: CloneCell<Rc<ColorDescription>>, pub color_description: CloneCell<Rc<ColorDescription>>,
pub linear_color_description: CloneCell<Rc<ColorDescription>>, pub linear_color_description: CloneCell<Rc<ColorDescription>>,
@ -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 struct PersistentOutputState {
pub transform: Cell<Transform>, pub transform: Cell<Transform>,
pub scale: Cell<crate::scale::Scale>, pub scale: Cell<crate::scale::Scale>,
@ -123,6 +139,7 @@ pub struct PersistentOutputState {
pub vrr_cursor_hz: Cell<Option<f64>>, pub vrr_cursor_hz: Cell<Option<f64>>,
pub tearing_mode: Cell<&'static TearingMode>, pub tearing_mode: Cell<&'static TearingMode>,
pub brightness: Cell<Option<f64>>, pub brightness: Cell<Option<f64>>,
pub blend_space: Cell<BlendSpace>,
} }
impl Default for PersistentOutputState { impl Default for PersistentOutputState {
@ -135,6 +152,7 @@ impl Default for PersistentOutputState {
vrr_cursor_hz: Default::default(), vrr_cursor_hz: Default::default(),
tearing_mode: Cell::new(&TearingMode::Never), tearing_mode: Cell::new(&TearingMode::Never),
brightness: Default::default(), brightness: Default::default(),
blend_space: Cell::new(BlendSpace::Srgb),
} }
} }
} }
@ -179,7 +197,7 @@ impl WlOutputGlobal {
height_mm: i32, height_mm: i32,
output_id: &Rc<OutputId>, output_id: &Rc<OutputId>,
persistent_state: &Rc<PersistentOutputState>, persistent_state: &Rc<PersistentOutputState>,
transfer_functions: Vec<BackendTransferFunction>, eotfs: Vec<BackendEotfs>,
color_spaces: Vec<BackendColorSpace>, color_spaces: Vec<BackendColorSpace>,
primaries: Primaries, primaries: Primaries,
luminance: Option<BackendLuminance>, luminance: Option<BackendLuminance>,
@ -205,7 +223,7 @@ impl WlOutputGlobal {
format: Cell::new(XRGB8888), format: Cell::new(XRGB8888),
width_mm, width_mm,
height_mm, height_mm,
transfer_functions, eotfs,
color_spaces, color_spaces,
primaries, primaries,
luminance, luminance,
@ -215,9 +233,9 @@ impl WlOutputGlobal {
persistent: persistent_state.clone(), persistent: persistent_state.clone(),
opt: Default::default(), opt: Default::default(),
damage_matrix: 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), 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()), linear_color_description: CloneCell::new(state.color_manager.srgb_linear().clone()),
color_description_listeners: Default::default(), color_description_listeners: Default::default(),
}; };
@ -345,7 +363,7 @@ impl WlOutputGlobal {
pub fn update_color_description(&self) -> bool { pub fn update_color_description(&self) -> bool {
let mut luminance = Luminance::SRGB; let mut luminance = Luminance::SRGB;
let tf = match self.btf.get() { let tf = match self.btf.get() {
BackendTransferFunction::Default => { BackendEotfs::Default => {
if let Some(brightness) = self.persistent.brightness.get() { if let Some(brightness) = self.persistent.brightness.get() {
let output_max = match self.luminance { let output_max = match self.luminance {
None => 80.0, None => 80.0,
@ -353,14 +371,14 @@ impl WlOutputGlobal {
}; };
luminance.white.0 = luminance.max.0 * brightness / output_max; luminance.white.0 = luminance.max.0 * brightness / output_max;
} }
TransferFunction::Srgb Eotf::Gamma22
} }
BackendTransferFunction::Pq => { BackendEotfs::Pq => {
luminance = Luminance::ST2084_PQ; luminance = Luminance::ST2084_PQ;
if let Some(brightness) = self.persistent.brightness.get() { if let Some(brightness) = self.persistent.brightness.get() {
luminance.white.0 = brightness; luminance.white.0 = brightness;
} }
TransferFunction::St2084Pq Eotf::St2084Pq
} }
}; };
let mut target_luminance = luminance.to_target(); let mut target_luminance = luminance.to_target();
@ -386,10 +404,7 @@ impl WlOutputGlobal {
max_cll, max_cll,
max_fall, max_fall,
); );
let cd_linear = self let cd_linear = self.state.color_manager.get_with_tf(&cd, Eotf::Linear);
.state
.color_manager
.get_with_tf(&cd, TransferFunction::Linear);
self.linear_color_description.set(cd_linear.clone()); self.linear_color_description.set(cd_linear.clone());
self.color_description.set(cd.clone()).id != cd.id self.color_description.set(cd.clone()).id != cd.id
} }

View file

@ -1723,7 +1723,7 @@ impl WlSurface {
pub fn color_description(&self) -> Rc<ColorDescription> { pub fn color_description(&self) -> Rc<ColorDescription> {
match self.color_description.get() { match self.color_description.get() {
Some(cd) => cd, Some(cd) => cd,
None => self.client.state.color_manager.srgb_srgb().clone(), None => self.client.state.color_manager.srgb_gamma22().clone(),
} }
} }

View file

@ -138,7 +138,7 @@ impl TestBackend {
non_desktop: false, non_desktop: false,
non_desktop_effective: false, non_desktop_effective: false,
vrr_capable: false, vrr_capable: false,
transfer_functions: vec![], eotfs: vec![],
color_spaces: vec![], color_spaces: vec![],
primaries: Primaries::SRGB, primaries: Primaries::SRGB,
luminance: None, luminance: None,
@ -152,7 +152,7 @@ impl TestBackend {
tearing: false, tearing: false,
format: XRGB8888, format: XRGB8888,
color_space: Default::default(), color_space: Default::default(),
transfer_function: Default::default(), eotf: Default::default(),
}, },
}; };
Self { Self {

View file

@ -1,6 +1,6 @@
use { use {
crate::{ crate::{
cmm::cmm_transfer_function::TransferFunction, cmm::cmm_eotf::Eotf,
it::{ it::{
test_error::TestResult, test_ifs::test_buffer::TestBuffer, test_object::TestObject, test_error::TestResult, test_ifs::test_buffer::TestBuffer, test_object::TestObject,
test_transport::TestTransport, test_transport::TestTransport,
@ -32,7 +32,7 @@ impl TestSinglePixelBufferManager {
destroyed: Cell::new(false), destroyed: Cell::new(false),
}); });
let map = |c: f32| (c as f64 * u32::MAX as f64) as u32; 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.tran.send(CreateU32RgbaBuffer {
self_id: self.id, self_id: self.id,
id: obj.id, id: obj.id,

View file

@ -51,7 +51,7 @@ async fn test(run: Rc<TestRun>) -> TestResult {
non_desktop: false, non_desktop: false,
non_desktop_effective: false, non_desktop_effective: false,
vrr_capable: false, vrr_capable: false,
transfer_functions: vec![], eotfs: vec![],
color_spaces: vec![], color_spaces: vec![],
primaries: Primaries::SRGB, primaries: Primaries::SRGB,
luminance: None, luminance: None,
@ -65,7 +65,7 @@ async fn test(run: Rc<TestRun>) -> TestResult {
tearing: false, tearing: false,
format: XRGB8888, format: XRGB8888,
color_space: Default::default(), color_space: Default::default(),
transfer_function: Default::default(), eotf: Default::default(),
}, },
}; };
run.backend run.backend

View file

@ -1,6 +1,6 @@
use { use {
crate::{ crate::{
cmm::cmm_transfer_function::TransferFunction, cmm::cmm_eotf::Eotf,
format::ARGB8888, format::ARGB8888,
gfx_api::{GfxContext, GfxTexture}, gfx_api::{GfxContext, GfxTexture},
pango::{ pango::{
@ -79,7 +79,7 @@ pub fn render(
let data = create_data(font, width, height, scale)?; let data = create_data(font, width, height, scale)?;
data.layout.set_text(text); data.layout.set_text(text);
let font_height = data.layout.pixel_size().1; 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_operator(CAIRO_OPERATOR_SOURCE);
data.cctx.set_source_rgba(r as _, g as _, b as _, a as _); data.cctx.set_source_rgba(r as _, g as _, b as _, a as _);
let y = y.unwrap_or((height - font_height) / 2); let y = y.unwrap_or((height - font_height) / 2);

View file

@ -192,7 +192,7 @@ impl GuiElement for Button {
} }
fn render_at(&self, color_manager: &ColorManager, r: &mut RendererBase, x1: f32, y1: f32) { 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 srgb = &srgb_srgb.linear;
let x2 = x1 + self.data.width.get(); let x2 = x1 + self.data.width.get();
let y2 = y1 + self.data.height.get(); let y2 = y1 + self.data.height.get();
@ -331,7 +331,7 @@ impl GuiElement for Label {
AcquireSync::None, AcquireSync::None,
ReleaseSync::None, ReleaseSync::None,
false, false,
color_manager.srgb_srgb(), color_manager.srgb_gamma22(),
); );
} }
} }
@ -644,10 +644,10 @@ impl WindowData {
let res = buf.fb.render_custom( let res = buf.fb.render_custom(
AcquireSync::Implicit, AcquireSync::Implicit,
ReleaseSync::Implicit, ReleaseSync::Implicit,
self.dpy.state.color_manager.srgb_srgb(), self.dpy.state.color_manager.srgb_gamma22(),
self.scale.get(), self.scale.get(),
Some(&Color::from_gray_srgb(0)), Some(&Color::from_gray_srgb(0)),
&self.dpy.state.color_manager.srgb_srgb().linear, &self.dpy.state.color_manager.srgb_gamma22().linear,
None, None,
self.dpy.state.color_manager.srgb_linear(), self.dpy.state.color_manager.srgb_linear(),
&mut |r| { &mut |r| {

View file

@ -79,7 +79,7 @@ impl Renderer<'_> {
} }
let theme = &self.state.theme; let theme = &self.state.theme;
let th = theme.sizes.title_height.get(); 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; let srgb = &srgb_srgb.linear;
if let Some(fs) = &fullscreen { if let Some(fs) = &fullscreen {
fs.node_render(self, x, y, None); fs.node_render(self, x, y, None);
@ -135,7 +135,7 @@ impl Renderer<'_> {
AcquireSync::None, AcquireSync::None,
ReleaseSync::None, ReleaseSync::None,
false, false,
self.state.color_manager.srgb_srgb(), self.state.color_manager.srgb_gamma22(),
); );
} }
if let Some(status) = &rd.status if let Some(status) = &rd.status
@ -219,7 +219,7 @@ impl Renderer<'_> {
self.base.fill_boxes( self.base.fill_boxes(
std::slice::from_ref(&pos.at_point(x, y)), std::slice::from_ref(&pos.at_point(x, y)),
&Color::from_srgba_straight(20, 20, 20, 255), &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) if let Some(tex) = placeholder.textures.borrow().get(&self.base.scale)
&& let Some(texture) = tex.texture() && let Some(texture) = tex.texture()
@ -240,7 +240,7 @@ impl Renderer<'_> {
AcquireSync::None, AcquireSync::None,
ReleaseSync::None, ReleaseSync::None,
false, false,
self.state.color_manager.srgb_srgb(), self.state.color_manager.srgb_gamma22(),
); );
} }
self.render_tl_aux(placeholder.tl_data(), bounds, true); 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) { 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 srgb = &srgb_srgb.linear;
let rd = container.render_data.borrow_mut(); let rd = container.render_data.borrow_mut();
let c = self.state.theme.colors.unfocused_title_background.get(); let c = self.state.theme.colors.unfocused_title_background.get();
@ -367,7 +367,7 @@ impl Renderer<'_> {
slice::from_ref(bounds), slice::from_ref(bounds),
&color, &color,
None, 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( self.base.fill_boxes(
slice::from_ref(rect), slice::from_ref(rect),
&color, &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() { if !rect.is_empty() {
let color = Color::from_u32_premultiplied( let color = Color::from_u32_premultiplied(
cd.transfer_function, cd.eotf, color[0], color[1], color[2], color[3],
color[0],
color[1],
color[2],
color[3],
); );
self.base.ops.push(GfxApiOpt::Sync); self.base.ops.push(GfxApiOpt::Sync);
self.base 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 + 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(), 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; let srgb = &srgb_srgb.linear;
self.base.fill_boxes(&borders, &bc, srgb); self.base.fill_boxes(&borders, &bc, srgb);
let title = [Rect::new_sized(x + bw, y + bw, pos.width() - 2 * bw, th).unwrap()]; let title = [Rect::new_sized(x + bw, y + bw, pos.width() - 2 * bw, th).unwrap()];

View file

@ -79,7 +79,7 @@ pub fn take_screenshot(
fb.render_node( fb.render_node(
AcquireSync::Unnecessary, AcquireSync::Unnecessary,
ReleaseSync::Implicit, ReleaseSync::Implicit,
state.color_manager.srgb_srgb(), state.color_manager.srgb_gamma22(),
state.root.deref(), state.root.deref(),
state, state,
Some(state.root.extents.get()), Some(state.root.extents.get()),

View file

@ -476,9 +476,9 @@ impl ConnectorData {
if old.format != s.format { if old.format != s.format {
self.head_managers.handle_format_change(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 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 { if old.mode != s.mode {
self.head_managers.handle_mode_change(s.mode); self.head_managers.handle_mode_change(s.mode);
@ -1290,7 +1290,7 @@ impl State {
AcquireSync::Unnecessary, AcquireSync::Unnecessary,
ReleaseSync::None, ReleaseSync::None,
transform, transform,
self.color_manager.srgb_srgb(), self.color_manager.srgb_gamma22(),
position, position,
true, true,
x_off - capture.rect.x1(), x_off - capture.rect.x1(),

View file

@ -9,7 +9,7 @@ use {
ifs::{ ifs::{
head_management::{HeadManagers, HeadState}, head_management::{HeadManagers, HeadState},
jay_tray_v1::JayTrayV1Global, jay_tray_v1::JayTrayV1Global,
wl_output::{PersistentOutputState, WlOutputGlobal}, wl_output::{BlendSpace, PersistentOutputState, WlOutputGlobal},
}, },
output_schedule::OutputSchedule, output_schedule::OutputSchedule,
state::{ConnectorData, OutputData, State}, state::{ConnectorData, OutputData, State},
@ -41,7 +41,7 @@ pub fn handle(state: &Rc<State>, connector: &Rc<dyn Connector>) {
tearing: false, tearing: false,
format: XRGB8888, format: XRGB8888,
color_space: Default::default(), color_space: Default::default(),
transfer_function: Default::default(), eotf: Default::default(),
}; };
let id = connector.id(); let id = connector.id();
let name = Rc::new(connector.kernel_id().to_string()); let name = Rc::new(connector.kernel_id().to_string());
@ -67,7 +67,7 @@ pub fn handle(state: &Rc<State>, connector: &Rc<dyn Connector>) {
tearing_mode: Default::default(), tearing_mode: Default::default(),
format: backend_state.format, format: backend_state.format,
color_space: backend_state.color_space, color_space: backend_state.color_space,
transfer_function: backend_state.transfer_function, eotf: backend_state.eotf,
supported_formats: Default::default(), supported_formats: Default::default(),
brightness: None, brightness: None,
}; };
@ -183,6 +183,7 @@ impl ConnectorHandler {
vrr_cursor_hz: Cell::new(self.state.default_vrr_cursor_hz.get()), vrr_cursor_hz: Cell::new(self.state.default_vrr_cursor_hz.get()),
tearing_mode: Cell::new(self.state.default_tearing_mode.get()), tearing_mode: Cell::new(self.state.default_tearing_mode.get()),
brightness: Cell::new(None), brightness: Cell::new(None),
blend_space: Cell::new(BlendSpace::Srgb),
}); });
self.state self.state
.persistent_output_states .persistent_output_states
@ -199,7 +200,7 @@ impl ConnectorHandler {
info.height_mm, info.height_mm,
&output_id, &output_id,
&desired_state, &desired_state,
info.transfer_functions.clone(), info.eotfs.clone(),
info.color_spaces.clone(), info.color_spaces.clone(),
info.primaries, info.primaries,
info.luminance, info.luminance,

View file

@ -1,6 +1,6 @@
use { use {
crate::{ crate::{
cmm::cmm_transfer_function::TransferFunction, cmm::cmm_eotf::Eotf,
cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker, PendingJob}, cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker, PendingJob},
format::ARGB8888, format::ARGB8888,
gfx_api::{ gfx_api::{
@ -188,7 +188,7 @@ fn render(
data.layout.set_text(text); data.layout.set_text(text);
} }
let font_height = data.layout.pixel_size().1; 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_operator(CAIRO_OPERATOR_SOURCE);
data.cctx.set_source_rgba(r as _, g as _, b as _, a as _); data.cctx.set_source_rgba(r as _, g as _, b as _, a as _);
let y = y.unwrap_or((height - font_height) / 2); let y = y.unwrap_or((height - font_height) / 2);

View file

@ -1,7 +1,7 @@
#![expect(clippy::excessive_precision)] #![expect(clippy::excessive_precision)]
use { use {
crate::{cmm::cmm_transfer_function::TransferFunction, utils::clonecell::CloneCell}, crate::{cmm::cmm_eotf::Eotf, utils::clonecell::CloneCell},
num_traits::Float, num_traits::Float,
std::{cell::Cell, cmp::Ordering, ops::Mul, sync::Arc}, std::{cell::Cell, cmp::Ordering, ops::Mul, sync::Arc},
}; };
@ -68,14 +68,7 @@ impl Color {
a: 1.0, a: 1.0,
}; };
pub fn new(transfer_function: TransferFunction, mut r: f32, mut g: f32, mut b: f32) -> Self { pub fn new(eotf: Eotf, 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)
}
}
#[inline(always)] #[inline(always)]
fn linear(c: f32) -> f32 { fn linear(c: f32) -> f32 {
c c
@ -86,23 +79,6 @@ impl Color {
let den = 18.8515625 - 18.6875 * cp; let den = 18.8515625 - 18.6875 * cp;
(num / den).powf(1.0 / 0.1593017578125) (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 { fn st240(c: f32) -> f32 {
if c < 0.0913 { if c < 0.0913 {
c / 4.0 c / 4.0
@ -120,10 +96,13 @@ impl Color {
c.powf(2.6) * 52.37 / 48.0 c.powf(2.6) * 52.37 / 48.0
} }
fn gamma22(c: f32) -> f32 { 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 { fn gamma28(c: f32) -> f32 {
c.powf(2.8) c.signum() * c.abs().powf(2.8)
} }
macro_rules! convert { macro_rules! convert {
($tf:ident) => {{ ($tf:ident) => {{
@ -132,30 +111,22 @@ impl Color {
b = $tf(b); b = $tf(b);
}}; }};
} }
match transfer_function { match eotf {
TransferFunction::Srgb => convert!(srgb), Eotf::Linear => convert!(linear),
TransferFunction::Linear => convert!(linear), Eotf::St2084Pq => convert!(st2084_pq),
TransferFunction::St2084Pq => convert!(st2084_pq), Eotf::Bt1886 => convert!(gamma24),
TransferFunction::Bt1886 => convert!(bt1886), Eotf::Gamma22 => convert!(gamma22),
TransferFunction::Gamma22 => convert!(gamma22), Eotf::Gamma28 => convert!(gamma28),
TransferFunction::Gamma28 => convert!(gamma28), Eotf::St240 => convert!(st240),
TransferFunction::St240 => convert!(st240), Eotf::Log100 => convert!(log100),
TransferFunction::ExtSrgb => convert!(ext_srgb), Eotf::Log316 => convert!(log316),
TransferFunction::Log100 => convert!(log100), Eotf::St428 => convert!(st428),
TransferFunction::Log316 => convert!(log316),
TransferFunction::St428 => convert!(st428),
} }
Self { r, g, b, a: 1.0 } Self { r, g, b, a: 1.0 }
} }
pub fn new_premultiplied( pub fn new_premultiplied(eotf: Eotf, mut r: f32, mut g: f32, mut b: f32, a: f32) -> Self {
transfer_function: TransferFunction, if eotf == Eotf::Linear {
mut r: f32,
mut g: f32,
mut b: f32,
a: f32,
) -> Self {
if transfer_function == TransferFunction::Linear {
return Self { r, g, b, a }; return Self { r, g, b, a };
} }
if a < 1.0 && a > 0.0 { if a < 1.0 && a > 0.0 {
@ -163,7 +134,7 @@ impl Color {
*c /= a; *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 { if a < 1.0 {
c = c * a; c = c * a;
} }
@ -179,40 +150,22 @@ impl Color {
} }
pub fn from_srgb(r: u8, g: u8, b: u8) -> Self { 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 { pub fn from_srgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
Self::new_premultiplied( Self::new_premultiplied(Eotf::Gamma22, to_f32(r), to_f32(g), to_f32(b), to_f32(a))
TransferFunction::Srgb,
to_f32(r),
to_f32(g),
to_f32(b),
to_f32(a),
)
} }
pub fn from_u32_premultiplied( pub fn from_u32_premultiplied(eotf: Eotf, r: u32, g: u32, b: u32, a: u32) -> Self {
transfer_function: TransferFunction,
r: u32,
g: u32,
b: u32,
a: u32,
) -> Self {
fn to_f32(c: u32) -> f32 { fn to_f32(c: u32) -> f32 {
((c as f64) / (u32::MAX as f64)) as f32 ((c as f64) / (u32::MAX as f64)) as f32
} }
Self::new_premultiplied( Self::new_premultiplied(eotf, to_f32(r), to_f32(g), to_f32(b), to_f32(a))
transfer_function,
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 { 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 { if a < 255 {
c = c * to_f32(a); c = c * to_f32(a);
} }
@ -220,23 +173,16 @@ impl Color {
} }
pub fn to_srgba_premultiplied(self) -> [u8; 4] { 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)] [to_u8(r), to_u8(g), to_u8(b), to_u8(a)]
} }
pub fn to_array(self, transfer_function: TransferFunction) -> [f32; 4] { pub fn to_array(self, eotf: Eotf) -> [f32; 4] {
self.to_array2(transfer_function, None) self.to_array2(eotf, None)
} }
pub fn to_array2(self, transfer_function: TransferFunction, alpha: Option<f32>) -> [f32; 4] { pub fn to_array2(self, eotf: Eotf, alpha: Option<f32>) -> [f32; 4] {
let mut res = [self.r, self.g, self.b, self.a]; 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 { fn linear(c: f32) -> f32 {
c c
} }
@ -246,22 +192,6 @@ impl Color {
let den = 1.0 + 18.6875 * c.powf(0.1593017578125); let den = 1.0 + 18.6875 * c.powf(0.1593017578125);
(num / den).powf(78.84375) (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 { fn st240(c: f32) -> f32 {
if c < 0.0228 { if c < 0.0228 {
4.0 * c 4.0 * c
@ -285,10 +215,13 @@ impl Color {
(48.0 * c / 52.37).powf(1.0 / 2.6) (48.0 * c / 52.37).powf(1.0 / 2.6)
} }
fn gamma22(c: f32) -> f32 { 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 { fn gamma28(c: f32) -> f32 {
c.powf(1.0 / 2.8) c.signum() * c.abs().powf(1.0 / 2.8)
} }
macro_rules! convert { macro_rules! convert {
($tf:ident) => {{ ($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 { if self.a < 1.0 && self.a > 0.0 {
for c in &mut res[..3] { for c in &mut res[..3] {
*c /= self.a; *c /= self.a;
} }
} }
match transfer_function { match eotf {
TransferFunction::Srgb => convert!(srgb), Eotf::Linear => convert!(linear),
TransferFunction::Linear => convert!(linear), Eotf::St2084Pq => convert!(st2084_pq),
TransferFunction::St2084Pq => convert!(st2084_pq), Eotf::Bt1886 => convert!(gamma24),
TransferFunction::Bt1886 => convert!(bt1886), Eotf::Gamma22 => convert!(gamma22),
TransferFunction::Gamma22 => convert!(gamma22), Eotf::Gamma28 => convert!(gamma28),
TransferFunction::Gamma28 => convert!(gamma28), Eotf::St240 => convert!(st240),
TransferFunction::St240 => convert!(st240), Eotf::Log100 => convert!(log100),
TransferFunction::ExtSrgb => convert!(ext_srgb), Eotf::Log316 => convert!(log316),
TransferFunction::Log100 => convert!(log100), Eotf::St428 => convert!(st428),
TransferFunction::Log316 => convert!(log316),
TransferFunction::St428 => convert!(st428),
} }
if self.a < 1.0 { if self.a < 1.0 {
for c in &mut res[..3] { for c in &mut res[..3] {
@ -343,7 +274,7 @@ impl Color {
impl From<jay_config::theme::Color> for Color { impl From<jay_config::theme::Color> for Color {
fn from(f: jay_config::theme::Color) -> Self { fn from(f: jay_config::theme::Color) -> Self {
let [r, g, b, a] = f.to_f32_premultiplied(); 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)
} }
} }

View file

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

View file

@ -1,8 +1,7 @@
use { use {
crate::{ crate::{
backend::{ backend::{
BackendColorSpace, BackendConnectorState, BackendTransferFunction, HardwareCursor, BackendColorSpace, BackendConnectorState, BackendEotfs, HardwareCursor, KeyState, Mode,
KeyState, Mode,
}, },
client::ClientId, client::ClientId,
cmm::cmm_description::ColorDescription, cmm::cmm_description::ColorDescription,
@ -14,7 +13,7 @@ use {
jay_output::JayOutput, jay_output::JayOutput,
jay_screencast::JayScreencast, jay_screencast::JayScreencast,
wl_buffer::WlBufferStorage, wl_buffer::WlBufferStorage,
wl_output::WlOutputGlobal, wl_output::{BlendSpace, WlOutputGlobal},
wl_seat::{ wl_seat::{
BTN_LEFT, NodeSeatState, SeatId, WlSeatGlobal, collect_kb_foci2, BTN_LEFT, NodeSeatState, SeatId, WlSeatGlobal, collect_kb_foci2,
tablet::{TabletTool, TabletToolChanges, TabletToolId}, tablet::{TabletTool, TabletToolChanges, TabletToolId},
@ -415,7 +414,7 @@ impl OutputNode {
AcquireSync::Implicit, AcquireSync::Implicit,
ReleaseSync::Implicit, ReleaseSync::Implicit,
self.global.persistent.transform.get(), self.global.persistent.transform.get(),
self.state.color_manager.srgb_srgb(), self.state.color_manager.srgb_gamma22(),
self.global.pos.get(), self.global.pos.get(),
render_hardware_cursors, render_hardware_cursors,
x_off - capture.rect.x1(), x_off - capture.rect.x1(),
@ -928,7 +927,7 @@ impl OutputNode {
} }
pub fn update_state(self: &Rc<Self>, old: BackendConnectorState, state: BackendConnectorState) { pub fn update_state(self: &Rc<Self>, 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 { if old.vrr != state.vrr {
self.schedule.set_vrr_enabled(state.vrr); self.schedule.set_vrr_enabled(state.vrr);
} }
@ -938,7 +937,7 @@ impl OutputNode {
self.global.format.set(state.format); 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_btf = self.global.btf.replace(btf);
let old_bcs = self.global.bcs.replace(bcs); let old_bcs = self.global.bcs.replace(bcs);
if (old_btf, old_bcs) == (btf, 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( fn find_stacked_at(
&self, &self,
stack: &LinkedList<Rc<dyn StackedNode>>, stack: &LinkedList<Rc<dyn StackedNode>>,

View file

@ -33,7 +33,7 @@ use {
logging::LogLevel, logging::LogLevel,
status::MessageFormat, status::MessageFormat,
theme::Color, theme::Color,
video::{ColorSpace, Format, GfxApi, TearingMode, TransferFunction, Transform, VrrMode}, video::{BlendSpace, ColorSpace, Eotf, Format, GfxApi, TearingMode, Transform, VrrMode},
window::{ContentType, TileState, WindowType}, window::{ContentType, TileState, WindowType},
workspace::WorkspaceDisplayOrder, workspace::WorkspaceDisplayOrder,
xwayland::XScalingMode, xwayland::XScalingMode,
@ -347,8 +347,9 @@ pub struct Output {
pub tearing: Option<Tearing>, pub tearing: Option<Tearing>,
pub format: Option<Format>, pub format: Option<Format>,
pub color_space: Option<ColorSpace>, pub color_space: Option<ColorSpace>,
pub transfer_function: Option<TransferFunction>, pub eotf: Option<Eotf>,
pub brightness: Option<Option<f64>>, pub brightness: Option<Option<f64>>,
pub blend_space: Option<BlendSpace>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View file

@ -19,7 +19,7 @@ use {
}, },
}, },
indexmap::IndexMap, indexmap::IndexMap,
jay_config::video::{ColorSpace, TransferFunction, Transform}, jay_config::video::{BlendSpace, ColorSpace, Eotf, Transform},
thiserror::Error, thiserror::Error,
}; };
@ -51,7 +51,7 @@ impl Parser for OutputParser<'_> {
let mut ext = Extractor::new(self.cx, span, table); let mut ext = Extractor::new(self.cx, span, table);
let ( let (
(name, match_val, x, y, scale, transform, mode, vrr_val, tearing_val, format_val), (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(( ) = ext.extract((
( (
opt(str("name")), opt(str("name")),
@ -69,6 +69,7 @@ impl Parser for OutputParser<'_> {
recover(opt(str("color-space"))), recover(opt(str("color-space"))),
recover(opt(str("transfer-function"))), recover(opt(str("transfer-function"))),
opt(val("brightness")), opt(val("brightness")),
recover(opt(str("blend-space"))),
), ),
))?; ))?;
let transform = match transform { let transform = match transform {
@ -103,17 +104,13 @@ impl Parser for OutputParser<'_> {
} }
}, },
}; };
let transfer_function = match transfer_function { let eotf = match eotf {
None => None, None => None,
Some(tf) => match tf.value { Some(tf) => match tf.value {
"default" => Some(TransferFunction::DEFAULT), "default" => Some(Eotf::DEFAULT),
"pq" => Some(TransferFunction::PQ), "pq" => Some(Eotf::PQ),
_ => { _ => {
log::warn!( log::warn!("Unknown EOTF {}: {}", tf.value, self.cx.error3(tf.span));
"Unknown transfer function {}: {}",
tf.value,
self.cx.error3(tf.span)
);
None 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 { Ok(Output {
name: name.despan().map(|v| v.to_string()), name: name.despan().map(|v| v.to_string()),
match_: match_val.parse_map(&mut OutputMatchParser(self.cx))?, match_: match_val.parse_map(&mut OutputMatchParser(self.cx))?,
@ -193,8 +205,9 @@ impl Parser for OutputParser<'_> {
tearing, tearing,
format, format,
color_space, color_space,
transfer_function, eotf,
brightness, brightness,
blend_space,
}) })
} }
} }

View file

@ -44,7 +44,7 @@ use {
theme::{reset_colors, reset_font, reset_sizes, set_font}, theme::{reset_colors, reset_font, reset_sizes, set_font},
toggle_float_above_fullscreen, toggle_show_bar, toggle_float_above_fullscreen, toggle_show_bar,
video::{ 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_connector_connected, on_connector_disconnected, on_graphics_initialized,
on_new_connector, on_new_drm_device, set_direct_scanout_enabled, set_gfx_api, on_new_connector, on_new_drm_device, set_direct_scanout_enabled, set_gfx_api,
set_tearing_mode, set_vrr_cursor_hz, set_vrr_mode, set_tearing_mode, set_vrr_cursor_hz, set_vrr_mode,
@ -769,14 +769,17 @@ impl Output {
if let Some(format) = self.format { if let Some(format) = self.format {
c.set_format(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 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); c.set_colors(cs, tf);
} }
if let Some(brightness) = self.brightness { if let Some(brightness) = self.brightness {
c.set_brightness(brightness); c.set_brightness(brightness);
} }
if let Some(bs) = self.blend_space {
c.set_blend_space(bs);
}
} }
} }

View file

@ -572,6 +572,14 @@
} }
] ]
}, },
"BlendSpace": {
"type": "string",
"description": "A color blend space.\n",
"enum": [
"srgb",
"linear"
]
},
"Brightness": { "Brightness": {
"description": "The brightness setting of an output.\n", "description": "The brightness setting of an output.\n",
"anyOf": [ "anyOf": [
@ -1141,6 +1149,14 @@
} }
] ]
}, },
"Eotf": {
"type": "string",
"description": "The EOTF of an output.\n",
"enum": [
"default",
"pq"
]
},
"Exec": { "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", "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": [ "anyOf": [
@ -1641,12 +1657,16 @@
"$ref": "#/$defs/ColorSpace" "$ref": "#/$defs/ColorSpace"
}, },
"transfer-function": { "transfer-function": {
"description": "The transfer function of the output.\n", "description": "The EOTF of the output.\n",
"$ref": "#/$defs/TransferFunction" "$ref": "#/$defs/Eotf"
}, },
"brightness": { "brightness": {
"description": "The brightness of the output.\n\nThis setting has no effect unless the vulkan renderer is used.\n", "description": "The brightness of the output.\n\nThis setting has no effect unless the vulkan renderer is used.\n",
"$ref": "#/$defs/Brightness" "$ref": "#/$defs/Brightness"
},
"blend-space": {
"description": "The blend space of the output.\n\nThe default is `srgb`.\n",
"$ref": "#/$defs/BlendSpace"
} }
}, },
"required": [ "required": [
@ -1902,14 +1922,6 @@
"floating" "floating"
] ]
}, },
"TransferFunction": {
"type": "string",
"description": "The transfer function of an output.\n",
"enum": [
"default",
"pq"
]
},
"Transform": { "Transform": {
"type": "string", "type": "string",
"description": "An output transformation.", "description": "An output transformation.",

View file

@ -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. The value of this field should be a string.
<a name="types-BlendSpace"></a>
### `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.
<a name="types-Brightness"></a> <a name="types-Brightness"></a>
### `Brightness` ### `Brightness`
@ -814,7 +833,7 @@ The string should have one of the following values:
The default brightness setting. The default brightness setting.
The behavior depends on the transfer function: The behavior depends on the EOTF:
- `default`: The maximum brightness of the output. - `default`: The maximum brightness of the output.
- `PQ`: 203 cd/m^2 - `PQ`: 203 cd/m^2
@ -2303,6 +2322,25 @@ The table has the following fields:
The numbers should be integers. The numbers should be integers.
<a name="types-Eotf"></a>
### `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.
<a name="types-Exec"></a> <a name="types-Exec"></a>
### `Exec` ### `Exec`
@ -3517,9 +3555,9 @@ The table has the following fields:
- `transfer-function` (optional): - `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): - `brightness` (optional):
@ -3529,6 +3567,14 @@ The table has the following fields:
The value of this field should be a [Brightness](#types-Brightness). 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).
<a name="types-OutputMatch"></a> <a name="types-OutputMatch"></a>
### `OutputMatch` ### `OutputMatch`
@ -4215,25 +4261,6 @@ The string should have one of the following values:
<a name="types-TransferFunction"></a>
### `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.
<a name="types-Transform"></a> <a name="types-Transform"></a>
### `Transform` ### `Transform`

View file

@ -1956,10 +1956,10 @@ Output:
description: | description: |
The color space of the output. The color space of the output.
transfer-function: transfer-function:
ref: TransferFunction ref: Eotf
required: false required: false
description: | description: |
The transfer function of the output. The EOTF of the output.
brightness: brightness:
ref: Brightness ref: Brightness
required: false required: false
@ -1967,6 +1967,13 @@ Output:
The brightness of the output. The brightness of the output.
This setting has no effect unless the vulkan renderer is used. 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: Transform:
@ -3293,15 +3300,15 @@ ColorSpace:
description: The BT.2020 color space. description: The BT.2020 color space.
TransferFunction: Eotf:
description: | description: |
The transfer function of an output. The EOTF of an output.
kind: string kind: string
values: values:
- value: default - value: default
description: The default transfer function (usually sRGB). description: The default EOTF (usually gamma22).
- value: pq - value: pq
description: The PQ transfer function. description: The PQ EOTF.
Brightness: Brightness:
@ -3317,7 +3324,7 @@ Brightness:
description: | description: |
The default brightness setting. The default brightness setting.
The behavior depends on the transfer function: The behavior depends on the EOTF:
- `default`: The maximum brightness of the output. - `default`: The maximum brightness of the output.
- `PQ`: 203 cd/m^2 - `PQ`: 203 cd/m^2
@ -4029,3 +4036,14 @@ WorkspaceDisplayOrder:
description: Workspaces are not sorted and can be manually dragged. description: Workspaces are not sorted and can be manually dragged.
- value: sorted - value: sorted
description: Workspaces are sorted alphabetically and cannot be manually dragged. 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.

View file

@ -83,7 +83,7 @@ request set_flip_margin (since = 10) {
request set_colors (since = 15) { request set_colors (since = 15) {
output: str, output: str,
color_space: str, color_space: str,
transfer_function: str, eotf: str,
} }
request set_brightness (since = 16) { request set_brightness (since = 16) {
@ -95,6 +95,11 @@ request unset_brightness (since = 16) {
output: str, output: str,
} }
request set_blend_space (since = 21) {
output: str,
blend_space: str,
}
# events # events
event global { event global {
@ -184,12 +189,12 @@ event current_color_space (since = 15) {
color_space: str, color_space: str,
} }
event supported_transfer_function (since = 15) { event supported_eotf (since = 15) {
transfer_function: str, eotf: str,
} }
event current_transfer_function (since = 15) { event current_eotf (since = 15) {
transfer_function: str, eotf: str,
} }
event brightness_range (since = 16) { event brightness_range (since = 16) {
@ -201,3 +206,7 @@ event brightness_range (since = 16) {
event brightness (since = 16) { event brightness (since = 16) {
lux: pod(f64), lux: pod(f64),
} }
event blend_space (since = 21) {
blend_space: str,
}