1
0
Fork 0
forked from wry/wry

Merge pull request #399 from mahkoh/jorth/cm-4

color-management: add more capabilities
This commit is contained in:
mahkoh 2025-03-08 18:09:07 +01:00 committed by GitHub
commit 9e20e32338
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
63 changed files with 2509 additions and 330 deletions

View file

@ -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() {
@ -558,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;
@ -747,12 +753,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 +800,7 @@ impl MetalConnector {
None => {
output.perform_screencopies(
&fb.tex,
self.state.color_manager.srgb_srgb(),
None,
&AcquireSync::Unnecessary,
ReleaseSync::None,
@ -804,6 +813,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,

View file

@ -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<SyncFile>) -> Result<Option<SyncFile>, MetalError> {
pub fn copy_to_dev(
&self,
cm: &ColorManager,
sync_file: Option<SyncFile>,
) -> Result<Option<SyncFile>, 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,

View file

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

View file

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

8
src/cmm.rs Normal file
View file

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

View file

@ -0,0 +1,78 @@
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<ColorDescriptionId, 3>;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct ColorDescriptionId(u32);
impl From<u32> for ColorDescriptionId {
fn from(value: u32) -> Self {
Self(value)
}
}
impl From<ColorDescriptionId> 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<Xyz, Local>,
pub local_from_xyz: ColorMatrix<Local, Xyz>,
pub luminance: Luminance,
pub(super) shared: Rc<Shared>,
}
#[derive(Debug)]
pub struct ColorDescription {
pub id: ColorDescriptionId,
pub linear: Rc<LinearColorDescription>,
#[expect(dead_code)]
pub named_primaries: Option<NamedPrimaries>,
pub transfer_function: TransferFunction,
pub(super) shared: Rc<Shared>,
}
impl LinearColorDescription {
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);
}
}

71
src/cmm/cmm_luminance.rs Normal file
View file

@ -0,0 +1,71 @@
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),
};
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<Xyz, Xyz> {
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],
])
}

200
src/cmm/cmm_manager.rs Normal file
View file

@ -0,0 +1,200 @@
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<LinearDescriptionKey, Weak<LinearColorDescription>>,
complete_descriptions: CopyHashMap<CompleteDescriptionKey, Weak<ColorDescription>>,
shared: Rc<Shared>,
srgb_srgb: Rc<ColorDescription>,
srgb_linear: Rc<ColorDescription>,
windows_scrgb: Rc<ColorDescription>,
}
#[derive(Debug, Default)]
pub(super) struct Shared {
pub(super) dead_linear: NumCell<usize>,
pub(super) dead_complete: NumCell<usize>,
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<NamedPrimaries>,
transfer_function: TransferFunction,
}
impl ColorManager {
pub fn new() -> Rc<Self> {
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,
})
}
pub fn srgb_srgb(&self) -> &Rc<ColorDescription> {
&self.srgb_srgb
}
pub fn srgb_linear(&self) -> &Rc<ColorDescription> {
&self.srgb_linear
}
pub fn windows_scrgb(&self) -> &Rc<ColorDescription> {
&self.windows_scrgb
}
pub fn get_description(
self: &Rc<Self>,
named_primaries: Option<NamedPrimaries>,
primaries: Primaries,
luminance: Luminance,
transfer_function: TransferFunction,
) -> Rc<ColorDescription> {
get_description(
&self.shared,
&self.linear_descriptions,
&self.complete_descriptions,
&self.linear_ids,
named_primaries,
primaries,
luminance,
transfer_function,
)
}
}
fn get_description(
shared: &Rc<Shared>,
linear_descriptions: &CopyHashMap<LinearDescriptionKey, Weak<LinearColorDescription>>,
complete_descriptions: &CopyHashMap<CompleteDescriptionKey, Weak<ColorDescription>>,
linear_ids: &LinearColorDescriptionIds,
named_primaries: Option<NamedPrimaries>,
primaries: Primaries,
luminance: Luminance,
transfer_function: TransferFunction,
) -> Rc<ColorDescription> {
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
}

111
src/cmm/cmm_primaries.rs Normal file
View file

@ -0,0 +1,111 @@
use {crate::utils::ordered_float::F64, std::hash::Hash};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum NamedPrimaries {
Srgb,
PalM,
Pal,
Ntsc,
GenericFilm,
Bt2020,
Cie1931Xyz,
DciP3,
DisplayP3,
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 {
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,
}
}
}

201
src/cmm/cmm_tests.rs Normal file
View file

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

View file

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

257
src/cmm/cmm_transform.rs Normal file
View file

