From c37567f1cdadf0b5e02a96ffa4a12024b549c7bb Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 8 Sep 2025 18:37:07 +0200 Subject: [PATCH] color-management: implement set_tf_power --- src/cmm/cmm_eotf.rs | 24 +++++++++++ src/gfx_apis/vulkan/eotfs.rs | 4 ++ src/gfx_apis/vulkan/renderer.rs | 42 +++++++++++++++---- src/gfx_apis/vulkan/shaders/eotfs.glsl | 3 ++ .../color_management/wp_color_manager_v1.rs | 2 + .../wp_image_description_creator_params_v1.rs | 23 +++++++--- .../wp_image_description_info_v1.rs | 41 +++++++++++------- src/theme.rs | 10 +++++ 8 files changed, 121 insertions(+), 28 deletions(-) diff --git a/src/cmm/cmm_eotf.rs b/src/cmm/cmm_eotf.rs index 6811928b..55cce4c8 100644 --- a/src/cmm/cmm_eotf.rs +++ b/src/cmm/cmm_eotf.rs @@ -9,4 +9,28 @@ pub enum Eotf { Log100, Log316, St428, + Pow(EotfPow), +} + +const MUL: u32 = 10_000; +const MUL_F32: f32 = MUL as f32; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub struct EotfPow(pub u32); + +impl EotfPow { + pub const MIN: Self = Self(10_000); + pub const LINEAR: Self = Self(10_000); + pub const GAMMA22: Self = Self(22_000); + pub const GAMMA24: Self = Self(24_000); + pub const GAMMA28: Self = Self(28_000); + pub const MAX: Self = Self(100_000); + + pub fn eotf_f32(self) -> f32 { + self.0 as f32 / MUL_F32 + } + + pub fn inv_eotf_f32(self) -> f32 { + MUL_F32 / self.0 as f32 + } } diff --git a/src/gfx_apis/vulkan/eotfs.rs b/src/gfx_apis/vulkan/eotfs.rs index 12f799df..fec7cb35 100644 --- a/src/gfx_apis/vulkan/eotfs.rs +++ b/src/gfx_apis/vulkan/eotfs.rs @@ -9,6 +9,7 @@ pub const EOTF_ST240: u32 = 6; pub const EOTF_LOG100: u32 = 8; pub const EOTF_LOG316: u32 = 9; pub const EOTF_ST428: u32 = 10; +pub const EOTF_POW: u32 = 11; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Linearize)] pub enum VulkanEotf { @@ -21,6 +22,7 @@ pub enum VulkanEotf { Log100, Log316, St428, + Pow, } pub trait EotfExt: Sized { @@ -48,6 +50,7 @@ impl EotfExt for Eotf { Log100, Log316, St428, + Pow, } } } @@ -64,6 +67,7 @@ impl VulkanEotf { Self::Log100 => EOTF_LOG100, Self::Log316 => EOTF_LOG316, Self::St428 => EOTF_ST428, + Self::Pow => EOTF_POW, } } } diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index 4531165b..7ea1d6d2 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -3,6 +3,7 @@ use { async_engine::{AsyncEngine, SpawnedFuture}, cmm::{ cmm_description::{ColorDescription, LinearColorDescription, LinearColorDescriptionId}, + cmm_eotf::{Eotf, EotfPow}, cmm_transform::ColorMatrix, }, cpu_worker::PendingJob, @@ -2327,21 +2328,48 @@ impl ColorTransforms { #[derive(Default)] struct EotfArgsCache { - map: AHashMap<(), EotfArg>, + map: AHashMap<(EotfPow, bool), EotfArg>, } struct EotfArg { - _offset: DeviceSize, + offset: DeviceSize, } impl EotfArgsCache { fn get_offset( &mut self, - _desc: &ColorDescription, - _inv: bool, - _uniform_buffer_offset_mask: DeviceSize, - _writer: &mut GenericBufferWriter, + desc: &ColorDescription, + inv: bool, + uniform_buffer_offset_mask: DeviceSize, + writer: &mut GenericBufferWriter, ) -> Option { - None + let Eotf::Pow(pow) = desc.eotf else { + return None; + }; + let ct = match self.map.entry((pow, inv)) { + Entry::Occupied(o) => o.into_mut(), + Entry::Vacant(e) => { + if inv { + let data = InvEotfArgs { + arg1: pow.inv_eotf_f32(), + arg2: 0.0, + arg3: 0.0, + arg4: 0.0, + }; + let offset = writer.write(uniform_buffer_offset_mask, &data); + e.insert(EotfArg { offset }) + } else { + let data = EotfArgs { + arg1: pow.eotf_f32(), + arg2: 0.0, + arg3: 0.0, + arg4: 0.0, + }; + let offset = writer.write(uniform_buffer_offset_mask, &data); + e.insert(EotfArg { offset }) + } + } + }; + Some(ct.offset) } } diff --git a/src/gfx_apis/vulkan/shaders/eotfs.glsl b/src/gfx_apis/vulkan/shaders/eotfs.glsl index d8816785..fa54f8aa 100644 --- a/src/gfx_apis/vulkan/shaders/eotfs.glsl +++ b/src/gfx_apis/vulkan/shaders/eotfs.glsl @@ -13,6 +13,7 @@ #define TF_LOG100 8 #define TF_LOG316 9 #define TF_ST428 10 +#define TF_POW 11 vec3 eotf_st2084_pq(vec3 c) { c = clamp(c, 0.0, 1.0); @@ -92,6 +93,7 @@ vec3 apply_eotf(vec3 c) { case TF_LOG100: return eotf_log100(c); case TF_LOG316: return eotf_log316(c); case TF_ST428: return eotf_st428(c); + case TF_POW: return sign(c) * pow(abs(c), vec3(cm_eotf_args.arg1)); default: return c; } } @@ -107,6 +109,7 @@ vec3 apply_inv_eotf(vec3 c) { case TF_LOG100: return inv_eotf_log100(c); case TF_LOG316: return inv_eotf_log316(c); case TF_ST428: return inv_eotf_st428(c); + case TF_POW: return sign(c) * pow(abs(c), vec3(cm_inv_eotf_args.arg1)); default: return c; } } diff --git a/src/ifs/color_management/wp_color_manager_v1.rs b/src/ifs/color_management/wp_color_manager_v1.rs index 422c2dc0..71977dd5 100644 --- a/src/ifs/color_management/wp_color_manager_v1.rs +++ b/src/ifs/color_management/wp_color_manager_v1.rs @@ -5,6 +5,7 @@ use { ifs::{ color_management::{ FEATURE_EXTENDED_TARGET_VOLUME, FEATURE_SET_MASTERING_DISPLAY_PRIMARIES, + FEATURE_SET_TF_POWER, consts::{ FEATURE_PARAMETRIC, FEATURE_SET_LUMINANCES, FEATURE_SET_PRIMARIES, FEATURE_WINDOWS_SCRGB, PRIMARIES_ADOBE_RGB, PRIMARIES_BT2020, @@ -79,6 +80,7 @@ impl WpColorManagerV1 { self.send_supported_feature(FEATURE_SET_PRIMARIES); self.send_supported_feature(FEATURE_SET_LUMINANCES); self.send_supported_feature(FEATURE_SET_MASTERING_DISPLAY_PRIMARIES); + self.send_supported_feature(FEATURE_SET_TF_POWER); self.send_supported_feature(FEATURE_EXTENDED_TARGET_VOLUME); self.send_supported_feature(FEATURE_WINDOWS_SCRGB); self.send_supported_tf_named(TRANSFER_FUNCTION_BT1886); diff --git a/src/ifs/color_management/wp_image_description_creator_params_v1.rs b/src/ifs/color_management/wp_image_description_creator_params_v1.rs index 29750626..6a232577 100644 --- a/src/ifs/color_management/wp_image_description_creator_params_v1.rs +++ b/src/ifs/color_management/wp_image_description_creator_params_v1.rs @@ -2,7 +2,7 @@ use { crate::{ client::{Client, ClientError}, cmm::{ - cmm_eotf::Eotf, + cmm_eotf::{Eotf, EotfPow}, cmm_luminance::{Luminance, TargetLuminance}, cmm_primaries::{NamedPrimaries, Primaries}, }, @@ -125,8 +125,21 @@ impl WpImageDescriptionCreatorParamsV1RequestHandler for WpImageDescriptionCreat Ok(()) } - fn set_tf_power(&self, _req: SetTfPower, _slf: &Rc) -> Result<(), Self::Error> { - Err(WpImageDescriptionCreatorParamsV1Error::SetTfPowerNotSupported) + fn set_tf_power(&self, req: SetTfPower, _slf: &Rc) -> Result<(), Self::Error> { + let pow = EotfPow(req.eexp); + if pow < EotfPow::MIN || pow > EotfPow::MAX { + return Err(WpImageDescriptionCreatorParamsV1Error::SetTfPowerOutOfBounds); + } + let tf = match pow { + EotfPow::LINEAR => Eotf::Linear, + EotfPow::GAMMA22 => Eotf::Gamma22, + EotfPow::GAMMA28 => Eotf::Gamma28, + _ => Eotf::Pow(pow), + }; + if self.tf.replace(Some(tf)).is_some() { + return Err(WpImageDescriptionCreatorParamsV1Error::TfAlreadySet); + } + Ok(()) } fn set_primaries_named( @@ -259,8 +272,8 @@ pub enum WpImageDescriptionCreatorParamsV1Error { ClientError(Box), #[error("{} is not a supported named primary", .0)] UnsupportedPrimaries(u32), - #[error("set_tf_power is not supported")] - SetTfPowerNotSupported, + #[error("The exponent is out of bounds")] + SetTfPowerOutOfBounds, #[error("{} is not a supported named EOTF", .0)] UnsupportedTf(u32), #[error("The EOTF has already been set")] diff --git a/src/ifs/color_management/wp_image_description_info_v1.rs b/src/ifs/color_management/wp_image_description_info_v1.rs index 53f63a4c..f51a2bc1 100644 --- a/src/ifs/color_management/wp_image_description_info_v1.rs +++ b/src/ifs/color_management/wp_image_description_info_v1.rs @@ -1,7 +1,11 @@ use { crate::{ client::Client, - cmm::{cmm_description::ColorDescription, cmm_eotf::Eotf, cmm_primaries::NamedPrimaries}, + cmm::{ + cmm_description::ColorDescription, + cmm_eotf::{Eotf, EotfPow}, + cmm_primaries::NamedPrimaries, + }, ifs::color_management::{ MIN_LUM_MUL, PRIMARIES_ADOBE_RGB, PRIMARIES_BT2020, PRIMARIES_CIE1931_XYZ, PRIMARIES_DCI_P3, PRIMARIES_DISPLAY_P3, PRIMARIES_GENERIC_FILM, PRIMARIES_MUL, @@ -28,17 +32,24 @@ pub struct WpImageDescriptionInfoV1 { impl WpImageDescriptionInfoV1 { pub fn send_description(&self, d: &ColorDescription) { - let tf = match d.eotf { - Eotf::Linear => TRANSFER_FUNCTION_EXT_LINEAR, - Eotf::St2084Pq => TRANSFER_FUNCTION_ST2084_PQ, - Eotf::Bt1886 => TRANSFER_FUNCTION_BT1886, - Eotf::Gamma22 => TRANSFER_FUNCTION_GAMMA22, - Eotf::Gamma28 => TRANSFER_FUNCTION_GAMMA28, - Eotf::St240 => TRANSFER_FUNCTION_ST240, - Eotf::Log100 => TRANSFER_FUNCTION_LOG_100, - Eotf::Log316 => TRANSFER_FUNCTION_LOG_316, - Eotf::St428 => TRANSFER_FUNCTION_ST428, - }; + 'tf: { + let tf = match d.eotf { + Eotf::Linear => TRANSFER_FUNCTION_EXT_LINEAR, + Eotf::St2084Pq => TRANSFER_FUNCTION_ST2084_PQ, + Eotf::Bt1886 => TRANSFER_FUNCTION_BT1886, + Eotf::Gamma22 => TRANSFER_FUNCTION_GAMMA22, + Eotf::Gamma28 => TRANSFER_FUNCTION_GAMMA28, + Eotf::St240 => TRANSFER_FUNCTION_ST240, + Eotf::Log100 => TRANSFER_FUNCTION_LOG_100, + Eotf::Log316 => TRANSFER_FUNCTION_LOG_316, + Eotf::St428 => TRANSFER_FUNCTION_ST428, + Eotf::Pow(e) => { + self.send_tf_power(e); + break 'tf; + } + }; + self.send_tf_named(tf); + } self.send_primaries(&d.linear.primaries); if let Some(n) = d.named_primaries { let n = match n { @@ -55,7 +66,6 @@ impl WpImageDescriptionInfoV1 { }; self.send_primaries_named(n); } - self.send_tf_named(tf); self.send_luminances(&d.linear.luminance); self.send_target_primaries(&d.linear.target_primaries); self.send_target_luminances(&d.linear.target_luminance); @@ -103,11 +113,10 @@ impl WpImageDescriptionInfoV1 { }); } - #[expect(dead_code)] - pub fn send_tf_power(&self, eexp: f64) { + pub fn send_tf_power(&self, e: EotfPow) { self.client.event(TfPower { self_id: self.id, - eexp: (eexp * 10_000.0) as u32, + eexp: e.0, }); } diff --git a/src/theme.rs b/src/theme.rs index 5cf3fa27..06b1c335 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -121,6 +121,11 @@ impl Color { Eotf::Log100 => convert!(log100), Eotf::Log316 => convert!(log316), Eotf::St428 => convert!(st428), + Eotf::Pow(n) => { + let e = n.eotf_f32(); + let pow = |c: f32| -> f32 { c.signum() * c.abs().powf(e) }; + convert!(pow) + } } Self { r, g, b, a: 1.0 } } @@ -246,6 +251,11 @@ impl Color { Eotf::Log100 => convert!(log100), Eotf::Log316 => convert!(log316), Eotf::St428 => convert!(st428), + Eotf::Pow(n) => { + let e = n.inv_eotf_f32(); + let pow = |c: f32| -> f32 { c.signum() * c.abs().powf(e) }; + convert!(pow) + } } if self.a < 1.0 { for c in &mut res[..3] {