1
0
Fork 0
forked from wry/wry

Merge pull request #839 from mahkoh/jorth/render-intents

Add support for more render intents
This commit is contained in:
mahkoh 2026-03-29 16:51:27 +02:00 committed by GitHub
commit c50242562a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 238 additions and 63 deletions

View file

@ -3,6 +3,7 @@ pub mod cmm_eotf;
pub mod cmm_luminance; pub mod cmm_luminance;
pub mod cmm_manager; pub mod cmm_manager;
pub mod cmm_primaries; pub mod cmm_primaries;
pub mod cmm_render_intent;
#[cfg(test)] #[cfg(test)]
mod cmm_tests; mod cmm_tests;
pub mod cmm_transform; pub mod cmm_transform;

View file

@ -5,6 +5,7 @@ use {
cmm_luminance::{Luminance, TargetLuminance, white_balance}, cmm_luminance::{Luminance, TargetLuminance, white_balance},
cmm_manager::Shared, cmm_manager::Shared,
cmm_primaries::{NamedPrimaries, Primaries}, cmm_primaries::{NamedPrimaries, Primaries},
cmm_render_intent::RenderIntent,
cmm_transform::{ColorMatrix, Local, Xyz, bradford_adjustment}, cmm_transform::{ColorMatrix, Local, Xyz, bradford_adjustment},
}, },
utils::ordered_float::F64, utils::ordered_float::F64,
@ -39,12 +40,17 @@ pub struct ColorDescription {
} }
impl LinearColorDescription { impl LinearColorDescription {
pub fn color_transform(&self, target: &Self) -> ColorMatrix { pub fn color_transform(&self, target: &Self, intent: RenderIntent) -> ColorMatrix {
let mut mat = target.local_from_xyz; let mut mat = target.local_from_xyz;
if self.luminance != target.luminance { if self.luminance != target.luminance {
mat *= white_balance(&self.luminance, &target.luminance, target.primaries.wp); mat *= white_balance(
&self.luminance,
&target.luminance,
target.primaries.wp,
intent,
);
} }
if self.primaries.wp != target.primaries.wp { if self.primaries.wp != target.primaries.wp && intent.bradford_adjustment() {
mat *= bradford_adjustment(self.primaries.wp, target.primaries.wp); mat *= bradford_adjustment(self.primaries.wp, target.primaries.wp);
} }
mat * self.xyz_from_local mat * self.xyz_from_local

View file

@ -1,5 +1,8 @@
use crate::{ use crate::{
cmm::cmm_transform::{ColorMatrix, Xyz}, cmm::{
cmm_render_intent::RenderIntent,
cmm_transform::{ColorMatrix, Xyz},
},
utils::ordered_float::F64, utils::ordered_float::F64,
}; };
@ -68,12 +71,19 @@ impl Default for Luminance {
} }
#[expect(non_snake_case)] #[expect(non_snake_case)]
pub fn white_balance(from: &Luminance, to: &Luminance, w_to: (F64, F64)) -> ColorMatrix<Xyz, Xyz> { pub fn white_balance(
from: &Luminance,
to: &Luminance,
w_to: (F64, F64),
intent: RenderIntent,
) -> ColorMatrix<Xyz, Xyz> {
let a = ((from.max - from.min) / (to.max - to.min) * (to.white - to.min) let a = ((from.max - from.min) / (to.max - to.min) * (to.white - to.min)
/ (from.white - from.min)) / (from.white - from.min))
.0; .0;
// let d = ((from.min - to.min) / (to.max - to.min)).0.max(0.0); let d = match intent.black_point_compensation() {
let d = 0.0; true => 0.0,
false => ((from.min - to.min) / (to.max - to.min)).0,
};
let s = a - d; let s = a - d;
let (F64(x_to), F64(y_to)) = w_to; let (F64(x_to), F64(y_to)) = w_to;
let X_to = x_to / y_to; let X_to = x_to / y_to;

View file

@ -0,0 +1,49 @@
use crate::{
ifs::color_management::{
ABSOLUTE_NO_ADAPTATION_SINCE, RENDER_INTENT_ABSOLUTE_NO_ADAPTATION,
RENDER_INTENT_PERCEPTUAL, RENDER_INTENT_RELATIVE, RENDER_INTENT_RELATIVE_BPC,
},
object::Version,
};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
pub enum RenderIntent {
#[default]
Perceptual,
Relative,
RelativeBpc,
AbsoluteNoAdaptation,
}
impl RenderIntent {
pub fn from_wayland(intent: u32, version: Version) -> Option<Self> {
let res = match intent {
RENDER_INTENT_PERCEPTUAL => Self::Perceptual,
RENDER_INTENT_RELATIVE => Self::Relative,
RENDER_INTENT_RELATIVE_BPC => Self::RelativeBpc,
RENDER_INTENT_ABSOLUTE_NO_ADAPTATION if version >= ABSOLUTE_NO_ADAPTATION_SINCE => {
Self::AbsoluteNoAdaptation
}
_ => return None,
};
Some(res)
}
pub fn black_point_compensation(self) -> bool {
match self {
RenderIntent::Perceptual => true,
RenderIntent::RelativeBpc => true,
RenderIntent::Relative => false,
RenderIntent::AbsoluteNoAdaptation => false,
}
}
pub fn bradford_adjustment(self) -> bool {
match self {
RenderIntent::Perceptual => true,
RenderIntent::RelativeBpc => true,
RenderIntent::Relative => true,
RenderIntent::AbsoluteNoAdaptation => false,
}
}
}

View file

@ -136,7 +136,7 @@ mod matrices {
mod transforms { mod transforms {
use crate::cmm::{ use crate::cmm::{
cmm_eotf::Eotf, cmm_luminance::Luminance, cmm_manager::ColorManager, cmm_eotf::Eotf, cmm_luminance::Luminance, cmm_manager::ColorManager,
cmm_primaries::Primaries, cmm_primaries::Primaries, cmm_render_intent::RenderIntent,
}; };
fn check(p1: Primaries, p2: Primaries, expected: [[f64; 4]; 3]) { fn check(p1: Primaries, p2: Primaries, expected: [[f64; 4]; 3]) {
@ -155,7 +155,9 @@ mod transforms {
}; };
let d1 = d(p1); let d1 = d(p1);
let d2 = d(p2); let d2 = d(p2);
let m = d1.linear.color_transform(&d2.linear); let m = d1
.linear
.color_transform(&d2.linear, RenderIntent::Perceptual);
println!("{:#?}", m); println!("{:#?}", m);
assert!((m.0[0][0].0 - expected[0][0]).abs() < 0.001); 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][1].0 - expected[0][1]).abs() < 0.001);

View file

@ -1,6 +1,7 @@
use { use {
crate::{ crate::{
async_engine::AsyncEngine, async_engine::AsyncEngine,
cmm::cmm_render_intent::RenderIntent,
fixed::Fixed, fixed::Fixed,
format::ARGB8888, format::ARGB8888,
gfx_api::{AcquireSync, AlphaMode, GfxContext, GfxError, GfxTexture, ReleaseSync}, gfx_api::{AcquireSync, AlphaMode, GfxContext, GfxError, GfxTexture, ReleaseSync},
@ -398,6 +399,7 @@ fn render_img(image: &InstantiatedCursorImage, renderer: &mut Renderer, x: Fixed
ReleaseSync::None, ReleaseSync::None,
false, false,
renderer.state.color_manager.srgb_gamma22(), renderer.state.color_manager.srgb_gamma22(),
RenderIntent::Perceptual,
AlphaMode::PremultipliedElectrical, AlphaMode::PremultipliedElectrical,
); );
} }
@ -424,6 +426,7 @@ impl Cursor for StaticCursor {
ReleaseSync::None, ReleaseSync::None,
false, false,
renderer.state.color_manager.srgb_gamma22(), renderer.state.color_manager.srgb_gamma22(),
RenderIntent::Perceptual,
AlphaMode::PremultipliedElectrical, AlphaMode::PremultipliedElectrical,
); );
} }
@ -468,6 +471,7 @@ impl Cursor for AnimatedCursor {
ReleaseSync::None, ReleaseSync::None,
false, false,
renderer.state.color_manager.srgb_gamma22(), renderer.state.color_manager.srgb_gamma22(),
RenderIntent::Perceptual,
AlphaMode::PremultipliedElectrical, AlphaMode::PremultipliedElectrical,
); );
} }