@ -0,0 +1,257 @@
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<To = Local, From = Local>(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<T, U> Copy for ColorMatrix<T, U> {}
impl<T, U> Clone for ColorMatrix<T, U> {
fn clone(&self) -> Self {
*self
}
}
impl<T, U> PartialEq<Self> for ColorMatrix<T, U> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T, U> Eq for ColorMatrix<T, U> {}
impl<T, U> Hash for ColorMatrix<T, U> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl<T, U> Debug for ColorMatrix<T, U> {
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<T, U, V> Mul<ColorMatrix<U, T>> for ColorMatrix<V, U> {
type Output = ColorMatrix<V, T>;
fn mul(self, rhs: ColorMatrix<U, T>) -> 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<U, V> MulAssign<ColorMatrix<U, U>> for ColorMatrix<V, U> {
fn mul_assign(&mut self, rhs: ColorMatrix<U, U>) {
*self = *self * rhs;
}
}
impl<T, U> Mul<[f64; 3]> for ColorMatrix<T, U> {
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<T, U> Mul<Color> for ColorMatrix<T, U> {
type Output = Color;
fn mul(self, rhs: Color) -> Self::Output {
let mut rgba = rhs.to_array(TransferFunction::Linear);
let 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<T, U> ColorMatrix<T, U> {
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)
}
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<Bradford, Xyz> {
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<Xyz, Bradford> {
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<Xyz, Xyz> {
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<Xyz, Local>, ColorMatrix<Local, Xyz>) {
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),
)
}
}

View file

@ -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},
@ -152,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)),
@ -266,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),
@ -284,6 +286,7 @@ fn start_compositor2(
data_control_device_ids: Default::default(),
workspace_managers: Default::default(),
color_management_enabled: Cell::new(false),
color_manager,
});
state.tracker.register(ClientId::from_raw(0));
create_dummy_output(&state);

View file

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

View file

@ -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(),
);
}
}

View file

