From 82085a3858dddedc0596b67ef31d451ad3be997c Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 1 Mar 2025 13:54:12 +0100 Subject: [PATCH 1/7] cmm: add color-management module --- src/cli/damage_tracking.rs | 2 +- src/cmm.rs | 8 + src/cmm/cmm_description.rs | 81 ++++++ src/cmm/cmm_luminance.rs | 72 +++++ src/cmm/cmm_manager.rs | 204 ++++++++++++++ src/cmm/cmm_primaries.rs | 121 ++++++++ src/cmm/cmm_tests.rs | 201 ++++++++++++++ src/cmm/cmm_transfer_function.rs | 5 + src/cmm/cmm_transform.rs | 258 ++++++++++++++++++ src/compositor.rs | 2 + src/config/handler.rs | 3 +- src/gfx_apis/gl.rs | 3 +- src/gfx_apis/gl/renderer/framebuffer.rs | 3 +- src/gfx_apis/vulkan/renderer.rs | 3 +- src/ifs/jay_damage_tracking.rs | 3 +- .../test_single_pixel_buffer_manager.rs | 3 +- src/macros.rs | 4 +- src/main.rs | 4 +- src/portal/ptl_text.rs | 3 +- src/renderer.rs | 3 +- src/state.rs | 3 + src/text.rs | 3 +- src/theme.rs | 8 +- src/utils.rs | 2 + src/utils/free_list.rs | 93 +++++++ src/utils/free_list/tests.rs | 40 +++ src/utils/ordered_float.rs | 67 +++++ 27 files changed, 1182 insertions(+), 20 deletions(-) create mode 100644 src/cmm.rs create mode 100644 src/cmm/cmm_description.rs create mode 100644 src/cmm/cmm_luminance.rs create mode 100644 src/cmm/cmm_manager.rs create mode 100644 src/cmm/cmm_primaries.rs create mode 100644 src/cmm/cmm_tests.rs create mode 100644 src/cmm/cmm_transfer_function.rs create mode 100644 src/cmm/cmm_transform.rs create mode 100644 src/utils/free_list.rs create mode 100644 src/utils/free_list/tests.rs create mode 100644 src/utils/ordered_float.rs diff --git a/src/cli/damage_tracking.rs b/src/cli/damage_tracking.rs index 726a0532..9532ff1b 100644 --- a/src/cli/damage_tracking.rs +++ b/src/cli/damage_tracking.rs @@ -1,7 +1,7 @@ use { crate::{ cli::{GlobalArgs, color::parse_color, duration::parse_duration}, - theme::TransferFunction, + cmm::cmm_transfer_function::TransferFunction, tools::tool_client::{ToolClient, with_tool_client}, wire::jay_damage_tracking::{SetVisualizerColor, SetVisualizerDecay, SetVisualizerEnabled}, }, diff --git a/src/cmm.rs b/src/cmm.rs new file mode 100644 index 00000000..dac260c1 --- /dev/null +++ b/src/cmm.rs @@ -0,0 +1,8 @@ +pub mod cmm_description; +pub mod cmm_luminance; +pub mod cmm_manager; +pub mod cmm_primaries; +#[cfg(test)] +mod cmm_tests; +pub mod cmm_transfer_function; +pub mod cmm_transform; diff --git a/src/cmm/cmm_description.rs b/src/cmm/cmm_description.rs new file mode 100644 index 00000000..a1c1a499 --- /dev/null +++ b/src/cmm/cmm_description.rs @@ -0,0 +1,81 @@ +use { + crate::{ + cmm::{ + cmm_luminance::{Luminance, white_balance}, + cmm_manager::Shared, + cmm_primaries::{NamedPrimaries, Primaries}, + cmm_transfer_function::TransferFunction, + cmm_transform::{ColorMatrix, Local, Xyz, bradford_adjustment}, + }, + utils::free_list::FreeList, + }, + std::rc::Rc, +}; + +linear_ids!(LinearColorDescriptionIds, LinearColorDescriptionId, u64); + +pub type ColorDescriptionIds = FreeList; + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct ColorDescriptionId(u32); + +impl From for ColorDescriptionId { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl From for u32 { + fn from(value: ColorDescriptionId) -> Self { + value.0 + } +} + +#[derive(Debug)] +pub struct LinearColorDescription { + pub id: LinearColorDescriptionId, + pub primaries: Primaries, + pub xyz_from_local: ColorMatrix, + pub local_from_xyz: ColorMatrix, + pub luminance: Luminance, + pub(super) shared: Rc, +} + +#[derive(Debug)] +pub struct ColorDescription { + pub id: ColorDescriptionId, + #[expect(dead_code)] + pub linear: Rc, + #[expect(dead_code)] + pub named_primaries: Option, + #[expect(dead_code)] + pub transfer_function: TransferFunction, + pub(super) shared: Rc, +} + +impl LinearColorDescription { + #[expect(dead_code)] + pub fn color_transform(&self, target: &Self) -> ColorMatrix { + let mut mat = target.local_from_xyz; + if self.luminance != target.luminance { + mat *= white_balance(&self.luminance, &target.luminance, target.primaries.wp); + } + if self.primaries.wp != target.primaries.wp { + mat *= bradford_adjustment(self.primaries.wp, target.primaries.wp); + } + mat * self.xyz_from_local + } +} + +impl Drop for LinearColorDescription { + fn drop(&mut self) { + self.shared.dead_linear.fetch_add(1); + } +} + +impl Drop for ColorDescription { + fn drop(&mut self) { + self.shared.dead_complete.fetch_add(1); + self.shared.complete_ids.release(self.id); + } +} diff --git a/src/cmm/cmm_luminance.rs b/src/cmm/cmm_luminance.rs new file mode 100644 index 00000000..6f5100af --- /dev/null +++ b/src/cmm/cmm_luminance.rs @@ -0,0 +1,72 @@ +use crate::{ + cmm::cmm_transform::{ColorMatrix, Xyz}, + utils::ordered_float::F64, +}; + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct Luminance { + pub min: F64, + pub max: F64, + pub white: F64, +} + +impl Luminance { + pub const SRGB: Self = Self { + min: F64(0.2), + max: F64(80.0), + white: F64(80.0), + }; + + #[expect(dead_code)] + pub const BT1886: Self = Self { + min: F64(0.01), + max: F64(100.0), + white: F64(100.0), + }; + + pub const ST2084_PQ: Self = Self { + min: F64(0.0), + max: F64(10000.0), + white: F64(203.0), + }; + + #[expect(dead_code)] + pub const HLG: Self = Self { + min: F64(0.005), + max: F64(1000.0), + white: F64(203.0), + }; + + pub const WINDOWS_SCRGB: Self = Self { + min: Self::ST2084_PQ.min, + max: Self::ST2084_PQ.max, + // This causes the white balance formula (with target ST2084_PQ) to simplify to + // `Y * 80 / 10000`, meaning that sRGB pure white maps to a luminance of + // 80 cd/m^2. + white: F64(Self::ST2084_PQ.white.0 / 80.0 * Self::ST2084_PQ.max.0), + }; +} + +impl Default for Luminance { + fn default() -> Self { + Self::SRGB + } +} + +#[expect(non_snake_case)] +pub fn white_balance(from: &Luminance, to: &Luminance, w_to: (F64, F64)) -> ColorMatrix { + let a = ((from.max - from.min) / (to.max - to.min) * (to.white - from.min) + / (from.white - from.min)) + .0; + let d = ((from.min - to.min) / (to.max - to.min)).0.max(0.0); + let s = a - d; + let (F64(x_to), F64(y_to)) = w_to; + let X_to = x_to / y_to; + let Y_to = 1.0; + let Z_to = (1.0 - x_to - y_to) / y_to; + ColorMatrix::new([ + [s, 0.0, 0.0, d * X_to], + [0.0, s, 0.0, d * Y_to], + [0.0, 0.0, s, d * Z_to], + ]) +} diff --git a/src/cmm/cmm_manager.rs b/src/cmm/cmm_manager.rs new file mode 100644 index 00000000..23505765 --- /dev/null +++ b/src/cmm/cmm_manager.rs @@ -0,0 +1,204 @@ +use { + crate::{ + cmm::{ + cmm_description::{ + ColorDescription, ColorDescriptionIds, LinearColorDescription, + LinearColorDescriptionId, LinearColorDescriptionIds, + }, + cmm_luminance::Luminance, + cmm_primaries::{NamedPrimaries, Primaries}, + cmm_transfer_function::TransferFunction, + }, + utils::{copyhashmap::CopyHashMap, numcell::NumCell}, + }, + std::rc::{Rc, Weak}, +}; + +pub struct ColorManager { + linear_ids: LinearColorDescriptionIds, + linear_descriptions: CopyHashMap>, + complete_descriptions: CopyHashMap>, + shared: Rc, + srgb_srgb: Rc, + srgb_linear: Rc, + windows_scrgb: Rc, +} + +#[derive(Debug, Default)] +pub(super) struct Shared { + pub(super) dead_linear: NumCell, + pub(super) dead_complete: NumCell, + pub(super) complete_ids: ColorDescriptionIds, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +struct LinearDescriptionKey { + primaries: Primaries, + luminance: Luminance, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +struct CompleteDescriptionKey { + linear: LinearColorDescriptionId, + named_primaries: Option, + transfer_function: TransferFunction, +} + +impl ColorManager { + pub fn new() -> Rc { + let linear_ids = LinearColorDescriptionIds::default(); + let linear_descriptions = CopyHashMap::default(); + let complete_descriptions = CopyHashMap::default(); + let shared = Rc::new(Shared::default()); + let _ = shared.complete_ids.acquire(); + let srgb_srgb = get_description( + &shared, + &linear_descriptions, + &complete_descriptions, + &linear_ids, + Some(NamedPrimaries::Srgb), + Primaries::SRGB, + Luminance::SRGB, + TransferFunction::Srgb, + ); + let srgb_linear = get_description( + &shared, + &linear_descriptions, + &complete_descriptions, + &linear_ids, + Some(NamedPrimaries::Srgb), + Primaries::SRGB, + Luminance::SRGB, + TransferFunction::Linear, + ); + let windows_scrgb = get_description( + &shared, + &linear_descriptions, + &complete_descriptions, + &linear_ids, + Some(NamedPrimaries::Srgb), + Primaries::SRGB, + Luminance::WINDOWS_SCRGB, + TransferFunction::Linear, + ); + Rc::new(Self { + linear_ids, + linear_descriptions, + complete_descriptions, + shared, + srgb_srgb, + srgb_linear, + windows_scrgb, + }) + } + + #[expect(dead_code)] + pub fn srgb_srgb(&self) -> &Rc { + &self.srgb_srgb + } + + #[expect(dead_code)] + pub fn srgb_linear(&self) -> &Rc { + &self.srgb_linear + } + + #[expect(dead_code)] + pub fn windows_scrgb(&self) -> &Rc { + &self.windows_scrgb + } + + #[expect(dead_code)] + pub fn get_description( + self: &Rc, + named_primaries: Option, + primaries: Primaries, + luminance: Luminance, + transfer_function: TransferFunction, + ) -> Rc { + get_description( + &self.shared, + &self.linear_descriptions, + &self.complete_descriptions, + &self.linear_ids, + named_primaries, + primaries, + luminance, + transfer_function, + ) + } +} + +fn get_description( + shared: &Rc, + linear_descriptions: &CopyHashMap>, + complete_descriptions: &CopyHashMap>, + linear_ids: &LinearColorDescriptionIds, + named_primaries: Option, + primaries: Primaries, + luminance: Luminance, + transfer_function: TransferFunction, +) -> Rc { + macro_rules! gc { + ($d:ident, $i:expr) => { + if $d.len() > 16 && $i.get() * 2 > $d.len() { + $d.lock().retain(|_, d| d.strong_count() > 0); + $i.set(0); + } + }; + } + gc!(linear_descriptions, &shared.dead_linear); + gc!(complete_descriptions, &shared.dead_complete); + let key = LinearDescriptionKey { + primaries, + luminance, + }; + if let Some(d) = linear_descriptions.get(&key) { + if let Some(d) = d.upgrade() { + let key = CompleteDescriptionKey { + linear: d.id, + named_primaries, + transfer_function, + }; + if let Some(d) = complete_descriptions.get(&key) { + if let Some(d) = d.upgrade() { + return d; + } + shared.dead_complete.fetch_sub(1); + } + let d = Rc::new(ColorDescription { + id: shared.complete_ids.acquire(), + linear: d, + named_primaries, + transfer_function, + shared: shared.clone(), + }); + complete_descriptions.set(key, Rc::downgrade(&d)); + return d; + } + shared.dead_linear.fetch_sub(1); + } + let (xyz_from_local, local_from_xyz) = primaries.matrices(); + let d = Rc::new(LinearColorDescription { + id: linear_ids.next(), + primaries, + xyz_from_local, + local_from_xyz, + luminance, + shared: shared.clone(), + }); + linear_descriptions.set(key, Rc::downgrade(&d)); + let key = CompleteDescriptionKey { + linear: d.id, + named_primaries, + transfer_function, + }; + let d = Rc::new(ColorDescription { + id: shared.complete_ids.acquire(), + linear: d, + named_primaries, + transfer_function, + shared: shared.clone(), + }); + complete_descriptions.set(key, Rc::downgrade(&d)); + d +} diff --git a/src/cmm/cmm_primaries.rs b/src/cmm/cmm_primaries.rs new file mode 100644 index 00000000..39f0be05 --- /dev/null +++ b/src/cmm/cmm_primaries.rs @@ -0,0 +1,121 @@ +use {crate::utils::ordered_float::F64, std::hash::Hash}; + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub enum NamedPrimaries { + Srgb, + #[expect(dead_code)] + PalM, + #[expect(dead_code)] + Pal, + #[expect(dead_code)] + Ntsc, + #[expect(dead_code)] + GenericFilm, + #[expect(dead_code)] + Bt2020, + #[expect(dead_code)] + Cie1931Xyz, + #[expect(dead_code)] + DciP3, + #[expect(dead_code)] + DisplayP3, + #[expect(dead_code)] + AdobeRgb, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct Primaries { + pub r: (F64, F64), + pub g: (F64, F64), + pub b: (F64, F64), + pub wp: (F64, F64), +} + +impl Primaries { + pub const SRGB: Self = Self { + r: (F64(0.64), F64(0.33)), + g: (F64(0.3), F64(0.6)), + b: (F64(0.15), F64(0.06)), + wp: (F64(0.3127), F64(0.3290)), + }; + + pub const PAL_M: Self = Self { + r: (F64(0.67), F64(0.33)), + g: (F64(0.21), F64(0.71)), + b: (F64(0.14), F64(0.08)), + wp: (F64(0.310), F64(0.316)), + }; + + pub const PAL: Self = Self { + r: (F64(0.64), F64(0.33)), + g: (F64(0.29), F64(0.60)), + b: (F64(0.15), F64(0.06)), + wp: (F64(0.3127), F64(0.3290)), + }; + + pub const NTSC: Self = Self { + r: (F64(0.630), F64(0.340)), + g: (F64(0.310), F64(0.595)), + b: (F64(0.155), F64(0.070)), + wp: (F64(0.3127), F64(0.3290)), + }; + + pub const GENERIC_FILM: Self = Self { + r: (F64(0.681), F64(0.319)), + g: (F64(0.243), F64(0.692)), + b: (F64(0.145), F64(0.049)), + wp: (F64(0.310), F64(0.316)), + }; + + pub const BT2020: Self = Self { + r: (F64(0.708), F64(0.292)), + g: (F64(0.170), F64(0.797)), + b: (F64(0.131), F64(0.046)), + wp: (F64(0.3127), F64(0.3290)), + }; + + pub const CIE1931_XYZ: Self = Self { + r: (F64(1.0), F64(0.0)), + g: (F64(0.0), F64(1.0)), + b: (F64(0.0), F64(0.0)), + wp: (F64(1.0 / 3.0), F64(1.0 / 3.0)), + }; + + pub const DCI_P3: Self = Self { + r: (F64(0.680), F64(0.320)), + g: (F64(0.265), F64(0.690)), + b: (F64(0.150), F64(0.060)), + wp: (F64(0.314), F64(0.351)), + }; + + pub const DISPLAY_P3: Self = Self { + r: (F64(0.680), F64(0.320)), + g: (F64(0.265), F64(0.690)), + b: (F64(0.150), F64(0.060)), + wp: (F64(0.3127), F64(0.3290)), + }; + + pub const ADOBE_RGB: Self = Self { + r: (F64(0.64), F64(0.33)), + g: (F64(0.21), F64(0.71)), + b: (F64(0.15), F64(0.06)), + wp: (F64(0.3127), F64(0.3290)), + }; +} +impl NamedPrimaries { + #[expect(dead_code)] + pub const fn primaries(self) -> Primaries { + match self { + NamedPrimaries::Srgb => Primaries::SRGB, + NamedPrimaries::PalM => Primaries::PAL_M, + NamedPrimaries::Pal => Primaries::PAL, + NamedPrimaries::Ntsc => Primaries::NTSC, + NamedPrimaries::GenericFilm => Primaries::GENERIC_FILM, + NamedPrimaries::Bt2020 => Primaries::BT2020, + NamedPrimaries::Cie1931Xyz => Primaries::CIE1931_XYZ, + NamedPrimaries::DciP3 => Primaries::DCI_P3, + NamedPrimaries::DisplayP3 => Primaries::DISPLAY_P3, + NamedPrimaries::AdobeRgb => Primaries::ADOBE_RGB, + } + } +} diff --git a/src/cmm/cmm_tests.rs b/src/cmm/cmm_tests.rs new file mode 100644 index 00000000..52b98d72 --- /dev/null +++ b/src/cmm/cmm_tests.rs @@ -0,0 +1,201 @@ +mod matrices { + use crate::{cmm::cmm_primaries::Primaries, utils::ordered_float::F64}; + + fn check(primaries: Primaries, expected: [[f64; 4]; 3]) { + let (ltg, gtl) = primaries.matrices(); + println!("{:#?}", ltg); + assert!((ltg.0[0][0].0 - expected[0][0]).abs() < 0.001); + assert!((ltg.0[0][1].0 - expected[0][1]).abs() < 0.001); + assert!((ltg.0[0][2].0 - expected[0][2]).abs() < 0.001); + assert!((ltg.0[0][3].0 - expected[0][3]).abs() < 0.001); + assert!((ltg.0[1][0].0 - expected[1][0]).abs() < 0.001); + assert!((ltg.0[1][1].0 - expected[1][1]).abs() < 0.001); + assert!((ltg.0[1][2].0 - expected[1][2]).abs() < 0.001); + assert!((ltg.0[1][3].0 - expected[1][3]).abs() < 0.001); + assert!((ltg.0[2][0].0 - expected[2][0]).abs() < 0.001); + assert!((ltg.0[2][1].0 - expected[2][1]).abs() < 0.001); + assert!((ltg.0[2][2].0 - expected[2][2]).abs() < 0.001); + assert!((ltg.0[2][3].0 - expected[2][3]).abs() < 0.001); + let roundtrip = gtl * ltg; + assert!((roundtrip.0[0][0].0 - 1.0).abs() < 0.001); + assert!((roundtrip.0[0][1].0 - 0.0).abs() < 0.001); + assert!((roundtrip.0[0][2].0 - 0.0).abs() < 0.001); + assert!((roundtrip.0[0][3].0 - 0.0).abs() < 0.001); + assert!((roundtrip.0[1][0].0 - 0.0).abs() < 0.001); + assert!((roundtrip.0[1][1].0 - 1.0).abs() < 0.001); + assert!((roundtrip.0[1][2].0 - 0.0).abs() < 0.001); + assert!((roundtrip.0[1][3].0 - 0.0).abs() < 0.001); + assert!((roundtrip.0[2][0].0 - 0.0).abs() < 0.001); + assert!((roundtrip.0[2][1].0 - 0.0).abs() < 0.001); + assert!((roundtrip.0[2][2].0 - 1.0).abs() < 0.001); + assert!((roundtrip.0[2][3].0 - 0.0).abs() < 0.001); + } + + #[test] + fn srgb() { + check( + Primaries::SRGB, + [ + [0.4124564, 0.3575761, 0.1804375, 0.0], + [0.2126729, 0.7151522, 0.0721750, 0.0], + [0.0193339, 0.1191920, 0.9503041, 0.0], + ], + ); + } + + #[test] + fn cie1931_xyz() { + check( + Primaries::CIE1931_XYZ, + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + ], + ); + } + + #[test] + fn adobe_rgb() { + check( + Primaries::ADOBE_RGB, + [ + [0.5767309, 0.1855540, 0.1881852, 0.0], + [0.2973769, 0.6273491, 0.0752741, 0.0], + [0.0270343, 0.0706872, 0.9911085, 0.0], + ], + ); + } + + #[test] + fn apple_rgb() { + check( + Primaries { + r: (F64(0.625), F64(0.34)), + g: (F64(0.28), F64(0.595)), + b: (F64(0.155), F64(0.07)), + wp: (F64(0.31271), F64(0.32902)), + }, + [ + [0.4497288, 0.3162486, 0.1844926, 0.0], + [0.2446525, 0.6720283, 0.0833192, 0.0], + [0.0251848, 0.1411824, 0.9224628, 0.0], + ], + ); + } + + #[test] + fn bt2020() { + check( + Primaries::BT2020, + [ + [0.636958, 0.144617, 0.168881, 0.0], + [0.262700, 0.677998, 0.059302, 0.0], + [0.000000, 0.028073, 1.060985, 0.0], + ], + ); + } + + #[test] + fn pal() { + check( + Primaries::PAL, + [ + [0.4306190, 0.3415419, 0.1783091, 0.0], + [0.2220379, 0.7066384, 0.0713236, 0.0], + [0.0201853, 0.1295504, 0.9390944, 0.0], + ], + ); + } + + #[test] + fn dci_p3() { + check( + Primaries::DCI_P3, + [ + [0.445170, 0.277134, 0.172283, 0.0], + [0.209492, 0.721595, 0.068913, 0.0], + [-0.000000, 0.047061, 0.907355, 0.0], + ], + ); + } + + #[test] + fn display_p3() { + check( + Primaries::DISPLAY_P3, + [ + [0.486571, 0.265668, 0.198217, 0.0], + [0.228975, 0.691739, 0.079287, 0.0], + [-0.000000, 0.045113, 1.043944, 0.0], + ], + ); + } +} + +mod transforms { + use crate::cmm::{ + cmm_luminance::Luminance, cmm_manager::ColorManager, cmm_primaries::Primaries, + cmm_transfer_function::TransferFunction, + }; + + fn check(p1: Primaries, p2: Primaries, expected: [[f64; 4]; 3]) { + let manager = ColorManager::new(); + let d = |p| manager.get_description(None, p, Luminance::SRGB, TransferFunction::Linear); + let d1 = d(p1); + let d2 = d(p2); + let m = d1.linear.color_transform(&d2.linear); + println!("{:#?}", m); + assert!((m.0[0][0].0 - expected[0][0]).abs() < 0.001); + assert!((m.0[0][1].0 - expected[0][1]).abs() < 0.001); + assert!((m.0[0][2].0 - expected[0][2]).abs() < 0.001); + assert!((m.0[0][3].0 - expected[0][3]).abs() < 0.001); + assert!((m.0[1][0].0 - expected[1][0]).abs() < 0.001); + assert!((m.0[1][1].0 - expected[1][1]).abs() < 0.001); + assert!((m.0[1][2].0 - expected[1][2]).abs() < 0.001); + assert!((m.0[1][3].0 - expected[1][3]).abs() < 0.001); + assert!((m.0[2][0].0 - expected[2][0]).abs() < 0.001); + assert!((m.0[2][1].0 - expected[2][1]).abs() < 0.001); + assert!((m.0[2][2].0 - expected[2][2]).abs() < 0.001); + assert!((m.0[2][3].0 - expected[2][3]).abs() < 0.001); + } + + #[test] + fn srgb_to_bt2020() { + check( + Primaries::SRGB, + Primaries::BT2020, + [ + [0.627404, 0.329283, 0.043313, 0.0], + [0.069097, 0.919540, 0.011362, 0.0], + [0.016391, 0.088013, 0.895595, 0.0], + ], + ) + } + + #[test] + fn bt2020_to_srgb() { + check( + Primaries::BT2020, + Primaries::SRGB, + [ + [1.660491, -0.587641, -0.072850, 0.0], + [-0.124550, 1.132900, -0.008349, 0.0], + [-0.018151, -0.100579, 1.118730, 0.0], + ], + ) + } + + #[test] + fn srgb_to_dci_p3() { + check( + Primaries::SRGB, + Primaries::DCI_P3, + [ + [0.868580, 0.128919, 0.002501, 0.0], + [0.034540, 0.961811, 0.003648, 0.0], + [0.016771, 0.071040, 0.912189, 0.0], + ], + ) + } +} diff --git a/src/cmm/cmm_transfer_function.rs b/src/cmm/cmm_transfer_function.rs new file mode 100644 index 00000000..22fc9920 --- /dev/null +++ b/src/cmm/cmm_transfer_function.rs @@ -0,0 +1,5 @@ +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum TransferFunction { + Srgb, + Linear, +} diff --git a/src/cmm/cmm_transform.rs b/src/cmm/cmm_transform.rs new file mode 100644 index 00000000..c175caef --- /dev/null +++ b/src/cmm/cmm_transform.rs @@ -0,0 +1,258 @@ +use { + crate::{ + cmm::{cmm_primaries::Primaries, cmm_transfer_function::TransferFunction}, + theme::Color, + utils::{debug_fn::debug_fn, ordered_float::F64}, + }, + std::{ + fmt::{Debug, Formatter}, + hash::{Hash, Hasher}, + marker::PhantomData, + ops::{Mul, MulAssign}, + }, +}; + +pub struct ColorMatrix(pub [[F64; 4]; 3], PhantomData<(To, From)>); + +#[derive(Copy, Clone)] +pub struct Local; +#[derive(Copy, Clone)] +pub struct Xyz; +#[derive(Copy, Clone)] +pub struct Bradford; + +impl Copy for ColorMatrix {} + +impl Clone for ColorMatrix { + fn clone(&self) -> Self { + *self + } +} + +impl PartialEq for ColorMatrix { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for ColorMatrix {} + +impl Hash for ColorMatrix { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + +impl Debug for ColorMatrix { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("ColorMatrix") + .field(&format_matrix(&self.0)) + .finish() + } +} + +fn format_matrix<'a>(m: &'a [[F64; 4]; 3]) -> impl Debug + use<'a> { + debug_fn(move |f| { + let iter = m + .iter() + .copied() + .chain(Some([F64(0.0), F64(0.0), F64(0.0), F64(1.0)])) + .enumerate(); + if f.alternate() { + for (idx, row) in iter { + if idx > 0 { + f.write_str("\n")?; + } + write!( + f, + "{:7.4} {:7.4} {:7.4} {:7.4}", + row[0], row[1], row[2], row[3] + )?; + } + } else { + f.write_str("[")?; + for (idx, row) in iter { + if idx > 0 { + f.write_str(", ")?; + } + write!( + f, + "[{:.4}, {:.4}, {:.4}, {:.4}]", + row[0], row[1], row[2], row[3] + )?; + } + f.write_str("]")?; + } + Ok(()) + }) +} + +impl Mul> for ColorMatrix { + type Output = ColorMatrix; + + fn mul(self, rhs: ColorMatrix) -> Self::Output { + let a = &self.0; + let b = &rhs.0; + macro_rules! mul { + ($ar:expr, $bc:expr) => { + a[$ar][0] * b[0][$bc] + a[$ar][1] * b[1][$bc] + a[$ar][2] * b[2][$bc] + }; + } + let m = [ + [mul!(0, 0), mul!(0, 1), mul!(0, 2), mul!(0, 3) + a[0][3]], + [mul!(1, 0), mul!(1, 1), mul!(1, 2), mul!(1, 3) + a[1][3]], + [mul!(2, 0), mul!(2, 1), mul!(2, 2), mul!(2, 3) + a[2][3]], + ]; + ColorMatrix(m, PhantomData) + } +} + +impl MulAssign> for ColorMatrix { + fn mul_assign(&mut self, rhs: ColorMatrix) { + *self = *self * rhs; + } +} + +impl Mul<[f64; 3]> for ColorMatrix { + type Output = [f64; 3]; + + fn mul(self, rhs: [f64; 3]) -> Self::Output { + let a = &self.0; + macro_rules! mul { + ($ar:expr) => { + a[$ar][0].0 * rhs[0] + a[$ar][1].0 * rhs[1] + a[$ar][2].0 * rhs[2] + }; + } + [mul!(0), mul!(1), mul!(2)] + } +} + +impl Mul for ColorMatrix { + type Output = Color; + + fn mul(self, rhs: Color) -> Self::Output { + let mut rgba = rhs.to_array(TransferFunction::Linear); + let a = rgba[3]; + if a < 1.0 && a > 0.0 { + for c in &mut rgba[..3] { + *c /= a; + } + } + 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); + if a < 1.0 { + color = color * a; + } + color + } +} + +impl ColorMatrix { + pub const fn new(m: [[f64; 4]; 3]) -> Self { + let m = [ + [F64(m[0][0]), F64(m[0][1]), F64(m[0][2]), F64(m[0][3])], + [F64(m[1][0]), F64(m[1][1]), F64(m[1][2]), F64(m[1][3])], + [F64(m[2][0]), F64(m[2][1]), F64(m[2][2]), F64(m[2][3])], + ]; + Self(m, PhantomData) + } + + #[expect(dead_code)] + pub const fn to_f32(&self) -> [[f32; 4]; 4] { + let m = &self.0; + macro_rules! map { + ($r:expr, $c:expr) => { + m[$r][$c].0 as f32 + }; + } + [ + [map!(0, 0), map!(0, 1), map!(0, 2), map!(0, 3)], + [map!(1, 0), map!(1, 1), map!(1, 2), map!(1, 3)], + [map!(2, 0), map!(2, 1), map!(2, 2), map!(2, 3)], + [0.0, 0.0, 0.0, 1.0], + ] + } +} + +impl ColorMatrix { + const BFD: Self = Self::new([ + [0.8951, 0.2664, -0.1614, 0.0], + [-0.7502, 1.7135, 0.0367, 0.0], + [0.0389, -0.0685, 1.0296, 0.0], + ]); +} + +impl ColorMatrix { + const BFD_INV: Self = Self::new([ + [0.9870, -0.1471, 0.1600, 0.0], + [0.4323, 0.5184, 0.0493, 0.0], + [-0.0085, 0.04, 0.9685, 0.0], + ]); +} + +#[expect(non_snake_case)] +pub fn bradford_adjustment(w_from: (F64, F64), w_to: (F64, F64)) -> ColorMatrix { + let (F64(x_from), F64(y_from)) = w_from; + let (F64(x_to), F64(y_to)) = w_to; + let X_from = x_from / y_from; + let Z_from = (1.0 - x_from - y_from) / y_from; + let X_to = x_to / y_to; + let Z_to = (1.0 - x_to - y_to) / y_to; + let [R_from, G_from, B_from] = ColorMatrix::BFD * [X_from, 1.0, Z_from]; + let [R_to, G_to, B_to] = ColorMatrix::BFD * [X_to, 1.0, Z_to]; + let adj = ColorMatrix::new([ + [R_to / R_from, 0.0, 0.0, 0.0], + [0.0, G_to / G_from, 0.0, 0.0], + [0.0, 0.0, B_to / B_from, 0.0], + ]); + ColorMatrix::BFD_INV * adj * ColorMatrix::BFD +} + +impl Primaries { + #[expect(non_snake_case)] + pub const fn matrices(&self) -> (ColorMatrix, ColorMatrix) { + let (F64(xw), F64(yw)) = self.wp; + let Xw = xw / yw; + let Zw = (1.0 - xw - yw) / yw; + let (F64(xr), F64(yr)) = self.r; + let (F64(xg), F64(yg)) = self.g; + let (F64(xb), F64(yb)) = self.b; + let zr = 1.0 - xr - yr; + let zg = 1.0 - xg - yg; + let zb = 1.0 - xb - yb; + let srx = yg * zb - zg * yb; + let sry = zg * xb - xg * zb; + let srz = xg * yb - yg * xb; + let sgx = zr * yb - yr * zb; + let sgz = yr * xb - xr * yb; + let sgy = xr * zb - zr * xb; + let sbx = yr * zg - zr * yg; + let sby = zr * xg - xr * zg; + let sbz = xr * yg - yr * xg; + let det = srz + sgz + sbz; + let sr = srx * Xw + sry + srz * Zw; + let sg = sgx * Xw + sgy + sgz * Zw; + let sb = sbx * Xw + sby + sbz * Zw; + let det_inv = 1.0 / det; + let sr_inv = 1.0 / sr; + let sg_inv = 1.0 / sg; + let sb_inv = 1.0 / sb; + let srp = sr * det_inv; + let sgp = sg * det_inv; + let sbp = sb * det_inv; + let XYZ_from_local = [ + [srp * xr, sgp * xg, sbp * xb, 0.0], + [srp * yr, sgp * yg, sbp * yb, 0.0], + [srp * zr, sgp * zg, sbp * zb, 0.0], + ]; + let local_from_XYZ = [ + [srx * sr_inv, sry * sr_inv, srz * sr_inv, 0.0], + [sgx * sg_inv, sgy * sg_inv, sgz * sg_inv, 0.0], + [sbx * sb_inv, sby * sb_inv, sbz * sb_inv, 0.0], + ]; + ( + ColorMatrix::new(XYZ_from_local), + ColorMatrix::new(local_from_XYZ), + ) + } +} diff --git a/src/compositor.rs b/src/compositor.rs index 06ca97d2..e9ffb99c 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -12,6 +12,7 @@ use { cli::{CliBackend, GlobalArgs, RunArgs}, client::{ClientId, Clients}, clientmem::{self, ClientMemError}, + cmm::cmm_manager::ColorManager, config::ConfigProxy, cpu_worker::{CpuWorker, CpuWorkerError}, damage::{DamageVisualizer, visualize_damage}, @@ -284,6 +285,7 @@ fn start_compositor2( data_control_device_ids: Default::default(), workspace_managers: Default::default(), color_management_enabled: Cell::new(false), + color_manager: ColorManager::new(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/config/handler.rs b/src/config/handler.rs index fb4ee4e5..aa416023 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -5,6 +5,7 @@ use { self, ConnectorId, DrmDeviceId, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceId, }, + cmm::cmm_transfer_function::TransferFunction, compositor::MAX_EXTENTS, config::ConfigProxy, format::config_formats, @@ -14,7 +15,7 @@ use { output_schedule::map_cursor_hz, scale::Scale, state::{ConnectorData, DeviceHandlerData, DrmDevData, OutputData, State}, - theme::{Color, ThemeSized, TransferFunction}, + theme::{Color, ThemeSized}, tree::{ ContainerNode, ContainerSplit, FloatNode, Node, NodeVisitorBase, OutputNode, TearingMode, VrrMode, WsMoveConfig, move_ws_to_output, diff --git a/src/gfx_apis/gl.rs b/src/gfx_apis/gl.rs index ff86fc95..ff3dc526 100644 --- a/src/gfx_apis/gl.rs +++ b/src/gfx_apis/gl.rs @@ -67,6 +67,7 @@ macro_rules! dynload { use { crate::{ + cmm::cmm_transfer_function::TransferFunction, gfx_api::{ AcquireSync, CopyTexture, FillRect, GfxApiOpt, GfxContext, GfxError, GfxTexture, ReleaseSync, SyncFile, @@ -84,7 +85,7 @@ use { GL_TRIANGLE_STRIP, GL_TRIANGLES, }, }, - theme::{Color, TransferFunction}, + theme::Color, utils::{errorfmt::ErrorFmt, rc_eq::rc_eq, vecstorage::VecStorage}, video::{ dmabuf::DMA_BUF_SYNC_READ, diff --git a/src/gfx_apis/gl/renderer/framebuffer.rs b/src/gfx_apis/gl/renderer/framebuffer.rs index 550fdd18..714cdc69 100644 --- a/src/gfx_apis/gl/renderer/framebuffer.rs +++ b/src/gfx_apis/gl/renderer/framebuffer.rs @@ -1,5 +1,6 @@ use { crate::{ + cmm::cmm_transfer_function::TransferFunction, format::Format, gfx_api::{ AcquireSync, AsyncShmGfxTextureCallback, GfxApiOpt, GfxBlendBuffer, GfxError, @@ -18,7 +19,7 @@ use { sys::{GL_ONE, GL_ONE_MINUS_SRC_ALPHA}, }, rect::Region, - theme::{Color, TransferFunction}, + theme::Color, }, std::{ cell::Cell, diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index 2258d07c..b46e5af7 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -1,6 +1,7 @@ use { crate::{ async_engine::{AsyncEngine, SpawnedFuture}, + cmm::cmm_transfer_function::TransferFunction, cpu_worker::PendingJob, format::XRGB8888, gfx_api::{ @@ -30,7 +31,7 @@ use { }, io_uring::IoUring, rect::{Rect, Region}, - theme::{Color, TransferFunction}, + theme::Color, utils::{copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, stack::Stack}, video::dmabuf::{DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE, dma_buf_export_sync_file}, }, diff --git a/src/ifs/jay_damage_tracking.rs b/src/ifs/jay_damage_tracking.rs index 021f512c..126a2c10 100644 --- a/src/ifs/jay_damage_tracking.rs +++ b/src/ifs/jay_damage_tracking.rs @@ -1,10 +1,11 @@ use { crate::{ client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientError}, + cmm::cmm_transfer_function::TransferFunction, globals::{Global, GlobalName}, leaks::Tracker, object::{Object, Version}, - theme::{Color, TransferFunction}, + theme::Color, wire::{ JayCompositorId, jay_damage_tracking::{ diff --git a/src/it/test_ifs/test_single_pixel_buffer_manager.rs b/src/it/test_ifs/test_single_pixel_buffer_manager.rs index 9fc989bb..4685279c 100644 --- a/src/it/test_ifs/test_single_pixel_buffer_manager.rs +++ b/src/it/test_ifs/test_single_pixel_buffer_manager.rs @@ -1,10 +1,11 @@ use { crate::{ + cmm::cmm_transfer_function::TransferFunction, it::{ test_error::TestResult, test_ifs::test_buffer::TestBuffer, test_object::TestObject, test_transport::TestTransport, }, - theme::{Color, TransferFunction}, + theme::Color, wire::{WpSinglePixelBufferManagerV1Id, wp_single_pixel_buffer_manager_v1::*}, }, std::{cell::Cell, rc::Rc}, diff --git a/src/macros.rs b/src/macros.rs index 3579eee2..13d1db66 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -185,10 +185,10 @@ macro_rules! shared_ids { } macro_rules! linear_ids { - ($ids:ident, $id:ident) => { + ($ids:ident, $id:ident $(,)?) => { linear_ids!($ids, $id, u32); }; - ($ids:ident, $id:ident, $ty:ty) => { + ($ids:ident, $id:ident, $ty:ty $(,)?) => { pub struct $ids { next: crate::utils::numcell::NumCell<$ty>, } diff --git a/src/main.rs b/src/main.rs index 05032eed..6c9052e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,7 +35,8 @@ clippy::manual_is_ascii_check, clippy::needless_borrow, clippy::unnecessary_cast, - clippy::manual_flatten + clippy::manual_flatten, + clippy::manual_bits )] #![warn(clippy::allow_attributes, unsafe_op_in_unsafe_fn)] @@ -54,6 +55,7 @@ mod bugs; mod cli; mod client; mod clientmem; +mod cmm; mod compositor; mod config; mod cpu_worker; diff --git a/src/portal/ptl_text.rs b/src/portal/ptl_text.rs index 32ecc0ed..02ea796d 100644 --- a/src/portal/ptl_text.rs +++ b/src/portal/ptl_text.rs @@ -1,5 +1,6 @@ use { crate::{ + cmm::cmm_transfer_function::TransferFunction, format::ARGB8888, gfx_api::{GfxContext, GfxTexture}, pango::{ @@ -7,7 +8,7 @@ use { consts::{CAIRO_FORMAT_ARGB32, CAIRO_OPERATOR_SOURCE}, }, rect::Rect, - theme::{Color, TransferFunction}, + theme::Color, }, std::{ops::Neg, rc::Rc, sync::Arc}, }; diff --git a/src/renderer.rs b/src/renderer.rs index 7965c620..6948a611 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,5 +1,6 @@ use { crate::{ + cmm::cmm_transfer_function::TransferFunction, gfx_api::{AcquireSync, GfxApiOpt, ReleaseSync, SampleRect}, ifs::wl_surface::{ SurfaceBuffer, WlSurface, @@ -11,7 +12,7 @@ use { renderer::renderer_base::RendererBase, scale::Scale, state::State, - theme::{Color, TransferFunction}, + theme::Color, tree::{ ContainerNode, DisplayNode, FloatNode, OutputNode, PlaceholderNode, ToplevelData, ToplevelNodeBase, WorkspaceNode, diff --git a/src/state.rs b/src/state.rs index d56b523f..cb136dc6 100644 --- a/src/state.rs +++ b/src/state.rs @@ -11,6 +11,7 @@ use { cli::RunArgs, client::{Client, ClientId, Clients, NUM_CACHED_SERIAL_RANGES, SerialRange}, clientmem::ClientMemOffset, + cmm::cmm_manager::ColorManager, compositor::LIBEI_SOCKET, config::ConfigProxy, cpu_worker::CpuWorker, @@ -234,6 +235,8 @@ pub struct State { pub data_control_device_ids: DataControlDeviceIds, pub workspace_managers: WorkspaceManagerState, pub color_management_enabled: Cell, + #[expect(dead_code)] + pub color_manager: Rc, } // impl Drop for State { diff --git a/src/text.rs b/src/text.rs index c8b2ab0c..06b49a1c 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1,5 +1,6 @@ use { crate::{ + cmm::cmm_transfer_function::TransferFunction, cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker, PendingJob}, format::ARGB8888, gfx_api::{ @@ -14,7 +15,7 @@ use { }, }, rect::{Rect, Region}, - theme::{Color, TransferFunction}, + theme::Color, utils::{ clonecell::CloneCell, double_buffered::DoubleBuffered, on_drop_event::OnDropEvent, }, diff --git a/src/theme.rs b/src/theme.rs index 72e594b7..3a19fabc 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,14 +1,8 @@ use { - crate::utils::clonecell::CloneCell, + crate::{cmm::cmm_transfer_function::TransferFunction, utils::clonecell::CloneCell}, std::{cell::Cell, cmp::Ordering, ops::Mul, sync::Arc}, }; -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum TransferFunction { - Srgb, - Linear, -} - #[derive(Copy, Clone, Debug, PartialEq)] pub struct Color { r: f32, diff --git a/src/utils.rs b/src/utils.rs index 96d65756..ba49cba1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -18,6 +18,7 @@ pub mod double_click_state; pub mod errorfmt; pub mod event_listener; pub mod fdcloser; +pub mod free_list; pub mod geometric_decay; pub mod gfx_api_ext; pub mod hash_map_ext; @@ -37,6 +38,7 @@ pub mod opaque; pub mod opaque_cell; pub mod opt; pub mod option_ext; +pub mod ordered_float; pub mod oserror; pub mod page_size; pub mod pending_serial; diff --git a/src/utils/free_list.rs b/src/utils/free_list.rs new file mode 100644 index 00000000..129e58f6 --- /dev/null +++ b/src/utils/free_list.rs @@ -0,0 +1,93 @@ +#[cfg(test)] +mod tests; + +use { + crate::utils::ptr_ext::MutPtrExt, + std::{ + array, + cell::UnsafeCell, + fmt::{Debug, Formatter}, + marker::PhantomData, + }, +}; + +type Seg = usize; +const SEG_SIZE: usize = size_of::() * 8; + +pub struct FreeList { + levels: UnsafeCell<[Vec; N]>, + _phantom: PhantomData, +} + +impl Default for FreeList { + fn default() -> Self { + Self { + levels: UnsafeCell::new(array::from_fn(|_| Vec::new())), + _phantom: Default::default(), + } + } +} + +impl Debug for FreeList { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("FreeList") + .field("levels", self.get()) + .finish() + } +} + +impl FreeList { + fn get(&self) -> &mut [Vec; N] { + unsafe { self.levels.get().deref_mut() } + } + + pub fn release(&self, n: T) + where + T: Into, + { + let mut ext = n.into() as usize; + let mut int; + let levels = self.get(); + assert!(ext / SEG_SIZE < levels[0].len()); + for level in self.get() { + int = ext % SEG_SIZE; + ext /= SEG_SIZE; + unsafe { + *level.get_unchecked_mut(ext) |= 1 << int; + } + } + } + + pub fn acquire(&self) -> T + where + u32: Into, + { + let mut ext = 'last: { + let level = &mut self.get()[N - 1]; + for (idx, &seg) in level.iter().enumerate() { + if seg != 0 { + break 'last idx; + } + } + level.len() + }; + for level in self.get().iter_mut().rev() { + if ext == level.len() { + level.push(!0); + } + let seg = unsafe { level.get_unchecked(ext) }; + ext = SEG_SIZE * ext + seg.trailing_zeros() as usize; + } + let id = ext as u32; + for level in self.get().iter_mut() { + let int = ext % SEG_SIZE; + ext /= SEG_SIZE; + let seg = unsafe { level.get_unchecked_mut(ext) }; + *seg &= !(1 << int); + if *seg != 0 { + break; + } + } + id.into() + } +} diff --git a/src/utils/free_list/tests.rs b/src/utils/free_list/tests.rs new file mode 100644 index 00000000..a8c4eae5 --- /dev/null +++ b/src/utils/free_list/tests.rs @@ -0,0 +1,40 @@ +use crate::utils::free_list::FreeList; + +#[test] +fn test() { + let list = FreeList::::default(); + for i in 0..4097 { + assert_eq!(list.acquire(), i); + } + list.release(100); + assert_eq!(list.acquire(), 100); + assert_eq!(list.acquire(), 4097); + for i in 1..22 { + list.release(i); + } + for i in 1..22 { + assert_eq!(list.acquire(), i); + } + assert_eq!(list.acquire(), 4098); + for i in 64..128 { + list.release(i); + } + for i in 64..128 { + assert_eq!(list.acquire(), i); + } + assert_eq!(list.acquire(), 4099); + for i in 0..4100 { + list.release(i); + } + for i in 0..4101 { + assert_eq!(list.acquire(), i); + } +} + +#[test] +#[should_panic] +fn release_out_of_bounds() { + let list = FreeList::::default(); + list.acquire(); + list.release(500); +} diff --git a/src/utils/ordered_float.rs b/src/utils/ordered_float.rs new file mode 100644 index 00000000..4a458a61 --- /dev/null +++ b/src/utils/ordered_float.rs @@ -0,0 +1,67 @@ +use std::{ + fmt::{Debug, Display, Formatter}, + hash::{Hash, Hasher}, + ops::{Add, Div, Mul, Sub}, +}; + +#[derive(Copy, Clone)] +#[repr(transparent)] +pub struct F64(pub f64); + +impl Eq for F64 {} + +impl PartialEq for F64 { + fn eq(&self, other: &Self) -> bool { + self.0.to_bits() == other.0.to_bits() + } +} + +impl Hash for F64 { + fn hash(&self, state: &mut H) { + self.0.to_bits().hash(state); + } +} + +impl Add for F64 { + type Output = Self; + + fn add(self, rhs: F64) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl Sub for F64 { + type Output = Self; + + fn sub(self, rhs: F64) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl Mul for F64 { + type Output = Self; + + fn mul(self, rhs: F64) -> Self::Output { + Self(self.0 * rhs.0) + } +} + +impl Div for F64 { + type Output = Self; + + fn div(self, rhs: F64) -> Self::Output { + Self(self.0 / rhs.0) + } +} + +impl Display for F64 { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.0, f) + } +} + +impl Debug for F64 { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&self.0, f) + } +} From a1748811381338dff75e5cd5a26c51b83977ba94 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 1 Mar 2025 14:06:42 +0100 Subject: [PATCH 2/7] gfx: attach color descriptions --- src/backends/metal/present.rs | 10 ++- src/backends/metal/video.rs | 21 ++++- src/backends/x.rs | 2 + src/cmm/cmm_description.rs | 2 - src/cmm/cmm_manager.rs | 2 - src/compositor.rs | 5 +- src/cursor.rs | 3 + src/cursor_user.rs | 1 + src/damage.rs | 8 +- src/gfx_api.rs | 84 ++++++++++++++++++- src/gfx_apis/gl.rs | 12 ++- src/gfx_apis/gl/renderer/framebuffer.rs | 8 +- src/gfx_apis/vulkan/image.rs | 4 + .../ext_image_copy_capture_frame_v1.rs | 6 ++ .../ext_image_copy_capture_session_v1.rs | 3 + src/ifs/jay_screencast.rs | 6 ++ src/it/test_gfx_api.rs | 4 + src/portal.rs | 3 + src/portal/ptr_gui.rs | 29 +++++-- src/renderer.rs | 71 +++++++++++----- src/renderer/renderer_base.rs | 41 +++++++-- src/screenshoter.rs | 2 + src/state.rs | 17 +++- src/tree/output.rs | 9 ++ 24 files changed, 291 insertions(+), 62 deletions(-) diff --git a/src/backends/metal/present.rs b/src/backends/metal/present.rs index e89574f1..35a299c3 100644 --- a/src/backends/metal/present.rs +++ b/src/backends/metal/present.rs @@ -451,7 +451,9 @@ impl MetalConnector { }; self.state.present_hardware_cursor(node, &mut c); if c.cursor_swap_buffer { - c.sync_file = c.cursor_buffer.copy_to_dev(c.sync_file)?; + c.sync_file = c + .cursor_buffer + .copy_to_dev(&self.state.color_manager, c.sync_file)?; } self.cursor_swap_buffer.set(c.cursor_swap_buffer); if c.sync_file.is_some() { @@ -747,12 +749,14 @@ impl MetalConnector { .perform_render_pass( AcquireSync::Unnecessary, ReleaseSync::Explicit, + self.state.color_manager.srgb_srgb(), &latched.pass, &latched.damage, buffer.blend_buffer.as_ref(), + self.state.color_manager.srgb_linear(), ) .map_err(MetalError::RenderFrame)?; - sync_file = buffer.copy_to_dev(sf)?; + sync_file = buffer.copy_to_dev(&self.state.color_manager, sf)?; fb = buffer.drm.clone(); tex = buffer.render_tex.clone(); } @@ -792,6 +796,7 @@ impl MetalConnector { None => { output.perform_screencopies( &fb.tex, + self.state.color_manager.srgb_srgb(), None, &AcquireSync::Unnecessary, ReleaseSync::None, @@ -804,6 +809,7 @@ impl MetalConnector { Some(dsd) => { output.perform_screencopies( &dsd.tex, + self.state.color_manager.srgb_srgb(), dsd.resv.as_ref(), &dsd.acquire_sync, dsd.release_sync, diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index c49c68c0..0897f8cb 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -14,6 +14,7 @@ use { POST_COMMIT_MARGIN_DELTA, PresentFb, }, }, + cmm::cmm_manager::ColorManager, drm_feedback::DrmFeedback, edid::{CtaDataBlock, Descriptor, EdidExtension}, format::{ARGB8888, Format, XRGB8888}, @@ -2704,7 +2705,11 @@ impl MetalBackend { Err(e) => return Err(MetalError::ImportFb(e)), }; dev_fb - .clear(AcquireSync::Unnecessary, ReleaseSync::None) + .clear( + AcquireSync::Unnecessary, + ReleaseSync::None, + self.state.color_manager.srgb_srgb(), + ) .map_err(MetalError::Clear)?; let (dev_tex, render_tex, render_fb, render_bo) = if dev.id == render_ctx.dev_id { let render_tex = match dev_img.to_texture() { @@ -2758,7 +2763,11 @@ impl MetalBackend { Err(e) => return Err(MetalError::ImportFb(e)), }; render_fb - .clear(AcquireSync::Unnecessary, ReleaseSync::None) + .clear( + AcquireSync::Unnecessary, + ReleaseSync::None, + self.state.color_manager.srgb_srgb(), + ) .map_err(MetalError::Clear)?; let render_tex = match render_img.to_texture() { Ok(fb) => fb, @@ -3030,7 +3039,11 @@ impl RenderBuffer { .unwrap_or_else(|| self.dev_fb.clone()) } - pub fn copy_to_dev(&self, sync_file: Option) -> Result, MetalError> { + pub fn copy_to_dev( + &self, + cm: &ColorManager, + sync_file: Option, + ) -> Result, MetalError> { let Some(tex) = &self.dev_tex else { return Ok(sync_file); }; @@ -3038,7 +3051,9 @@ impl RenderBuffer { .copy_texture( AcquireSync::Unnecessary, ReleaseSync::Explicit, + cm.srgb_srgb(), tex, + cm.srgb_srgb(), None, AcquireSync::from_sync_file(sync_file), ReleaseSync::None, diff --git a/src/backends/x.rs b/src/backends/x.rs index 6bfd3aae..8cf4442d 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -751,11 +751,13 @@ impl XBackend { let res = self.state.present_output( &node, &image.fb.get(), + self.state.color_manager.srgb_srgb(), AcquireSync::Implicit, ReleaseSync::Implicit, &image.tex.get(), true, None, + self.state.color_manager.srgb_linear(), ); if let Err(e) = res { log::error!("Could not render screen: {}", ErrorFmt(e)); diff --git a/src/cmm/cmm_description.rs b/src/cmm/cmm_description.rs index a1c1a499..efdc7143 100644 --- a/src/cmm/cmm_description.rs +++ b/src/cmm/cmm_description.rs @@ -44,11 +44,9 @@ pub struct LinearColorDescription { #[derive(Debug)] pub struct ColorDescription { pub id: ColorDescriptionId, - #[expect(dead_code)] pub linear: Rc, #[expect(dead_code)] pub named_primaries: Option, - #[expect(dead_code)] pub transfer_function: TransferFunction, pub(super) shared: Rc, } diff --git a/src/cmm/cmm_manager.rs b/src/cmm/cmm_manager.rs index 23505765..1244e2d6 100644 --- a/src/cmm/cmm_manager.rs +++ b/src/cmm/cmm_manager.rs @@ -92,12 +92,10 @@ impl ColorManager { }) } - #[expect(dead_code)] pub fn srgb_srgb(&self) -> &Rc { &self.srgb_srgb } - #[expect(dead_code)] pub fn srgb_linear(&self) -> &Rc { &self.srgb_linear } diff --git a/src/compositor.rs b/src/compositor.rs index e9ffb99c..3b902ead 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -153,6 +153,7 @@ fn start_compositor2( let scales = RefCounted::default(); scales.add(Scale::from_int(1)); let cpu_worker = Rc::new(CpuWorker::new(&ring, &engine)?); + let color_manager = ColorManager::new(); let state = Rc::new(State { kb_ctx, backend: CloneCell::new(Rc::new(DummyBackend)), @@ -267,7 +268,7 @@ fn start_compositor2( tablet_ids: Default::default(), tablet_tool_ids: Default::default(), tablet_pad_ids: Default::default(), - damage_visualizer: DamageVisualizer::new(&engine), + damage_visualizer: DamageVisualizer::new(&engine, &color_manager), default_vrr_mode: Cell::new(VrrMode::NEVER), default_vrr_cursor_hz: Cell::new(None), default_tearing_mode: Cell::new(TearingMode::VARIANT_3), @@ -285,7 +286,7 @@ fn start_compositor2( data_control_device_ids: Default::default(), workspace_managers: Default::default(), color_management_enabled: Cell::new(false), - color_manager: ColorManager::new(), + color_manager, }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/cursor.rs b/src/cursor.rs index 95bbeaf2..e09c3dc7 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -398,6 +398,7 @@ fn render_img(image: &InstantiatedCursorImage, renderer: &mut Renderer, x: Fixed AcquireSync::None, ReleaseSync::None, false, + renderer.state.color_manager.srgb_srgb(), ); } } @@ -422,6 +423,7 @@ impl Cursor for StaticCursor { AcquireSync::None, ReleaseSync::None, false, + renderer.state.color_manager.srgb_srgb(), ); } } @@ -464,6 +466,7 @@ impl Cursor for AnimatedCursor { AcquireSync::None, ReleaseSync::None, false, + renderer.state.color_manager.srgb_srgb(), ); } } diff --git a/src/cursor_user.rs b/src/cursor_user.rs index 6b6d526e..e1970e2d 100644 --- a/src/cursor_user.rs +++ b/src/cursor_user.rs @@ -507,6 +507,7 @@ impl CursorUser { &self.group.state, scale, transform, + self.group.state.color_manager.srgb_srgb(), ); match res { Ok(sync_file) => { diff --git a/src/damage.rs b/src/damage.rs index 88b0aadc..39d877ae 100644 --- a/src/damage.rs +++ b/src/damage.rs @@ -1,6 +1,7 @@ use { crate::{ async_engine::AsyncEngine, + cmm::cmm_manager::ColorManager, fixed::Fixed, gfx_api::GfxApiOpt, ifs::wl_output::WlOutputGlobal, @@ -77,6 +78,7 @@ pub struct DamageVisualizer { enabled: Cell, decay: Cell, color: Cell, + color_manager: Rc, } const MAX_RECTS: usize = 100_000; @@ -87,7 +89,7 @@ struct Damage { } impl DamageVisualizer { - pub fn new(eng: &Rc) -> Self { + pub fn new(eng: &Rc, color_manager: &Rc) -> Self { Self { eng: eng.clone(), entries: Default::default(), @@ -95,6 +97,7 @@ impl DamageVisualizer { enabled: Default::default(), decay: Cell::new(Duration::from_secs(2)), color: Cell::new(Color::from_srgba_straight(255, 0, 0, 128)), + color_manager: color_manager.clone(), } } @@ -162,13 +165,14 @@ 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; for entry in entries.iter().rev() { let region = Region::new(entry.rect); let region = region.subtract(&used); if region.is_not_empty() { let age = (now - entry.time).as_millis() as u64 as f32 / decay_millis; let color = base_color * (1.0 - age); - renderer.fill_boxes2(region.rects(), &color, dx, dy); + renderer.fill_boxes2(region.rects(), &color, srgb, dx, dy); used = used.union(®ion); } } diff --git a/src/gfx_api.rs b/src/gfx_api.rs index e832a8e1..302f63af 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -1,6 +1,7 @@ use { crate::{ allocator::Allocator, + cmm::cmm_description::{ColorDescription, LinearColorDescription}, cpu_worker::CpuWorker, cursor::Cursor, damage::DamageVisualizer, @@ -41,6 +42,7 @@ pub enum GfxApiOpt { pub struct GfxRenderPass { pub ops: Vec, pub clear: Option, + pub clear_cd: Rc, } #[derive(Default, Debug, Copy, Clone, PartialEq)] @@ -194,6 +196,8 @@ pub struct FillRect { pub rect: FramebufferRect, pub color: Color, pub alpha: Option, + #[expect(dead_code)] + pub cd: Rc, } impl FillRect { @@ -215,6 +219,8 @@ pub struct CopyTexture { pub release_sync: ReleaseSync, pub alpha: Option, pub opaque: bool, + #[expect(dead_code)] + pub cd: Rc, } #[derive(Clone, Debug)] @@ -299,10 +305,13 @@ pub trait GfxFramebuffer: Debug { self: Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, + cd: &Rc, ops: &[GfxApiOpt], clear: Option<&Color>, + clear_cd: &Rc, region: &Region, blend_buffer: Option<&Rc>, + blend_cd: &Rc, ) -> Result, GfxError>; fn format(&self) -> &'static Format; @@ -333,17 +342,23 @@ impl dyn GfxFramebuffer { self: &Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, + cd: &Rc, ops: &[GfxApiOpt], clear: Option<&Color>, + clear_cd: &Rc, blend_buffer: Option<&Rc>, + blend_cd: &Rc, ) -> Result, GfxError> { self.clone().render_with_region( acquire_sync, release_sync, + cd, ops, clear, + clear_cd, &self.full_region(), blend_buffer, + blend_cd, ) } @@ -351,17 +366,35 @@ impl dyn GfxFramebuffer { self: &Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, + cd: &Rc, ) -> Result, GfxError> { - self.clear_with(acquire_sync, release_sync, &Color::TRANSPARENT) + self.clear_with( + acquire_sync, + release_sync, + cd, + &Color::TRANSPARENT, + &cd.linear, + ) } pub fn clear_with( self: &Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, + cd: &Rc, color: &Color, + color_cd: &Rc, ) -> Result, GfxError> { - self.render(acquire_sync, release_sync, &[], Some(color), None) + self.render( + acquire_sync, + release_sync, + cd, + &[], + Some(color), + color_cd, + None, + cd, + ) } pub fn logical_size(&self, transform: Transform) -> (i32, i32) { @@ -381,7 +414,9 @@ impl dyn GfxFramebuffer { self: &Rc, fb_acquire_sync: AcquireSync, fb_release_sync: ReleaseSync, + fb_cd: &Rc, texture: &Rc, + texture_cd: &Rc, resv: Option<&Rc>, acquire_sync: AcquireSync, release_sync: ReleaseSync, @@ -404,24 +439,46 @@ impl dyn GfxFramebuffer { acquire_sync, release_sync, false, + texture_cd, ); let clear = self.format().has_alpha.then_some(&Color::TRANSPARENT); - self.render(fb_acquire_sync, fb_release_sync, &ops, clear, None) + self.render( + fb_acquire_sync, + fb_release_sync, + fb_cd, + &ops, + clear, + &fb_cd.linear, + None, + fb_cd, + ) } pub fn render_custom( self: &Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, + cd: &Rc, scale: Scale, clear: Option<&Color>, + clear_cd: &Rc, blend_buffer: Option<&Rc>, + blend_cd: &Rc, f: &mut dyn FnMut(&mut RendererBase), ) -> Result, GfxError> { let mut ops = vec![]; let mut renderer = self.renderer_base(&mut ops, scale, Transform::None); f(&mut renderer); - self.render(acquire_sync, release_sync, &ops, clear, blend_buffer) + self.render( + acquire_sync, + release_sync, + cd, + &ops, + clear, + clear_cd, + blend_buffer, + blend_cd, + ) } pub fn create_render_pass( @@ -456,17 +513,22 @@ impl dyn GfxFramebuffer { self: &Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, + cd: &Rc, pass: &GfxRenderPass, region: &Region, blend_buffer: Option<&Rc>, + blend_cd: &Rc, ) -> Result, GfxError> { self.clone().render_with_region( acquire_sync, release_sync, + cd, &pass.ops, pass.clear.as_ref(), + &pass.clear_cd, region, blend_buffer, + blend_cd, ) } @@ -474,6 +536,7 @@ impl dyn GfxFramebuffer { self: &Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, + cd: &Rc, node: &OutputNode, state: &State, cursor_rect: Option, @@ -481,10 +544,12 @@ impl dyn GfxFramebuffer { render_hardware_cursor: bool, fill_black_in_grace_period: bool, blend_buffer: Option<&Rc>, + blend_cd: &Rc, ) -> Result, GfxError> { self.render_node( acquire_sync, release_sync, + cd, node, state, cursor_rect, @@ -495,6 +560,7 @@ impl dyn GfxFramebuffer { fill_black_in_grace_period, node.global.persistent.transform.get(), blend_buffer, + blend_cd, ) } @@ -502,6 +568,7 @@ impl dyn GfxFramebuffer { self: &Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, + cd: &Rc, node: &dyn Node, state: &State, cursor_rect: Option, @@ -512,6 +579,7 @@ impl dyn GfxFramebuffer { fill_black_in_grace_period: bool, transform: Transform, blend_buffer: Option<&Rc>, + blend_cd: &Rc, ) -> Result, GfxError> { let pass = self.create_render_pass( node, @@ -528,9 +596,11 @@ impl dyn GfxFramebuffer { self.perform_render_pass( acquire_sync, release_sync, + cd, &pass, &self.full_region(), blend_buffer, + blend_cd, ) } @@ -542,6 +612,7 @@ impl dyn GfxFramebuffer { state: &State, scale: Scale, transform: Transform, + cd: &Rc, ) -> Result, GfxError> { let mut ops = vec![]; let mut renderer = Renderer { @@ -557,9 +628,12 @@ impl dyn GfxFramebuffer { self.render( acquire_sync, release_sync, + cd, &ops, Some(&Color::TRANSPARENT), + &cd.linear, None, + cd, ) } } @@ -832,6 +906,7 @@ pub fn create_render_pass( return GfxRenderPass { ops: vec![], clear: Some(Color::SOLID_BLACK), + clear_cd: state.color_manager.srgb_srgb().linear.clone(), }; } let mut ops = vec![]; @@ -898,6 +973,7 @@ pub fn create_render_pass( GfxRenderPass { ops, clear: Some(c), + clear_cd: state.color_manager.srgb_srgb().linear.clone(), } } diff --git a/src/gfx_apis/gl.rs b/src/gfx_apis/gl.rs index ff3dc526..d9b62411 100644 --- a/src/gfx_apis/gl.rs +++ b/src/gfx_apis/gl.rs @@ -69,7 +69,7 @@ use { crate::{ cmm::cmm_transfer_function::TransferFunction, gfx_api::{ - AcquireSync, CopyTexture, FillRect, GfxApiOpt, GfxContext, GfxError, GfxTexture, + AcquireSync, CopyTexture, FramebufferRect, GfxApiOpt, GfxContext, GfxError, GfxTexture, ReleaseSync, SyncFile, }, gfx_apis::gl::{ @@ -205,10 +205,15 @@ enum RenderError { #[derive(Default)] struct GfxGlState { triangles: RefCell>, - fill_rect: VecStorage, + fill_rect: VecStorage, copy_tex: VecStorage<&'static CopyTexture>, } +struct GlFillRect { + pub rect: FramebufferRect, + pub color: Color, +} + fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) -> Option { let mut state = fb.ctx.gl_state.borrow_mut(); let state = &mut *state; @@ -236,10 +241,9 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) -> Option { } } GfxApiOpt::FillRect(f) => { - fill_rect.push(FillRect { + fill_rect.push(GlFillRect { rect: f.rect, color: f.effective_color(), - alpha: None, }); i += 1; } diff --git a/src/gfx_apis/gl/renderer/framebuffer.rs b/src/gfx_apis/gl/renderer/framebuffer.rs index 714cdc69..e952778f 100644 --- a/src/gfx_apis/gl/renderer/framebuffer.rs +++ b/src/gfx_apis/gl/renderer/framebuffer.rs @@ -1,6 +1,9 @@ use { crate::{ - cmm::cmm_transfer_function::TransferFunction, + cmm::{ + cmm_description::{ColorDescription, LinearColorDescription}, + cmm_transfer_function::TransferFunction, + }, format::Format, gfx_api::{ AcquireSync, AsyncShmGfxTextureCallback, GfxApiOpt, GfxBlendBuffer, GfxError, @@ -105,10 +108,13 @@ impl GfxFramebuffer for Framebuffer { self: Rc, acquire_sync: AcquireSync, _release_sync: ReleaseSync, + _cd: &Rc, ops: &[GfxApiOpt], clear: Option<&Color>, + _clear_cd: &Rc, _region: &Region, _blend_buffer: Option<&Rc>, + _blend_cd: &Rc, ) -> Result, GfxError> { (*self) .render(acquire_sync, ops, clear) diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs index 8b269883..b1ddcdcd 100644 --- a/src/gfx_apis/vulkan/image.rs +++ b/src/gfx_apis/vulkan/image.rs @@ -1,5 +1,6 @@ use { crate::{ + cmm::cmm_description::{ColorDescription, LinearColorDescription}, format::Format, gfx_api::{ AcquireSync, AsyncShmGfxTexture, AsyncShmGfxTextureCallback, @@ -552,10 +553,13 @@ impl GfxFramebuffer for VulkanImage { self: Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, + _cd: &Rc, ops: &[GfxApiOpt], clear: Option<&Color>, + _clear_cd: &Rc, region: &Region, blend_buffer: Option<&Rc>, + _blend_cd: &Rc, ) -> Result, GfxError> { let mut blend_buffer = blend_buffer.map(|b| b.clone().into_vk(&self.renderer.device.device)); diff --git a/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs b/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs index ce4ee503..d90612e4 100644 --- a/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs +++ b/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs @@ -1,6 +1,7 @@ use { crate::{ client::{Client, ClientError}, + cmm::cmm_description::ColorDescription, gfx_api::{ AcquireSync, AsyncShmGfxTextureCallback, BufferResv, GfxError, GfxFramebuffer, GfxTexture, ReleaseSync, STAGING_DOWNLOAD, SyncFile, @@ -199,6 +200,7 @@ impl ExtImageCopyCaptureFrameV1 { self: &Rc, on: &OutputNode, texture: &Rc, + cd: &Rc, resv: Option<&Rc>, acquire_sync: &AcquireSync, release_sync: ReleaseSync, @@ -215,10 +217,12 @@ impl ExtImageCopyCaptureFrameV1 { resv, acquire_sync, release_sync, + cd, &fb, aq, re, jay_config::video::Transform::None, + self.client.state.color_manager.srgb_srgb(), on.global.pos.get(), render_hardware_cursors, x_off, @@ -236,6 +240,7 @@ impl ExtImageCopyCaptureFrameV1 { fb.render_node( aq, re, + self.client.state.color_manager.srgb_srgb(), node, &self.client.state, Some(node.node_absolute_position()), @@ -246,6 +251,7 @@ impl ExtImageCopyCaptureFrameV1 { false, jay_config::video::Transform::None, None, + self.client.state.color_manager.srgb_linear(), ) }); } diff --git a/src/ifs/ext_image_copy/ext_image_copy_capture_session_v1.rs b/src/ifs/ext_image_copy/ext_image_copy_capture_session_v1.rs index 65f7dc34..3ac1c7c6 100644 --- a/src/ifs/ext_image_copy/ext_image_copy_capture_session_v1.rs +++ b/src/ifs/ext_image_copy/ext_image_copy_capture_session_v1.rs @@ -1,6 +1,7 @@ use { crate::{ client::{Client, ClientError}, + cmm::cmm_description::ColorDescription, format::{FORMATS, Format}, gfx_api::{ AcquireSync, BufferResv, GfxInternalFramebuffer, GfxStagingBuffer, GfxTexture, @@ -213,6 +214,7 @@ impl ExtImageCopyCaptureSessionV1 { self: &Rc, on: &OutputNode, texture: &Rc, + cd: &Rc, resv: Option<&Rc>, acquire_sync: &AcquireSync, release_sync: ReleaseSync, @@ -226,6 +228,7 @@ impl ExtImageCopyCaptureSessionV1 { frame.copy_texture( on, texture, + cd, resv, acquire_sync, release_sync, diff --git a/src/ifs/jay_screencast.rs b/src/ifs/jay_screencast.rs index bde24705..f55ccec0 100644 --- a/src/ifs/jay_screencast.rs +++ b/src/ifs/jay_screencast.rs @@ -2,6 +2,7 @@ use { crate::{ allocator::{AllocatorError, BO_USE_LINEAR, BO_USE_RENDERING, BufferObject}, client::{Client, ClientError}, + cmm::cmm_description::ColorDescription, format::XRGB8888, gfx_api::{ AcquireSync, BufferResv, GfxContext, GfxError, GfxFramebuffer, GfxTexture, ReleaseSync, @@ -193,6 +194,7 @@ impl JayScreencast { let res = buffer.fb.render_node( AcquireSync::Implicit, ReleaseSync::Implicit, + self.client.state.color_manager.srgb_srgb(), tl.tl_as_node(), &self.client.state, Some(tl.node_absolute_position()), @@ -203,6 +205,7 @@ impl JayScreencast { false, Transform::None, None, + self.client.state.color_manager.srgb_linear(), ); match res { Ok(_) => { @@ -304,6 +307,7 @@ impl JayScreencast { &self, on: &OutputNode, texture: &Rc, + cd: &Rc, resv: Option<&Rc>, acquire_sync: &AcquireSync, release_sync: ReleaseSync, @@ -332,10 +336,12 @@ impl JayScreencast { resv, acquire_sync, release_sync, + cd, &buffer.fb, AcquireSync::Implicit, ReleaseSync::Implicit, Transform::None, + self.client.state.color_manager.srgb_srgb(), on.global.pos.get(), render_hardware_cursors, x_off, diff --git a/src/it/test_gfx_api.rs b/src/it/test_gfx_api.rs index 1069fbd9..658358da 100644 --- a/src/it/test_gfx_api.rs +++ b/src/it/test_gfx_api.rs @@ -1,6 +1,7 @@ use { crate::{ allocator::{Allocator, AllocatorError, BufferObject, BufferUsage}, + cmm::cmm_description::{ColorDescription, LinearColorDescription}, cpu_worker::CpuWorker, format::{ARGB8888, Format, XRGB8888}, gfx_api::{ @@ -391,10 +392,13 @@ impl GfxFramebuffer for TestGfxFb { self: Rc, _acquire_sync: AcquireSync, _release_sync: ReleaseSync, + _cd: &Rc, ops: &[GfxApiOpt], clear: Option<&Color>, + _clear_cd: &Rc, _region: &Region, _blend_buffer: Option<&Rc>, + _blend_cd: &Rc, ) -> Result, GfxError> { let fb_points = |width: i32, height: i32, rect: &FramebufferRect| { let points = rect.to_points(); diff --git a/src/portal.rs b/src/portal.rs index 9dc71936..e07942d9 100644 --- a/src/portal.rs +++ b/src/portal.rs @@ -10,6 +10,7 @@ use { crate::{ async_engine::AsyncEngine, cli::GlobalArgs, + cmm::cmm_manager::ColorManager, dbus::{ BUS_DEST, BUS_PATH, DBUS_NAME_FLAG_DO_NOT_QUEUE, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER, Dbus, DbusSocket, @@ -207,6 +208,7 @@ async fn run_async( render_ctxs: Default::default(), dma_buf_ids: Default::default(), pw_con: pw_con.as_ref().map(|c| c.con.clone()), + color_manager: ColorManager::new(), }); if let Some(pw_con) = &pw_con { pw_con.con.owner.set(Some(state.clone())); @@ -302,6 +304,7 @@ struct PortalState { render_ctxs: CopyHashMap>, dma_buf_ids: Rc, pw_con: Option>, + color_manager: Rc, } impl PortalState { diff --git a/src/portal/ptr_gui.rs b/src/portal/ptr_gui.rs index 458634c0..edc3865c 100644 --- a/src/portal/ptr_gui.rs +++ b/src/portal/ptr_gui.rs @@ -2,6 +2,7 @@ use { crate::{ allocator::{BO_USE_RENDERING, BufferObject, BufferUsage}, async_engine::{Phase, SpawnedFuture}, + cmm::cmm_manager::ColorManager, cursor::KnownCursor, fixed::Fixed, format::ARGB8888, @@ -59,7 +60,7 @@ pub trait GuiElement { max_width: f32, max_height: f32, ) -> (f32, f32); - fn render_at(&self, r: &mut RendererBase, x: f32, y: f32); + fn render_at(&self, color_manager: &ColorManager, r: &mut RendererBase, x: f32, y: f32); fn child_at(&self, x: f32, y: f32) -> Option>; fn hover_cursor(&self) -> KnownCursor { @@ -190,7 +191,9 @@ impl GuiElement for Button { (extents.width, extents.height) } - fn render_at(&self, r: &mut RendererBase, x1: f32, y1: f32) { + fn render_at(&self, color_manager: &ColorManager, r: &mut RendererBase, x1: f32, y1: f32) { + let srgb_srgb = color_manager.srgb_srgb(); + let srgb = &srgb_srgb.linear; let x2 = x1 + self.data.width.get(); let y2 = y1 + self.data.height.get(); let border = self.border.get(); @@ -201,7 +204,7 @@ impl GuiElement for Button { (x1, y1 + border, x1 + border, y2 - border), (x2 - border, y1 + border, x2, y2 - border), ]; - r.fill_boxes_f(&rects, &self.border_color.get()); + r.fill_boxes_f(&rects, &self.border_color.get(), srgb); } { let rects = [(x1 + border, y1 + border, x2 - border, y2 - border)]; @@ -209,7 +212,7 @@ impl GuiElement for Button { true => self.bg_color.get(), false => self.bg_hover_color.get(), }; - r.fill_boxes_f(&rects, &color); + r.fill_boxes_f(&rects, &color, srgb); } if let Some(tex) = self.tex.get() { let (tx, ty) = r.scale_point_f(x1 + self.tex_off_x.get(), y1 + self.tex_off_y.get()); @@ -226,6 +229,7 @@ impl GuiElement for Button { AcquireSync::None, ReleaseSync::None, false, + srgb_srgb, ); } } @@ -311,7 +315,7 @@ impl GuiElement for Label { (width as f32 / scale, height as f32 / scale) } - fn render_at(&self, r: &mut RendererBase, x: f32, y: f32) { + fn render_at(&self, color_manager: &ColorManager, r: &mut RendererBase, x: f32, y: f32) { if let Some(tex) = self.tex.get() { let (tx, ty) = r.scale_point_f(x, y); r.render_texture( @@ -327,6 +331,7 @@ impl GuiElement for Label { AcquireSync::None, ReleaseSync::None, false, + color_manager.srgb_srgb(), ); } } @@ -439,9 +444,14 @@ impl GuiElement for Flow { (w.min(max_width), h.min(max_height)) } - fn render_at(&self, r: &mut RendererBase, x: f32, y: f32) { + fn render_at(&self, color_manager: &ColorManager, r: &mut RendererBase, x: f32, y: f32) { for element in self.elements.borrow_mut().deref() { - element.render_at(r, x + element.data().x.get(), y + element.data().y.get()); + element.render_at( + color_manager, + r, + x + element.data().x.get(), + y + element.data().y.get(), + ); } } @@ -634,12 +644,15 @@ impl WindowData { let res = buf.fb.render_custom( AcquireSync::Implicit, ReleaseSync::Implicit, + self.dpy.state.color_manager.srgb_srgb(), self.scale.get(), Some(&Color::from_gray_srgb(0)), + &self.dpy.state.color_manager.srgb_srgb().linear, None, + self.dpy.state.color_manager.srgb_linear(), &mut |r| { if let Some(content) = self.content.get() { - content.render_at(r, 0.0, 0.0) + content.render_at(&self.dpy.state.color_manager, r, 0.0, 0.0) } }, ); diff --git a/src/renderer.rs b/src/renderer.rs index 6948a611..22b73182 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,6 +1,5 @@ use { crate::{ - cmm::cmm_transfer_function::TransferFunction, gfx_api::{AcquireSync, GfxApiOpt, ReleaseSync, SampleRect}, ifs::wl_surface::{ SurfaceBuffer, WlSurface, @@ -78,6 +77,8 @@ 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_srgb.linear; if let Some(fs) = fullscreen { fs.tl_as_node().node_render(self, x, y, None); } else { @@ -90,26 +91,28 @@ impl Renderer<'_> { let bar_bg = self.base.scale_rect(bar_bg); let c = theme.colors.bar_background.get(); self.base - .fill_boxes3(slice::from_ref(&bar_bg), &c, None, x, y, true); + .fill_boxes3(slice::from_ref(&bar_bg), &c, None, srgb, x, y, true); let rd = output.render_data.borrow_mut(); if let Some(aw) = &rd.active_workspace { let c = match aw.captured { true => theme.colors.captured_focused_title_background.get(), false => theme.colors.focused_title_background.get(), }; - self.base.fill_boxes2(slice::from_ref(&aw.rect), &c, x, y); + self.base + .fill_boxes2(slice::from_ref(&aw.rect), &c, srgb, x, y); } let c = theme.colors.separator.get(); self.base - .fill_boxes2(slice::from_ref(&rd.underline), &c, x, y); + .fill_boxes2(slice::from_ref(&rd.underline), &c, srgb, x, y); let c = theme.colors.unfocused_title_background.get(); - self.base.fill_boxes2(&rd.inactive_workspaces, &c, x, y); + self.base + .fill_boxes2(&rd.inactive_workspaces, &c, srgb, x, y); let c = theme.colors.captured_unfocused_title_background.get(); self.base - .fill_boxes2(&rd.captured_inactive_workspaces, &c, x, y); + .fill_boxes2(&rd.captured_inactive_workspaces, &c, srgb, x, y); let c = theme.colors.attention_requested_background.get(); self.base - .fill_boxes2(&rd.attention_requested_workspaces, &c, x, y); + .fill_boxes2(&rd.attention_requested_workspaces, &c, srgb, x, y); let scale = output.global.persistent.scale.get(); for title in &rd.titles { let (x, y) = self.base.scale_point(x + title.tex_x, y + title.tex_y); @@ -126,6 +129,7 @@ impl Renderer<'_> { AcquireSync::None, ReleaseSync::None, false, + self.state.color_manager.srgb_srgb(), ); } if let Some(status) = &rd.status { @@ -144,6 +148,7 @@ impl Renderer<'_> { AcquireSync::None, ReleaseSync::None, false, + srgb_srgb, ); } } @@ -182,7 +187,7 @@ impl Renderer<'_> { if ws.render_highlight.get() > 0 { let color = self.state.theme.colors.highlight.get(); let bounds = ws.position.get().at_point(x, y + th + 1); - self.base.fill_boxes(&[bounds], &color); + self.base.fill_boxes(&[bounds], &color, srgb); } } } @@ -204,6 +209,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, ); if let Some(tex) = placeholder.textures.borrow().get(&self.base.scale) { if let Some(texture) = tex.texture() { @@ -223,6 +229,7 @@ impl Renderer<'_> { AcquireSync::None, ReleaseSync::None, false, + self.state.color_manager.srgb_srgb(), ); } } @@ -231,17 +238,21 @@ 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_srgb.linear; let rd = container.render_data.borrow_mut(); let c = self.state.theme.colors.unfocused_title_background.get(); - self.base.fill_boxes2(&rd.title_rects, &c, x, y); + self.base.fill_boxes2(&rd.title_rects, &c, srgb, x, y); let c = self.state.theme.colors.focused_title_background.get(); - self.base.fill_boxes2(&rd.active_title_rects, &c, x, y); + self.base + .fill_boxes2(&rd.active_title_rects, &c, srgb, x, y); let c = self.state.theme.colors.attention_requested_background.get(); - self.base.fill_boxes2(&rd.attention_title_rects, &c, x, y); + self.base + .fill_boxes2(&rd.attention_title_rects, &c, srgb, x, y); let c = self.state.theme.colors.separator.get(); - self.base.fill_boxes2(&rd.underline_rects, &c, x, y); + self.base.fill_boxes2(&rd.underline_rects, &c, srgb, x, y); let c = self.state.theme.colors.border.get(); - self.base.fill_boxes2(&rd.border_rects, &c, x, y); + self.base.fill_boxes2(&rd.border_rects, &c, srgb, x, y); if let Some(lar) = &rd.last_active_rect { let c = self .state @@ -249,7 +260,8 @@ impl Renderer<'_> { .colors .focused_inactive_title_background .get(); - self.base.fill_boxes2(std::slice::from_ref(lar), &c, x, y); + self.base + .fill_boxes2(std::slice::from_ref(lar), &c, srgb, x, y); } if let Some(titles) = rd.titles.get(&self.base.scale) { for title in titles { @@ -269,6 +281,7 @@ impl Renderer<'_> { AcquireSync::None, ReleaseSync::None, false, + srgb_srgb, ); } } @@ -341,14 +354,22 @@ impl Renderer<'_> { }; let color = self.state.theme.colors.highlight.get(); self.base.ops.push(GfxApiOpt::Sync); - self.base - .fill_scaled_boxes(slice::from_ref(bounds), &color, None); + self.base.fill_scaled_boxes( + slice::from_ref(bounds), + &color, + None, + &self.state.color_manager.srgb_srgb().linear, + ); } pub fn render_highlight(&mut self, rect: &Rect) { let color = self.state.theme.colors.highlight.get(); self.base.ops.push(GfxApiOpt::Sync); - self.base.fill_boxes(slice::from_ref(rect), &color); + self.base.fill_boxes( + slice::from_ref(rect), + &color, + &self.state.color_manager.srgb_srgb().linear, + ); } pub fn render_surface(&mut self, surface: &WlSurface, x: i32, y: i32, bounds: Option<&Rect>) { @@ -423,6 +444,7 @@ impl Renderer<'_> { bounds: Option<&Rect>, ) { let alpha = surface.alpha(); + let cd = self.state.color_manager.srgb_srgb(); if let Some(tex) = buffer.buffer.get_texture(surface) { let mut opaque = surface.opaque(); if !opaque && tex.format().has_alpha { @@ -441,6 +463,7 @@ impl Renderer<'_> { AcquireSync::Unnecessary, buffer.release_sync, opaque, + cd, ); } else if let Some(color) = &buffer.buffer.color { if let Some(rect) = Rect::new_sized(x, y, tsize.0, tsize.1) { @@ -450,14 +473,15 @@ impl Renderer<'_> { }; if !rect.is_empty() { let color = Color::from_u32_premultiplied( - TransferFunction::Srgb, + cd.transfer_function, color[0], color[1], color[2], color[3], ); self.base.ops.push(GfxApiOpt::Sync); - self.base.fill_scaled_boxes(&[rect], &color, alpha); + self.base + .fill_scaled_boxes(&[rect], &color, alpha, &cd.linear); } } } else { @@ -489,12 +513,14 @@ 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(), ]; - self.base.fill_boxes(&borders, &bc); + let srgb_srgb = self.state.color_manager.srgb_srgb(); + 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()]; - self.base.fill_boxes(&title, &tc); + self.base.fill_boxes(&title, &tc, srgb); let title_underline = [Rect::new_sized(x + bw, y + bw + th, pos.width() - 2 * bw, 1).unwrap()]; - self.base.fill_boxes(&title_underline, &uc); + self.base.fill_boxes(&title_underline, &uc, srgb); if let Some(title) = floating.title_textures.borrow().get(&self.base.scale) { if let Some(texture) = title.texture() { let rect = floating.title_rect.get().move_(x, y); @@ -513,6 +539,7 @@ impl Renderer<'_> { AcquireSync::None, ReleaseSync::None, false, + srgb_srgb, ); } } diff --git a/src/renderer/renderer_base.rs b/src/renderer/renderer_base.rs index b5413b87..f245a6a8 100644 --- a/src/renderer/renderer_base.rs +++ b/src/renderer/renderer_base.rs @@ -1,5 +1,6 @@ use { crate::{ + cmm::cmm_description::{ColorDescription, LinearColorDescription}, gfx_api::{ AcquireSync, BufferResv, CopyTexture, FillRect, FramebufferRect, GfxApiOpt, GfxTexture, ReleaseSync, SampleRect, @@ -64,16 +65,29 @@ impl RendererBase<'_> { rect } - pub fn fill_scaled_boxes(&mut self, boxes: &[Rect], color: &Color, alpha: Option) { - self.fill_boxes3(boxes, color, alpha, 0, 0, true); + pub fn fill_scaled_boxes( + &mut self, + boxes: &[Rect], + color: &Color, + alpha: Option, + cd: &Rc, + ) { + self.fill_boxes3(boxes, color, alpha, cd, 0, 0, true); } - pub fn fill_boxes(&mut self, boxes: &[Rect], color: &Color) { - self.fill_boxes3(boxes, color, None, 0, 0, false); + pub fn fill_boxes(&mut self, boxes: &[Rect], color: &Color, cd: &Rc) { + self.fill_boxes3(boxes, color, None, cd, 0, 0, false); } - pub fn fill_boxes2(&mut self, boxes: &[Rect], color: &Color, dx: i32, dy: i32) { - self.fill_boxes3(boxes, color, None, dx, dy, false); + pub fn fill_boxes2( + &mut self, + boxes: &[Rect], + color: &Color, + cd: &Rc, + dx: i32, + dy: i32, + ) { + self.fill_boxes3(boxes, color, None, cd, dx, dy, false); } pub fn fill_boxes3( @@ -81,6 +95,7 @@ impl RendererBase<'_> { boxes: &[Rect], color: &Color, alpha: Option, + cd: &Rc, dx: i32, dy: i32, scaled: bool, @@ -106,18 +121,25 @@ impl RendererBase<'_> { ), color: *color, alpha, + cd: cd.clone(), })); } } - pub fn fill_boxes_f(&mut self, boxes: &[(f32, f32, f32, f32)], color: &Color) { - self.fill_boxes2_f(boxes, color, 0.0, 0.0); + pub fn fill_boxes_f( + &mut self, + boxes: &[(f32, f32, f32, f32)], + color: &Color, + cd: &Rc, + ) { + self.fill_boxes2_f(boxes, color, cd, 0.0, 0.0); } pub fn fill_boxes2_f( &mut self, boxes: &[(f32, f32, f32, f32)], color: &Color, + cd: &Rc, dx: f32, dy: f32, ) { @@ -139,6 +161,7 @@ impl RendererBase<'_> { ), color: *color, alpha: None, + cd: cd.clone(), })); } } @@ -157,6 +180,7 @@ impl RendererBase<'_> { acquire_sync: AcquireSync, release_sync: ReleaseSync, opaque: bool, + cd: &Rc, ) { let mut texcoord = tpoints.unwrap_or_else(SampleRect::identity); @@ -200,6 +224,7 @@ impl RendererBase<'_> { acquire_sync, release_sync, opaque, + cd: cd.clone(), })); } } diff --git a/src/screenshoter.rs b/src/screenshoter.rs index 657551d4..b89356c1 100644 --- a/src/screenshoter.rs +++ b/src/screenshoter.rs @@ -79,6 +79,7 @@ pub fn take_screenshot( fb.render_node( AcquireSync::Unnecessary, ReleaseSync::Implicit, + state.color_manager.srgb_srgb(), state.root.deref(), state, Some(state.root.extents.get()), @@ -89,6 +90,7 @@ pub fn take_screenshot( false, Transform::None, None, + state.color_manager.srgb_linear(), )?; let drm = match allocator.drm() { Some(drm) => Some(drm.dup_render()?.fd().clone()), diff --git a/src/state.rs b/src/state.rs index cb136dc6..703d8f7a 100644 --- a/src/state.rs +++ b/src/state.rs @@ -11,7 +11,7 @@ use { cli::RunArgs, client::{Client, ClientId, Clients, NUM_CACHED_SERIAL_RANGES, SerialRange}, clientmem::ClientMemOffset, - cmm::cmm_manager::ColorManager, + cmm::{cmm_description::ColorDescription, cmm_manager::ColorManager}, compositor::LIBEI_SOCKET, config::ConfigProxy, cpu_worker::CpuWorker, @@ -235,7 +235,6 @@ pub struct State { pub data_control_device_ids: DataControlDeviceIds, pub workspace_managers: WorkspaceManagerState, pub color_management_enabled: Cell, - #[expect(dead_code)] pub color_manager: Rc, } @@ -979,15 +978,18 @@ impl State { &self, output: &OutputNode, fb: &Rc, + cd: &Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, tex: &Rc, render_hw_cursor: bool, blend_buffer: Option<&Rc>, + blend_cd: &Rc, ) -> Result, GfxError> { let sync_file = fb.render_output( acquire_sync, release_sync, + cd, output, self, Some(output.global.pos.get()), @@ -995,10 +997,12 @@ impl State { render_hw_cursor, true, blend_buffer, + blend_cd, )?; output.latched(false); output.perform_screencopies( tex, + cd, None, &AcquireSync::Unnecessary, ReleaseSync::None, @@ -1016,10 +1020,12 @@ impl State { resv: Option<&Rc>, acquire_sync: &AcquireSync, release_sync: ReleaseSync, + src_cd: &Rc, target: &Rc, target_acquire_sync: AcquireSync, target_release_sync: ReleaseSync, target_transform: Transform, + target_cd: &Rc, position: Rect, render_hardware_cursors: bool, x_off: i32, @@ -1053,6 +1059,7 @@ impl State { acquire_sync.clone(), release_sync, false, + src_cd, ); if render_hardware_cursors { if let Some(cursor_user_group) = self.cursor_user_group_hardware_cursor.get() { @@ -1069,15 +1076,19 @@ impl State { target.render( target_acquire_sync, target_release_sync, + target_cd, &ops, Some(&Color::SOLID_BLACK), + &target_cd.linear, None, + target_cd, ) } pub fn perform_shm_screencopy( &self, src: &Rc, + src_cd: &Rc, acquire_sync: &AcquireSync, position: Rect, x_off: i32, @@ -1108,10 +1119,12 @@ impl State { None, acquire_sync, ReleaseSync::None, + src_cd, &fb.clone().into_fb(), AcquireSync::Unnecessary, ReleaseSync::None, transform, + self.color_manager.srgb_srgb(), position, true, x_off - capture.rect.x1(), diff --git a/src/tree/output.rs b/src/tree/output.rs index 44fceb82..2d291819 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -2,6 +2,7 @@ use { crate::{ backend::{HardwareCursor, KeyState, Mode}, client::ClientId, + cmm::cmm_description::ColorDescription, cursor::KnownCursor, fixed::Fixed, gfx_api::{AcquireSync, BufferResv, GfxTexture, ReleaseSync}, @@ -252,6 +253,7 @@ impl OutputNode { pub fn perform_screencopies( &self, tex: &Rc, + cd: &Rc, resv: Option<&Rc>, acquire_sync: &AcquireSync, release_sync: ReleaseSync, @@ -267,6 +269,7 @@ impl OutputNode { } self.perform_wlr_screencopies( tex, + cd, resv, acquire_sync, release_sync, @@ -279,6 +282,7 @@ impl OutputNode { sc.copy_texture( self, tex, + cd, resv, acquire_sync, release_sync, @@ -292,6 +296,7 @@ impl OutputNode { sc.copy_texture( self, tex, + cd, resv, acquire_sync, release_sync, @@ -306,6 +311,7 @@ impl OutputNode { pub fn perform_wlr_screencopies( &self, tex: &Rc, + cd: &Rc, resv: Option<&Rc>, acquire_sync: &AcquireSync, release_sync: ReleaseSync, @@ -337,6 +343,7 @@ impl OutputNode { WlBufferStorage::Shm { mem, stride } => { let res = self.state.perform_shm_screencopy( tex, + cd, acquire_sync, self.global.pos.get(), x_off, @@ -375,10 +382,12 @@ impl OutputNode { resv, acquire_sync, release_sync, + cd, &fb, AcquireSync::Implicit, ReleaseSync::Implicit, self.global.persistent.transform.get(), + self.state.color_manager.srgb_srgb(), self.global.pos.get(), render_hardware_cursors, x_off - capture.rect.x1(), From b5044d7fe7e2a34a15ea4ee69d1d6722ed052dfb Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 1 Mar 2025 14:59:50 +0100 Subject: [PATCH 3/7] vulkan: take transfer functions from color descriptions --- src/cmm/cmm_transfer_function.rs | 4 +- src/gfx_api.rs | 1 - src/gfx_apis/vulkan/image.rs | 6 +- src/gfx_apis/vulkan/renderer.rs | 282 ++++++++++++++-------- src/gfx_apis/vulkan/transfer_functions.rs | 15 ++ 5 files changed, 199 insertions(+), 109 deletions(-) diff --git a/src/cmm/cmm_transfer_function.rs b/src/cmm/cmm_transfer_function.rs index 22fc9920..50613a64 100644 --- a/src/cmm/cmm_transfer_function.rs +++ b/src/cmm/cmm_transfer_function.rs @@ -1,4 +1,6 @@ -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +use linearize::Linearize; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Linearize)] pub enum TransferFunction { Srgb, Linear, diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 302f63af..3e436f77 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -219,7 +219,6 @@ pub struct CopyTexture { pub release_sync: ReleaseSync, pub alpha: Option, pub opaque: bool, - #[expect(dead_code)] pub cd: Rc, } diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs index b1ddcdcd..b74ce224 100644 --- a/src/gfx_apis/vulkan/image.rs +++ b/src/gfx_apis/vulkan/image.rs @@ -553,13 +553,13 @@ impl GfxFramebuffer for VulkanImage { self: Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, - _cd: &Rc, + cd: &Rc, ops: &[GfxApiOpt], clear: Option<&Color>, _clear_cd: &Rc, region: &Region, blend_buffer: Option<&Rc>, - _blend_cd: &Rc, + blend_cd: &Rc, ) -> Result, GfxError> { let mut blend_buffer = blend_buffer.map(|b| b.clone().into_vk(&self.renderer.device.device)); @@ -578,10 +578,12 @@ impl GfxFramebuffer for VulkanImage { &self, acquire_sync, release_sync, + cd, ops, clear, region, blend_buffer, + blend_cd, ) .map_err(|e| e.into()) } diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index b46e5af7..df3ae761 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -1,9 +1,8 @@ use { crate::{ async_engine::{AsyncEngine, SpawnedFuture}, - cmm::cmm_transfer_function::TransferFunction, + cmm::{cmm_description::ColorDescription, cmm_transfer_function::TransferFunction}, cpu_worker::PendingJob, - format::XRGB8888, gfx_api::{ AcquireSync, BufferResv, BufferResvUser, GfxApiOpt, GfxBlendBuffer, GfxFormat, GfxTexture, GfxWriteModifier, ReleaseSync, SyncFile, @@ -27,7 +26,7 @@ use { OUT_FRAG, OUT_VERT, OutPushConstants, TEX_FRAG, TEX_VERT, TexPushConstants, TexVertex, VulkanShader, }, - transfer_functions::{TF_LINEAR, TF_SRGB}, + transfer_functions::{TF_LINEAR, TransferFunctionExt}, }, io_uring::IoUring, rect::{Rect, Region}, @@ -58,7 +57,6 @@ use { std::{ borrow::Cow, cell::{Cell, RefCell}, - collections::hash_map::Entry, fmt::{Debug, Formatter}, mem, ops::Range, @@ -72,8 +70,11 @@ use { pub struct VulkanRenderer { pub(super) formats: Rc>, pub(super) device: Rc, - pub(super) pipelines: StaticMap>>, - pub(super) out_pipelines: RefCell>>, + pub(super) fill_pipelines: CopyHashMap, + pub(super) tex_pipelines: + StaticMap>>, + pub(super) out_pipelines: + StaticMap>>, pub(super) gfx_command_buffers: CachedCommandBuffers, pub(super) transfer_command_buffers: Option, pub(super) wait_semaphores: Stack>, @@ -131,13 +132,13 @@ pub(super) struct UsedTexture { release_sync: ReleaseSync, } -#[derive(Copy, Clone, Linearize)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Linearize)] pub(super) enum TexCopyType { Identity, Multiply, } -#[derive(Copy, Clone, Linearize)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Linearize)] pub(super) enum TexSourceType { Opaque, HasAlpha, @@ -188,6 +189,7 @@ struct VulkanTexOp { copy_type: TexCopyType, range_address: DeviceAddress, instances: u32, + tex_cd: Rc, } struct VulkanFillOp { @@ -225,9 +227,25 @@ pub(super) struct PendingFrame { _used_buffers: ArrayVec, } -pub(super) struct VulkanFormatPipelines { - pub(super) fill: StaticMap>, - pub(super) tex: StaticMap>>, +type FillPipelines = Rc>>; + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +struct TexPipelineKey { + tex_copy_type: TexCopyType, + tex_source_type: TexSourceType, + eotf: TransferFunction, +} + +pub(super) struct TexPipelines { + format: vk::Format, + oetf: TransferFunction, + pipelines: CopyHashMap>, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub(super) struct OutPipelineKey { + format: vk::Format, + eotf: TransferFunction, } impl VulkanDevice { @@ -319,7 +337,8 @@ impl VulkanDevice { let render = Rc::new(VulkanRenderer { formats: Rc::new(formats), device: self.clone(), - pipelines: Default::default(), + fill_pipelines: Default::default(), + tex_pipelines: Default::default(), out_pipelines: Default::default(), gfx_command_buffers, transfer_command_buffers, @@ -350,23 +369,16 @@ impl VulkanDevice { blend_buffers: Default::default(), shader_buffer_cache, }); - render.get_or_create_pipelines(XRGB8888.vk_format, RenderPass::FrameBuffer)?; Ok(render) } } impl VulkanRenderer { - fn get_or_create_pipelines( + fn get_or_create_fill_pipelines( &self, format: vk::Format, - pass: RenderPass, - ) -> Result, VulkanError> { - let (eotf, oetf) = match pass { - RenderPass::BlendBuffer => (TF_SRGB, TF_LINEAR), - RenderPass::FrameBuffer => (TF_SRGB, TF_SRGB), - }; - let pipelines = &self.pipelines[pass]; - if let Some(pl) = pipelines.get(&format) { + ) -> Result { + if let Some(pl) = self.fill_pipelines.get(&format) { return Ok(pl); } let create_fill_pipeline = |src_has_alpha| { @@ -382,55 +394,116 @@ impl VulkanRenderer { blend: src_has_alpha, src_has_alpha, has_alpha_mult: false, - eotf, - oetf, + // all transformations are applied in the compositor + eotf: TF_LINEAR, + oetf: TF_LINEAR, descriptor_set_layouts: Default::default(), }; self.device.create_pipeline2(info, push_size) }; - let fill_opaque = create_fill_pipeline(false)?; - let fill_alpha = create_fill_pipeline(true)?; - let create_tex_pipeline = |src_has_alpha, has_alpha_mult| { - let push_size = if self.device.descriptor_buffer.is_some() { - size_of::() - } else { - size_of::() - }; - let info = PipelineCreateInfo { - format, - vert: self.tex_vert_shader.clone(), - frag: self.tex_frag_shader.clone(), - blend: src_has_alpha || has_alpha_mult, - src_has_alpha, - has_alpha_mult, - eotf, - oetf, - descriptor_set_layouts: self.tex_descriptor_set_layouts.clone(), - }; - self.device.create_pipeline2(info, push_size) - }; - let tex_opaque = create_tex_pipeline(false, false)?; - let tex_alpha = create_tex_pipeline(true, false)?; - let tex_mult_opaque = create_tex_pipeline(false, true)?; - let tex_mult_alpha = create_tex_pipeline(true, true)?; - let format_pipelines = Rc::new(VulkanFormatPipelines { - fill: static_map! { - TexSourceType::HasAlpha => fill_alpha.clone(), - TexSourceType::Opaque => fill_opaque.clone(), - }, - tex: static_map! { - TexCopyType::Identity => static_map! { - TexSourceType::HasAlpha => tex_alpha.clone(), - TexSourceType::Opaque => tex_opaque.clone(), - }, - TexCopyType::Multiply => static_map! { - TexSourceType::HasAlpha => tex_mult_alpha.clone(), - TexSourceType::Opaque => tex_mult_opaque.clone(), - }, - }, + let fill_pipelines = Rc::new(static_map! { + TexSourceType::HasAlpha => create_fill_pipeline(true)?, + TexSourceType::Opaque => create_fill_pipeline(false)?, }); - pipelines.set(format, format_pipelines.clone()); - Ok(format_pipelines) + self.fill_pipelines.set(format, fill_pipelines.clone()); + Ok(fill_pipelines) + } + + fn get_or_create_tex_pipelines( + &self, + format: vk::Format, + target_cd: &ColorDescription, + ) -> Rc { + let pipelines = &self.tex_pipelines[target_cd.transfer_function]; + match pipelines.get(&format) { + Some(pl) => pl, + _ => { + let pl = Rc::new(TexPipelines { + format, + oetf: target_cd.transfer_function, + pipelines: Default::default(), + }); + pipelines.set(format, pl.clone()); + pl + } + } + } + + fn get_or_create_tex_pipeline( + &self, + pipelines: &TexPipelines, + tex_cd: &ColorDescription, + tex_copy_type: TexCopyType, + tex_source_type: TexSourceType, + ) -> Result, VulkanError> { + let key = TexPipelineKey { + tex_copy_type, + tex_source_type, + eotf: tex_cd.transfer_function, + }; + if let Some(pl) = pipelines.pipelines.get(&key) { + return Ok(pl); + } + let src_has_alpha = match tex_source_type { + TexSourceType::Opaque => false, + TexSourceType::HasAlpha => true, + }; + let has_alpha_mult = match tex_copy_type { + TexCopyType::Identity => false, + TexCopyType::Multiply => true, + }; + let push_size = if self.device.descriptor_buffer.is_some() { + size_of::() + } else { + size_of::() + }; + let info = PipelineCreateInfo { + format: pipelines.format, + vert: self.tex_vert_shader.clone(), + frag: self.tex_frag_shader.clone(), + blend: src_has_alpha || has_alpha_mult, + src_has_alpha, + has_alpha_mult, + eotf: key.eotf.to_vulkan(), + oetf: pipelines.oetf.to_vulkan(), + descriptor_set_layouts: self.tex_descriptor_set_layouts.clone(), + }; + let pl = self.device.create_pipeline2(info, push_size)?; + pipelines.pipelines.set(key, pl.clone()); + Ok(pl) + } + + fn get_or_create_out_pipeline( + &self, + format: vk::Format, + bb_cd: &ColorDescription, + fb_cd: &ColorDescription, + ) -> Result, VulkanError> { + let key = OutPipelineKey { + format, + eotf: bb_cd.transfer_function, + }; + let pipelines = &self.out_pipelines[fb_cd.transfer_function]; + if let Some(pl) = pipelines.get(&key) { + return Ok(pl); + } + let mut descriptor_set_layouts = ArrayVec::new(); + descriptor_set_layouts.push(self.out_descriptor_set_layout.clone().unwrap()); + let out = self + .device + .create_pipeline::(PipelineCreateInfo { + format: key.format, + vert: self.out_vert_shader.clone().unwrap(), + frag: self.out_frag_shader.clone().unwrap(), + blend: false, + src_has_alpha: true, + has_alpha_mult: false, + eotf: key.eotf.to_vulkan(), + oetf: fb_cd.transfer_function.to_vulkan(), + descriptor_set_layouts, + })?; + pipelines.set(key, out.clone()); + Ok(out) } pub(super) fn allocate_point(&self) -> u64 { @@ -511,7 +584,12 @@ impl VulkanRenderer { Ok(()) } - fn convert_ops(&self, opts: &[GfxApiOpt]) { + fn convert_ops( + &self, + opts: &[GfxApiOpt], + blend_cd: &ColorDescription, + fb_cd: &ColorDescription, + ) { zone!("convert_ops"); let memory = &mut *self.memory.borrow_mut(); for ops in memory.ops.values_mut() { @@ -590,8 +668,8 @@ impl VulkanRenderer { continue; } let tf = match pass { - RenderPass::BlendBuffer => TransferFunction::Linear, - RenderPass::FrameBuffer => TransferFunction::Srgb, + RenderPass::BlendBuffer => blend_cd.transfer_function, + RenderPass::FrameBuffer => fb_cd.transfer_function, }; let ops = &mut memory.ops_tmp[pass]; let lo = memory.fill_targets.len(); @@ -673,6 +751,7 @@ impl VulkanRenderer { copy_type, range_address: 0, instances: 0, + tex_cd: ct.cd.clone(), })); } } @@ -867,6 +946,7 @@ impl VulkanRenderer { target: &VulkanImage, clear: Option<&Color>, pass: RenderPass, + target_cd: &ColorDescription, ) { zone!("begin_rendering"); let memory = &mut *self.memory.borrow_mut(); @@ -877,10 +957,7 @@ impl VulkanRenderer { if clear_rects.is_not_empty() { let clear_value = ClearValue { color: ClearColorValue { - float32: match pass { - RenderPass::BlendBuffer => clear.to_array(TransferFunction::Linear), - RenderPass::FrameBuffer => clear.to_array(TransferFunction::Srgb), - }, + float32: clear.to_array(target_cd.transfer_function), }, }; let use_load_clear = clear_rects.len() == 1 && { @@ -970,10 +1047,12 @@ impl VulkanRenderer { buf: CommandBuffer, target: &VulkanImage, pass: RenderPass, + target_cd: &ColorDescription, ) -> Result<(), VulkanError> { zone!("record_draws"); let memory = &*self.memory.borrow(); - let pipelines = self.get_or_create_pipelines(target.format.vk_format, pass)?; + let fill_pl = self.get_or_create_fill_pipelines(target.format.vk_format)?; + let tex_pl = self.get_or_create_tex_pipelines(target.format.vk_format, target_cd); let dev = &self.device.device; let mut current_pipeline = None; let mut bind = |pipeline: &VulkanPipeline| { @@ -987,7 +1066,7 @@ impl VulkanRenderer { for opt in &memory.ops[pass] { match opt { VulkanOp::Fill(r) => { - let pipeline = &pipelines.fill[r.source_type]; + let pipeline = &fill_pl[r.source_type]; bind(pipeline); if self.device.descriptor_buffer.is_some() { let push = FillPushConstants { @@ -1027,8 +1106,13 @@ impl VulkanRenderer { } VulkanOp::Tex(c) => { let tex = &c.tex; - let pipeline = &pipelines.tex[c.copy_type][c.source_type]; - bind(pipeline); + let pipeline = self.get_or_create_tex_pipeline( + &tex_pl, + &c.tex_cd, + c.copy_type, + c.source_type, + )?; + bind(&pipeline); let image_info = DescriptorImageInfo::default() .image_view(tex.texture_view) .image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL); @@ -1119,33 +1203,14 @@ impl VulkanRenderer { &self, buf: CommandBuffer, fb: &VulkanImage, + fb_cd: &ColorDescription, bb: &VulkanImage, + bb_cd: &ColorDescription, ) -> Result<(), VulkanError> { zone!("blend_buffer_copy"); let memory = &*self.memory.borrow(); let db = self.device.descriptor_buffer.as_ref().unwrap(); - let pipeline = match self.out_pipelines.borrow_mut().entry(fb.format.vk_format) { - Entry::Occupied(pipeline) => pipeline.get().clone(), - Entry::Vacant(e) => { - let mut descriptor_set_layouts = ArrayVec::new(); - descriptor_set_layouts.push(self.out_descriptor_set_layout.clone().unwrap()); - let out = self - .device - .create_pipeline::(PipelineCreateInfo { - format: fb.format.vk_format, - vert: self.out_vert_shader.clone().unwrap(), - frag: self.out_frag_shader.clone().unwrap(), - blend: false, - src_has_alpha: true, - has_alpha_mult: false, - eotf: TF_LINEAR, - oetf: TF_SRGB, - descriptor_set_layouts, - })?; - e.insert(out.clone()); - out - } - }; + let pipeline = self.get_or_create_out_pipeline(fb.format.vk_format, bb_cd, fb_cd)?; let push = OutPushConstants { vertices: memory.out_address, }; @@ -1531,20 +1596,24 @@ impl VulkanRenderer { fb: &Rc, fb_acquire_sync: AcquireSync, fb_release_sync: ReleaseSync, + fb_cd: &Rc, opts: &[GfxApiOpt], clear: Option<&Color>, region: &Region, blend_buffer: Option>, + blend_cd: &Rc, ) -> Result, VulkanError> { zone!("execute"); let res = self.try_execute( fb, fb_acquire_sync, fb_release_sync, + fb_cd, opts, clear, region, blend_buffer, + blend_cd, ); let sync_file = { let mut memory = self.memory.borrow_mut(); @@ -1707,17 +1776,19 @@ impl VulkanRenderer { fb: &Rc, fb_acquire_sync: AcquireSync, fb_release_sync: ReleaseSync, + fb_cd: &Rc, opts: &[GfxApiOpt], clear: Option<&Color>, region: &Region, mut blend_buffer: Option>, + bb_cd: &Rc, ) -> Result<(), VulkanError> { self.check_defunct()?; self.create_regions(fb, opts, clear, region, blend_buffer.as_deref()); self.elide_blend_buffer(&mut blend_buffer); let bb = blend_buffer.as_deref(); let buf = self.gfx_command_buffers.allocate()?; - self.convert_ops(opts); + self.convert_ops(opts, bb_cd, fb_cd); self.create_data_buffer()?; self.collect_memory(); self.begin_command_buffer(buf.buffer)?; @@ -1726,18 +1797,19 @@ impl VulkanRenderer { self.set_viewport(buf.buffer, fb); if let Some(bb) = bb { zone!("blend buffer pass"); + let rp = RenderPass::BlendBuffer; self.blend_buffer_initial_barrier(buf.buffer, bb); - self.begin_rendering(buf.buffer, bb, clear, RenderPass::BlendBuffer); - self.record_draws(buf.buffer, bb, RenderPass::BlendBuffer)?; + self.begin_rendering(buf.buffer, bb, clear, rp, bb_cd); + self.record_draws(buf.buffer, bb, rp, bb_cd)?; self.end_rendering(buf.buffer); self.blend_buffer_final_barrier(buf.buffer, bb); } { zone!("frame buffer pass"); - self.begin_rendering(buf.buffer, fb, clear, RenderPass::FrameBuffer); - self.record_draws(buf.buffer, fb, RenderPass::FrameBuffer)?; + self.begin_rendering(buf.buffer, fb, clear, RenderPass::FrameBuffer, fb_cd); + self.record_draws(buf.buffer, fb, RenderPass::FrameBuffer, fb_cd)?; if let Some(bb) = bb { - self.blend_buffer_copy(buf.buffer, fb, bb)?; + self.blend_buffer_copy(buf.buffer, fb, fb_cd, bb, bb_cd)?; } self.end_rendering(buf.buffer); } diff --git a/src/gfx_apis/vulkan/transfer_functions.rs b/src/gfx_apis/vulkan/transfer_functions.rs index b267ce1b..b5bc2acc 100644 --- a/src/gfx_apis/vulkan/transfer_functions.rs +++ b/src/gfx_apis/vulkan/transfer_functions.rs @@ -1,2 +1,17 @@ +use crate::cmm::cmm_transfer_function::TransferFunction; + pub const TF_SRGB: u32 = 0; pub const TF_LINEAR: u32 = 1; + +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, + } + } +} From c4d0fdd4bb7ad674fcaf1d5909cfbf37c9a1ad19 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 1 Mar 2025 15:58:02 +0100 Subject: [PATCH 4/7] vulkan: apply color space transforms to colors --- src/cmm/cmm_description.rs | 1 - src/gfx_api.rs | 1 - src/gfx_apis/vulkan/image.rs | 3 +- src/gfx_apis/vulkan/renderer.rs | 81 +++++++++++++++++++++++++++++---- 4 files changed, 73 insertions(+), 13 deletions(-) diff --git a/src/cmm/cmm_description.rs b/src/cmm/cmm_description.rs index efdc7143..eef9a923 100644 --- a/src/cmm/cmm_description.rs +++ b/src/cmm/cmm_description.rs @@ -52,7 +52,6 @@ pub struct ColorDescription { } impl LinearColorDescription { - #[expect(dead_code)] pub fn color_transform(&self, target: &Self) -> ColorMatrix { let mut mat = target.local_from_xyz; if self.luminance != target.luminance { diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 3e436f77..6ae04642 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -196,7 +196,6 @@ pub struct FillRect { pub rect: FramebufferRect, pub color: Color, pub alpha: Option, - #[expect(dead_code)] pub cd: Rc, } diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs index b74ce224..c0cab9f3 100644 --- a/src/gfx_apis/vulkan/image.rs +++ b/src/gfx_apis/vulkan/image.rs @@ -556,7 +556,7 @@ impl GfxFramebuffer for VulkanImage { cd: &Rc, ops: &[GfxApiOpt], clear: Option<&Color>, - _clear_cd: &Rc, + clear_cd: &Rc, region: &Region, blend_buffer: Option<&Rc>, blend_cd: &Rc, @@ -581,6 +581,7 @@ impl GfxFramebuffer for VulkanImage { cd, ops, clear, + clear_cd, region, blend_buffer, blend_cd, diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index df3ae761..2886cb7f 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -1,7 +1,11 @@ use { crate::{ async_engine::{AsyncEngine, SpawnedFuture}, - cmm::{cmm_description::ColorDescription, cmm_transfer_function::TransferFunction}, + cmm::{ + cmm_description::{ColorDescription, LinearColorDescription, LinearColorDescriptionId}, + cmm_transfer_function::TransferFunction, + cmm_transform::ColorMatrix, + }, cpu_worker::PendingJob, gfx_api::{ AcquireSync, BufferResv, BufferResvUser, GfxApiOpt, GfxBlendBuffer, GfxFormat, @@ -57,6 +61,7 @@ use { std::{ borrow::Cow, cell::{Cell, RefCell}, + collections::hash_map::Entry, fmt::{Debug, Formatter}, mem, ops::Range, @@ -169,6 +174,7 @@ pub(super) struct Memory { tex_targets: Vec<[Point; 2]>, data_buffer: Vec, out_address: DeviceAddress, + color_transforms: ColorTransforms, } type Point = [[f32; 2]; 4]; @@ -601,6 +607,7 @@ impl VulkanRenderer { memory.tex_targets.clear(); memory.fill_targets.clear(); memory.data_buffer.clear(); + memory.color_transforms.map.clear(); let sync = |memory: &mut Memory| { for pass in RenderPass::variants() { let ops = &mut memory.ops_tmp[pass]; @@ -667,10 +674,6 @@ impl VulkanRenderer { if !bounds.intersects(&target) { continue; } - let tf = match pass { - RenderPass::BlendBuffer => blend_cd.transfer_function, - RenderPass::FrameBuffer => fb_cd.transfer_function, - }; let ops = &mut memory.ops_tmp[pass]; let lo = memory.fill_targets.len(); for region in &memory.paint_regions[pass] { @@ -684,7 +687,15 @@ impl VulkanRenderer { if lo == hi { continue; } - let color = fr.color.to_array2(tf, fr.alpha); + let target_cd = match pass { + RenderPass::BlendBuffer => blend_cd, + RenderPass::FrameBuffer => fb_cd, + }; + let tf = target_cd.transfer_function; + let color = memory + .color_transforms + .apply_to_color(&fr.cd, target_cd, fr.color); + let color = color.to_array2(tf, fr.alpha); let source_type = match color[3] < 1.0 { false => TexSourceType::Opaque, true => TexSourceType::HasAlpha, @@ -945,6 +956,7 @@ impl VulkanRenderer { buf: CommandBuffer, target: &VulkanImage, clear: Option<&Color>, + clear_cd: &LinearColorDescription, pass: RenderPass, target_cd: &ColorDescription, ) { @@ -955,9 +967,12 @@ impl VulkanRenderer { let clear_rects = &memory.clear_rects[pass]; if let Some(clear) = clear { if clear_rects.is_not_empty() { + let color = memory + .color_transforms + .apply_to_color(clear_cd, target_cd, *clear); let clear_value = ClearValue { color: ClearColorValue { - float32: clear.to_array(target_cd.transfer_function), + float32: color.to_array(target_cd.transfer_function), }, }; let use_load_clear = clear_rects.len() == 1 && { @@ -1599,6 +1614,7 @@ impl VulkanRenderer { fb_cd: &Rc, opts: &[GfxApiOpt], clear: Option<&Color>, + clear_cd: &Rc, region: &Region, blend_buffer: Option>, blend_cd: &Rc, @@ -1611,6 +1627,7 @@ impl VulkanRenderer { fb_cd, opts, clear, + clear_cd, region, blend_buffer, blend_cd, @@ -1779,6 +1796,7 @@ impl VulkanRenderer { fb_cd: &Rc, opts: &[GfxApiOpt], clear: Option<&Color>, + clear_cd: &Rc, region: &Region, mut blend_buffer: Option>, bb_cd: &Rc, @@ -1799,15 +1817,16 @@ impl VulkanRenderer { zone!("blend buffer pass"); let rp = RenderPass::BlendBuffer; self.blend_buffer_initial_barrier(buf.buffer, bb); - self.begin_rendering(buf.buffer, bb, clear, rp, bb_cd); + self.begin_rendering(buf.buffer, bb, clear, clear_cd, rp, bb_cd); self.record_draws(buf.buffer, bb, rp, bb_cd)?; self.end_rendering(buf.buffer); self.blend_buffer_final_barrier(buf.buffer, bb); } { zone!("frame buffer pass"); - self.begin_rendering(buf.buffer, fb, clear, RenderPass::FrameBuffer, fb_cd); - self.record_draws(buf.buffer, fb, RenderPass::FrameBuffer, fb_cd)?; + let rp = RenderPass::FrameBuffer; + self.begin_rendering(buf.buffer, fb, clear, clear_cd, rp, fb_cd); + self.record_draws(buf.buffer, fb, rp, fb_cd)?; if let Some(bb) = bb { self.blend_buffer_copy(buf.buffer, fb, fb_cd, bb, bb_cd)?; } @@ -2028,3 +2047,45 @@ where let y2 = y2.min(fb.height as i32); Some([x1, y1, x2, y2]) } + +#[derive(Default)] +struct ColorTransforms { + map: AHashMap<[LinearColorDescriptionId; 2], ColorTransform>, +} + +struct ColorTransform { + matrix: ColorMatrix, +} + +impl ColorTransforms { + fn get_or_create( + &mut self, + src: &LinearColorDescription, + dst: &ColorDescription, + ) -> Option<&mut ColorTransform> { + if src.id == dst.linear.id { + return None; + } + let ct = match self.map.entry([src.id, dst.linear.id]) { + Entry::Occupied(o) => o.into_mut(), + Entry::Vacant(e) => { + let matrix = src.color_transform(&dst.linear); + let ct = ColorTransform { matrix }; + e.insert(ct) + } + }; + Some(ct) + } + + fn apply_to_color( + &mut self, + src: &LinearColorDescription, + dst: &ColorDescription, + mut color: Color, + ) -> Color { + if let Some(ct) = self.get_or_create(src, dst) { + color = ct.matrix * color; + }; + color + } +} From 8e65de91f9f7140814cf3edd9d72e4a714c11c97 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 1 Mar 2025 19:19:27 +0100 Subject: [PATCH 5/7] vulkan: apply color space transforms to textures --- src/cmm/cmm_transform.rs | 1 - src/gfx_apis/vulkan/blend_buffer.rs | 2 - src/gfx_apis/vulkan/buffer_cache.rs | 52 +++++- src/gfx_apis/vulkan/descriptor.rs | 21 ++- src/gfx_apis/vulkan/device.rs | 22 ++- src/gfx_apis/vulkan/image.rs | 8 +- src/gfx_apis/vulkan/pipeline.rs | 6 +- src/gfx_apis/vulkan/renderer.rs | 156 ++++++++++++++---- src/gfx_apis/vulkan/shaders.rs | 8 + .../vulkan/shaders/frag_spec_const.glsl | 1 + src/gfx_apis/vulkan/shaders/tex.frag | 20 ++- src/gfx_apis/vulkan/shm_image.rs | 2 - 12 files changed, 233 insertions(+), 66 deletions(-) diff --git a/src/cmm/cmm_transform.rs b/src/cmm/cmm_transform.rs index c175caef..898f060a 100644 --- a/src/cmm/cmm_transform.rs +++ b/src/cmm/cmm_transform.rs @@ -157,7 +157,6 @@ impl ColorMatrix { Self(m, PhantomData) } - #[expect(dead_code)] pub const fn to_f32(&self) -> [[f32; 4]; 4] { let m = &self.0; macro_rules! map { diff --git a/src/gfx_apis/vulkan/blend_buffer.rs b/src/gfx_apis/vulkan/blend_buffer.rs index cc1213e8..5845453c 100644 --- a/src/gfx_apis/vulkan/blend_buffer.rs +++ b/src/gfx_apis/vulkan/blend_buffer.rs @@ -106,8 +106,6 @@ impl VulkanRenderer { ty: VulkanImageMemory::Blend(allocation), bridge: None, sampled_image_descriptor: self.sampled_image_descriptor(view), - descriptor_buffer_version: Default::default(), - descriptor_buffer_offset: Default::default(), execution_version: Default::default(), }); cached.insert_entry(Rc::downgrade(&img)); diff --git a/src/gfx_apis/vulkan/buffer_cache.rs b/src/gfx_apis/vulkan/buffer_cache.rs index dec2b0e8..58d43a01 100644 --- a/src/gfx_apis/vulkan/buffer_cache.rs +++ b/src/gfx_apis/vulkan/buffer_cache.rs @@ -12,7 +12,8 @@ use { DeviceSize, }, gpu_alloc::UsageFlags, - std::{cell::RefCell, mem::ManuallyDrop, rc::Rc}, + std::{cell::RefCell, mem::ManuallyDrop, ops::Deref, rc::Rc}, + uapi::Packed, }; pub struct VulkanBufferCache { @@ -20,6 +21,7 @@ pub struct VulkanBufferCache { allocator: Rc, buffers: RefCell>, usage: BufferUsageFlags, + min_alignment: DeviceSize, } pub struct VulkanBuffer { @@ -40,12 +42,14 @@ impl VulkanBufferCache { device: &Rc, allocator: &Rc, usage: BufferUsageFlags, + min_alignment: DeviceSize, ) -> Rc { Rc::new(Self { device: device.clone(), allocator: allocator.clone(), buffers: Default::default(), usage, + min_alignment, }) } @@ -55,25 +59,27 @@ impl VulkanBufferCache { for_sampler: bool, ) -> Rc { let mut usage = BufferUsageFlags::SHADER_DEVICE_ADDRESS; + let mut min_alignment = 1; if for_sampler { usage |= BufferUsageFlags::SAMPLER_DESCRIPTOR_BUFFER_EXT; } else { usage |= BufferUsageFlags::RESOURCE_DESCRIPTOR_BUFFER_EXT; + if device.is_anv { + // https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/33903 + min_alignment = 4096; + } } - Self::new(device, allocator, usage) + Self::new(device, allocator, usage, min_alignment) } pub fn usage(&self) -> BufferUsageFlags { self.usage } - pub fn allocate( - self: &Rc, - capacity: DeviceSize, - align: DeviceSize, - ) -> Result { + pub fn allocate(self: &Rc, capacity: DeviceSize) -> Result { const MIN_ALLOCATION: DeviceSize = 1024; - let capacity = (capacity.max(MIN_ALLOCATION) + align - 1) & !(align - 1); + let align_mask = self.min_alignment - 1; + let capacity = (capacity.max(MIN_ALLOCATION) + align_mask) & !align_mask; let mut smallest = None; let mut smallest_size = DeviceSize::MAX; let mut fitting = None; @@ -116,7 +122,7 @@ impl VulkanBufferCache { let destroy_buffer = OnDrop(|| unsafe { self.device.device.destroy_buffer(buffer, None) }); let mut memory_requirements = unsafe { self.device.device.get_buffer_memory_requirements(buffer) }; - memory_requirements.alignment = memory_requirements.alignment.max(align); + memory_requirements.alignment = memory_requirements.alignment.max(self.min_alignment); let allocation = { let flags = UsageFlags::UPLOAD | UsageFlags::FAST_DEVICE_ACCESS @@ -162,3 +168,31 @@ impl Drop for VulkanBufferUnused { } } } + +#[derive(Default)] +pub struct GenericBufferWriter { + buf: Vec, +} + +impl GenericBufferWriter { + pub fn clear(&mut self) { + self.buf.clear(); + } + + pub fn write(&mut self, offset_mask: DeviceSize, data: &(impl Packed + ?Sized)) -> DeviceSize { + let mut offset = self.buf.len() as DeviceSize; + let mask = offset_mask | (align_of_val(data) as DeviceSize - 1); + offset = (offset + mask) & !mask; + self.buf.resize(offset as usize, 0); + self.buf.extend_from_slice(uapi::as_bytes(data)); + offset + } +} + +impl Deref for GenericBufferWriter { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.buf + } +} diff --git a/src/gfx_apis/vulkan/descriptor.rs b/src/gfx_apis/vulkan/descriptor.rs index 9f78e8d3..7d020944 100644 --- a/src/gfx_apis/vulkan/descriptor.rs +++ b/src/gfx_apis/vulkan/descriptor.rs @@ -15,7 +15,7 @@ pub(super) struct VulkanDescriptorSetLayout { pub(super) device: Rc, pub(super) layout: DescriptorSetLayout, pub(super) size: DeviceSize, - pub(super) offsets: ArrayVec, + pub(super) offsets: ArrayVec, pub(super) _sampler: Option>, } @@ -87,12 +87,20 @@ impl VulkanDevice { pub(super) fn create_tex_resource_descriptor_set_layout( self: &Rc, ) -> Result, VulkanError> { - let binding = DescriptorSetLayoutBinding::default() - .stage_flags(ShaderStageFlags::FRAGMENT) - .descriptor_count(1) - .descriptor_type(DescriptorType::SAMPLED_IMAGE); + let bindings = [ + DescriptorSetLayoutBinding::default() + .binding(0) + .stage_flags(ShaderStageFlags::FRAGMENT) + .descriptor_count(1) + .descriptor_type(DescriptorType::SAMPLED_IMAGE), + DescriptorSetLayoutBinding::default() + .binding(1) + .stage_flags(ShaderStageFlags::FRAGMENT) + .descriptor_count(1) + .descriptor_type(DescriptorType::UNIFORM_BUFFER), + ]; let create_info = DescriptorSetLayoutCreateInfo::default() - .bindings(slice::from_ref(&binding)) + .bindings(&bindings) .flags(DescriptorSetLayoutCreateFlags::DESCRIPTOR_BUFFER_EXT); let layout = unsafe { self.device.create_descriptor_set_layout(&create_info, None) }; let layout = layout.map_err(VulkanError::CreateDescriptorSetLayout)?; @@ -101,6 +109,7 @@ impl VulkanDevice { let mut offsets = ArrayVec::new(); unsafe { offsets.push(db.get_descriptor_set_layout_binding_offset(layout, 0)); + offsets.push(db.get_descriptor_set_layout_binding_offset(layout, 1)); } Ok(Rc::new(VulkanDescriptorSetLayout { device: self.clone(), diff --git a/src/gfx_apis/vulkan/device.rs b/src/gfx_apis/vulkan/device.rs index be2471d6..976af41b 100644 --- a/src/gfx_apis/vulkan/device.rs +++ b/src/gfx_apis/vulkan/device.rs @@ -37,8 +37,9 @@ use { PhysicalDeviceDrmPropertiesEXT, PhysicalDeviceDynamicRenderingFeatures, PhysicalDeviceExternalSemaphoreInfo, PhysicalDeviceProperties, PhysicalDeviceProperties2, PhysicalDeviceSynchronization2Features, - PhysicalDeviceTimelineSemaphoreFeatures, PhysicalDeviceVulkan12Properties, Queue, - QueueFlags, + PhysicalDeviceTimelineSemaphoreFeatures, + PhysicalDeviceUniformBufferStandardLayoutFeatures, PhysicalDeviceVulkan12Properties, + Queue, QueueFlags, }, }, isnt::std_1::collections::IsntHashMap2Ext, @@ -75,6 +76,8 @@ pub struct VulkanDevice { pub(super) sampler_descriptor_size: usize, pub(super) sampled_image_descriptor_size: usize, pub(super) is_anv: bool, + pub(super) uniform_buffer_offset_mask: DeviceSize, + pub(super) uniform_buffer_descriptor_size: usize, } impl Drop for VulkanDevice { @@ -308,6 +311,9 @@ impl VulkanInstance { PhysicalDeviceDescriptorBufferFeaturesEXT::default().descriptor_buffer(true); let mut buffer_device_address_features = PhysicalDeviceBufferDeviceAddressFeatures::default().buffer_device_address(true); + let mut uniform_buffer_standard_layout_features = + PhysicalDeviceUniformBufferStandardLayoutFeatures::default() + .uniform_buffer_standard_layout(true); let mut queue_create_infos = ArrayVec::<_, 2>::new(); queue_create_infos.push( DeviceQueueCreateInfo::default() @@ -325,6 +331,7 @@ impl VulkanInstance { .push_next(&mut semaphore_features) .push_next(&mut synchronization2_features) .push_next(&mut dynamic_rendering_features) + .push_next(&mut uniform_buffer_standard_layout_features) .queue_create_infos(&queue_create_infos) .enabled_extension_names(&enabled_extensions); if supports_descriptor_buffer { @@ -382,6 +389,14 @@ impl VulkanInstance { let mut descriptor_buffer_offset_mask = 0; let mut sampler_descriptor_size = 0; let mut sampled_image_descriptor_size = 0; + let mut uniform_buffer_descriptor_size = 0; + let uniform_buffer_offset_mask = physical_device_properties2 + .properties + .limits + .min_uniform_buffer_offset_alignment + .checked_next_power_of_two() + .unwrap() + - 1; if supports_descriptor_buffer { descriptor_buffer_offset_mask = descriptor_buffer_props .descriptor_buffer_offset_alignment @@ -390,6 +405,7 @@ impl VulkanInstance { - 1; sampler_descriptor_size = descriptor_buffer_props.sampler_descriptor_size; sampled_image_descriptor_size = descriptor_buffer_props.sampled_image_descriptor_size; + uniform_buffer_descriptor_size = descriptor_buffer_props.uniform_buffer_descriptor_size; } let memory_properties = unsafe { self.instance.get_physical_device_memory_properties(phy_dev) }; @@ -432,6 +448,8 @@ impl VulkanInstance { blend_limits, is_anv: physical_device_vulkan12_properties.driver_id == DriverId::INTEL_OPEN_SOURCE_MESA, + uniform_buffer_offset_mask, + uniform_buffer_descriptor_size, })) } } diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs index c0cab9f3..ab49b254 100644 --- a/src/gfx_apis/vulkan/image.rs +++ b/src/gfx_apis/vulkan/image.rs @@ -21,8 +21,8 @@ use { ash::vk::{ BindImageMemoryInfo, BindImagePlaneMemoryInfo, ComponentMapping, ComponentSwizzle, DescriptorDataEXT, DescriptorGetInfoEXT, DescriptorImageInfo, DescriptorType, DeviceMemory, - DeviceSize, Extent3D, ExternalMemoryHandleTypeFlags, ExternalMemoryImageCreateInfo, - FormatFeatureFlags, Image, ImageAspectFlags, ImageCreateFlags, ImageCreateInfo, + Extent3D, ExternalMemoryHandleTypeFlags, ExternalMemoryImageCreateInfo, FormatFeatureFlags, + Image, ImageAspectFlags, ImageCreateFlags, ImageCreateInfo, ImageDrmFormatModifierExplicitCreateInfoEXT, ImageLayout, ImageMemoryRequirementsInfo2, ImagePlaneMemoryRequirementsInfo, ImageSubresourceRange, ImageTiling, ImageType, ImageUsageFlags, ImageView, ImageViewCreateInfo, ImageViewType, ImportMemoryFdInfoKHR, @@ -66,8 +66,6 @@ pub struct VulkanImage { pub(super) ty: VulkanImageMemory, pub(super) bridge: Option, pub(super) sampled_image_descriptor: Box<[u8]>, - pub(super) descriptor_buffer_version: Cell, - pub(super) descriptor_buffer_offset: Cell, pub(super) execution_version: Cell, } @@ -468,8 +466,6 @@ impl VulkanDmaBufImageTemplate { }), bridge, sampled_image_descriptor: self.renderer.sampled_image_descriptor(texture_view), - descriptor_buffer_version: Cell::new(0), - descriptor_buffer_offset: Cell::new(0), execution_version: Cell::new(0), })) } diff --git a/src/gfx_apis/vulkan/pipeline.rs b/src/gfx_apis/vulkan/pipeline.rs index 30804a47..d0fc3073 100644 --- a/src/gfx_apis/vulkan/pipeline.rs +++ b/src/gfx_apis/vulkan/pipeline.rs @@ -42,6 +42,7 @@ pub(super) struct PipelineCreateInfo { pub(super) eotf: u32, pub(super) oetf: u32, pub(super) descriptor_set_layouts: ArrayVec, 2>, + pub(super) has_color_management_data: bool, } impl VulkanDevice { @@ -77,8 +78,8 @@ impl VulkanDevice { }; let destroy_layout = OnDrop(|| unsafe { self.device.destroy_pipeline_layout(pipeline_layout, None) }); - let mut frag_spec_data = ArrayVec::<_, { 4 * 4 }>::new(); - let mut frag_spec_entries = ArrayVec::<_, 4>::new(); + let mut frag_spec_data = ArrayVec::<_, { 5 * 4 }>::new(); + let mut frag_spec_entries = ArrayVec::<_, 5>::new(); let mut frag_spec_entry = |data: &[u8]| { let entry = SpecializationMapEntry::default() .constant_id(frag_spec_entries.len() as _) @@ -91,6 +92,7 @@ impl VulkanDevice { 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.has_color_management_data as u32).to_ne_bytes()); let frag_spec = SpecializationInfo::default() .map_entries(&frag_spec_entries) .data(&frag_spec_data); diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index 2886cb7f..bdc873be 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -14,7 +14,7 @@ use { gfx_apis::vulkan::{ VulkanError, allocator::{VulkanAllocator, VulkanThreadedAllocator}, - buffer_cache::{VulkanBuffer, VulkanBufferCache}, + buffer_cache::{GenericBufferWriter, VulkanBuffer, VulkanBufferCache}, command::{VulkanCommandBuffer, VulkanCommandPool}, descriptor::VulkanDescriptorSetLayout, descriptor_buffer::VulkanDescriptorBufferWriter, @@ -27,8 +27,8 @@ 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, TexPushConstants, - TexVertex, VulkanShader, + OUT_FRAG, OUT_VERT, OutPushConstants, TEX_FRAG, TEX_VERT, TexColorManagementData, + TexPushConstants, TexVertex, VulkanShader, }, transfer_functions::{TF_LINEAR, TransferFunctionExt}, }, @@ -46,9 +46,10 @@ use { self, AccessFlags2, AttachmentLoadOp, AttachmentStoreOp, BufferUsageFlags, ClearAttachment, ClearColorValue, ClearRect, ClearValue, CommandBuffer, CommandBufferBeginInfo, CommandBufferSubmitInfo, CommandBufferUsageFlags, - CopyImageInfo2, DependencyInfoKHR, DescriptorBufferBindingInfoEXT, DescriptorImageInfo, - DescriptorType, DeviceAddress, DeviceSize, Extent2D, Extent3D, ImageAspectFlags, - ImageCopy2, ImageLayout, ImageMemoryBarrier2, ImageSubresourceLayers, + CopyImageInfo2, DependencyInfoKHR, DescriptorAddressInfoEXT, + DescriptorBufferBindingInfoEXT, DescriptorDataEXT, DescriptorGetInfoEXT, + DescriptorImageInfo, DescriptorType, DeviceAddress, DeviceSize, Extent2D, Extent3D, + ImageAspectFlags, ImageCopy2, ImageLayout, ImageMemoryBarrier2, ImageSubresourceLayers, ImageSubresourceRange, Offset2D, Offset3D, PipelineBindPoint, PipelineStageFlags2, QUEUE_FAMILY_FOREIGN_EXT, Rect2D, RenderingAttachmentInfo, RenderingInfo, SemaphoreSubmitInfo, SemaphoreSubmitInfoKHR, ShaderStageFlags, SubmitInfo2, Viewport, @@ -108,6 +109,7 @@ pub struct VulkanRenderer { pub(super) resource_descriptor_buffer_cache: Rc, pub(super) blend_buffers: RefCell>>, pub(super) shader_buffer_cache: Rc, + pub(super) uniform_buffer_cache: Rc, } pub(super) struct CachedCommandBuffers { @@ -159,7 +161,7 @@ pub(super) struct Memory { wait_semaphore_infos: Vec>, release_fence: Option>, release_sync_file: Option, - used_buffers: ArrayVec, + used_buffers: ArrayVec, paint_bounds: StaticMap>, paint_regions: StaticMap>, clear_rects: StaticMap>, @@ -175,6 +177,9 @@ pub(super) struct Memory { data_buffer: Vec, out_address: DeviceAddress, color_transforms: ColorTransforms, + uniform_buffer_writer: GenericBufferWriter, + uniform_buffer_descriptor_cache: Option>, + blend_buffer_descriptor_buffer_offset: DeviceAddress, } type Point = [[f32; 2]; 4]; @@ -196,6 +201,8 @@ struct VulkanTexOp { range_address: DeviceAddress, instances: u32, tex_cd: Rc, + color_management_data_address: Option, + resource_descriptor_buffer_offset: DeviceAddress, } struct VulkanFillOp { @@ -230,7 +237,7 @@ pub(super) struct PendingFrame { wait_semaphores: Cell>>, waiter: Cell>>, _release_fence: Option>, - _used_buffers: ArrayVec, + _used_buffers: ArrayVec, } type FillPipelines = Rc>>; @@ -240,6 +247,7 @@ struct TexPipelineKey { tex_copy_type: TexCopyType, tex_source_type: TexSourceType, eotf: TransferFunction, + has_color_management_data: bool, } pub(super) struct TexPipelines { @@ -338,7 +346,13 @@ impl VulkanDevice { let shader_buffer_cache = { // TODO: https://github.com/KhronosGroup/Vulkan-Samples/issues/1286 let usage = BufferUsageFlags::SHADER_DEVICE_ADDRESS | BufferUsageFlags::STORAGE_BUFFER; - VulkanBufferCache::new(self, &allocator, usage) + let align = 8; + VulkanBufferCache::new(self, &allocator, usage, align) + }; + let uniform_buffer_cache = { + let usage = BufferUsageFlags::SHADER_DEVICE_ADDRESS | BufferUsageFlags::UNIFORM_BUFFER; + let align = align_of::() as DeviceSize; + VulkanBufferCache::new(self, &allocator, usage, align) }; let render = Rc::new(VulkanRenderer { formats: Rc::new(formats), @@ -374,6 +388,7 @@ impl VulkanDevice { resource_descriptor_buffer_cache, blend_buffers: Default::default(), shader_buffer_cache, + uniform_buffer_cache, }); Ok(render) } @@ -404,6 +419,7 @@ impl VulkanRenderer { eotf: TF_LINEAR, oetf: TF_LINEAR, descriptor_set_layouts: Default::default(), + has_color_management_data: false, }; self.device.create_pipeline2(info, push_size) }; @@ -441,11 +457,13 @@ impl VulkanRenderer { tex_cd: &ColorDescription, tex_copy_type: TexCopyType, tex_source_type: TexSourceType, + has_color_management_data: bool, ) -> Result, VulkanError> { let key = TexPipelineKey { tex_copy_type, tex_source_type, eotf: tex_cd.transfer_function, + has_color_management_data, }; if let Some(pl) = pipelines.pipelines.get(&key) { return Ok(pl); @@ -473,6 +491,7 @@ impl VulkanRenderer { eotf: key.eotf.to_vulkan(), oetf: pipelines.oetf.to_vulkan(), descriptor_set_layouts: self.tex_descriptor_set_layouts.clone(), + has_color_management_data, }; let pl = self.device.create_pipeline2(info, push_size)?; pipelines.pipelines.set(key, pl.clone()); @@ -507,6 +526,7 @@ impl VulkanRenderer { eotf: key.eotf.to_vulkan(), oetf: fb_cd.transfer_function.to_vulkan(), descriptor_set_layouts, + has_color_management_data: false, })?; pipelines.set(key, out.clone()); Ok(out) @@ -525,7 +545,6 @@ impl VulkanRenderer { return Ok(()); }; zone!("create_descriptor_buffers"); - let version = self.allocate_point(); let memory = &mut *self.memory.borrow_mut(); let sampler_writer = &mut memory.sampler_descriptor_buffer_writer; sampler_writer.clear(); @@ -538,30 +557,47 @@ impl VulkanRenderer { } let resource_writer = &mut memory.resource_descriptor_buffer_writer; resource_writer.clear(); + let uniform_buffer_descriptor_cache = memory + .uniform_buffer_descriptor_cache + .get_or_insert_with(|| { + vec![0u8; self.device.uniform_buffer_descriptor_size].into_boxed_slice() + }); if let Some(bb) = bb { let layout = self.out_descriptor_set_layout.as_ref().unwrap(); - let offset = resource_writer.next_offset(); - bb.descriptor_buffer_offset.set(offset); + 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); } let tex_descriptor_set_layout = &self.tex_descriptor_set_layouts[1]; for pass in RenderPass::variants() { - for cmd in &memory.ops[pass] { + for cmd in &mut memory.ops[pass] { let VulkanOp::Tex(c) = cmd else { continue; }; let tex = &c.tex; - if tex.descriptor_buffer_version.replace(version) == version { - continue; - } - let offset = resource_writer.next_offset(); - tex.descriptor_buffer_offset.set(offset); + c.resource_descriptor_buffer_offset = resource_writer.next_offset(); let mut writer = resource_writer.add_set(tex_descriptor_set_layout); writer.write( tex_descriptor_set_layout.offsets[0], &tex.sampled_image_descriptor, ); + if let Some(addr) = c.color_management_data_address { + let uniform_buffer = DescriptorAddressInfoEXT::default() + .address(addr) + .range(size_of::() as _); + let info = DescriptorGetInfoEXT::default() + .ty(DescriptorType::UNIFORM_BUFFER) + .data(DescriptorDataEXT { + p_uniform_buffer: &uniform_buffer, + }); + unsafe { + db.get_descriptor(&info, uniform_buffer_descriptor_cache); + } + writer.write( + tex_descriptor_set_layout.offsets[1], + uniform_buffer_descriptor_cache, + ); + } } } let mut infos = ArrayVec::<_, 2>::new(); @@ -569,12 +605,7 @@ impl VulkanRenderer { (&sampler_writer, &self.sampler_descriptor_buffer_cache), (&resource_writer, &self.resource_descriptor_buffer_cache), ] { - let mut min_alignment = 1; - if self.device.is_anv { - // https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/33903 - min_alignment = 4096; - } - let buffer = cache.allocate(writer.len() as DeviceSize, min_alignment)?; + let buffer = cache.allocate(writer.len() as DeviceSize)?; buffer.buffer.allocation.upload(|ptr, _| unsafe { ptr::copy_nonoverlapping(writer.as_ptr(), ptr, writer.len()) })?; @@ -607,6 +638,7 @@ impl VulkanRenderer { memory.tex_targets.clear(); memory.fill_targets.clear(); memory.data_buffer.clear(); + memory.uniform_buffer_writer.clear(); memory.color_transforms.map.clear(); let sync = |memory: &mut Memory| { for pass in RenderPass::variants() { @@ -751,6 +783,16 @@ impl VulkanRenderer { true => TexSourceType::HasAlpha, false => TexSourceType::Opaque, }; + let target_cd = match pass { + RenderPass::BlendBuffer => blend_cd, + RenderPass::FrameBuffer => fb_cd, + }; + let color_management_data_address = memory.color_transforms.get_offset( + &ct.cd.linear, + target_cd, + self.device.uniform_buffer_offset_mask, + &mut memory.uniform_buffer_writer, + ); ops.push(VulkanOp::Tex(VulkanTexOp { tex: tex.clone(), range: lo..hi, @@ -763,6 +805,8 @@ impl VulkanRenderer { range_address: 0, instances: 0, tex_cd: ct.cd.clone(), + color_management_data_address, + resource_descriptor_buffer_offset: 0, })); } } @@ -792,7 +836,7 @@ impl VulkanRenderer { if buf.is_empty() { return Ok(()); } - let buffer = self.shader_buffer_cache.allocate(buf.len() as _, 8)?; + let buffer = self.shader_buffer_cache.allocate(buf.len() as _)?; buffer.buffer.allocation.upload(|ptr, _| unsafe { ptr::copy_nonoverlapping(buf.as_ptr(), ptr, buf.len()); })?; @@ -813,6 +857,33 @@ impl VulkanRenderer { Ok(()) } + fn create_uniform_buffer(&self) -> Result<(), VulkanError> { + if self.device.descriptor_buffer.is_none() { + return Ok(()); + } + zone!("create_uniform_buffer"); + let memory = &mut *self.memory.borrow_mut(); + let buf = &memory.uniform_buffer_writer; + if buf.is_empty() { + return Ok(()); + } + let buffer = self.uniform_buffer_cache.allocate(buf.len() as _)?; + buffer.buffer.allocation.upload(|ptr, _| unsafe { + ptr::copy_nonoverlapping(buf.as_ptr(), ptr, buf.len()); + })?; + for ops in memory.ops.values_mut() { + for op in ops { + if let VulkanOp::Tex(c) = op { + if let Some(addr) = &mut c.color_management_data_address { + *addr += buffer.buffer.address; + } + } + } + } + memory.used_buffers.push(buffer); + Ok(()) + } + fn collect_memory(&self) { zone!("collect_memory"); let memory = &mut *self.memory.borrow_mut(); @@ -1126,6 +1197,7 @@ impl VulkanRenderer { &c.tex_cd, c.copy_type, c.source_type, + c.color_management_data_address.is_some(), )?; bind(&pipeline); let image_info = DescriptorImageInfo::default() @@ -1143,7 +1215,7 @@ impl VulkanRenderer { pipeline.pipeline_layout, 0, &[0, 1], - &[0, tex.descriptor_buffer_offset.get()], + &[0, c.resource_descriptor_buffer_offset], ); dev.cmd_push_constants( buf, @@ -1219,7 +1291,6 @@ impl VulkanRenderer { buf: CommandBuffer, fb: &VulkanImage, fb_cd: &ColorDescription, - bb: &VulkanImage, bb_cd: &ColorDescription, ) -> Result<(), VulkanError> { zone!("blend_buffer_copy"); @@ -1239,7 +1310,7 @@ impl VulkanRenderer { pipeline.pipeline_layout, 0, &[1], - &[bb.descriptor_buffer_offset.get()], + &[memory.blend_buffer_descriptor_buffer_offset], ); dev.cmd_push_constants( buf, @@ -1808,6 +1879,7 @@ impl VulkanRenderer { let buf = self.gfx_command_buffers.allocate()?; self.convert_ops(opts, bb_cd, fb_cd); self.create_data_buffer()?; + self.create_uniform_buffer()?; self.collect_memory(); self.begin_command_buffer(buf.buffer)?; self.create_descriptor_buffers(buf.buffer, bb)?; @@ -1827,8 +1899,8 @@ impl VulkanRenderer { let rp = RenderPass::FrameBuffer; self.begin_rendering(buf.buffer, fb, clear, clear_cd, rp, fb_cd); self.record_draws(buf.buffer, fb, rp, fb_cd)?; - if let Some(bb) = bb { - self.blend_buffer_copy(buf.buffer, fb, fb_cd, bb, bb_cd)?; + if bb.is_some() { + self.blend_buffer_copy(buf.buffer, fb, fb_cd, bb_cd)?; } self.end_rendering(buf.buffer); } @@ -2055,6 +2127,7 @@ struct ColorTransforms { struct ColorTransform { matrix: ColorMatrix, + offset: Option, } impl ColorTransforms { @@ -2070,7 +2143,10 @@ impl ColorTransforms { Entry::Occupied(o) => o.into_mut(), Entry::Vacant(e) => { let matrix = src.color_transform(&dst.linear); - let ct = ColorTransform { matrix }; + let ct = ColorTransform { + matrix, + offset: None, + }; e.insert(ct) } }; @@ -2088,4 +2164,22 @@ impl ColorTransforms { }; color } + + fn get_offset( + &mut self, + src: &LinearColorDescription, + dst: &ColorDescription, + uniform_buffer_offset_mask: DeviceSize, + writer: &mut GenericBufferWriter, + ) -> Option { + let ct = self.get_or_create(src, dst)?; + if ct.offset.is_none() { + let data = TexColorManagementData { + matrix: ct.matrix.to_f32(), + }; + let offset = writer.write(uniform_buffer_offset_mask, &data); + ct.offset = Some(offset); + } + ct.offset + } } diff --git a/src/gfx_apis/vulkan/shaders.rs b/src/gfx_apis/vulkan/shaders.rs index 5fa21821..be199c8a 100644 --- a/src/gfx_apis/vulkan/shaders.rs +++ b/src/gfx_apis/vulkan/shaders.rs @@ -61,6 +61,14 @@ pub struct TexPushConstants { unsafe impl Packed for TexPushConstants {} +#[derive(Copy, Clone, Debug)] +#[repr(C, align(16))] +pub struct TexColorManagementData { + pub matrix: [[f32; 4]; 4], +} + +unsafe impl Packed for TexColorManagementData {} + #[derive(Copy, Clone, Debug)] #[repr(C)] pub struct LegacyTexPushConstants { diff --git a/src/gfx_apis/vulkan/shaders/frag_spec_const.glsl b/src/gfx_apis/vulkan/shaders/frag_spec_const.glsl index ada35281..9bbc6c7b 100644 --- a/src/gfx_apis/vulkan/shaders/frag_spec_const.glsl +++ b/src/gfx_apis/vulkan/shaders/frag_spec_const.glsl @@ -5,5 +5,6 @@ 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 = 4) const bool has_matrix = false; #endif diff --git a/src/gfx_apis/vulkan/shaders/tex.frag b/src/gfx_apis/vulkan/shaders/tex.frag index f9b18731..d7a6a978 100644 --- a/src/gfx_apis/vulkan/shaders/tex.frag +++ b/src/gfx_apis/vulkan/shaders/tex.frag @@ -1,25 +1,35 @@ #version 450 +#extension GL_EXT_scalar_block_layout : require + #include "frag_spec_const.glsl" #include "transfer_functions.glsl" #include "tex.common.glsl" layout(set = 0, binding = 0) uniform sampler sam; layout(set = 1, binding = 0) uniform texture2D tex; +layout(set = 1, binding = 1, row_major, std430) uniform ColorManagementData { + mat4x4 matrix; +} cm_data; layout(location = 0) in vec2 tex_pos; layout(location = 0) out vec4 out_color; void main() { vec4 c = textureLod(sampler2D(tex, sam), tex_pos, 0); - if (eotf != oetf) { + if (eotf != oetf || has_matrix) { + vec3 rgb = c.rgb; if (src_has_alpha) { - c.rgb /= mix(c.a, 1.0, c.a == 0.0); + rgb /= mix(c.a, 1.0, c.a == 0.0); } - c.rgb = apply_eotf(c.rgb); - c.rgb = apply_oetf(c.rgb); + rgb = apply_eotf(rgb); + if (has_matrix) { + rgb = (cm_data.matrix * vec4(rgb, 1.0)).rgb; + } + rgb = apply_oetf(rgb); if (src_has_alpha) { - c.rgb *= c.a; + rgb *= c.a; } + c.rgb = rgb; } if (has_alpha_multiplier) { if (src_has_alpha) { diff --git a/src/gfx_apis/vulkan/shm_image.rs b/src/gfx_apis/vulkan/shm_image.rs index 240d1e11..b7cd130a 100644 --- a/src/gfx_apis/vulkan/shm_image.rs +++ b/src/gfx_apis/vulkan/shm_image.rs @@ -460,8 +460,6 @@ impl VulkanRenderer { ty: VulkanImageMemory::Internal(shm), bridge: None, sampled_image_descriptor: self.sampled_image_descriptor(view), - descriptor_buffer_version: Cell::new(0), - descriptor_buffer_offset: Cell::new(0), execution_version: Cell::new(0), }); let shm = match &img.ty { From e92de36f7ab5b3911ebb2283343c46026b71819b Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 1 Mar 2025 19:35:04 +0100 Subject: [PATCH 6/7] cmm: add more transfer functions --- src/cmm/cmm_transfer_function.rs | 9 ++ .../vulkan/shaders/transfer_functions.glsl | 144 +++++++++++++++++- src/gfx_apis/vulkan/transfer_functions.rs | 18 +++ src/theme.rs | 116 ++++++++++++++ 4 files changed, 281 insertions(+), 6 deletions(-) diff --git a/src/cmm/cmm_transfer_function.rs b/src/cmm/cmm_transfer_function.rs index 50613a64..b7c15e0e 100644 --- a/src/cmm/cmm_transfer_function.rs +++ b/src/cmm/cmm_transfer_function.rs @@ -4,4 +4,13 @@ use linearize::Linearize; pub enum TransferFunction { Srgb, Linear, + St2084Pq, + Bt1886, + Gamma22, + Gamma28, + St240, + ExtSrgb, + Log100, + Log316, + St428, } diff --git a/src/gfx_apis/vulkan/shaders/transfer_functions.glsl b/src/gfx_apis/vulkan/shaders/transfer_functions.glsl index d051d13d..7526fb4a 100644 --- a/src/gfx_apis/vulkan/shaders/transfer_functions.glsl +++ b/src/gfx_apis/vulkan/shaders/transfer_functions.glsl @@ -3,8 +3,17 @@ #include "frag_spec_const.glsl" -#define SRGB 0 -#define LINEAR 1 +#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( @@ -23,18 +32,141 @@ vec3 oetf_srgb(vec3 c) { ); } +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) { + 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) { + return pow(c, vec3(2.6)) * vec3(52.37 / 48.0); +} + +vec3 oetf_st428(vec3 c) { + return pow(vec3(48.0) * c / vec3(52.37), vec3(1.0 / 2.6)); +} + vec3 apply_eotf(vec3 c) { switch (eotf) { - case SRGB: return eotf_srgb(c); - case LINEAR: return c; + 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(c, vec3(2.2)); + case TF_GAMMA28: return pow(c, 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 SRGB: return oetf_srgb(c); - case LINEAR: return c; + 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(c, vec3(1.0 / 2.2)); + case TF_GAMMA28: return pow(c, 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; } } diff --git a/src/gfx_apis/vulkan/transfer_functions.rs b/src/gfx_apis/vulkan/transfer_functions.rs index b5bc2acc..c1354d67 100644 --- a/src/gfx_apis/vulkan/transfer_functions.rs +++ b/src/gfx_apis/vulkan/transfer_functions.rs @@ -2,6 +2,15 @@ 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; @@ -12,6 +21,15 @@ impl TransferFunctionExt for TransferFunction { match self { TransferFunction::Srgb => TF_SRGB, TransferFunction::Linear => TF_LINEAR, + TransferFunction::St2084Pq => TF_ST2084_PQ, + TransferFunction::Bt1886 => TF_BT1887, + TransferFunction::Gamma22 => TF_GAMMA22, + TransferFunction::Gamma28 => TF_GAMMA28, + TransferFunction::St240 => TF_ST240, + TransferFunction::ExtSrgb => TF_EXT_SRGB, + TransferFunction::Log100 => TF_LOG100, + TransferFunction::Log316 => TF_LOG316, + TransferFunction::St428 => TF_ST428, } } } diff --git a/src/theme.rs b/src/theme.rs index 3a19fabc..8d78d814 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,5 +1,8 @@ +#![expect(clippy::excessive_precision)] + use { crate::{cmm::cmm_transfer_function::TransferFunction, utils::clonecell::CloneCell}, + num_traits::Float, std::{cell::Cell, cmp::Ordering, ops::Mul, sync::Arc}, }; @@ -77,6 +80,51 @@ impl Color { fn linear(c: f32) -> f32 { c } + fn st2084_pq(c: f32) -> f32 { + let cp = c.powf(1.0 / 78.84375); + let num = (cp - 0.8359375).max(0.0); + 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 + } else { + ((c + 0.1115) / 1.1115).powf(1.0 / 0.45) + } + } + fn log100(c: f32) -> f32 { + 10.0.powf(2.0 * (c - 1.0)) + } + fn log316(c: f32) -> f32 { + 10.0.powf(2.5 * (c - 1.0)) + } + fn st428(c: f32) -> f32 { + c.powf(2.6) * 52.37 / 48.0 + } + fn gamma22(c: f32) -> f32 { + c.powf(2.2) + } + fn gamma28(c: f32) -> f32 { + c.powf(2.8) + } macro_rules! convert { ($tf:ident) => {{ r = $tf(r); @@ -87,6 +135,15 @@ impl Color { 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), } Self { r, g, b, a: 1.0 } } @@ -185,6 +242,56 @@ impl Color { fn linear(c: f32) -> f32 { c } + fn st2084_pq(c: f32) -> f32 { + let c = c.clamp(0.0, 1.0); + let num = 0.8359375 + 18.8515625 * c.powf(0.1593017578125); + 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 + } else { + 1.1115 * c.powf(0.45) - 0.1115 + } + } + fn log100(c: f32) -> f32 { + let c = c.clamp(0.0, 1.0); + if c < 0.01 { 0.0 } else { 1.0 + c.log10() / 2.0 } + } + fn log316(c: f32) -> f32 { + let c = c.clamp(0.0, 1.0); + if c < 10.0.sqrt() / 1000.0 { + 0.0 + } else { + 1.0 + c.log10() / 2.5 + } + } + fn st428(c: f32) -> f32 { + (48.0 * c / 52.37).powf(1.0 / 2.6) + } + fn gamma22(c: f32) -> f32 { + c.powf(1.0 / 2.2) + } + fn gamma28(c: f32) -> f32 { + c.powf(1.0 / 2.8) + } macro_rules! convert { ($tf:ident) => {{ for c in &mut res[..3] { @@ -201,6 +308,15 @@ impl Color { 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), } if self.a < 1.0 { for c in &mut res[..3] { From 8f992f7cef4b7ba648b515fd69290fb4e1015562 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sun, 2 Mar 2025 15:16:07 +0100 Subject: [PATCH 7/7] color-management: add more capabilities --- src/backends/metal/present.rs | 4 + src/cmm/cmm_luminance.rs | 1 - src/cmm/cmm_manager.rs | 2 - src/cmm/cmm_primaries.rs | 10 -- src/ifs/color_management.rs | 89 ++++++----- .../wp_color_management_output_v1.rs | 3 +- ...wp_color_management_surface_feedback_v1.rs | 3 +- .../color_management/wp_color_manager_v1.rs | 75 ++++++++-- .../wp_image_description_creator_params_v1.rs | 140 +++++++++++++++--- .../wp_image_description_v1.rs | 6 +- src/ifs/wl_surface.rs | 29 +++- .../wp_color_management_surface_v1.rs | 24 ++- src/renderer.rs | 4 +- 13 files changed, 293 insertions(+), 97 deletions(-) rename src/ifs/{color_management => wl_surface}/wp_color_management_surface_v1.rs (66%) diff --git a/src/backends/metal/present.rs b/src/backends/metal/present.rs index 35a299c3..3af4f794 100644 --- a/src/backends/metal/present.rs +++ b/src/backends/metal/present.rs @@ -560,6 +560,10 @@ impl MetalConnector { } return None; }; + if ct.cd.id != self.state.color_manager.srgb_srgb().id { + // Direct scanout requires identical color descriptions. + return None; + } if ct.alpha.is_some() { // Direct scanout with alpha factor is not supported. return None; diff --git a/src/cmm/cmm_luminance.rs b/src/cmm/cmm_luminance.rs index 6f5100af..760f50f5 100644 --- a/src/cmm/cmm_luminance.rs +++ b/src/cmm/cmm_luminance.rs @@ -17,7 +17,6 @@ impl Luminance { white: F64(80.0), }; - #[expect(dead_code)] pub const BT1886: Self = Self { min: F64(0.01), max: F64(100.0), diff --git a/src/cmm/cmm_manager.rs b/src/cmm/cmm_manager.rs index 1244e2d6..6837fadc 100644 --- a/src/cmm/cmm_manager.rs +++ b/src/cmm/cmm_manager.rs @@ -100,12 +100,10 @@ impl ColorManager { &self.srgb_linear } - #[expect(dead_code)] pub fn windows_scrgb(&self) -> &Rc { &self.windows_scrgb } - #[expect(dead_code)] pub fn get_description( self: &Rc, named_primaries: Option, diff --git a/src/cmm/cmm_primaries.rs b/src/cmm/cmm_primaries.rs index 39f0be05..5541d8ee 100644 --- a/src/cmm/cmm_primaries.rs +++ b/src/cmm/cmm_primaries.rs @@ -3,23 +3,14 @@ use {crate::utils::ordered_float::F64, std::hash::Hash}; #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub enum NamedPrimaries { Srgb, - #[expect(dead_code)] PalM, - #[expect(dead_code)] Pal, - #[expect(dead_code)] Ntsc, - #[expect(dead_code)] GenericFilm, - #[expect(dead_code)] Bt2020, - #[expect(dead_code)] Cie1931Xyz, - #[expect(dead_code)] DciP3, - #[expect(dead_code)] DisplayP3, - #[expect(dead_code)] AdobeRgb, } @@ -103,7 +94,6 @@ impl Primaries { }; } impl NamedPrimaries { - #[expect(dead_code)] pub const fn primaries(self) -> Primaries { match self { NamedPrimaries::Srgb => Primaries::SRGB, diff --git a/src/ifs/color_management.rs b/src/ifs/color_management.rs index 21a66ec8..d55efdbf 100644 --- a/src/ifs/color_management.rs +++ b/src/ifs/color_management.rs @@ -1,56 +1,63 @@ +pub use consts::*; + pub mod wp_color_management_output_v1; pub mod wp_color_management_surface_feedback_v1; -pub mod wp_color_management_surface_v1; pub mod wp_color_manager_v1; pub mod wp_image_description_creator_icc_v1; pub mod wp_image_description_creator_params_v1; pub mod wp_image_description_info_v1; pub mod wp_image_description_v1; +const PRIMARIES_MUL: f64 = 1_000_000.0; +const PRIMARIES_MUL_INV: f64 = 1.0 / PRIMARIES_MUL; + +const MIN_LUM_MUL: f64 = 10_000.0; +const MIN_LUM_MUL_INV: f64 = 1.0 / MIN_LUM_MUL; + #[expect(dead_code)] mod consts { - pub(super) const RENDER_INTENT_PERCEPTUAL: u32 = 0; - pub(super) const RENDER_INTENT_RELATIVE: u32 = 1; - pub(super) const RENDER_INTENT_SATURATION: u32 = 2; - pub(super) const RENDER_INTENT_ABSOLUTE: u32 = 3; - pub(super) const RENDER_INTENT_RELATIVE_BPC: u32 = 4; + pub const RENDER_INTENT_PERCEPTUAL: u32 = 0; + pub const RENDER_INTENT_RELATIVE: u32 = 1; + pub const RENDER_INTENT_SATURATION: u32 = 2; + pub const RENDER_INTENT_ABSOLUTE: u32 = 3; + pub const RENDER_INTENT_RELATIVE_BPC: u32 = 4; - pub(super) const FEATURE_ICC_V2_V4: u32 = 0; - pub(super) const FEATURE_PARAMETRIC: u32 = 1; - pub(super) const FEATURE_SET_PRIMARIES: u32 = 2; - pub(super) const FEATURE_SET_TF_POWER: u32 = 3; - pub(super) const FEATURE_SET_LUMINANCES: u32 = 4; - pub(super) const FEATURE_SET_MASTERING_DISPLAY_PRIMARIES: u32 = 5; - pub(super) const FEATURE_EXTENDED_TARGET_VOLUME: u32 = 6; - pub(super) const FEATURE_WINDOWS_SCRGB: u32 = 7; + pub const FEATURE_ICC_V2_V4: u32 = 0; + pub const FEATURE_PARAMETRIC: u32 = 1; + pub const FEATURE_SET_PRIMARIES: u32 = 2; + pub const FEATURE_SET_TF_POWER: u32 = 3; + pub const FEATURE_SET_LUMINANCES: u32 = 4; + pub const FEATURE_SET_MASTERING_DISPLAY_PRIMARIES: u32 = 5; + pub const FEATURE_EXTENDED_TARGET_VOLUME: u32 = 6; + pub const FEATURE_WINDOWS_SCRGB: u32 = 7; - pub(super) const PRIMARIES_SRGB: u32 = 1; - pub(super) const PRIMARIES_PAL_M: u32 = 2; - pub(super) const PRIMARIES_PAL: u32 = 3; - pub(super) const PRIMARIES_NTSC: u32 = 4; - pub(super) const PRIMARIES_GENERIC_FILM: u32 = 5; - pub(super) const PRIMARIES_BT2020: u32 = 6; - pub(super) const PRIMARIES_CIE1931_XYZ: u32 = 7; - pub(super) const PRIMARIES_DCI_P3: u32 = 8; - pub(super) const PRIMARIES_DISPLAY_P3: u32 = 9; - pub(super) const PRIMARIES_ADOBE_RGB: u32 = 10; + pub const PRIMARIES_SRGB: u32 = 1; + pub const PRIMARIES_PAL_M: u32 = 2; + pub const PRIMARIES_PAL: u32 = 3; + pub const PRIMARIES_NTSC: u32 = 4; + pub const PRIMARIES_GENERIC_FILM: u32 = 5; + pub const PRIMARIES_BT2020: u32 = 6; + pub const PRIMARIES_CIE1931_XYZ: u32 = 7; + pub const PRIMARIES_DCI_P3: u32 = 8; + pub const PRIMARIES_DISPLAY_P3: u32 = 9; + pub const PRIMARIES_ADOBE_RGB: u32 = 10; - pub(super) const TRANSFER_FUNCTION_BT1886: u32 = 1; - pub(super) const TRANSFER_FUNCTION_GAMMA22: u32 = 2; - pub(super) const TRANSFER_FUNCTION_GAMMA28: u32 = 3; - pub(super) const TRANSFER_FUNCTION_ST240: u32 = 4; - pub(super) const TRANSFER_FUNCTION_EXT_LINEAR: u32 = 5; - pub(super) const TRANSFER_FUNCTION_LOG_100: u32 = 6; - pub(super) const TRANSFER_FUNCTION_LOG_316: u32 = 7; - pub(super) const TRANSFER_FUNCTION_XVYCC: u32 = 8; - pub(super) const TRANSFER_FUNCTION_SRGB: u32 = 9; - pub(super) const TRANSFER_FUNCTION_EXT_SRGB: u32 = 10; - pub(super) const TRANSFER_FUNCTION_ST2084_PQ: u32 = 11; - pub(super) const TRANSFER_FUNCTION_ST428: u32 = 12; - pub(super) const TRANSFER_FUNCTION_HLG: u32 = 13; + pub const TRANSFER_FUNCTION_BT1886: u32 = 1; + pub const TRANSFER_FUNCTION_GAMMA22: u32 = 2; + pub const TRANSFER_FUNCTION_GAMMA28: u32 = 3; + pub const TRANSFER_FUNCTION_ST240: u32 = 4; + pub const TRANSFER_FUNCTION_EXT_LINEAR: u32 = 5; + pub const TRANSFER_FUNCTION_LOG_100: u32 = 6; + pub const TRANSFER_FUNCTION_LOG_316: u32 = 7; + pub const TRANSFER_FUNCTION_XVYCC: u32 = 8; + pub const TRANSFER_FUNCTION_SRGB: u32 = 9; + pub const TRANSFER_FUNCTION_EXT_SRGB: u32 = 10; + pub const TRANSFER_FUNCTION_ST2084_PQ: u32 = 11; + pub const TRANSFER_FUNCTION_ST428: u32 = 12; + pub const TRANSFER_FUNCTION_HLG: u32 = 13; - pub(super) const CAUSE_LOW_VERSION: u32 = 0; - pub(super) const CAUSE_UNSUPPORTED: u32 = 1; - pub(super) const CAUSE_OPERATING_SYSTEM: u32 = 2; - pub(super) const CAUSE_NO_OUTPUT: u32 = 3; + pub const CAUSE_LOW_VERSION: u32 = 0; + pub const CAUSE_UNSUPPORTED: u32 = 1; + pub const CAUSE_OPERATING_SYSTEM: u32 = 2; + pub const CAUSE_NO_OUTPUT: u32 = 3; } diff --git a/src/ifs/color_management/wp_color_management_output_v1.rs b/src/ifs/color_management/wp_color_management_output_v1.rs index e23d6ebb..bbcb58a8 100644 --- a/src/ifs/color_management/wp_color_management_output_v1.rs +++ b/src/ifs/color_management/wp_color_management_output_v1.rs @@ -43,10 +43,11 @@ impl WpColorManagementOutputV1RequestHandler for WpColorManagementOutputV1 { client: self.client.clone(), version: self.version, tracker: Default::default(), + description: self.client.state.color_manager.srgb_srgb().clone(), }); track!(self.client, obj); self.client.add_client_obj(&obj)?; - obj.send_ready(0); + obj.send_ready(); Ok(()) } } diff --git a/src/ifs/color_management/wp_color_management_surface_feedback_v1.rs b/src/ifs/color_management/wp_color_management_surface_feedback_v1.rs index 6fb30be6..0b166fe6 100644 --- a/src/ifs/color_management/wp_color_management_surface_feedback_v1.rs +++ b/src/ifs/color_management/wp_color_management_surface_feedback_v1.rs @@ -30,10 +30,11 @@ impl WpColorManagementSurfaceFeedbackV1 { client: self.client.clone(), version: self.version, tracker: Default::default(), + description: self.client.state.color_manager.srgb_srgb().clone(), }); track!(self.client, obj); self.client.add_client_obj(&obj)?; - obj.send_ready(0); + obj.send_ready(); Ok(()) } } diff --git a/src/ifs/color_management/wp_color_manager_v1.rs b/src/ifs/color_management/wp_color_manager_v1.rs index acdf3d9d..05546d97 100644 --- a/src/ifs/color_management/wp_color_manager_v1.rs +++ b/src/ifs/color_management/wp_color_manager_v1.rs @@ -2,15 +2,27 @@ use { crate::{ client::{Client, ClientError}, globals::{Global, GlobalName}, - ifs::color_management::{ - consts::{ - FEATURE_PARAMETRIC, PRIMARIES_SRGB, RENDER_INTENT_PERCEPTUAL, - TRANSFER_FUNCTION_SRGB, + ifs::{ + color_management::{ + consts::{ + FEATURE_PARAMETRIC, FEATURE_SET_LUMINANCES, FEATURE_SET_PRIMARIES, + FEATURE_WINDOWS_SCRGB, PRIMARIES_ADOBE_RGB, PRIMARIES_BT2020, + PRIMARIES_CIE1931_XYZ, PRIMARIES_DCI_P3, PRIMARIES_DISPLAY_P3, + PRIMARIES_GENERIC_FILM, PRIMARIES_NTSC, PRIMARIES_PAL, PRIMARIES_PAL_M, + PRIMARIES_SRGB, RENDER_INTENT_PERCEPTUAL, TRANSFER_FUNCTION_BT1886, + TRANSFER_FUNCTION_EXT_LINEAR, TRANSFER_FUNCTION_EXT_SRGB, + TRANSFER_FUNCTION_GAMMA22, TRANSFER_FUNCTION_GAMMA28, + TRANSFER_FUNCTION_LOG_100, TRANSFER_FUNCTION_LOG_316, TRANSFER_FUNCTION_SRGB, + TRANSFER_FUNCTION_ST240, TRANSFER_FUNCTION_ST428, TRANSFER_FUNCTION_ST2084_PQ, + }, + wp_color_management_output_v1::WpColorManagementOutputV1, + wp_color_management_surface_feedback_v1::WpColorManagementSurfaceFeedbackV1, + wp_image_description_creator_params_v1::WpImageDescriptionCreatorParamsV1, + wp_image_description_v1::WpImageDescriptionV1, + }, + wl_surface::wp_color_management_surface_v1::{ + WpColorManagementSurfaceV1, WpColorManagementSurfaceV1Error, }, - wp_color_management_output_v1::WpColorManagementOutputV1, - wp_color_management_surface_feedback_v1::WpColorManagementSurfaceFeedbackV1, - wp_color_management_surface_v1::WpColorManagementSurfaceV1, - wp_image_description_creator_params_v1::WpImageDescriptionCreatorParamsV1, }, leaks::Tracker, object::{Object, Version}, @@ -63,8 +75,30 @@ impl WpColorManagerV1 { fn send_capabilities(&self) { self.send_supported_intent(RENDER_INTENT_PERCEPTUAL); self.send_supported_feature(FEATURE_PARAMETRIC); + self.send_supported_feature(FEATURE_SET_PRIMARIES); + self.send_supported_feature(FEATURE_SET_LUMINANCES); + self.send_supported_feature(FEATURE_WINDOWS_SCRGB); + self.send_supported_tf_named(TRANSFER_FUNCTION_BT1886); + self.send_supported_tf_named(TRANSFER_FUNCTION_GAMMA22); + self.send_supported_tf_named(TRANSFER_FUNCTION_GAMMA28); + self.send_supported_tf_named(TRANSFER_FUNCTION_ST240); + self.send_supported_tf_named(TRANSFER_FUNCTION_EXT_LINEAR); + self.send_supported_tf_named(TRANSFER_FUNCTION_LOG_100); + self.send_supported_tf_named(TRANSFER_FUNCTION_LOG_316); self.send_supported_tf_named(TRANSFER_FUNCTION_SRGB); + self.send_supported_tf_named(TRANSFER_FUNCTION_EXT_SRGB); + self.send_supported_tf_named(TRANSFER_FUNCTION_ST2084_PQ); + self.send_supported_tf_named(TRANSFER_FUNCTION_ST428); self.send_supported_primaries_named(PRIMARIES_SRGB); + self.send_supported_primaries_named(PRIMARIES_PAL_M); + self.send_supported_primaries_named(PRIMARIES_PAL); + self.send_supported_primaries_named(PRIMARIES_NTSC); + self.send_supported_primaries_named(PRIMARIES_GENERIC_FILM); + self.send_supported_primaries_named(PRIMARIES_BT2020); + self.send_supported_primaries_named(PRIMARIES_CIE1931_XYZ); + self.send_supported_primaries_named(PRIMARIES_DCI_P3); + self.send_supported_primaries_named(PRIMARIES_DISPLAY_P3); + self.send_supported_primaries_named(PRIMARIES_ADOBE_RGB); self.send_done(); } @@ -123,15 +157,17 @@ impl WpColorManagerV1RequestHandler for WpColorManagerV1 { } fn get_surface(&self, req: GetSurface, _slf: &Rc) -> Result<(), Self::Error> { - let _ = self.client.lookup(req.surface)?; + let surface = self.client.lookup(req.surface)?; let obj = Rc::new(WpColorManagementSurfaceV1 { id: req.id, client: self.client.clone(), version: self.version, tracker: Default::default(), + surface: surface.clone(), }); track!(self.client, obj); self.client.add_client_obj(&obj)?; + obj.install()?; Ok(()) } @@ -170,6 +206,9 @@ impl WpColorManagerV1RequestHandler for WpColorManagerV1 { client: self.client.clone(), version: self.version, tracker: Default::default(), + tf: Default::default(), + primaries: Default::default(), + luminance: Default::default(), }); track!(self.client, obj); self.client.add_client_obj(&obj)?; @@ -178,10 +217,20 @@ impl WpColorManagerV1RequestHandler for WpColorManagerV1 { fn create_windows_scrgb( &self, - _req: CreateWindowsScrgb, + req: CreateWindowsScrgb, _slf: &Rc, ) -> Result<(), Self::Error> { - Err(WpColorManagerV1Error::CreateWindowsScrgbNotSupported) + let obj = Rc::new(WpImageDescriptionV1 { + id: req.image_description, + client: self.client.clone(), + version: self.version, + tracker: Default::default(), + description: self.client.state.color_manager.windows_scrgb().clone(), + }); + track!(self.client, obj); + self.client.add_client_obj(&obj)?; + obj.send_ready(); + Ok(()) } } @@ -222,7 +271,7 @@ pub enum WpColorManagerV1Error { ClientError(Box), #[error("create_icc_creator is not supported")] CreateIccCreatorNotSupported, - #[error("create_windows_scrgb is not supported")] - CreateWindowsScrgbNotSupported, + #[error(transparent)] + Surface(#[from] WpColorManagementSurfaceV1Error), } efrom!(WpColorManagerV1Error, ClientError); diff --git a/src/ifs/color_management/wp_image_description_creator_params_v1.rs b/src/ifs/color_management/wp_image_description_creator_params_v1.rs index c17684d6..a4d7dc2f 100644 --- a/src/ifs/color_management/wp_image_description_creator_params_v1.rs +++ b/src/ifs/color_management/wp_image_description_creator_params_v1.rs @@ -1,12 +1,27 @@ use { crate::{ client::{Client, ClientError}, + cmm::{ + cmm_luminance::Luminance, + cmm_primaries::{NamedPrimaries, Primaries}, + cmm_transfer_function::TransferFunction, + }, ifs::color_management::{ - consts::{PRIMARIES_SRGB, TRANSFER_FUNCTION_SRGB}, + MIN_LUM_MUL_INV, PRIMARIES_MUL_INV, + consts::{ + PRIMARIES_ADOBE_RGB, PRIMARIES_BT2020, PRIMARIES_CIE1931_XYZ, PRIMARIES_DCI_P3, + PRIMARIES_DISPLAY_P3, PRIMARIES_GENERIC_FILM, PRIMARIES_NTSC, PRIMARIES_PAL, + PRIMARIES_PAL_M, PRIMARIES_SRGB, TRANSFER_FUNCTION_BT1886, + TRANSFER_FUNCTION_EXT_LINEAR, TRANSFER_FUNCTION_EXT_SRGB, + TRANSFER_FUNCTION_GAMMA22, TRANSFER_FUNCTION_GAMMA28, TRANSFER_FUNCTION_LOG_100, + TRANSFER_FUNCTION_LOG_316, TRANSFER_FUNCTION_SRGB, TRANSFER_FUNCTION_ST240, + TRANSFER_FUNCTION_ST428, TRANSFER_FUNCTION_ST2084_PQ, + }, wp_image_description_v1::WpImageDescriptionV1, }, leaks::Tracker, object::{Object, Version}, + utils::ordered_float::F64, wire::{ WpImageDescriptionCreatorParamsV1Id, wp_image_description_creator_params_v1::{ @@ -16,7 +31,7 @@ use { }, }, }, - std::rc::Rc, + std::{cell::Cell, rc::Rc}, thiserror::Error, }; @@ -25,30 +40,74 @@ pub struct WpImageDescriptionCreatorParamsV1 { pub client: Rc, pub version: Version, pub tracker: Tracker, + pub tf: Cell>, + pub primaries: Cell, Primaries)>>, + pub luminance: Cell>, } impl WpImageDescriptionCreatorParamsV1RequestHandler for WpImageDescriptionCreatorParamsV1 { type Error = WpImageDescriptionCreatorParamsV1Error; fn create(&self, req: Create, _slf: &Rc) -> Result<(), Self::Error> { + let Some(transfer_function) = 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, + _ => Luminance::SRGB, + }; + let mut luminance = self.luminance.get().unwrap_or(default_luminance); + if transfer_function == TransferFunction::St2084Pq { + luminance.max.0 = luminance.min.0 + 10_000.0; + } + if luminance.max.0 <= luminance.min.0 || luminance.white.0 <= luminance.min.0 { + return Err(WpImageDescriptionCreatorParamsV1Error::MinLuminanceTooLow); + } + let description = self.client.state.color_manager.get_description( + named_primaries, + primaries, + luminance, + transfer_function, + ); let obj = Rc::new(WpImageDescriptionV1 { id: req.image_description, client: self.client.clone(), version: self.version, tracker: Default::default(), + description, }); track!(self.client, obj); self.client.add_client_obj(&obj)?; - obj.send_ready(0); + obj.send_ready(); self.client.remove_obj(self)?; Ok(()) } fn set_tf_named(&self, req: SetTfNamed, _slf: &Rc) -> Result<(), Self::Error> { - if req.tf != TRANSFER_FUNCTION_SRGB { - return Err(WpImageDescriptionCreatorParamsV1Error::UnsupportedTf( - req.tf, - )); + 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, + _ => { + return Err(WpImageDescriptionCreatorParamsV1Error::UnsupportedTf( + req.tf, + )); + } + }; + if self.tf.replace(Some(tf)).is_some() { + return Err(WpImageDescriptionCreatorParamsV1Error::TfAlreadySet); } Ok(()) } @@ -62,20 +121,55 @@ impl WpImageDescriptionCreatorParamsV1RequestHandler for WpImageDescriptionCreat req: SetPrimariesNamed, _slf: &Rc, ) -> Result<(), Self::Error> { - if req.primaries != PRIMARIES_SRGB { - return Err( - WpImageDescriptionCreatorParamsV1Error::UnsupportedPrimaries(req.primaries), - ); + let primaries = match req.primaries { + PRIMARIES_SRGB => NamedPrimaries::Srgb, + PRIMARIES_PAL_M => NamedPrimaries::PalM, + PRIMARIES_PAL => NamedPrimaries::Pal, + PRIMARIES_NTSC => NamedPrimaries::Ntsc, + PRIMARIES_GENERIC_FILM => NamedPrimaries::GenericFilm, + PRIMARIES_BT2020 => NamedPrimaries::Bt2020, + PRIMARIES_CIE1931_XYZ => NamedPrimaries::Cie1931Xyz, + PRIMARIES_DCI_P3 => NamedPrimaries::DciP3, + PRIMARIES_DISPLAY_P3 => NamedPrimaries::DisplayP3, + PRIMARIES_ADOBE_RGB => NamedPrimaries::AdobeRgb, + _ => { + return Err( + WpImageDescriptionCreatorParamsV1Error::UnsupportedPrimaries(req.primaries), + ); + } + }; + let primaries = (Some(primaries), primaries.primaries()); + if self.primaries.replace(Some(primaries)).is_some() { + return Err(WpImageDescriptionCreatorParamsV1Error::PrimariesAlreadySet); } Ok(()) } - fn set_primaries(&self, _req: SetPrimaries, _slf: &Rc) -> Result<(), Self::Error> { - Err(WpImageDescriptionCreatorParamsV1Error::SetPrimariesNotSupported) + fn set_primaries(&self, req: SetPrimaries, _slf: &Rc) -> Result<(), Self::Error> { + let map = |n: i32| F64(n as f64 * PRIMARIES_MUL_INV); + let primaries = Primaries { + r: (map(req.r_x), map(req.r_y)), + g: (map(req.g_x), map(req.g_y)), + b: (map(req.b_x), map(req.b_y)), + wp: (map(req.w_x), map(req.w_y)), + }; + let primaries = (None, primaries); + if self.primaries.replace(Some(primaries)).is_some() { + return Err(WpImageDescriptionCreatorParamsV1Error::PrimariesAlreadySet); + } + Ok(()) } - fn set_luminances(&self, _req: SetLuminances, _slf: &Rc) -> Result<(), Self::Error> { - Err(WpImageDescriptionCreatorParamsV1Error::SetLuminancesNotSupported) + fn set_luminances(&self, req: SetLuminances, _slf: &Rc) -> Result<(), Self::Error> { + let luminance = Luminance { + min: F64(req.min_lum as f64 * MIN_LUM_MUL_INV), + max: F64(req.max_lum as f64), + white: F64(req.reference_lum as f64), + }; + if self.luminance.replace(Some(luminance)).is_some() { + return Err(WpImageDescriptionCreatorParamsV1Error::LuminancesAlreadySet); + } + Ok(()) } fn set_mastering_display_primaries( @@ -120,15 +214,23 @@ pub enum WpImageDescriptionCreatorParamsV1Error { SetMasteringLuminanceNotSupported, #[error("set_mastering_display_primaries is not supported")] SetMasteringDisplayPrimariesNotSupported, - #[error("set_luminances is not supported")] - SetLuminancesNotSupported, - #[error("set_primaries is not supported")] - SetPrimariesNotSupported, #[error("{} is not a supported named primary", .0)] UnsupportedPrimaries(u32), #[error("set_tf_power is not supported")] SetTfPowerNotSupported, #[error("{} is not a supported named transfer function", .0)] UnsupportedTf(u32), + #[error("The transfer function has already been set")] + TfAlreadySet, + #[error("The primaries have already been set")] + PrimariesAlreadySet, + #[error("The luminances have already been set")] + LuminancesAlreadySet, + #[error("The minimum luminance is too low")] + MinLuminanceTooLow, + #[error("The transfer function was not set")] + TfNotSet, + #[error("The primaries were not set")] + PrimariesNotSet, } efrom!(WpImageDescriptionCreatorParamsV1Error, ClientError); diff --git a/src/ifs/color_management/wp_image_description_v1.rs b/src/ifs/color_management/wp_image_description_v1.rs index 82d05875..00ec4d02 100644 --- a/src/ifs/color_management/wp_image_description_v1.rs +++ b/src/ifs/color_management/wp_image_description_v1.rs @@ -1,6 +1,7 @@ use { crate::{ client::{Client, ClientError}, + cmm::cmm_description::ColorDescription, ifs::color_management::wp_image_description_info_v1::WpImageDescriptionInfoV1, leaks::Tracker, object::{Object, Version}, @@ -15,6 +16,7 @@ pub struct WpImageDescriptionV1 { pub client: Rc, pub version: Version, pub tracker: Tracker, + pub description: Rc, } impl WpImageDescriptionV1 { @@ -27,10 +29,10 @@ impl WpImageDescriptionV1 { }); } - pub fn send_ready(&self, identity: u32) { + pub fn send_ready(&self) { self.client.event(Ready { self_id: self.id, - identity, + identity: self.description.id.into(), }); } } diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 8f7b4cf2..cc0f97f0 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -5,6 +5,7 @@ pub mod ext_session_lock_surface_v1; pub mod tray; pub mod wl_subsurface; pub mod wp_alpha_modifier_surface_v1; +pub mod wp_color_management_surface_v1; pub mod wp_commit_timer_v1; pub mod wp_fifo_v1; pub mod wp_fractional_scale_v1; @@ -22,6 +23,7 @@ use { crate::{ backend::KeyState, client::{Client, ClientError}, + cmm::cmm_description::ColorDescription, cursor_user::{CursorUser, CursorUserId}, damage::DamageMatrix, drm_feedback::DrmFeedback, @@ -104,6 +106,7 @@ use { rc::{Rc, Weak}, }, thiserror::Error, + wp_color_management_surface_v1::WpColorManagementSurfaceV1, zwp_idle_inhibitor_v1::ZwpIdleInhibitorV1, }; @@ -332,6 +335,8 @@ pub struct WlSurface { commit_timer: CloneCell>>, before_latch_listener: EventListener, is_opaque: Cell, + color_management_surface: CloneCell>>, + color_description: CloneCell>>, } impl Debug for WlSurface { @@ -461,6 +466,7 @@ struct PendingState { fifo_barrier_wait: bool, commit_time: Option, tray_item_ack_serial: Option, + color_description: Option>>, } struct AttachedSubsurfaceState { @@ -513,6 +519,7 @@ impl PendingState { opt!(alpha_multiplier); opt!(commit_time); opt!(tray_item_ack_serial); + opt!(color_description); { let (dx1, dy1) = self.offset; let (dx2, dy2) = mem::take(&mut next.offset); @@ -670,6 +677,8 @@ impl WlSurface { commit_timer: Default::default(), before_latch_listener: EventListener::new(slf.clone()), is_opaque: Cell::new(false), + color_management_surface: Default::default(), + color_description: Default::default(), } } @@ -1137,6 +1146,11 @@ impl WlSurface { } } } + let mut color_description_changed = false; + if let Some(desc) = pending.color_description.take() { + color_description_changed = true; + self.color_description.set(desc); + } let mut alpha_changed = false; if let Some(alpha) = pending.alpha_multiplier.take() { alpha_changed = true; @@ -1144,8 +1158,11 @@ impl WlSurface { } let buffer_abs_pos = self.buffer_abs_pos.get(); let mut max_surface_size = buffer_abs_pos.size(); - let mut damage_full = - scale_changed || buffer_transform_changed || viewport_changed || alpha_changed; + let mut damage_full = scale_changed + || buffer_transform_changed + || viewport_changed + || alpha_changed + || color_description_changed; let mut buffer_changed = false; let mut old_raw_size = None; let (mut dx, mut dy) = mem::take(&mut pending.offset); @@ -1658,6 +1675,13 @@ impl WlSurface { pub fn opaque_region(&self) -> Option> { self.opaque_region.get() } + + pub fn color_description(&self) -> Rc { + match self.color_description.get() { + Some(cd) => cd, + None => self.client.state.color_manager.srgb_srgb().clone(), + } + } } object_base! { @@ -1689,6 +1713,7 @@ impl Object for WlSurface { self.text_input_connections.clear(); self.fifo.take(); self.commit_timer.take(); + self.color_management_surface.take(); } } diff --git a/src/ifs/color_management/wp_color_management_surface_v1.rs b/src/ifs/wl_surface/wp_color_management_surface_v1.rs similarity index 66% rename from src/ifs/color_management/wp_color_management_surface_v1.rs rename to src/ifs/wl_surface/wp_color_management_surface_v1.rs index 6d8cf7f3..4666c53d 100644 --- a/src/ifs/color_management/wp_color_management_surface_v1.rs +++ b/src/ifs/wl_surface/wp_color_management_surface_v1.rs @@ -1,7 +1,7 @@ use { crate::{ client::{Client, ClientError}, - ifs::color_management::consts::RENDER_INTENT_PERCEPTUAL, + ifs::{color_management, wl_surface::WlSurface}, leaks::Tracker, object::{Object, Version}, wire::{ @@ -21,12 +21,26 @@ pub struct WpColorManagementSurfaceV1 { pub client: Rc, pub version: Version, pub tracker: Tracker, + pub surface: Rc, +} + +impl WpColorManagementSurfaceV1 { + pub fn install(self: &Rc) -> Result<(), WpColorManagementSurfaceV1Error> { + if self.surface.color_management_surface.is_some() { + return Err(WpColorManagementSurfaceV1Error::HasSurface); + } + self.surface + .color_management_surface + .set(Some(self.clone())); + Ok(()) + } } impl WpColorManagementSurfaceV1RequestHandler for WpColorManagementSurfaceV1 { type Error = WpColorManagementSurfaceV1Error; fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.surface.color_management_surface.take(); self.client.remove_obj(self)?; Ok(()) } @@ -36,12 +50,13 @@ impl WpColorManagementSurfaceV1RequestHandler for WpColorManagementSurfaceV1 { req: SetImageDescription, _slf: &Rc, ) -> Result<(), Self::Error> { - let _ = self.client.lookup(req.image_description)?; - if req.render_intent != RENDER_INTENT_PERCEPTUAL { + if req.render_intent != color_management::RENDER_INTENT_PERCEPTUAL { return Err(WpColorManagementSurfaceV1Error::UnsupportedRenderIntent( req.render_intent, )); } + let desc = self.client.lookup(req.image_description)?; + self.surface.pending.borrow_mut().color_description = Some(Some(desc.description.clone())); Ok(()) } @@ -50,6 +65,7 @@ impl WpColorManagementSurfaceV1RequestHandler for WpColorManagementSurfaceV1 { _req: UnsetImageDescription, _slf: &Rc, ) -> Result<(), Self::Error> { + self.surface.pending.borrow_mut().color_description = Some(None); Ok(()) } } @@ -69,5 +85,7 @@ pub enum WpColorManagementSurfaceV1Error { ClientError(Box), #[error("{} is not a supported render intent", .0)] UnsupportedRenderIntent(u32), + #[error("wl_surface already has a color-management extension")] + HasSurface, } efrom!(WpColorManagementSurfaceV1Error, ClientError); diff --git a/src/renderer.rs b/src/renderer.rs index 22b73182..afa16502 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -444,7 +444,7 @@ impl Renderer<'_> { bounds: Option<&Rect>, ) { let alpha = surface.alpha(); - let cd = self.state.color_manager.srgb_srgb(); + let cd = surface.color_description(); if let Some(tex) = buffer.buffer.get_texture(surface) { let mut opaque = surface.opaque(); if !opaque && tex.format().has_alpha { @@ -463,7 +463,7 @@ impl Renderer<'_> { AcquireSync::Unnecessary, buffer.release_sync, opaque, - cd, + &cd, ); } else if let Some(color) = &buffer.buffer.color { if let Some(rect) = Rect::new_sized(x, y, tsize.0, tsize.1) {