View file

@ -1,7 +1,7 @@
use { use {
crate::{ crate::{
async_engine::AsyncEngine, async_engine::AsyncEngine,
cmm::cmm_manager::ColorManager, cmm::{cmm_manager::ColorManager, cmm_render_intent::RenderIntent},
fixed::Fixed, fixed::Fixed,
ifs::wl_output::WlOutputGlobal, ifs::wl_output::WlOutputGlobal,
rect::{Rect, Region}, rect::{Rect, Region},
@ -169,7 +169,14 @@ impl DamageVisualizer {
if region.is_not_empty() { if region.is_not_empty() {
let age = (now - entry.time).as_millis() as u64 as f32 / decay_millis; let age = (now - entry.time).as_millis() as u64 as f32 / decay_millis;
let color = base_color * (1.0 - age); let color = base_color * (1.0 - age);
renderer.fill_boxes2(region.rects(), &color, srgb, dx, dy); renderer.fill_boxes2(
region.rects(),
&color,
srgb,
RenderIntent::Perceptual,
dx,
dy,
);
used = used.union_cow(&region).into_owned(); used = used.union_cow(&region).into_owned();
} }
} }

View file

@ -1,7 +1,10 @@
use { use {
crate::{ crate::{
allocator::Allocator, allocator::Allocator,
cmm::cmm_description::{ColorDescription, LinearColorDescription}, cmm::{
cmm_description::{ColorDescription, LinearColorDescription},
cmm_render_intent::RenderIntent,
},
cpu_worker::CpuWorker, cpu_worker::CpuWorker,
cursor::Cursor, cursor::Cursor,
damage::DamageVisualizer, damage::DamageVisualizer,
@ -255,6 +258,7 @@ pub struct FillRect {
pub rect: FramebufferRect, pub rect: FramebufferRect,
pub color: Color, pub color: Color,
pub alpha: Option<f32>, pub alpha: Option<f32>,
pub render_intent: RenderIntent,
pub cd: Rc<LinearColorDescription>, pub cd: Rc<LinearColorDescription>,
} }
@ -277,6 +281,7 @@ pub struct CopyTexture {
pub release_sync: ReleaseSync, pub release_sync: ReleaseSync,
pub alpha: Option<f32>, pub alpha: Option<f32>,
pub opaque: bool, pub opaque: bool,
pub render_intent: RenderIntent,
pub cd: Rc<ColorDescription>, pub cd: Rc<ColorDescription>,
pub alpha_mode: AlphaMode, pub alpha_mode: AlphaMode,
} }
@ -512,6 +517,7 @@ impl dyn GfxFramebuffer {
release_sync, release_sync,
false, false,
texture_cd, texture_cd,
RenderIntent::Perceptual,
AlphaMode::PremultipliedElectrical, AlphaMode::PremultipliedElectrical,
); );
let clear = self.format().has_alpha.then_some(&Color::TRANSPARENT); let clear = self.format().has_alpha.then_some(&Color::TRANSPARENT);

View file

@ -4,6 +4,7 @@ use {
cmm::{ cmm::{
cmm_description::{ColorDescription, LinearColorDescription, LinearColorDescriptionId}, cmm_description::{ColorDescription, LinearColorDescription, LinearColorDescriptionId},
cmm_eotf::{Eotf, EotfPow, bt1886_eotf_args, bt1886_inv_eotf_args}, cmm_eotf::{Eotf, EotfPow, bt1886_eotf_args, bt1886_inv_eotf_args},
cmm_render_intent::RenderIntent,
cmm_transform::ColorMatrix, cmm_transform::ColorMatrix,
}, },
cpu_worker::PendingJob, cpu_worker::PendingJob,
@ -817,9 +818,12 @@ impl VulkanRenderer {
RenderPass::FrameBuffer => fb_cd, RenderPass::FrameBuffer => fb_cd,
}; };
let tf = target_cd.eotf; let tf = target_cd.eotf;
let color = memory let color = memory.color_transforms.apply_to_color(
.color_transforms &fr.cd,
.apply_to_color(&fr.cd, target_cd, fr.color); target_cd,
fr.render_intent,
fr.color,
);
let color = color.to_array2(tf, fr.alpha); let color = color.to_array2(tf, fr.alpha);
let source_type = match color[3] < 1.0 { let source_type = match color[3] < 1.0 {
false => TexSourceType::Opaque, false => TexSourceType::Opaque,
@ -883,6 +887,7 @@ impl VulkanRenderer {
let color_management_data_address = memory.color_transforms.get_offset( let color_management_data_address = memory.color_transforms.get_offset(
&ct.cd.linear, &ct.cd.linear,
target_cd, target_cd,
ct.render_intent,
self.device.uniform_buffer_offset_mask, self.device.uniform_buffer_offset_mask,
&mut memory.uniform_buffer_writer, &mut memory.uniform_buffer_writer,
); );
@ -934,6 +939,7 @@ impl VulkanRenderer {
memory.blend_buffer_color_management_data_address = memory.color_transforms.get_offset( memory.blend_buffer_color_management_data_address = memory.color_transforms.get_offset(
&bb_cd.linear, &bb_cd.linear,
fb_cd, fb_cd,
RenderIntent::Perceptual,
self.device.uniform_buffer_offset_mask, self.device.uniform_buffer_offset_mask,
&mut memory.uniform_buffer_writer, &mut memory.uniform_buffer_writer,
); );
@ -1192,9 +1198,12 @@ impl VulkanRenderer {
if let Some(clear) = clear if let Some(clear) = clear
&& clear_rects.is_not_empty() && clear_rects.is_not_empty()
{ {
let color = memory let color = memory.color_transforms.apply_to_color(
.color_transforms clear_cd,
.apply_to_color(clear_cd, target_cd, *clear); target_cd,
RenderIntent::Perceptual,
*clear,
);
let clear_value = ClearValue { let clear_value = ClearValue {
color: ClearColorValue { color: ClearColorValue {
float32: color.to_array(target_cd.eotf), float32: color.to_array(target_cd.eotf),
@ -2312,7 +2321,7 @@ where
#[derive(Default)] #[derive(Default)]
struct ColorTransforms { struct ColorTransforms {
map: AHashMap<[LinearColorDescriptionId; 2], ColorTransform>, map: AHashMap<([LinearColorDescriptionId; 2], RenderIntent), ColorTransform>,
} }
struct ColorTransform { struct ColorTransform {
@ -2325,14 +2334,15 @@ impl ColorTransforms {
&mut self, &mut self,
src: &LinearColorDescription, src: &LinearColorDescription,
dst: &ColorDescription, dst: &ColorDescription,
intent: RenderIntent,
) -> Option<&mut ColorTransform> { ) -> Option<&mut ColorTransform> {
if src.embeds_into(&dst.linear) { if src.embeds_into(&dst.linear) {
return None; return None;
} }
let ct = match self.map.entry([src.id, dst.linear.id]) { let ct = match self.map.entry(([src.id, dst.linear.id], intent)) {
Entry::Occupied(o) => o.into_mut(), Entry::Occupied(o) => o.into_mut(),
Entry::Vacant(e) => { Entry::Vacant(e) => {
let matrix = src.color_transform(&dst.linear); let matrix = src.color_transform(&dst.linear, intent);
let ct = ColorTransform { let ct = ColorTransform {
matrix, matrix,
offset: None, offset: None,
@ -2347,9 +2357,10 @@ impl ColorTransforms {
&mut self, &mut self,
src: &LinearColorDescription, src: &LinearColorDescription,
dst: &ColorDescription, dst: &ColorDescription,
intent: RenderIntent,
mut color: Color, mut color: Color,
) -> Color { ) -> Color {
if let Some(ct) = self.get_or_create(src, dst) { if let Some(ct) = self.get_or_create(src, dst, intent) {
color = ct.matrix * color; color = ct.matrix * color;
}; };
color color
@ -2359,10 +2370,11 @@ impl ColorTransforms {
&mut self, &mut self,
src: &LinearColorDescription, src: &LinearColorDescription,
dst: &ColorDescription, dst: &ColorDescription,
intent: RenderIntent,
uniform_buffer_offset_mask: DeviceSize, uniform_buffer_offset_mask: DeviceSize,
writer: &mut GenericBufferWriter, writer: &mut GenericBufferWriter,
) -> Option<DeviceSize> { ) -> Option<DeviceSize> {
let ct = self.get_or_create(src, dst)?; let ct = self.get_or_create(src, dst, intent)?;
if ct.offset.is_none() { if ct.offset.is_none() {
let data = ColorManagementData { let data = ColorManagementData {
matrix: ct.matrix.to_f32(), matrix: ct.matrix.to_f32(),

View file

@ -13,6 +13,7 @@ pub mod wp_image_description_v1;
const UNIQUE_CM_IDS_SINCE: Version = Version(2); const UNIQUE_CM_IDS_SINCE: Version = Version(2);
const SRGB_DEPRECATED_SINCE: Version = Version(2); const SRGB_DEPRECATED_SINCE: Version = Version(2);
const COMPOUND_POWER_2_4_SINCE: Version = Version(2); const COMPOUND_POWER_2_4_SINCE: Version = Version(2);
pub const ABSOLUTE_NO_ADAPTATION_SINCE: Version = Version(2);
const PRIMARIES_MUL: f64 = 1_000_000.0; const PRIMARIES_MUL: f64 = 1_000_000.0;
const PRIMARIES_MUL_INV: f64 = 1.0 / PRIMARIES_MUL; const PRIMARIES_MUL_INV: f64 = 1.0 / PRIMARIES_MUL;
@ -27,6 +28,7 @@ mod consts {
pub const RENDER_INTENT_SATURATION: u32 = 2; pub const RENDER_INTENT_SATURATION: u32 = 2;
pub const RENDER_INTENT_ABSOLUTE: u32 = 3; pub const RENDER_INTENT_ABSOLUTE: u32 = 3;
pub const RENDER_INTENT_RELATIVE_BPC: u32 = 4; pub const RENDER_INTENT_RELATIVE_BPC: u32 = 4;
pub const RENDER_INTENT_ABSOLUTE_NO_ADAPTATION: u32 = 5;
pub const FEATURE_ICC_V2_V4: u32 = 0; pub const FEATURE_ICC_V2_V4: u32 = 0;
pub const FEATURE_PARAMETRIC: u32 = 1; pub const FEATURE_PARAMETRIC: u32 = 1;

View file

@ -4,9 +4,11 @@ use {
globals::{Global, GlobalName}, globals::{Global, GlobalName},
ifs::{ ifs::{
color_management::{ color_management::{
COMPOUND_POWER_2_4_SINCE, FEATURE_EXTENDED_TARGET_VOLUME, ABSOLUTE_NO_ADAPTATION_SINCE, COMPOUND_POWER_2_4_SINCE,
FEATURE_SET_MASTERING_DISPLAY_PRIMARIES, FEATURE_SET_TF_POWER, FEATURE_EXTENDED_TARGET_VOLUME, FEATURE_SET_MASTERING_DISPLAY_PRIMARIES,
SRGB_DEPRECATED_SINCE, TRANSFER_FUNCTION_COMPOUND_POWER_2_4, FEATURE_SET_TF_POWER, RENDER_INTENT_ABSOLUTE_NO_ADAPTATION, RENDER_INTENT_RELATIVE,
RENDER_INTENT_RELATIVE_BPC, SRGB_DEPRECATED_SINCE,
TRANSFER_FUNCTION_COMPOUND_POWER_2_4,
consts::{ consts::{
FEATURE_PARAMETRIC, FEATURE_SET_LUMINANCES, FEATURE_SET_PRIMARIES, FEATURE_PARAMETRIC, FEATURE_SET_LUMINANCES, FEATURE_SET_PRIMARIES,
FEATURE_WINDOWS_SCRGB, PRIMARIES_ADOBE_RGB, PRIMARIES_BT2020, FEATURE_WINDOWS_SCRGB, PRIMARIES_ADOBE_RGB, PRIMARIES_BT2020,
@ -77,6 +79,11 @@ pub struct WpColorManagerV1 {
impl WpColorManagerV1 { impl WpColorManagerV1 {
fn send_capabilities(&self) { fn send_capabilities(&self) {
self.send_supported_intent(RENDER_INTENT_PERCEPTUAL); self.send_supported_intent(RENDER_INTENT_PERCEPTUAL);
self.send_supported_intent(RENDER_INTENT_RELATIVE);
self.send_supported_intent(RENDER_INTENT_RELATIVE_BPC);
if self.version >= ABSOLUTE_NO_ADAPTATION_SINCE {
self.send_supported_intent(RENDER_INTENT_ABSOLUTE_NO_ADAPTATION);
}
self.send_supported_feature(FEATURE_PARAMETRIC); self.send_supported_feature(FEATURE_PARAMETRIC);
self.send_supported_feature(FEATURE_SET_PRIMARIES); self.send_supported_feature(FEATURE_SET_PRIMARIES);
self.send_supported_feature(FEATURE_SET_LUMINANCES); self.send_supported_feature(FEATURE_SET_LUMINANCES);

View file

@ -26,7 +26,7 @@ use {
crate::{ crate::{
backend::{ButtonState, KeyState}, backend::{ButtonState, KeyState},
client::{Client, ClientError}, client::{Client, ClientError},
cmm::cmm_description::ColorDescription, cmm::{cmm_description::ColorDescription, cmm_render_intent::RenderIntent},
cursor_user::{CursorUser, CursorUserId}, cursor_user::{CursorUser, CursorUserId},
damage::DamageMatrix, damage::DamageMatrix,
drm_feedback::DrmFeedback, drm_feedback::DrmFeedback,
@ -337,6 +337,7 @@ pub struct WlSurface {
color_management_feedback: color_management_feedback:
CopyHashMap<WpColorManagementSurfaceFeedbackV1Id, Rc<WpColorManagementSurfaceFeedbackV1>>, CopyHashMap<WpColorManagementSurfaceFeedbackV1Id, Rc<WpColorManagementSurfaceFeedbackV1>>,
color_description: CloneCell<Option<Rc<ColorDescription>>>, color_description: CloneCell<Option<Rc<ColorDescription>>>,
render_intent: Cell<RenderIntent>,
color_representation_surface: CloneCell<Option<Rc<WpColorRepresentationSurfaceV1>>>, color_representation_surface: CloneCell<Option<Rc<WpColorRepresentationSurfaceV1>>>,
alpha_mode: Cell<AlphaMode>, alpha_mode: Cell<AlphaMode>,
} }
@ -476,7 +477,7 @@ struct PendingState {
fifo_barrier_wait: bool, fifo_barrier_wait: bool,
commit_time: Option<u64>, commit_time: Option<u64>,
tray_item_ack_serial: Option<u32>, tray_item_ack_serial: Option<u32>,
color_description: Option<Option<Rc<ColorDescription>>>, color_description: Option<Option<(RenderIntent, Rc<ColorDescription>)>>,
serial: Option<u64>, serial: Option<u64>,
alpha_mode: Option<AlphaMode>, alpha_mode: Option<AlphaMode>,
surface_release: SmallVec<[SurfaceRelease; 1]>, surface_release: SmallVec<[SurfaceRelease; 1]>,
@ -689,6 +690,7 @@ impl WlSurface {
color_management_surface: Default::default(), color_management_surface: Default::default(),
color_management_feedback: Default::default(), color_management_feedback: Default::default(),
color_description: Default::default(), color_description: Default::default(),
render_intent: Default::default(),
color_representation_surface: Default::default(), color_representation_surface: Default::default(),
alpha_mode: Default::default(), alpha_mode: Default::default(),
} }
@ -1210,7 +1212,9 @@ impl WlSurface {
let mut color_description_changed = false; let mut color_description_changed = false;
if let Some(desc) = pending.color_description.take() { if let Some(desc) = pending.color_description.take() {
color_description_changed = true; color_description_changed = true;
let (intent, desc) = desc.unzip();
self.color_description.set(desc); self.color_description.set(desc);
self.render_intent.set(intent.unwrap_or_default());
} }
let mut alpha_mode_changed = false; let mut alpha_mode_changed = false;
if let Some(alpha_mode) = pending.alpha_mode.take() if let Some(alpha_mode) = pending.alpha_mode.take()
@ -1756,6 +1760,10 @@ impl WlSurface {
} }
} }
pub fn render_intent(&self) -> RenderIntent {
self.render_intent.get()
}
pub fn add_color_management_feedback(&self, fb: &Rc<WpColorManagementSurfaceFeedbackV1>) { pub fn add_color_management_feedback(&self, fb: &Rc<WpColorManagementSurfaceFeedbackV1>) {
self.color_management_feedback.set(fb.id, fb.clone()); self.color_management_feedback.set(fb.id, fb.clone());
} }

View file

@ -1,7 +1,8 @@
use { use {
crate::{ crate::{
client::{Client, ClientError}, client::{Client, ClientError},
ifs::{color_management, wl_surface::WlSurface}, cmm::cmm_render_intent::RenderIntent,
ifs::wl_surface::WlSurface,
leaks::Tracker, leaks::Tracker,
object::{Object, Version}, object::{Object, Version},
wire::{ wire::{
@ -51,16 +52,16 @@ impl WpColorManagementSurfaceV1RequestHandler for WpColorManagementSurfaceV1 {
req: SetImageDescription, req: SetImageDescription,
_slf: &Rc<Self>, _slf: &Rc<Self>,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
if req.render_intent != color_management::RENDER_INTENT_PERCEPTUAL { let Some(intent) = RenderIntent::from_wayland(req.render_intent, self.version) else {
return Err(WpColorManagementSurfaceV1Error::UnsupportedRenderIntent( return Err(WpColorManagementSurfaceV1Error::UnsupportedRenderIntent(
req.render_intent, req.render_intent,
)); ));
} };
let desc = self.client.lookup(req.image_description)?; let desc = self.client.lookup(req.image_description)?;
if desc.description.is_none() { let Some(desc) = &desc.description else {
return Err(WpColorManagementSurfaceV1Error::NotReady); return Err(WpColorManagementSurfaceV1Error::NotReady);
} };
self.surface.pending.borrow_mut().color_description = Some(desc.description.clone()); self.surface.pending.borrow_mut().color_description = Some(Some((intent, desc.clone())));
Ok(()) Ok(())
} }

View file

@ -2,7 +2,7 @@ use {
crate::{ crate::{
allocator::{BO_USE_RENDERING, BufferObject, BufferUsage}, allocator::{BO_USE_RENDERING, BufferObject, BufferUsage},
async_engine::{Phase, SpawnedFuture}, async_engine::{Phase, SpawnedFuture},
cmm::cmm_manager::ColorManager, cmm::{cmm_manager::ColorManager, cmm_render_intent::RenderIntent},
cursor::KnownCursor, cursor::KnownCursor,
fixed::Fixed, fixed::Fixed,
format::ARGB8888, format::ARGB8888,
@ -207,7 +207,12 @@ impl GuiElement for Button {
(x1, y1 + border, x1 + border, y2 - border), (x1, y1 + border, x1 + border, y2 - border),
(x2 - border, y1 + border, x2, y2 - border), (x2 - border, y1 + border, x2, y2 - border),
]; ];
r.fill_boxes_f(&rects, &self.border_color.get(), srgb); r.fill_boxes_f(
&rects,
&self.border_color.get(),
srgb,
RenderIntent::Perceptual,
);
} }
{ {
let rects = [(x1 + border, y1 + border, x2 - border, y2 - border)]; let rects = [(x1 + border, y1 + border, x2 - border, y2 - border)];
@ -215,7 +220,7 @@ impl GuiElement for Button {
true => self.bg_color.get(), true => self.bg_color.get(),
false => self.bg_hover_color.get(), false => self.bg_hover_color.get(),
}; };
r.fill_boxes_f(&rects, &color, srgb); r.fill_boxes_f(&rects, &color, srgb, RenderIntent::Perceptual);
} }
if let Some(tex) = self.tex.get() { 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()); let (tx, ty) = r.scale_point_f(x1 + self.tex_off_x.get(), y1 + self.tex_off_y.get());
@ -233,6 +238,7 @@ impl GuiElement for Button {
ReleaseSync::None, ReleaseSync::None,
false, false,
srgb_srgb, srgb_srgb,
RenderIntent::Perceptual,
AlphaMode::PremultipliedElectrical, AlphaMode::PremultipliedElectrical,
); );
} }
@ -336,6 +342,7 @@ impl GuiElement for Label {
ReleaseSync::None, ReleaseSync::None,
false, false,
color_manager.srgb_gamma22(), color_manager.srgb_gamma22(),
RenderIntent::Perceptual,
AlphaMode::PremultipliedElectrical, AlphaMode::PremultipliedElectrical,
); );
} }

View file

@ -1,5 +1,6 @@
use { use {
crate::{ crate::{
cmm::cmm_render_intent::RenderIntent,
gfx_api::{AcquireSync, AlphaMode, GfxApiOpt, ReleaseSync, SampleRect}, gfx_api::{AcquireSync, AlphaMode, GfxApiOpt, ReleaseSync, SampleRect},
icons::{IconState, SizedIcons}, icons::{IconState, SizedIcons},
ifs::wl_surface::{ ifs::wl_surface::{
@ -80,6 +81,7 @@ impl Renderer<'_> {
let theme = &self.state.theme; let theme = &self.state.theme;
let srgb_srgb = self.state.color_manager.srgb_gamma22(); let srgb_srgb = self.state.color_manager.srgb_gamma22();
let srgb = &srgb_srgb.linear; let srgb = &srgb_srgb.linear;
let perceptual = RenderIntent::Perceptual;
if let Some(fs) = &fullscreen { if let Some(fs) = &fullscreen {
fs.node_render(self, x, y, None); fs.node_render(self, x, y, None);
} else { } else {
@ -97,7 +99,7 @@ impl Renderer<'_> {
let bar_bg = self.base.scale_rect(bar_bg); let bar_bg = self.base.scale_rect(bar_bg);
let c = theme.colors.bar_background.get(); let c = theme.colors.bar_background.get();
self.base self.base
.fill_scaled_boxes(slice::from_ref(&bar_bg), &c, None, srgb); .fill_scaled_boxes(slice::from_ref(&bar_bg), &c, None, srgb, perceptual);
self.base.sync(); self.base.sync();
let rd = output.render_data.borrow_mut(); let rd = output.render_data.borrow_mut();
if let Some(aw) = &rd.active_workspace { if let Some(aw) = &rd.active_workspace {
@ -106,7 +108,7 @@ impl Renderer<'_> {
false => theme.colors.focused_title_background.get(), false => theme.colors.focused_title_background.get(),
}; };
self.base self.base
.fill_boxes2(slice::from_ref(&aw.rect), &c, srgb, x, y); .fill_boxes2(slice::from_ref(&aw.rect), &c, srgb, perceptual, x, y);
} }
let mut c = theme.colors.separator.get(); let mut c = theme.colors.separator.get();
if let Some(ws) = &ws if let Some(ws) = &ws
@ -114,18 +116,30 @@ impl Renderer<'_> {
{ {
c = theme.colors.focused_title_background.get(); c = theme.colors.focused_title_background.get();
} }
self.base self.base.fill_boxes2(
.fill_boxes2(slice::from_ref(&rd.bar_separator), &c, srgb, x, y); slice::from_ref(&rd.bar_separator),
&c,
srgb,
perceptual,
x,
y,
);
let c = theme.colors.unfocused_title_background.get(); let c = theme.colors.unfocused_title_background.get();
self.base self.base
.fill_boxes2(&rd.inactive_workspaces, &c, srgb, x, y); .fill_boxes2(&rd.inactive_workspaces, &c, srgb, perceptual, x, y);
let c = theme.colors.captured_unfocused_title_background.get(); let c = theme.colors.captured_unfocused_title_background.get();
self.base self.base
.fill_boxes2(&rd.captured_inactive_workspaces, &c, srgb, x, y); .fill_boxes2(&rd.captured_inactive_workspaces, &c, srgb, perceptual, x, y);
self.base.sync(); self.base.sync();
let c = theme.colors.attention_requested_background.get(); let c = theme.colors.attention_requested_background.get();
self.base self.base.fill_boxes2(
.fill_boxes2(&rd.attention_requested_workspaces, &c, srgb, x, y); &rd.attention_requested_workspaces,
&c,
srgb,
perceptual,
x,
y,
);
let scale = output.global.persistent.scale.get(); let scale = output.global.persistent.scale.get();
for title in &rd.titles { for title in &rd.titles {
let (x, y) = self.base.scale_point(x + title.tex_x, y + title.tex_y); let (x, y) = self.base.scale_point(x + title.tex_x, y + title.tex_y);
@ -143,6 +157,7 @@ impl Renderer<'_> {
ReleaseSync::None, ReleaseSync::None,
false, false,
self.state.color_manager.srgb_gamma22(), self.state.color_manager.srgb_gamma22(),
perceptual,
AlphaMode::PremultipliedElectrical, AlphaMode::PremultipliedElectrical,
); );
} }
@ -166,6 +181,7 @@ impl Renderer<'_> {
ReleaseSync::None, ReleaseSync::None,
false, false,
srgb_srgb, srgb_srgb,
perceptual,
AlphaMode::PremultipliedElectrical, AlphaMode::PremultipliedElectrical,
); );
} }
@ -210,7 +226,7 @@ impl Renderer<'_> {
let color = self.state.theme.colors.highlight.get(); let color = self.state.theme.colors.highlight.get();
let bounds = output.workspace_rect_rel.get().move_(x, y); let bounds = output.workspace_rect_rel.get().move_(x, y);
self.base.sync(); self.base.sync();
self.base.fill_boxes(&[bounds], &color, srgb); self.base.fill_boxes(&[bounds], &color, srgb, perceptual);
} }
} }
@ -232,6 +248,7 @@ impl Renderer<'_> {
std::slice::from_ref(&pos.at_point(x, y)), std::slice::from_ref(&pos.at_point(x, y)),
&Color::from_srgba_straight(20, 20, 20, 255), &Color::from_srgba_straight(20, 20, 20, 255),
&self.state.color_manager.srgb_gamma22().linear, &self.state.color_manager.srgb_gamma22().linear,
RenderIntent::Perceptual,
); );
if let Some(tex) = placeholder.textures.borrow().get(&self.base.scale) if let Some(tex) = placeholder.textures.borrow().get(&self.base.scale)
&& let Some(texture) = tex.texture() && let Some(texture) = tex.texture()
@ -253,6 +270,7 @@ impl Renderer<'_> {
ReleaseSync::None, ReleaseSync::None,
false, false,
self.state.color_manager.srgb_gamma22(), self.state.color_manager.srgb_gamma22(),
RenderIntent::Perceptual,
AlphaMode::PremultipliedElectrical, AlphaMode::PremultipliedElectrical,
); );
} }
@ -263,19 +281,23 @@ impl Renderer<'_> {
{ {
let srgb_srgb = self.state.color_manager.srgb_gamma22(); let srgb_srgb = self.state.color_manager.srgb_gamma22();
let srgb = &srgb_srgb.linear; let srgb = &srgb_srgb.linear;
let perceptual = RenderIntent::Perceptual;
let rd = container.render_data.borrow_mut(); let rd = container.render_data.borrow_mut();
let c = self.state.theme.colors.unfocused_title_background.get(); let c = self.state.theme.colors.unfocused_title_background.get();
self.base.fill_boxes2(&rd.title_rects, &c, srgb, x, y); self.base
.fill_boxes2(&rd.title_rects, &c, srgb, perceptual, x, y);
let c = self.state.theme.colors.focused_title_background.get(); let c = self.state.theme.colors.focused_title_background.get();
self.base self.base
.fill_boxes2(&rd.active_title_rects, &c, srgb, x, y); .fill_boxes2(&rd.active_title_rects, &c, srgb, perceptual, x, y);
let c = self.state.theme.colors.attention_requested_background.get(); let c = self.state.theme.colors.attention_requested_background.get();
self.base self.base
.fill_boxes2(&rd.attention_title_rects, &c, srgb, x, y); .fill_boxes2(&rd.attention_title_rects, &c, srgb, perceptual, x, y);
let c = self.state.theme.colors.separator.get(); let c = self.state.theme.colors.separator.get();
self.base.fill_boxes2(&rd.underline_rects, &c, srgb, x, y); self.base
.fill_boxes2(&rd.underline_rects, &c, srgb, perceptual, x, y);
let c = self.state.theme.colors.border.get(); let c = self.state.theme.colors.border.get();
self.base.fill_boxes2(&rd.border_rects, &c, srgb, x, y); self.base
.fill_boxes2(&rd.border_rects, &c, srgb, perceptual, x, y);
if let Some(lar) = &rd.last_active_rect { if let Some(lar) = &rd.last_active_rect {
let c = self let c = self
.state .state
@ -284,7 +306,7 @@ impl Renderer<'_> {
.focused_inactive_title_background .focused_inactive_title_background
.get(); .get();
self.base self.base
.fill_boxes2(std::slice::from_ref(lar), &c, srgb, x, y); .fill_boxes2(std::slice::from_ref(lar), &c, srgb, perceptual, x, y);
} }
if let Some(titles) = rd.titles.get(&self.base.scale) { if let Some(titles) = rd.titles.get(&self.base.scale) {
for title in titles { for title in titles {
@ -305,6 +327,7 @@ impl Renderer<'_> {
ReleaseSync::None, ReleaseSync::None,
false, false,
srgb_srgb, srgb_srgb,
perceptual,
AlphaMode::PremultipliedElectrical, AlphaMode::PremultipliedElectrical,
); );
} }
@ -382,6 +405,7 @@ impl Renderer<'_> {
&color, &color,
None, None,
&self.state.color_manager.srgb_gamma22().linear, &self.state.color_manager.srgb_gamma22().linear,
RenderIntent::Perceptual,
); );
} }
@ -392,6 +416,7 @@ impl Renderer<'_> {
slice::from_ref(rect), slice::from_ref(rect),
&color, &color,
&self.state.color_manager.srgb_gamma22().linear, &self.state.color_manager.srgb_gamma22().linear,
RenderIntent::Perceptual,
); );
} }
@ -469,6 +494,7 @@ impl Renderer<'_> {
let buf = &buffer.buffer.buf; let buf = &buffer.buffer.buf;
let alpha = surface.alpha(); let alpha = surface.alpha();
let cd = surface.color_description(); let cd = surface.color_description();
let intent = surface.render_intent();
let alpha_mode = surface.alpha_mode(); let alpha_mode = surface.alpha_mode();
if let Some(tex) = buf.get_texture(surface) { if let Some(tex) = buf.get_texture(surface) {
let mut opaque = surface.opaque(); let mut opaque = surface.opaque();
@ -489,6 +515,7 @@ impl Renderer<'_> {
buffer.release_sync, buffer.release_sync,
opaque, opaque,
&cd, &cd,
intent,
alpha_mode, alpha_mode,
); );
} else if let Some(color) = &buf.color { } else if let Some(color) = &buf.color {
@ -503,7 +530,7 @@ impl Renderer<'_> {
); );
self.base.sync(); self.base.sync();
self.base self.base
.fill_scaled_boxes(&[rect], &color, alpha, &cd.linear); .fill_scaled_boxes(&[rect], &color, alpha, &cd.linear, intent);
} }
} }
} else { } else {
@ -539,21 +566,23 @@ impl Renderer<'_> {
]; ];
let srgb_srgb = self.state.color_manager.srgb_gamma22(); let srgb_srgb = self.state.color_manager.srgb_gamma22();
let srgb = &srgb_srgb.linear; let srgb = &srgb_srgb.linear;
self.base.fill_boxes(&borders, &bc, srgb); let perceptual = RenderIntent::Perceptual;
self.base.fill_boxes(&borders, &bc, srgb, perceptual);
let title = [Rect::new_sized_saturating( let title = [Rect::new_sized_saturating(
x + bw, x + bw,
y + bw, y + bw,
pos.width() - 2 * bw, pos.width() - 2 * bw,
th, th,
)]; )];
self.base.fill_boxes(&title, &tc, srgb); self.base.fill_boxes(&title, &tc, srgb, perceptual);
let title_underline = [Rect::new_sized_saturating( let title_underline = [Rect::new_sized_saturating(
x + bw, x + bw,
y + bw + th, y + bw + th,
pos.width() - 2 * bw, pos.width() - 2 * bw,
tuh, tuh,
)]; )];
self.base.fill_boxes(&title_underline, &uc, srgb); self.base
.fill_boxes(&title_underline, &uc, srgb, perceptual);
let rect = floating.title_rect.get().move_(x, y); let rect = floating.title_rect.get().move_(x, y);
let bounds = self.base.scale_rect(rect); let bounds = self.base.scale_rect(rect);
let (mut x1, y1) = rect.position(); let (mut x1, y1) = rect.position();
@ -586,6 +615,7 @@ impl Renderer<'_> {
ReleaseSync::None, ReleaseSync::None,
false, false,
srgb_srgb, srgb_srgb,
perceptual,
AlphaMode::PremultipliedElectrical, AlphaMode::PremultipliedElectrical,
); );
} }
@ -609,6 +639,7 @@ impl Renderer<'_> {
ReleaseSync::None, ReleaseSync::None,
false, false,
srgb_srgb, srgb_srgb,
perceptual,
AlphaMode::PremultipliedElectrical, AlphaMode::PremultipliedElectrical,
); );
} }

View file

@ -1,6 +1,9 @@
use { use {
crate::{ crate::{
cmm::cmm_description::{ColorDescription, LinearColorDescription}, cmm::{
cmm_description::{ColorDescription, LinearColorDescription},
cmm_render_intent::RenderIntent,
},
gfx_api::{ gfx_api::{
AcquireSync, AlphaMode, BufferResv, CopyTexture, FillRect, FramebufferRect, GfxApiOpt, AcquireSync, AlphaMode, BufferResv, CopyTexture, FillRect, FramebufferRect, GfxApiOpt,
GfxTexture, ReleaseSync, SampleRect, GfxTexture, ReleaseSync, SampleRect,
@ -70,12 +73,19 @@ impl RendererBase<'_> {
color: &Color, color: &Color,
alpha: Option<f32>, alpha: Option<f32>,
cd: &Rc<LinearColorDescription>, cd: &Rc<LinearColorDescription>,
render_intent: RenderIntent,
) { ) {
self.fill_boxes3(boxes, color, alpha, cd, 0, 0, true); self.fill_boxes3(boxes, color, alpha, cd, render_intent, 0, 0, true);
} }
pub fn fill_boxes(&mut self, boxes: &[Rect], color: &Color, cd: &Rc<LinearColorDescription>) { pub fn fill_boxes(
self.fill_boxes3(boxes, color, None, cd, 0, 0, false); &mut self,
boxes: &[Rect],
color: &Color,
cd: &Rc<LinearColorDescription>,
render_intent: RenderIntent,
) {
self.fill_boxes3(boxes, color, None, cd, render_intent, 0, 0, false);
} }
pub fn fill_boxes2( pub fn fill_boxes2(
@ -83,10 +93,11 @@ impl RendererBase<'_> {
boxes: &[Rect], boxes: &[Rect],
color: &Color, color: &Color,
cd: &Rc<LinearColorDescription>, cd: &Rc<LinearColorDescription>,
render_intent: RenderIntent,
dx: i32, dx: i32,
dy: i32, dy: i32,
) { ) {
self.fill_boxes3(boxes, color, None, cd, dx, dy, false); self.fill_boxes3(boxes, color, None, cd, render_intent, dx, dy, false);
} }
fn fill_boxes3( fn fill_boxes3(
@ -95,6 +106,7 @@ impl RendererBase<'_> {
color: &Color, color: &Color,
alpha: Option<f32>, alpha: Option<f32>,
cd: &Rc<LinearColorDescription>, cd: &Rc<LinearColorDescription>,
render_intent: RenderIntent,
dx: i32, dx: i32,
dy: i32, dy: i32,
scaled: bool, scaled: bool,
@ -120,6 +132,7 @@ impl RendererBase<'_> {
), ),
color: *color, color: *color,
alpha, alpha,
render_intent,
cd: cd.clone(), cd: cd.clone(),
})); }));
} }
@ -130,8 +143,9 @@ impl RendererBase<'_> {
boxes: &[(f32, f32, f32, f32)], boxes: &[(f32, f32, f32, f32)],
color: &Color, color: &Color,
cd: &Rc<LinearColorDescription>, cd: &Rc<LinearColorDescription>,
render_intent: RenderIntent,
) { ) {
self.fill_boxes2_f(boxes, color, cd, 0.0, 0.0); self.fill_boxes2_f(boxes, color, cd, render_intent, 0.0, 0.0);
} }
pub fn fill_boxes2_f( pub fn fill_boxes2_f(
@ -139,6 +153,7 @@ impl RendererBase<'_> {
boxes: &[(f32, f32, f32, f32)], boxes: &[(f32, f32, f32, f32)],
color: &Color, color: &Color,
cd: &Rc<LinearColorDescription>, cd: &Rc<LinearColorDescription>,
render_intent: RenderIntent,
dx: f32, dx: f32,
dy: f32, dy: f32,
) { ) {
@ -160,6 +175,7 @@ impl RendererBase<'_> {
), ),
color: *color, color: *color,
alpha: None, alpha: None,
render_intent,
cd: cd.clone(), cd: cd.clone(),
})); }));
} }
@ -180,6 +196,7 @@ impl RendererBase<'_> {
release_sync: ReleaseSync, release_sync: ReleaseSync,
opaque: bool, opaque: bool,
cd: &Rc<ColorDescription>, cd: &Rc<ColorDescription>,
render_intent: RenderIntent,
alpha_mode: AlphaMode, alpha_mode: AlphaMode,
) { ) {
// log::info!("rendering texture {:?}", std::ptr::from_ref(&**texture) as *const u8); // log::info!("rendering texture {:?}", std::ptr::from_ref(&**texture) as *const u8);
@ -227,6 +244,7 @@ impl RendererBase<'_> {
acquire_sync, acquire_sync,
release_sync, release_sync,
opaque, opaque,
render_intent,
cd: cd.clone(), cd: cd.clone(),
alpha_mode, alpha_mode,
})); }));

View file

@ -13,7 +13,10 @@ use {
cli::RunArgs, cli::RunArgs,
client::{Client, ClientCaps, ClientId, Clients, NUM_CACHED_SERIAL_RANGES, SerialRange}, client::{Client, ClientCaps, ClientId, Clients, NUM_CACHED_SERIAL_RANGES, SerialRange},
clientmem::ClientMemOffset, clientmem::ClientMemOffset,
cmm::{cmm_description::ColorDescription, cmm_manager::ColorManager}, cmm::{
cmm_description::ColorDescription, cmm_manager::ColorManager,
cmm_render_intent::RenderIntent,
},
compositor::{LIBEI_SOCKET, LogLevel}, compositor::{LIBEI_SOCKET, LogLevel},
config::ConfigProxy, config::ConfigProxy,
control_center::{ control_center::{
@ -1332,6 +1335,7 @@ impl State {
release_sync, release_sync,
false, false,
src_cd, src_cd,
RenderIntent::Perceptual,
AlphaMode::PremultipliedElectrical, AlphaMode::PremultipliedElectrical,
); );
if render_hardware_cursors if render_hardware_cursors