@ -507,6 +507,7 @@ impl CursorUser {
&self.group.state,
scale,
transform,
self.group.state.color_manager.srgb_srgb(),
);
match res {
Ok(sync_file) => {

View file

@ -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<bool>,
decay: Cell<Duration>,
color: Cell<Color>,
color_manager: Rc<ColorManager>,
}
const MAX_RECTS: usize = 100_000;
@ -87,7 +89,7 @@ struct Damage {
}
impl DamageVisualizer {
pub fn new(eng: &Rc<AsyncEngine>) -> Self {
pub fn new(eng: &Rc<AsyncEngine>, color_manager: &Rc<ColorManager>) -> 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(&region);
}
}

View file

@ -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<GfxApiOpt>,
pub clear: Option<Color>,
pub clear_cd: Rc<LinearColorDescription>,
}
#[derive(Default, Debug, Copy, Clone, PartialEq)]
@ -194,6 +196,7 @@ pub struct FillRect {
pub rect: FramebufferRect,
pub color: Color,
pub alpha: Option<f32>,
pub cd: Rc<LinearColorDescription>,
}
impl FillRect {
@ -215,6 +218,7 @@ pub struct CopyTexture {
pub release_sync: ReleaseSync,
pub alpha: Option<f32>,
pub opaque: bool,
pub cd: Rc<ColorDescription>,
}
#[derive(Clone, Debug)]
@ -299,10 +303,13 @@ pub trait GfxFramebuffer: Debug {
self: Rc<Self>,
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
cd: &Rc<ColorDescription>,
ops: &[GfxApiOpt],
clear: Option<&Color>,
clear_cd: &Rc<LinearColorDescription>,
region: &Region,
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
blend_cd: &Rc<ColorDescription>,
) -> Result<Option<SyncFile>, GfxError>;
fn format(&self) -> &'static Format;
@ -333,17 +340,23 @@ impl dyn GfxFramebuffer {
self: &Rc<Self>,
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
cd: &Rc<ColorDescription>,
ops: &[GfxApiOpt],
clear: Option<&Color>,
clear_cd: &Rc<LinearColorDescription>,
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
blend_cd: &Rc<ColorDescription>,
) -> Result<Option<SyncFile>, GfxError> {
self.clone().render_with_region(
acquire_sync,
release_sync,
cd,
ops,
clear,
clear_cd,
&self.full_region(),
blend_buffer,
blend_cd,
)
}
@ -351,17 +364,35 @@ impl dyn GfxFramebuffer {
self: &Rc<Self>,
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
cd: &Rc<ColorDescription>,
) -> Result<Option<SyncFile>, 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<Self>,
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
cd: &Rc<ColorDescription>,
color: &Color,
color_cd: &Rc<LinearColorDescription>,
) -> Result<Option<SyncFile>, 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 +412,9 @@ impl dyn GfxFramebuffer {
self: &Rc<Self>,
fb_acquire_sync: AcquireSync,
fb_release_sync: ReleaseSync,
fb_cd: &Rc<ColorDescription>,
texture: &Rc<dyn GfxTexture>,
texture_cd: &Rc<ColorDescription>,
resv: Option<&Rc<dyn BufferResv>>,
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
@ -404,24 +437,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<Self>,
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
cd: &Rc<ColorDescription>,
scale: Scale,
clear: Option<&Color>,
clear_cd: &Rc<LinearColorDescription>,
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
blend_cd: &Rc<ColorDescription>,
f: &mut dyn FnMut(&mut RendererBase),
) -> Result<Option<SyncFile>, 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 +511,22 @@ impl dyn GfxFramebuffer {
self: &Rc<Self>,
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
cd: &Rc<ColorDescription>,
pass: &GfxRenderPass,
region: &Region,
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
blend_cd: &Rc<ColorDescription>,
) -> Result<Option<SyncFile>, 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 +534,7 @@ impl dyn GfxFramebuffer {
self: &Rc<Self>,
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
cd: &Rc<ColorDescription>,
node: &OutputNode,
state: &State,
cursor_rect: Option<Rect>,
@ -481,10 +542,12 @@ impl dyn GfxFramebuffer {
render_hardware_cursor: bool,
fill_black_in_grace_period: bool,
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
blend_cd: &Rc<ColorDescription>,
) -> Result<Option<SyncFile>, GfxError> {
self.render_node(
acquire_sync,
release_sync,
cd,
node,
state,
cursor_rect,
@ -495,6 +558,7 @@ impl dyn GfxFramebuffer {
fill_black_in_grace_period,
node.global.persistent.transform.get(),
blend_buffer,
blend_cd,
)
}
@ -502,6 +566,7 @@ impl dyn GfxFramebuffer {
self: &Rc<Self>,
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
cd: &Rc<ColorDescription>,
node: &dyn Node,
state: &State,
cursor_rect: Option<Rect>,
@ -512,6 +577,7 @@ impl dyn GfxFramebuffer {
fill_black_in_grace_period: bool,
transform: Transform,
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
blend_cd: &Rc<ColorDescription>,
) -> Result<Option<SyncFile>, GfxError> {
let pass = self.create_render_pass(
node,
@ -528,9 +594,11 @@ impl dyn GfxFramebuffer {
self.perform_render_pass(
acquire_sync,
release_sync,
cd,
&pass,
&self.full_region(),
blend_buffer,
blend_cd,
)
}
@ -542,6 +610,7 @@ impl dyn GfxFramebuffer {
state: &State,
scale: Scale,
transform: Transform,
cd: &Rc<ColorDescription>,
) -> Result<Option<SyncFile>, GfxError> {
let mut ops = vec![];
let mut renderer = Renderer {
@ -557,9 +626,12 @@ impl dyn GfxFramebuffer {
self.render(
acquire_sync,
release_sync,
cd,
&ops,
Some(&Color::TRANSPARENT),
&cd.linear,
None,
cd,
)
}
}
@ -832,6 +904,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 +971,7 @@ pub fn create_render_pass(
GfxRenderPass {
ops,
clear: Some(c),
clear_cd: state.color_manager.srgb_srgb().linear.clone(),
}
}

View file

@ -67,8 +67,9 @@ macro_rules! dynload {
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::{
@ -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,
@ -204,10 +205,15 @@ enum RenderError {
#[derive(Default)]
struct GfxGlState {
triangles: RefCell<Vec<[f32; 2]>>,
fill_rect: VecStorage<FillRect>,
fill_rect: VecStorage<GlFillRect>,
copy_tex: VecStorage<&'static CopyTexture>,
}
struct GlFillRect {
pub rect: FramebufferRect,
pub color: Color,
}
fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) -> Option<SyncFile> {
let mut state = fb.ctx.gl_state.borrow_mut();
let state = &mut *state;
@ -235,10 +241,9 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) -> Option<SyncFile> {
}
}
GfxApiOpt::FillRect(f) => {
fill_rect.push(FillRect {
fill_rect.push(GlFillRect {
rect: f.rect,
color: f.effective_color(),
alpha: None,
});
i += 1;
}

View file

@ -1,5 +1,9 @@
use {
crate::{
cmm::{
cmm_description::{ColorDescription, LinearColorDescription},
cmm_transfer_function::TransferFunction,
},
format::Format,
gfx_api::{
AcquireSync, AsyncShmGfxTextureCallback, GfxApiOpt, GfxBlendBuffer, GfxError,
@ -18,7 +22,7 @@ use {
sys::{GL_ONE, GL_ONE_MINUS_SRC_ALPHA},
},
rect::Region,
theme::{Color, TransferFunction},
theme::Color,
},
std::{
cell::Cell,
@ -104,10 +108,13 @@ impl GfxFramebuffer for Framebuffer {
self: Rc<Self>,
acquire_sync: AcquireSync,
_release_sync: ReleaseSync,
_cd: &Rc<ColorDescription>,
ops: &[GfxApiOpt],
clear: Option<&Color>,
_clear_cd: &Rc<LinearColorDescription>,
_region: &Region,
_blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
_blend_cd: &Rc<ColorDescription>,
) -> Result<Option<SyncFile>, GfxError> {
(*self)
.render(acquire_sync, ops, clear)

View file

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

View file

@ -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<VulkanAllocator>,
buffers: RefCell<Vec<VulkanBufferUnused>>,
usage: BufferUsageFlags,
min_alignment: DeviceSize,
}
pub struct VulkanBuffer {
@ -40,12 +42,14 @@ impl VulkanBufferCache {
device: &Rc<VulkanDevice>,
allocator: &Rc<VulkanAllocator>,
usage: BufferUsageFlags,
min_alignment: DeviceSize,
) -> Rc<Self> {
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<Self> {
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<Self>,
capacity: DeviceSize,
align: DeviceSize,
) -> Result<VulkanBuffer, VulkanError> {
pub fn allocate(self: &Rc<Self>, capacity: DeviceSize) -> Result<VulkanBuffer, VulkanError> {
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<u8>,
}
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
}
}

View file

@ -15,7 +15,7 @@ pub(super) struct VulkanDescriptorSetLayout {
pub(super) device: Rc<VulkanDevice>,
pub(super) layout: DescriptorSetLayout,
pub(super) size: DeviceSize,
pub(super) offsets: ArrayVec<DeviceSize, 1>,
pub(super) offsets: ArrayVec<DeviceSize, 2>,
pub(super) _sampler: Option<Rc<VulkanSampler>>,
}
@ -87,12 +87,20 @@ impl VulkanDevice {
pub(super) fn create_tex_resource_descriptor_set_layout(
self: &Rc<Self>,
) -> Result<Rc<VulkanDescriptorSetLayout>, 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(),

View file

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

View file

@ -1,5 +1,6 @@
use {
crate::{
cmm::cmm_description::{ColorDescription, LinearColorDescription},
format::Format,
gfx_api::{
AcquireSync, AsyncShmGfxTexture, AsyncShmGfxTextureCallback,
@ -20,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,
@ -65,8 +66,6 @@ pub struct VulkanImage {
pub(super) ty: VulkanImageMemory,
pub(super) bridge: Option<VulkanFramebufferBridge>,
pub(super) sampled_image_descriptor: Box<[u8]>,
pub(super) descriptor_buffer_version: Cell<u64>,
pub(super) descriptor_buffer_offset: Cell<DeviceSize>,
pub(super) execution_version: Cell<u64>,
}
@ -467,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),
}))
}
@ -552,10 +549,13 @@ impl GfxFramebuffer for VulkanImage {
self: Rc<Self>,
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
cd: &Rc<ColorDescription>,
ops: &[GfxApiOpt],
clear: Option<&Color>,
clear_cd: &Rc<LinearColorDescription>,
region: &Region,
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
blend_cd: &Rc<ColorDescription>,
) -> Result<Option<SyncFile>, GfxError> {
let mut blend_buffer =
blend_buffer.map(|b| b.clone().into_vk(&self.renderer.device.device));
@ -574,10 +574,13 @@ impl GfxFramebuffer for VulkanImage {
&self,
acquire_sync,
release_sync,
cd,
ops,
clear,
clear_cd,
region,
blend_buffer,
blend_cd,
)
.map_err(|e| e.into())
}

View file

@ -42,6 +42,7 @@ pub(super) struct PipelineCreateInfo {
pub(super) eotf: u32,
pub(super) oetf: u32,
pub(super) descriptor_set_layouts: ArrayVec<Rc<VulkanDescriptorSetLayout>, 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);

View file

@ -1,8 +1,12 @@
use {
crate::{
async_engine::{AsyncEngine, SpawnedFuture},
cmm::{
cmm_description::{ColorDescription, LinearColorDescription, LinearColorDescriptionId},
cmm_transfer_function::TransferFunction,
cmm_transform::ColorMatrix,
},
cpu_worker::PendingJob,
format::XRGB8888,
gfx_api::{
AcquireSync, BufferResv, BufferResvUser, GfxApiOpt, GfxBlendBuffer, GfxFormat,
GfxTexture, GfxWriteModifier, ReleaseSync, SyncFile,
@ -10,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,
@ -23,14 +27,14 @@ 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, TF_SRGB},
transfer_functions::{TF_LINEAR, TransferFunctionExt},
},
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},
},
@ -42,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,
@ -71,8 +76,11 @@ use {
pub struct VulkanRenderer {
pub(super) formats: Rc<AHashMap<u32, GfxFormat>>,
pub(super) device: Rc<VulkanDevice>,
pub(super) pipelines: StaticMap<RenderPass, CopyHashMap<vk::Format, Rc<VulkanFormatPipelines>>>,
pub(super) out_pipelines: RefCell<AHashMap<vk::Format, Rc<VulkanPipeline>>>,
pub(super) fill_pipelines: CopyHashMap<vk::Format, FillPipelines>,
pub(super) tex_pipelines:
StaticMap<TransferFunction, CopyHashMap<vk::Format, Rc<TexPipelines>>>,
pub(super) out_pipelines:
StaticMap<TransferFunction, CopyHashMap<OutPipelineKey, Rc<VulkanPipeline>>>,
pub(super) gfx_command_buffers: CachedCommandBuffers,
pub(super) transfer_command_buffers: Option<CachedCommandBuffers>,
pub(super) wait_semaphores: Stack<Rc<VulkanSemaphore>>,
@ -101,6 +109,7 @@ pub struct VulkanRenderer {
pub(super) resource_descriptor_buffer_cache: Rc<VulkanBufferCache>,
pub(super) blend_buffers: RefCell<AHashMap<(u32, u32), Weak<VulkanImage>>>,
pub(super) shader_buffer_cache: Rc<VulkanBufferCache>,
pub(super) uniform_buffer_cache: Rc<VulkanBufferCache>,
}
pub(super) struct CachedCommandBuffers {
@ -130,13 +139,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,
@ -152,7 +161,7 @@ pub(super) struct Memory {
wait_semaphore_infos: Vec<SemaphoreSubmitInfo<'static>>,
release_fence: Option<Rc<VulkanFence>>,
release_sync_file: Option<SyncFile>,
used_buffers: ArrayVec<VulkanBuffer, 3>,
used_buffers: ArrayVec<VulkanBuffer, 4>,
paint_bounds: StaticMap<RenderPass, Option<PaintRegion>>,
paint_regions: StaticMap<RenderPass, Vec<PaintRegion>>,
clear_rects: StaticMap<RenderPass, Vec<ClearRect>>,
@ -167,6 +176,10 @@ pub(super) struct Memory {
tex_targets: Vec<[Point; 2]>,
data_buffer: Vec<u8>,
out_address: DeviceAddress,
color_transforms: ColorTransforms,
uniform_buffer_writer: GenericBufferWriter,
uniform_buffer_descriptor_cache: Option<Box<[u8]>>,
blend_buffer_descriptor_buffer_offset: DeviceAddress,
}
type Point = [[f32; 2]; 4];
@ -187,6 +200,9 @@ struct VulkanTexOp {
copy_type: TexCopyType,
range_address: DeviceAddress,
instances: u32,
tex_cd: Rc<ColorDescription>,
color_management_data_address: Option<DeviceAddress>,
resource_descriptor_buffer_offset: DeviceAddress,
}
struct VulkanFillOp {
@ -221,12 +237,29 @@ pub(super) struct PendingFrame {
wait_semaphores: Cell<Vec<Rc<VulkanSemaphore>>>,
waiter: Cell<Option<SpawnedFuture<()>>>,
_release_fence: Option<Rc<VulkanFence>>,
_used_buffers: ArrayVec<VulkanBuffer, 3>,
_used_buffers: ArrayVec<VulkanBuffer, 4>,
}
pub(super) struct VulkanFormatPipelines {
pub(super) fill: StaticMap<TexSourceType, Rc<VulkanPipeline>>,
pub(super) tex: StaticMap<TexCopyType, StaticMap<TexSourceType, Rc<VulkanPipeline>>>,
type FillPipelines = Rc<StaticMap<TexSourceType, Rc<VulkanPipeline>>>;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
struct TexPipelineKey {
tex_copy_type: TexCopyType,
tex_source_type: TexSourceType,
eotf: TransferFunction,
has_color_management_data: bool,
}
pub(super) struct TexPipelines {
format: vk::Format,
oetf: TransferFunction,
pipelines: CopyHashMap<TexPipelineKey, Rc<VulkanPipeline>>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub(super) struct OutPipelineKey {
format: vk::Format,
eotf: TransferFunction,
}
impl VulkanDevice {
@ -313,12 +346,19 @@ 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::<TexColorManagementData>() as DeviceSize;
VulkanBufferCache::new(self, &allocator, usage, align)
};
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,
@ -348,24 +388,18 @@ impl VulkanDevice {
resource_descriptor_buffer_cache,
blend_buffers: Default::default(),
shader_buffer_cache,
uniform_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<Rc<VulkanFormatPipelines>, 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<FillPipelines, VulkanError> {
if let Some(pl) = self.fill_pipelines.get(&format) {
return Ok(pl);
}
let create_fill_pipeline = |src_has_alpha| {
@ -381,55 +415,121 @@ 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(),
has_color_management_data: false,
};
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::<TexPushConstants>()
} else {
size_of::<LegacyTexPushConstants>()
};
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<TexPipelines> {
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,
has_color_management_data: bool,
) -> Result<Rc<VulkanPipeline>, 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);
}
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::<TexPushConstants>()
} else {
size_of::<LegacyTexPushConstants>()
};
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(),
has_color_management_data,
};
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<Rc<VulkanPipeline>, 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::<OutPushConstants>(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,
has_color_management_data: false,
})?;
pipelines.set(key, out.clone());
Ok(out)
}
pub(super) fn allocate_point(&self) -> u64 {
@ -445,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();
@ -458,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::<TexColorManagementData>() 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();
@ -489,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())
})?;
@ -510,7 +621,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() {
@ -522,6 +638,8 @@ 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() {
let ops = &mut memory.ops_tmp[pass];
@ -588,10 +706,6 @@ impl VulkanRenderer {
if !bounds.intersects(&target) {
continue;
}
let tf = match pass {
RenderPass::BlendBuffer => TransferFunction::Linear,
RenderPass::FrameBuffer => TransferFunction::Srgb,
};
let ops = &mut memory.ops_tmp[pass];
let lo = memory.fill_targets.len();
for region in &memory.paint_regions[pass] {
@ -605,7 +719,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,
@ -661,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,
@ -672,6 +804,9 @@ impl VulkanRenderer {
copy_type,
range_address: 0,
instances: 0,
tex_cd: ct.cd.clone(),
color_management_data_address,
resource_descriptor_buffer_offset: 0,
}));
}
}
@ -701,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());
})?;
@ -722,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();
@ -865,7 +1027,9 @@ impl VulkanRenderer {
buf: CommandBuffer,
target: &VulkanImage,
clear: Option<&Color>,
clear_cd: &LinearColorDescription,
pass: RenderPass,
target_cd: &ColorDescription,
) {
zone!("begin_rendering");
let memory = &mut *self.memory.borrow_mut();
@ -874,12 +1038,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: match pass {
RenderPass::BlendBuffer => clear.to_array(TransferFunction::Linear),
RenderPass::FrameBuffer => clear.to_array(TransferFunction::Srgb),
},
float32: color.to_array(target_cd.transfer_function),
},
};
let use_load_clear = clear_rects.len() == 1 && {
@ -969,10 +1133,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| {
@ -986,7 +1152,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 {
@ -1026,8 +1192,14 @@ 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,
c.color_management_data_address.is_some(),
)?;
bind(&pipeline);
let image_info = DescriptorImageInfo::default()
.image_view(tex.texture_view)
.image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL);
@ -1043,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,
@ -1118,33 +1290,13 @@ impl VulkanRenderer {
&self,
buf: CommandBuffer,
fb: &VulkanImage,
bb: &VulkanImage,
fb_cd: &ColorDescription,
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::<OutPushConstants>(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,
};
@ -1158,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,
@ -1530,20 +1682,26 @@ impl VulkanRenderer {
fb: &Rc<VulkanImage>,
fb_acquire_sync: AcquireSync,
fb_release_sync: ReleaseSync,
fb_cd: &Rc<ColorDescription>,
opts: &[GfxApiOpt],
clear: Option<&Color>,
clear_cd: &Rc<LinearColorDescription>,
region: &Region,
blend_buffer: Option<Rc<VulkanImage>>,
blend_cd: &Rc<ColorDescription>,
) -> Result<Option<SyncFile>, VulkanError> {
zone!("execute");
let res = self.try_execute(
fb,
fb_acquire_sync,
fb_release_sync,
fb_cd,
opts,
clear,
clear_cd,
region,
blend_buffer,
blend_cd,
);
let sync_file = {
let mut memory = self.memory.borrow_mut();
@ -1706,18 +1864,22 @@ impl VulkanRenderer {
fb: &Rc<VulkanImage>,
fb_acquire_sync: AcquireSync,
fb_release_sync: ReleaseSync,
fb_cd: &Rc<ColorDescription>,
opts: &[GfxApiOpt],
clear: Option<&Color>,
clear_cd: &Rc<LinearColorDescription>,
region: &Region,
mut blend_buffer: Option<Rc<VulkanImage>>,
bb_cd: &Rc<ColorDescription>,
) -> 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.create_uniform_buffer()?;
self.collect_memory();
self.begin_command_buffer(buf.buffer)?;
self.create_descriptor_buffers(buf.buffer, bb)?;
@ -1725,18 +1887,20 @@ 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, 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);
self.record_draws(buf.buffer, fb, RenderPass::FrameBuffer)?;
if let Some(bb) = bb {
self.blend_buffer_copy(buf.buffer, fb, bb)?;
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 bb.is_some() {
self.blend_buffer_copy(buf.buffer, fb, fb_cd, bb_cd)?;
}
self.end_rendering(buf.buffer);
}
@ -1955,3 +2119,67 @@ 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,
offset: Option<DeviceSize>,
}
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,
offset: None,
};
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
}
fn get_offset(
&mut self,
src: &LinearColorDescription,
dst: &ColorDescription,
uniform_buffer_offset_mask: DeviceSize,
writer: &mut GenericBufferWriter,
) -> Option<DeviceSize> {
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
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,2 +1,35 @@
use crate::cmm::cmm_transfer_function::TransferFunction;
pub const TF_SRGB: u32 = 0;
pub const TF_LINEAR: u32 = 1;
pub const TF_ST2084_PQ: u32 = 2;
pub const TF_BT1887: u32 = 3;
pub const TF_GAMMA22: u32 = 4;
pub const TF_GAMMA28: u32 = 5;
pub const TF_ST240: u32 = 6;
pub const TF_EXT_SRGB: u32 = 7;
pub const TF_LOG100: u32 = 8;
pub const TF_LOG316: u32 = 9;
pub const TF_ST428: u32 = 10;
pub trait TransferFunctionExt: Sized {
fn to_vulkan(self) -> u32;
}
impl TransferFunctionExt for TransferFunction {
fn to_vulkan(self) -> u32 {
match self {
TransferFunction::Srgb => TF_SRGB,
TransferFunction::Linear => TF_LINEAR,
TransferFunction::St2084Pq => TF_ST2084_PQ,
TransferFunction::Bt1886 => TF_BT1887,
TransferFunction::Gamma22 => TF_GAMMA22,
TransferFunction::Gamma28 => TF_GAMMA28,
TransferFunction::St240 => TF_ST240,
TransferFunction::ExtSrgb => TF_EXT_SRGB,
TransferFunction::Log100 => TF_LOG100,
TransferFunction::Log316 => TF_LOG316,
TransferFunction::St428 => TF_ST428,
}
}
}

View file

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

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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<Self>) -> 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<Self>,
) -> 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<ClientError>),
#[error("create_icc_creator is not supported")]
CreateIccCreatorNotSupported,
#[error("create_windows_scrgb is not supported")]
CreateWindowsScrgbNotSupported,
#[error(transparent)]
Surface(#[from] WpColorManagementSurfaceV1Error),
}
efrom!(WpColorManagerV1Error, ClientError);

View file

@ -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<Client>,
pub version: Version,
pub tracker: Tracker<Self>,
pub tf: Cell<Option<TransferFunction>>,
pub primaries: Cell<Option<(Option<NamedPrimaries>, Primaries)>>,
pub luminance: Cell<Option<Luminance>>,
}
impl WpImageDescriptionCreatorParamsV1RequestHandler for WpImageDescriptionCreatorParamsV1 {
type Error = WpImageDescriptionCreatorParamsV1Error;
fn create(&self, req: Create, _slf: &Rc<Self>) -> 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<Self>) -> 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<Self>,
) -> 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<Self>) -> Result<(), Self::Error> {
Err(WpImageDescriptionCreatorParamsV1Error::SetPrimariesNotSupported)
fn set_primaries(&self, req: SetPrimaries, _slf: &Rc<Self>) -> 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<Self>) -> Result<(), Self::Error> {
Err(WpImageDescriptionCreatorParamsV1Error::SetLuminancesNotSupported)
fn set_luminances(&self, req: SetLuminances, _slf: &Rc<Self>) -> 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);

View file

@ -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<Client>,
pub version: Version,
pub tracker: Tracker<Self>,
pub description: Rc<ColorDescription>,
}
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(),
});
}
}

