diff --git a/src/cmm/cmm_eotf.rs b/src/cmm/cmm_eotf.rs index 55cce4c8..40cf1ccb 100644 --- a/src/cmm/cmm_eotf.rs +++ b/src/cmm/cmm_eotf.rs @@ -1,9 +1,12 @@ +use crate::utils::ordered_float::F32; + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum Eotf { Linear, St2084Pq, - Bt1886, + Bt1886(F32), Gamma22, + Gamma24, Gamma28, St240, Log100, @@ -34,3 +37,23 @@ impl EotfPow { MUL_F32 / self.0 as f32 } } + +pub fn bt1886_eotf_args(c: F32) -> [f32; 4] { + let c = c.0; + let gamma = 1.0 / 2.4; + let a1 = 1.0 / (1.0 - c); + let a2 = 1.0 - c.powf(gamma); + let a3 = c.powf(gamma); + let a4 = c; + [a1, a2, a3, a4] +} + +pub fn bt1886_inv_eotf_args(c: F32) -> [f32; 4] { + let c = c.0; + let gamma = 1.0 / 2.4; + let a1 = 1.0 / (1.0 - c.powf(gamma)); + let a2 = 1.0 - c; + let a3 = c; + let a4 = c.powf(gamma); + [a1, a2, a3, a4] +} diff --git a/src/gfx_apis/vulkan/eotfs.rs b/src/gfx_apis/vulkan/eotfs.rs index fec7cb35..61d27a23 100644 --- a/src/gfx_apis/vulkan/eotfs.rs +++ b/src/gfx_apis/vulkan/eotfs.rs @@ -2,7 +2,7 @@ use {crate::cmm::cmm_eotf::Eotf, linearize::Linearize}; pub const EOTF_LINEAR: u32 = 1; pub const EOTF_ST2084_PQ: u32 = 2; -pub const EOTF_GAMMA24: u32 = 3; +pub const EOTF_BT1886: u32 = 3; pub const EOTF_GAMMA22: u32 = 4; pub const EOTF_GAMMA28: u32 = 5; pub const EOTF_ST240: u32 = 6; @@ -10,6 +10,7 @@ pub const EOTF_LOG100: u32 = 8; pub const EOTF_LOG316: u32 = 9; pub const EOTF_ST428: u32 = 10; pub const EOTF_POW: u32 = 11; +pub const EOTF_GAMMA24: u32 = 12; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Linearize)] pub enum VulkanEotf { @@ -17,6 +18,7 @@ pub enum VulkanEotf { St2084Pq, Bt1886, Gamma22, + Gamma24, Gamma28, St240, Log100, @@ -45,6 +47,7 @@ impl EotfExt for Eotf { St2084Pq, Bt1886, Gamma22, + Gamma24, Gamma28, St240, Log100, @@ -60,8 +63,9 @@ impl VulkanEotf { match self { Self::Linear => EOTF_LINEAR, Self::St2084Pq => EOTF_ST2084_PQ, - Self::Bt1886 => EOTF_GAMMA24, + Self::Bt1886 => EOTF_BT1886, Self::Gamma22 => EOTF_GAMMA22, + Self::Gamma24 => EOTF_GAMMA24, Self::Gamma28 => EOTF_GAMMA28, Self::St240 => EOTF_ST240, Self::Log100 => EOTF_LOG100, diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index 7ea1d6d2..6aae40b4 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -3,7 +3,7 @@ use { async_engine::{AsyncEngine, SpawnedFuture}, cmm::{ cmm_description::{ColorDescription, LinearColorDescription, LinearColorDescriptionId}, - cmm_eotf::{Eotf, EotfPow}, + cmm_eotf::{Eotf, EotfPow, bt1886_eotf_args, bt1886_inv_eotf_args}, cmm_transform::ColorMatrix, }, cpu_worker::PendingJob, @@ -35,7 +35,10 @@ use { io_uring::IoUring, rect::{Rect, Region}, theme::Color, - utils::{copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, stack::Stack}, + utils::{ + copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, ordered_float::F32, + stack::Stack, + }, video::dmabuf::{DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE, dma_buf_export_sync_file}, }, ahash::AHashMap, @@ -2328,7 +2331,13 @@ impl ColorTransforms { #[derive(Default)] struct EotfArgsCache { - map: AHashMap<(EotfPow, bool), EotfArg>, + map: AHashMap<(EotfCacheKey, bool), EotfArg>, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +enum EotfCacheKey { + Pow(EotfPow), + Bt1886(F32), } struct EotfArg { @@ -2343,27 +2352,43 @@ impl EotfArgsCache { uniform_buffer_offset_mask: DeviceSize, writer: &mut GenericBufferWriter, ) -> Option { - let Eotf::Pow(pow) = desc.eotf else { - return None; + let key = match desc.eotf { + Eotf::Bt1886(c) => EotfCacheKey::Bt1886(c), + Eotf::Pow(pow) => EotfCacheKey::Pow(pow), + _ => return None, }; - let ct = match self.map.entry((pow, inv)) { + let ct = match self.map.entry((key, inv)) { Entry::Occupied(o) => o.into_mut(), Entry::Vacant(e) => { + #[expect(unused_assignments)] + let [mut arg1, mut arg2, mut arg3, mut arg4] = [0.0; 4]; if inv { + match key { + EotfCacheKey::Pow(pow) => arg1 = pow.inv_eotf_f32(), + EotfCacheKey::Bt1886(c) => { + [arg1, arg2, arg3, arg4] = bt1886_inv_eotf_args(c); + } + } let data = InvEotfArgs { - arg1: pow.inv_eotf_f32(), - arg2: 0.0, - arg3: 0.0, - arg4: 0.0, + arg1, + arg2, + arg3, + arg4, }; let offset = writer.write(uniform_buffer_offset_mask, &data); e.insert(EotfArg { offset }) } else { + match key { + EotfCacheKey::Pow(pow) => arg1 = pow.eotf_f32(), + EotfCacheKey::Bt1886(c) => { + [arg1, arg2, arg3, arg4] = bt1886_eotf_args(c); + } + } let data = EotfArgs { - arg1: pow.eotf_f32(), - arg2: 0.0, - arg3: 0.0, - arg4: 0.0, + arg1, + arg2, + arg3, + arg4, }; let offset = writer.write(uniform_buffer_offset_mask, &data); e.insert(EotfArg { offset }) diff --git a/src/gfx_apis/vulkan/shaders/eotfs.glsl b/src/gfx_apis/vulkan/shaders/eotfs.glsl index fa54f8aa..5538a96b 100644 --- a/src/gfx_apis/vulkan/shaders/eotfs.glsl +++ b/src/gfx_apis/vulkan/shaders/eotfs.glsl @@ -6,7 +6,7 @@ #define TF_LINEAR 1 #define TF_ST2084_PQ 2 -#define TF_GAMMA24 3 +#define TF_BT1886 3 #define TF_GAMMA22 4 #define TF_GAMMA28 5 #define TF_ST240 6 @@ -14,6 +14,25 @@ #define TF_LOG316 9 #define TF_ST428 10 #define TF_POW 11 +#define TF_GAMMA24 12 + +vec3 eotf_bt1886(vec3 c) { + c = clamp(c, 0.0, 1.0); + float a1 = cm_eotf_args.arg1; + float a2 = cm_eotf_args.arg2; + float a3 = cm_eotf_args.arg3; + float a4 = cm_eotf_args.arg4; + return a1 * (pow(a2 * c + a3, vec3(2.4)) - a4); +} + +vec3 inv_eotf_bt1886(vec3 c) { + c = clamp(c, 0.0, 1.0); + float a1 = cm_inv_eotf_args.arg1; + float a2 = cm_inv_eotf_args.arg2; + float a3 = cm_inv_eotf_args.arg3; + float a4 = cm_inv_eotf_args.arg4; + return a1 * (pow(a2 * c + a3, vec3(1.0 / 2.4)) - a4); +} vec3 eotf_st2084_pq(vec3 c) { c = clamp(c, 0.0, 1.0); @@ -86,8 +105,9 @@ vec3 apply_eotf(vec3 c) { switch (eotf) { case TF_LINEAR: return c; case TF_ST2084_PQ: return eotf_st2084_pq(c); - case TF_GAMMA24: return sign(c) * pow(abs(c), vec3(2.4)); + case TF_BT1886: return eotf_bt1886(c); case TF_GAMMA22: return sign(c) * pow(abs(c), vec3(2.2)); + case TF_GAMMA24: return sign(c) * pow(abs(c), vec3(2.4)); case TF_GAMMA28: return sign(c) * pow(abs(c), vec3(2.8)); case TF_ST240: return eotf_st240(c); case TF_LOG100: return eotf_log100(c); @@ -102,8 +122,9 @@ vec3 apply_inv_eotf(vec3 c) { switch (inv_eotf) { case TF_LINEAR: return c; case TF_ST2084_PQ: return inv_eotf_st2084_pq(c); - case TF_GAMMA24: return sign(c) * pow(abs(c), vec3(1.0 / 2.4)); + case TF_BT1886: return inv_eotf_bt1886(c); case TF_GAMMA22: return sign(c) * pow(abs(c), vec3(1.0 / 2.2)); + case TF_GAMMA24: return sign(c) * pow(abs(c), vec3(1.0 / 2.4)); case TF_GAMMA28: return sign(c) * pow(abs(c), vec3(1.0 / 2.8)); case TF_ST240: return inv_eotf_st240(c); case TF_LOG100: return inv_eotf_log100(c); 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 6a232577..cc94695d 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 @@ -21,7 +21,7 @@ use { }, leaks::Tracker, object::{Object, Version}, - utils::ordered_float::F64, + utils::ordered_float::{F32, F64}, wire::{ WpImageDescriptionCreatorParamsV1Id, wp_image_description_creator_params_v1::{ @@ -53,14 +53,14 @@ impl WpImageDescriptionCreatorParamsV1RequestHandler for WpImageDescriptionCreat type Error = WpImageDescriptionCreatorParamsV1Error; fn create(&self, req: Create, _slf: &Rc) -> Result<(), Self::Error> { - let Some(eotf) = self.tf.get() else { + let Some(mut eotf) = self.tf.get() else { return Err(WpImageDescriptionCreatorParamsV1Error::TfNotSet); }; let Some((named_primaries, primaries)) = self.primaries.get() else { return Err(WpImageDescriptionCreatorParamsV1Error::PrimariesNotSet); }; let default_luminance = match eotf { - Eotf::Bt1886 => Luminance::BT1886, + Eotf::Bt1886 { .. } => Luminance::BT1886, Eotf::St2084Pq => Luminance::ST2084_PQ, _ => Luminance::SRGB, }; @@ -71,6 +71,13 @@ impl WpImageDescriptionCreatorParamsV1RequestHandler for WpImageDescriptionCreat if luminance.max.0 <= luminance.min.0 || luminance.white.0 <= luminance.min.0 { return Err(WpImageDescriptionCreatorParamsV1Error::MinLuminanceTooLow); } + if let Eotf::Bt1886(c) = &mut eotf { + if luminance.min.0 == 0.0 { + eotf = Eotf::Gamma24; + } else { + c.0 = (luminance.min.0 / luminance.max.0) as f32; + } + } let target_primaries = self.mastering_primaries.get().unwrap_or(primaries); let target_luminance = self .mastering_luminance @@ -102,7 +109,7 @@ impl WpImageDescriptionCreatorParamsV1RequestHandler for WpImageDescriptionCreat fn set_tf_named(&self, req: SetTfNamed, _slf: &Rc) -> Result<(), Self::Error> { let tf = match req.tf { - TRANSFER_FUNCTION_BT1886 => Eotf::Bt1886, + TRANSFER_FUNCTION_BT1886 => Eotf::Bt1886(F32(0.0)), TRANSFER_FUNCTION_GAMMA22 => Eotf::Gamma22, TRANSFER_FUNCTION_GAMMA28 => Eotf::Gamma28, TRANSFER_FUNCTION_ST240 => Eotf::St240, 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 f51a2bc1..7a745389 100644 --- a/src/ifs/color_management/wp_image_description_info_v1.rs +++ b/src/ifs/color_management/wp_image_description_info_v1.rs @@ -36,8 +36,12 @@ impl WpImageDescriptionInfoV1 { let tf = match d.eotf { Eotf::Linear => TRANSFER_FUNCTION_EXT_LINEAR, Eotf::St2084Pq => TRANSFER_FUNCTION_ST2084_PQ, - Eotf::Bt1886 => TRANSFER_FUNCTION_BT1886, + Eotf::Bt1886 { .. } => TRANSFER_FUNCTION_BT1886, Eotf::Gamma22 => TRANSFER_FUNCTION_GAMMA22, + Eotf::Gamma24 => { + self.send_tf_power(EotfPow::GAMMA24); + break 'tf; + } Eotf::Gamma28 => TRANSFER_FUNCTION_GAMMA28, Eotf::St240 => TRANSFER_FUNCTION_ST240, Eotf::Log100 => TRANSFER_FUNCTION_LOG_100, diff --git a/src/theme.rs b/src/theme.rs index 06b1c335..ec92cb8b 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,7 +1,10 @@ #![expect(clippy::excessive_precision)] use { - crate::{cmm::cmm_eotf::Eotf, utils::clonecell::CloneCell}, + crate::{ + cmm::cmm_eotf::{Eotf, bt1886_eotf_args, bt1886_inv_eotf_args}, + utils::clonecell::CloneCell, + }, num_traits::Float, std::{cell::Cell, cmp::Ordering, ops::Mul, sync::Arc}, }; @@ -114,8 +117,13 @@ impl Color { match eotf { Eotf::Linear => convert!(linear), Eotf::St2084Pq => convert!(st2084_pq), - Eotf::Bt1886 => convert!(gamma24), + Eotf::Bt1886(c) => { + let [a1, a2, a3, a4] = bt1886_eotf_args(c); + let bt1886 = |c: f32| -> f32 { a1 * ((a2 * c + a3).powf(2.4) - a4) }; + convert!(bt1886) + } Eotf::Gamma22 => convert!(gamma22), + Eotf::Gamma24 => convert!(gamma24), Eotf::Gamma28 => convert!(gamma28), Eotf::St240 => convert!(st240), Eotf::Log100 => convert!(log100), @@ -244,8 +252,13 @@ impl Color { match eotf { Eotf::Linear => convert!(linear), Eotf::St2084Pq => convert!(st2084_pq), - Eotf::Bt1886 => convert!(gamma24), + Eotf::Bt1886(c) => { + let [a1, a2, a3, a4] = bt1886_inv_eotf_args(c); + let bt1886 = |c: f32| -> f32 { a1 * ((a2 * c + a3).powf(1.0 / 2.4) - a4) }; + convert!(bt1886) + } Eotf::Gamma22 => convert!(gamma22), + Eotf::Gamma24 => convert!(gamma24), Eotf::Gamma28 => convert!(gamma28), Eotf::St240 => convert!(st240), Eotf::Log100 => convert!(log100), diff --git a/src/utils/ordered_float.rs b/src/utils/ordered_float.rs index 4a458a61..c19c3ec7 100644 --- a/src/utils/ordered_float.rs +++ b/src/utils/ordered_float.rs @@ -4,64 +4,71 @@ use std::{ ops::{Add, Div, Mul, Sub}, }; -#[derive(Copy, Clone)] -#[repr(transparent)] -pub struct F64(pub f64); +macro_rules! define { + ($big:ident, $little:ty) => { + #[derive(Copy, Clone)] + #[repr(transparent)] + pub struct $big(pub $little); -impl Eq for F64 {} + impl Eq for $big {} -impl PartialEq for F64 { - fn eq(&self, other: &Self) -> bool { - self.0.to_bits() == other.0.to_bits() - } + impl PartialEq for $big { + fn eq(&self, other: &Self) -> bool { + self.0.to_bits() == other.0.to_bits() + } + } + + impl Hash for $big { + fn hash(&self, state: &mut H) { + self.0.to_bits().hash(state); + } + } + + impl Add for $big { + type Output = Self; + + fn add(self, rhs: $big) -> Self::Output { + Self(self.0 + rhs.0) + } + } + + impl Sub for $big { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self(self.0 - rhs.0) + } + } + + impl Mul for $big { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + Self(self.0 * rhs.0) + } + } + + impl Div for $big { + type Output = Self; + + fn div(self, rhs: Self) -> Self::Output { + Self(self.0 / rhs.0) + } + } + + impl Display for $big { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.0, f) + } + } + + impl Debug for $big { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&self.0, f) + } + } + }; } -impl Hash for F64 { - fn hash(&self, state: &mut H) { - self.0.to_bits().hash(state); - } -} - -impl Add for F64 { - type Output = Self; - - fn add(self, rhs: F64) -> Self::Output { - Self(self.0 + rhs.0) - } -} - -impl Sub for F64 { - type Output = Self; - - fn sub(self, rhs: F64) -> Self::Output { - Self(self.0 - rhs.0) - } -} - -impl Mul for F64 { - type Output = Self; - - fn mul(self, rhs: F64) -> Self::Output { - Self(self.0 * rhs.0) - } -} - -impl Div for F64 { - type Output = Self; - - fn div(self, rhs: F64) -> Self::Output { - Self(self.0 / rhs.0) - } -} - -impl Display for F64 { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - Display::fmt(&self.0, f) - } -} - -impl Debug for F64 { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - Debug::fmt(&self.0, f) - } -} +define!(F64, f64); +define!(F32, f32);