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},
timer::Timer,
video::{
ColorSpace, Connector, DrmDevice, Format, GfxApi, Mode, TearingMode, TransferFunction,
BlendSpace, ColorSpace, Connector, DrmDevice, Eotf, Format, GfxApi, Mode, TearingMode,
Transform, VrrMode,
connector_type::{CON_UNKNOWN, ConnectorType},
},
@ -1042,16 +1042,18 @@ impl ConfigClient {
self.send(&ClientMessage::ConnectorSetFormat { connector, format });
}
pub fn connector_set_colors(
&self,
connector: Connector,
color_space: ColorSpace,
transfer_function: TransferFunction,
) {
pub fn connector_set_colors(&self, connector: Connector, color_space: ColorSpace, eotf: Eotf) {
self.send(&ClientMessage::ConnectorSetColors {
connector,
color_space,
transfer_function,
eotf,
});
}
pub fn connector_set_blend_space(&self, connector: Connector, blend_space: BlendSpace) {
self.send(&ClientMessage::ConnectorSetBlendSpace {
connector,
blend_space,
});
}

View file

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

View file

@ -273,26 +273,33 @@ impl Connector {
get!().connector_set_format(self, format);
}
/// Sets the color space and transfer function of the connector.
/// Sets the color space and EOTF of the connector.
///
/// By default, the default values are used which usually means sRGB color space with
/// sRGB transfer function.
/// gamma22 EOTF.
///
/// If the output supports it, HDR10 can be enabled by setting the color space to
/// BT.2020 and the transfer function to PQ.
/// BT.2020 and the EOTF to PQ.
///
/// Note that some displays might ignore incompatible settings.
pub fn set_colors(self, color_space: ColorSpace, transfer_function: TransferFunction) {
get!().connector_set_colors(self, color_space, transfer_function);
pub fn set_colors(self, color_space: ColorSpace, eotf: Eotf) {
get!().connector_set_colors(self, color_space, eotf);
}
/// Sets the space in which blending is performed for this output.
///
/// The default is [`BlendSpace::SRGB`]
pub fn set_blend_space(self, blend_space: BlendSpace) {
get!().connector_set_blend_space(self, blend_space);
}
/// Sets the brightness of the output.
///
/// By default or when `brightness` is `None`, the brightness depends on the
/// transfer function:
/// EOTF:
///
/// - [`TransferFunction::DEFAULT`]: The maximum brightness of the output.
/// - [`TransferFunction::PQ`]: 203 cd/m^2.
/// - [`Eotf::DEFAULT`]: The maximum brightness of the output.
/// - [`Eotf::PQ`]: 203 cd/m^2.
///
/// This should only be used with the PQ transfer function. If the default transfer
/// function is used, you should instead calibrate the hardware directly.
@ -718,13 +725,29 @@ impl ColorSpace {
pub const BT2020: Self = Self(1);
}
/// A transfer function.
/// An electro-optical transfer function (EOTF).
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct TransferFunction(pub u32);
pub struct Eotf(pub u32);
impl TransferFunction {
/// The default transfer function (usually sRGB).
#[deprecated = "use the Eotf type instead"]
pub type TransferFunction = Eotf;
impl Eotf {
/// The default EOTF (usually gamma22).
pub const DEFAULT: Self = Self(0);
/// The PQ transfer function.
/// The PQ EOTF.
pub const PQ: Self = Self(1);
}
/// A space in which color blending is performed.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct BlendSpace(pub u32);
impl BlendSpace {
/// The sRGB blend space with sRGB primaries and gamma22 transfer function. This is
/// the classic desktop blend space.
pub const SRGB: Self = Self(0);
/// The linear blend space performs blending in linear space, which is more physically
/// correct but leads to much lighter output when blending light and dark colors.
pub const LINEAR: Self = Self(1);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
use {
crate::{
cli::{GlobalArgs, color::parse_color, duration::parse_duration},
cmm::cmm_transfer_function::TransferFunction,
cmm::cmm_eotf::Eotf,
tools::tool_client::{ToolClient, with_tool_client},
wire::jay_damage_tracking::{SetVisualizerColor, SetVisualizerDecay, SetVisualizerEnabled},
},
@ -86,7 +86,7 @@ impl DamageTracking {
}
DamageTrackingCmd::SetColor(c) => {
let color = parse_color(&c.color);
let [r, g, b, a] = color.to_array(TransferFunction::Srgb);
let [r, g, b, a] = color.to_array(Eotf::Gamma22);
tc.send(SetVisualizerColor {
self_id: dt,
r,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -165,7 +165,7 @@ impl DamageVisualizer {
let dy = -cursor_rect.y1();
let decay_millis = decay.as_millis() as u64 as f32;
renderer.ops.push(GfxApiOpt::Sync);
let srgb = &self.color_manager.srgb_srgb().linear;
let srgb = &self.color_manager.srgb_gamma22().linear;
for entry in entries.iter().rev() {
let region = Region::new(entry.rect);
let region = region.subtract_cow(&used);

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

@ -69,6 +69,14 @@ pub struct TexColorManagementData {
unsafe impl Packed for TexColorManagementData {}
#[derive(Copy, Clone, Debug)]
#[repr(C, align(16))]
pub struct OutColorManagementData {
pub matrix: [[f32; 4]; 4],
}
unsafe impl Packed for OutColorManagementData {}
#[derive(Copy, Clone, Debug)]
#[repr(C)]
pub struct LegacyTexPushConstants {

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 = 1) const bool has_alpha_multiplier = false;
layout(constant_id = 2) const uint eotf = 0;
layout(constant_id = 3) const uint oetf = 0;
layout(constant_id = 3) const uint inv_eotf = 0;
layout(constant_id = 4) const bool has_matrix = false;
#endif

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
use {
crate::{
client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientError},
cmm::cmm_transfer_function::TransferFunction,
cmm::cmm_eotf::Eotf,
globals::{Global, GlobalName},
leaks::Tracker,
object::{Object, Version},
@ -97,7 +97,7 @@ impl JayDamageTrackingRequestHandler for JayDamageTracking {
req: SetVisualizerColor,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
let color = Color::new(TransferFunction::Srgb, req.r, req.g, req.b) * req.a;
let color = Color::new(Eotf::Gamma22, req.r, req.g, req.b) * req.a;
self.client.state.damage_visualizer.set_color(color);
Ok(())
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
use {
crate::{
cmm::cmm_transfer_function::TransferFunction,
cmm::cmm_eotf::Eotf,
it::{
test_error::TestResult, test_ifs::test_buffer::TestBuffer, test_object::TestObject,
test_transport::TestTransport,
@ -32,7 +32,7 @@ impl TestSinglePixelBufferManager {
destroyed: Cell::new(false),
});
let map = |c: f32| (c as f64 * u32::MAX as f64) as u32;
let [r, g, b, a] = color.to_array(TransferFunction::Srgb);
let [r, g, b, a] = color.to_array(Eotf::Gamma22);
self.tran.send(CreateU32RgbaBuffer {
self_id: self.id,
id: obj.id,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -476,9 +476,9 @@ impl ConnectorData {
if old.format != s.format {
self.head_managers.handle_format_change(s.format);
}
if (old.color_space, old.transfer_function) != (s.color_space, s.transfer_function) {
if (old.color_space, old.eotf) != (s.color_space, s.eotf) {
self.head_managers
.handle_colors_change(s.color_space, s.transfer_function);
.handle_colors_change(s.color_space, s.eotf);
}
if old.mode != s.mode {
self.head_managers.handle_mode_change(s.mode);
@ -1290,7 +1290,7 @@ impl State {
AcquireSync::Unnecessary,
ReleaseSync::None,
transform,
self.color_manager.srgb_srgb(),
self.color_manager.srgb_gamma22(),
position,
true,
x_off - capture.rect.x1(),

View file

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

View file

@ -1,6 +1,6 @@
use {
crate::{
cmm::cmm_transfer_function::TransferFunction,
cmm::cmm_eotf::Eotf,
cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker, PendingJob},
format::ARGB8888,
gfx_api::{
@ -188,7 +188,7 @@ fn render(
data.layout.set_text(text);
}
let font_height = data.layout.pixel_size().1;
let [r, g, b, a] = color.to_array(TransferFunction::Srgb);
let [r, g, b, a] = color.to_array(Eotf::Gamma22);
data.cctx.set_operator(CAIRO_OPERATOR_SOURCE);
data.cctx.set_source_rgba(r as _, g as _, b as _, a as _);
let y = y.unwrap_or((height - font_height) / 2);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -44,7 +44,7 @@ use {
theme::{reset_colors, reset_font, reset_sizes, set_font},
toggle_float_above_fullscreen, toggle_show_bar,
video::{
ColorSpace, Connector, DrmDevice, TransferFunction, connectors, drm_devices,
ColorSpace, Connector, DrmDevice, Eotf, connectors, drm_devices,
on_connector_connected, on_connector_disconnected, on_graphics_initialized,
on_new_connector, on_new_drm_device, set_direct_scanout_enabled, set_gfx_api,
set_tearing_mode, set_vrr_cursor_hz, set_vrr_mode,
@ -769,14 +769,17 @@ impl Output {
if let Some(format) = self.format {
c.set_format(format);
}
if self.color_space.is_some() || self.transfer_function.is_some() {
if self.color_space.is_some() || self.eotf.is_some() {
let cs = self.color_space.unwrap_or(ColorSpace::DEFAULT);
let tf = self.transfer_function.unwrap_or(TransferFunction::DEFAULT);
let tf = self.eotf.unwrap_or(Eotf::DEFAULT);
c.set_colors(cs, tf);
}
if let Some(brightness) = self.brightness {
c.set_brightness(brightness);
}
if let Some(bs) = self.blend_space {
c.set_blend_space(bs);
}
}
}

View file

@ -572,6 +572,14 @@
}
]
},
"BlendSpace": {
"type": "string",
"description": "A color blend space.\n",
"enum": [
"srgb",
"linear"
]
},
"Brightness": {
"description": "The brightness setting of an output.\n",
"anyOf": [
@ -1141,6 +1149,14 @@
}
]
},
"Eotf": {
"type": "string",
"description": "The EOTF of an output.\n",
"enum": [
"default",
"pq"
]
},
"Exec": {
"description": "Describes how to execute a program.\n\n- Example 1:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = \"alacritty\" }\n ```\n\n- Example 2:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = [\"notify-send\", \"hello world\"] }\n ```\n\n- Example 3:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = { prog = \"notify-send\", args = [\"hello world\"], env.WAYLAND_DISPLAY = \"2\" } }\n ```\n",
"anyOf": [
@ -1641,12 +1657,16 @@
"$ref": "#/$defs/ColorSpace"
},
"transfer-function": {
"description": "The transfer function of the output.\n",
"$ref": "#/$defs/TransferFunction"
"description": "The EOTF of the output.\n",
"$ref": "#/$defs/Eotf"
},
"brightness": {
"description": "The brightness of the output.\n\nThis setting has no effect unless the vulkan renderer is used.\n",
"$ref": "#/$defs/Brightness"
},
"blend-space": {
"description": "The blend space of the output.\n\nThe default is `srgb`.\n",
"$ref": "#/$defs/BlendSpace"
}
},
"required": [
@ -1902,14 +1922,6 @@
"floating"
]
},
"TransferFunction": {
"type": "string",
"description": "The transfer function of an output.\n",
"enum": [
"default",
"pq"
]
},
"Transform": {
"type": "string",
"description": "An output transformation.",

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

View file

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

View file

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