View file

@ -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<Self>,
on: &OutputNode,
texture: &Rc<dyn GfxTexture>,
cd: &Rc<ColorDescription>,
resv: Option<&Rc<dyn BufferResv>>,
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(),
)
});
}

View file

@ -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<Self>,
on: &OutputNode,
texture: &Rc<dyn GfxTexture>,
cd: &Rc<ColorDescription>,
resv: Option<&Rc<dyn BufferResv>>,
acquire_sync: &AcquireSync,
release_sync: ReleaseSync,
@ -226,6 +228,7 @@ impl ExtImageCopyCaptureSessionV1 {
frame.copy_texture(
on,
texture,
cd,
resv,
acquire_sync,
release_sync,

View file

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

View file

@ -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<dyn GfxTexture>,
cd: &Rc<ColorDescription>,
resv: Option<&Rc<dyn BufferResv>>,
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,

View file

@ -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<Option<Rc<WpCommitTimerV1>>>,
before_latch_listener: EventListener<dyn BeforeLatchListener>,
is_opaque: Cell<bool>,
color_management_surface: CloneCell<Option<Rc<WpColorManagementSurfaceV1>>>,
color_description: CloneCell<Option<Rc<ColorDescription>>>,
}
impl Debug for WlSurface {
@ -461,6 +466,7 @@ struct PendingState {
fifo_barrier_wait: bool,
commit_time: Option<u64>,
tray_item_ack_serial: Option<u32>,
color_description: Option<Option<Rc<ColorDescription>>>,
}
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<Rc<Region>> {
self.opaque_region.get()
}
pub fn color_description(&self) -> Rc<ColorDescription> {
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();
}
}

View file

@ -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<Client>,
pub version: Version,
pub tracker: Tracker<Self>,
pub surface: Rc<WlSurface>,
}
impl WpColorManagementSurfaceV1 {
pub fn install(self: &Rc<Self>) -> 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<Self>) -> 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<Self>,
) -> 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<Self>,
) -> Result<(), Self::Error> {
self.surface.pending.borrow_mut().color_description = Some(None);
Ok(())
}
}
@ -69,5 +85,7 @@ pub enum WpColorManagementSurfaceV1Error {
ClientError(Box<ClientError>),
#[error("{} is not a supported render intent", .0)]
UnsupportedRenderIntent(u32),
#[error("wl_surface already has a color-management extension")]
HasSurface,
}
efrom!(WpColorManagementSurfaceV1Error, ClientError);

View file

@ -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<Self>,
_acquire_sync: AcquireSync,
_release_sync: ReleaseSync,
_cd: &Rc<ColorDescription>,
ops: &[GfxApiOpt],
clear: Option<&Color>,
_clear_cd: &Rc<LinearColorDescription>,
_region: &Region,
_blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
_blend_cd: &Rc<ColorDescription>,
) -> Result<Option<SyncFile>, GfxError> {
let fb_points = |width: i32, height: i32, rect: &FramebufferRect| {
let points = rect.to_points();

View file

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

View file

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

View file

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

View file

@ -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<c::dev_t, Weak<PortalRenderCtx>>,
dma_buf_ids: Rc<DmaBufIds>,
pw_con: Option<Rc<PwCon>>,
color_manager: Rc<ColorManager>,
}
impl PortalState {

View file

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

View file

@ -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<Rc<dyn GuiElement>>;
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)
}
},
);

View file

@ -11,7 +11,7 @@ use {
renderer::renderer_base::RendererBase,
scale::Scale,
state::State,
theme::{Color, TransferFunction},
theme::Color,
tree::{
ContainerNode, DisplayNode, FloatNode, OutputNode, PlaceholderNode, ToplevelData,
ToplevelNodeBase, WorkspaceNode,
@ -77,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 {
@ -89,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);
@ -125,6 +129,7 @@ impl Renderer<'_> {
AcquireSync::None,
ReleaseSync::None,
false,
self.state.color_manager.srgb_srgb(),
);
}
if let Some(status) = &rd.status {
@ -143,6 +148,7 @@ impl Renderer<'_> {
AcquireSync::None,
ReleaseSync::None,
false,
srgb_srgb,
);
}
}
@ -181,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);
}
}
}
@ -203,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() {
@ -222,6 +229,7 @@ impl Renderer<'_> {
AcquireSync::None,
ReleaseSync::None,
false,
self.state.color_manager.srgb_srgb(),
);
}
}
@ -230,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
@ -248,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 {
@ -268,6 +281,7 @@ impl Renderer<'_> {
AcquireSync::None,
ReleaseSync::None,
false,
srgb_srgb,
);
}
}
@ -340,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>) {
@ -422,6 +444,7 @@ impl Renderer<'_> {
bounds: Option<&Rect>,
) {
let alpha = surface.alpha();
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 {
@ -440,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) {
@ -449,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 {
@ -488,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);
@ -512,6 +539,7 @@ impl Renderer<'_> {
AcquireSync::None,
ReleaseSync::None,
false,
srgb_srgb,
);
}
}

View file

@ -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<f32>) {
self.fill_boxes3(boxes, color, alpha, 0, 0, true);
pub fn fill_scaled_boxes(
&mut self,
boxes: &[Rect],
color: &Color,
alpha: Option<f32>,
cd: &Rc<LinearColorDescription>,
) {
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<LinearColorDescription>) {
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<LinearColorDescription>,
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<f32>,
cd: &Rc<LinearColorDescription>,
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<LinearColorDescription>,
) {
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<LinearColorDescription>,
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<ColorDescription>,
) {
let mut texcoord = tpoints.unwrap_or_else(SampleRect::identity);
@ -200,6 +224,7 @@ impl RendererBase<'_> {
acquire_sync,
release_sync,
opaque,
cd: cd.clone(),
}));
}
}

View file

@ -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()),

View file

@ -11,6 +11,7 @@ use {
cli::RunArgs,
client::{Client, ClientId, Clients, NUM_CACHED_SERIAL_RANGES, SerialRange},
clientmem::ClientMemOffset,
cmm::{cmm_description::ColorDescription, cmm_manager::ColorManager},
compositor::LIBEI_SOCKET,
config::ConfigProxy,
cpu_worker::CpuWorker,
@ -234,6 +235,7 @@ pub struct State {
pub data_control_device_ids: DataControlDeviceIds,
pub workspace_managers: WorkspaceManagerState,
pub color_management_enabled: Cell<bool>,
pub color_manager: Rc<ColorManager>,
}
// impl Drop for State {
@ -976,15 +978,18 @@ impl State {
&self,
output: &OutputNode,
fb: &Rc<dyn GfxFramebuffer>,
cd: &Rc<ColorDescription>,
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
tex: &Rc<dyn GfxTexture>,
render_hw_cursor: bool,
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
blend_cd: &Rc<ColorDescription>,
) -> Result<Option<SyncFile>, GfxError> {
let sync_file = fb.render_output(
acquire_sync,
release_sync,
cd,
output,
self,
Some(output.global.pos.get()),
@ -992,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,
@ -1013,10 +1020,12 @@ impl State {
resv: Option<&Rc<dyn BufferResv>>,
acquire_sync: &AcquireSync,
release_sync: ReleaseSync,
src_cd: &Rc<ColorDescription>,
target: &Rc<dyn GfxFramebuffer>,
target_acquire_sync: AcquireSync,
target_release_sync: ReleaseSync,
target_transform: Transform,
target_cd: &Rc<ColorDescription>,
position: Rect,
render_hardware_cursors: bool,
x_off: i32,
@ -1050,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() {
@ -1066,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<dyn GfxTexture>,
src_cd: &Rc<ColorDescription>,
acquire_sync: &AcquireSync,
position: Rect,
x_off: i32,
@ -1105,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(),

View file

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

View file

@ -1,14 +1,11 @@
#![expect(clippy::excessive_precision)]
use {
crate::utils::clonecell::CloneCell,
crate::{cmm::cmm_transfer_function::TransferFunction, utils::clonecell::CloneCell},
num_traits::Float,
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,
@ -83,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);
@ -93,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 }
}
@ -191,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] {
@ -207,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] {

View file

@ -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<dyn GfxTexture>,
cd: &Rc<ColorDescription>,
resv: Option<&Rc<dyn BufferResv>>,
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<dyn GfxTexture>,
cd: &Rc<ColorDescription>,
resv: Option<&Rc<dyn BufferResv>>,
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(),

View file

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

93
src/utils/free_list.rs Normal file
View file

@ -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::<Seg>() * 8;
pub struct FreeList<T, const N: usize> {
levels: UnsafeCell<[Vec<Seg>; N]>,
_phantom: PhantomData<T>,
}
impl<T, const N: usize> Default for FreeList<T, N> {
fn default() -> Self {
Self {
levels: UnsafeCell::new(array::from_fn(|_| Vec::new())),
_phantom: Default::default(),
}
}
}
impl<T, const N: usize> Debug for FreeList<T, N> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FreeList")
.field("levels", self.get())
.finish()
}
}
impl<T, const N: usize> FreeList<T, N> {
fn get(&self) -> &mut [Vec<Seg>; N] {
unsafe { self.levels.get().deref_mut() }
}
pub fn release(&self, n: T)
where
T: Into<u32>,
{
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<T>,
{
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()
}
}

View file

@ -0,0 +1,40 @@
use crate::utils::free_list::FreeList;
#[test]
fn test() {
let list = FreeList::<u32, 3>::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::<u32, 3>::default();
list.acquire();
list.release(500);
}

View file

@ -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<H: Hasher>(&self, state: &mut H) {
self.0.to_bits().hash(state);
}
}
impl Add<F64> for F64 {
type Output = Self;
fn add(self, rhs: F64) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl Sub<F64> for F64 {
type Output = Self;
fn sub(self, rhs: F64) -> Self::Output {
Self(self.0 - rhs.0)
}
}
impl Mul<F64> for F64 {
type Output = Self;
fn mul(self, rhs: F64) -> Self::Output {
Self(self.0 * rhs.0)
}
}
impl Div<F64> 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)
}
}