diff --git a/src/cmm/cmm_eotf.rs b/src/cmm/cmm_eotf.rs index b8980cca..40cf1ccb 100644 --- a/src/cmm/cmm_eotf.rs +++ b/src/cmm/cmm_eotf.rs @@ -1,14 +1,59 @@ -use linearize::Linearize; +use crate::utils::ordered_float::F32; -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Linearize)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum Eotf { Linear, St2084Pq, - Bt1886, + Bt1886(F32), Gamma22, + Gamma24, Gamma28, St240, 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 + } +} + +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/descriptor.rs b/src/gfx_apis/vulkan/descriptor.rs index 87e093ff..66f519d2 100644 --- a/src/gfx_apis/vulkan/descriptor.rs +++ b/src/gfx_apis/vulkan/descriptor.rs @@ -15,7 +15,7 @@ pub(super) struct VulkanDescriptorSetLayout { pub(super) device: Rc, pub(super) layout: DescriptorSetLayout, pub(super) size: DeviceSize, - pub(super) offsets: ArrayVec, + pub(super) offsets: ArrayVec, pub(super) _sampler: Option>, } @@ -98,6 +98,16 @@ impl VulkanDevice { .stage_flags(ShaderStageFlags::FRAGMENT) .descriptor_count(1) .descriptor_type(DescriptorType::UNIFORM_BUFFER), + DescriptorSetLayoutBinding::default() + .binding(2) + .stage_flags(ShaderStageFlags::FRAGMENT) + .descriptor_count(1) + .descriptor_type(DescriptorType::UNIFORM_BUFFER), + DescriptorSetLayoutBinding::default() + .binding(3) + .stage_flags(ShaderStageFlags::FRAGMENT) + .descriptor_count(1) + .descriptor_type(DescriptorType::UNIFORM_BUFFER), ]; let create_info = DescriptorSetLayoutCreateInfo::default() .bindings(&bindings) @@ -110,6 +120,8 @@ impl VulkanDevice { unsafe { offsets.push(db.get_descriptor_set_layout_binding_offset(layout, 0)); offsets.push(db.get_descriptor_set_layout_binding_offset(layout, 1)); + offsets.push(db.get_descriptor_set_layout_binding_offset(layout, 2)); + offsets.push(db.get_descriptor_set_layout_binding_offset(layout, 3)); } Ok(Rc::new(VulkanDescriptorSetLayout { device: self.clone(), diff --git a/src/gfx_apis/vulkan/eotfs.rs b/src/gfx_apis/vulkan/eotfs.rs index db5a6da4..61d27a23 100644 --- a/src/gfx_apis/vulkan/eotfs.rs +++ b/src/gfx_apis/vulkan/eotfs.rs @@ -1,31 +1,77 @@ -use crate::cmm::cmm_eotf::Eotf; +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; 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 { + Linear, + St2084Pq, + Bt1886, + Gamma22, + Gamma24, + Gamma28, + St240, + Log100, + Log316, + St428, + Pow, +} pub trait EotfExt: Sized { - fn to_vulkan(self) -> u32; + fn to_vulkan(self) -> VulkanEotf; } impl EotfExt for Eotf { - fn to_vulkan(self) -> u32 { - match self { - Eotf::Linear => EOTF_LINEAR, - Eotf::St2084Pq => EOTF_ST2084_PQ, - Eotf::Bt1886 => EOTF_GAMMA24, - Eotf::Gamma22 => EOTF_GAMMA22, - Eotf::Gamma28 => EOTF_GAMMA28, - Eotf::St240 => EOTF_ST240, - Eotf::Log100 => EOTF_LOG100, - Eotf::Log316 => EOTF_LOG316, - Eotf::St428 => EOTF_ST428, + fn to_vulkan(self) -> VulkanEotf { + macro_rules! map { + ($($name:ident,)*) => { + match self { + $( + Self::$name { .. } => VulkanEotf::$name, + )* + } + }; + } + map! { + Linear, + St2084Pq, + Bt1886, + Gamma22, + Gamma24, + Gamma28, + St240, + Log100, + Log316, + St428, + Pow, + } + } +} + +impl VulkanEotf { + pub fn to_vulkan(self) -> u32 { + match self { + Self::Linear => EOTF_LINEAR, + Self::St2084Pq => EOTF_ST2084_PQ, + Self::Bt1886 => EOTF_BT1886, + Self::Gamma22 => EOTF_GAMMA22, + Self::Gamma24 => EOTF_GAMMA24, + Self::Gamma28 => EOTF_GAMMA28, + Self::St240 => EOTF_ST240, + 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 2d63478d..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, + cmm_eotf::{Eotf, EotfPow, bt1886_eotf_args, bt1886_inv_eotf_args}, cmm_transform::ColorMatrix, }, cpu_worker::PendingJob, @@ -19,23 +19,26 @@ use { descriptor::VulkanDescriptorSetLayout, descriptor_buffer::VulkanDescriptorBufferWriter, device::VulkanDevice, - eotfs::{EOTF_LINEAR, EotfExt}, + eotfs::{EOTF_LINEAR, EotfExt, VulkanEotf}, fence::VulkanFence, image::{QueueFamily, QueueState, QueueTransfer, VulkanImage, VulkanImageMemory}, pipeline::{PipelineCreateInfo, VulkanPipeline}, sampler::VulkanSampler, semaphore::VulkanSemaphore, shaders::{ - FILL_FRAG, FILL_VERT, FillPushConstants, LEGACY_FILL_FRAG, LEGACY_FILL_VERT, - LEGACY_TEX_FRAG, LEGACY_TEX_VERT, LegacyFillPushConstants, LegacyTexPushConstants, - OUT_FRAG, OUT_VERT, OutColorManagementData, OutPushConstants, TEX_FRAG, TEX_VERT, - TexColorManagementData, TexPushConstants, TexVertex, VulkanShader, + ColorManagementData, EotfArgs, FILL_FRAG, FILL_VERT, FillPushConstants, + InvEotfArgs, 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, }, }, 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, @@ -78,8 +81,9 @@ pub struct VulkanRenderer { pub(super) formats: Rc>, pub(super) device: Rc, pub(super) fill_pipelines: CopyHashMap, - pub(super) tex_pipelines: StaticMap>>, - pub(super) out_pipelines: StaticMap>>, + pub(super) tex_pipelines: StaticMap>>, + pub(super) out_pipelines: + StaticMap>>, pub(super) gfx_command_buffers: CachedCommandBuffers, pub(super) transfer_command_buffers: Option, pub(super) wait_semaphores: Stack>, @@ -176,10 +180,16 @@ pub(super) struct Memory { data_buffer: Vec, out_address: DeviceAddress, color_transforms: ColorTransforms, + eotf_args_cache: EotfArgsCache, uniform_buffer_writer: GenericBufferWriter, uniform_buffer_descriptor_cache: Option>, + blend_buffer_inv_eotf_args_descriptor: Option>, + fb_inv_eotf_args_descriptor: Option>, blend_buffer_descriptor_buffer_offset: DeviceAddress, blend_buffer_color_management_data_address: Option, + blend_buffer_eotf_args_address: Option, + blend_buffer_inv_eotf_args_address: Option, + fb_inv_eotf_args_address: Option, } type Point = [[f32; 2]; 4]; @@ -202,6 +212,7 @@ struct VulkanTexOp { instances: u32, tex_cd: Rc, color_management_data_address: Option, + eotf_args_address: Option, resource_descriptor_buffer_offset: DeviceAddress, } @@ -246,20 +257,21 @@ type FillPipelines = Rc>>; struct TexPipelineKey { tex_copy_type: TexCopyType, tex_source_type: TexSourceType, - eotf: Eotf, + eotf: VulkanEotf, has_color_management_data: bool, } pub(super) struct TexPipelines { format: vk::Format, - eotf: Eotf, + eotf: VulkanEotf, pipelines: CopyHashMap>, } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub(super) struct OutPipelineKey { format: vk::Format, - eotf: Eotf, + eotf: VulkanEotf, + has_color_management_data: bool, } impl VulkanDevice { @@ -351,7 +363,9 @@ impl VulkanDevice { }; let uniform_buffer_cache = { let usage = BufferUsageFlags::SHADER_DEVICE_ADDRESS | BufferUsageFlags::UNIFORM_BUFFER; - let align = align_of::() as DeviceSize; + let align = align_of::() + .max(align_of::()) + .max(align_of::()) as DeviceSize; VulkanBufferCache::new(self, &allocator, usage, align) }; let render = Rc::new(VulkanRenderer { @@ -436,13 +450,14 @@ impl VulkanRenderer { format: vk::Format, target_cd: &ColorDescription, ) -> Rc { - let pipelines = &self.tex_pipelines[target_cd.eotf]; + let eotf = target_cd.eotf.to_vulkan(); + let pipelines = &self.tex_pipelines[eotf]; match pipelines.get(&format) { Some(pl) => pl, _ => { let pl = Rc::new(TexPipelines { format, - eotf: target_cd.eotf, + eotf, pipelines: Default::default(), }); pipelines.set(format, pl.clone()); @@ -462,7 +477,7 @@ impl VulkanRenderer { let key = TexPipelineKey { tex_copy_type, tex_source_type, - eotf: tex_cd.eotf, + eotf: tex_cd.eotf.to_vulkan(), has_color_management_data, }; if let Some(pl) = pipelines.pipelines.get(&key) { @@ -507,9 +522,11 @@ impl VulkanRenderer { ) -> Result, VulkanError> { let key = OutPipelineKey { format, - eotf: bb_cd.eotf, + eotf: bb_cd.eotf.to_vulkan(), + has_color_management_data, }; - let pipelines = &self.out_pipelines[fb_cd.eotf]; + let fb_eotf = fb_cd.eotf.to_vulkan(); + let pipelines = &self.out_pipelines[fb_eotf]; if let Some(pl) = pipelines.get(&key) { return Ok(pl); } @@ -525,7 +542,7 @@ impl VulkanRenderer { src_has_alpha: true, has_alpha_mult: false, eotf: key.eotf.to_vulkan(), - inv_eotf: fb_cd.eotf.to_vulkan(), + inv_eotf: fb_eotf.to_vulkan(), descriptor_set_layouts, has_color_management_data, })?; @@ -558,33 +575,73 @@ 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(); - 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); - if let Some(addr) = memory.blend_buffer_color_management_data_address { + macro_rules! ub_descriptor_cache { + ($field:ident) => { + memory.$field.get_or_insert_with(|| { + vec![0u8; self.device.uniform_buffer_descriptor_size].into_boxed_slice() + }) + }; + } + let uniform_buffer_descriptor_cache = ub_descriptor_cache!(uniform_buffer_descriptor_cache); + macro_rules! get_ub_descriptor { + ($addr:expr, $ty:ty, $descriptor:expr $(,)?) => {{ let uniform_buffer = DescriptorAddressInfoEXT::default() - .address(addr) - .range(size_of::() as _); + .address($addr) + .range(size_of::<$ty>() 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); + db.get_descriptor(&info, $descriptor); } - writer.write(layout.offsets[1], uniform_buffer_descriptor_cache); + &*$descriptor + }}; + ($addr:expr, $ty:ty $(,)?) => { + get_ub_descriptor!($addr, $ty, uniform_buffer_descriptor_cache) + }; + } + let mut blend_buffer_inv_eotf_args_descriptor = None; + if let Some(addr) = memory.blend_buffer_inv_eotf_args_address { + blend_buffer_inv_eotf_args_descriptor = Some(get_ub_descriptor!( + addr, + InvEotfArgs, + ub_descriptor_cache!(blend_buffer_inv_eotf_args_descriptor), + )); + } + let mut fb_inv_eotf_args_descriptor = None; + if let Some(addr) = memory.fb_inv_eotf_args_address { + fb_inv_eotf_args_descriptor = Some(get_ub_descriptor!( + addr, + InvEotfArgs, + ub_descriptor_cache!(fb_inv_eotf_args_descriptor), + )); + } + if let Some(bb) = bb { + let layout = self.out_descriptor_set_layout.as_ref().unwrap(); + 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); + if let Some(addr) = memory.blend_buffer_color_management_data_address { + writer.write( + layout.offsets[1], + get_ub_descriptor!(addr, ColorManagementData), + ); + } + if let Some(addr) = memory.blend_buffer_eotf_args_address { + writer.write(layout.offsets[2], get_ub_descriptor!(addr, EotfArgs)); + } + if let Some(desc) = fb_inv_eotf_args_descriptor { + writer.write(layout.offsets[3], desc); } } let tex_descriptor_set_layout = &self.tex_descriptor_set_layouts[1]; for pass in RenderPass::variants() { + let inv_eotf_desc = match pass { + RenderPass::BlendBuffer => blend_buffer_inv_eotf_args_descriptor, + RenderPass::FrameBuffer => fb_inv_eotf_args_descriptor, + }; for cmd in &mut memory.ops[pass] { let VulkanOp::Tex(c) = cmd else { continue; @@ -597,22 +654,20 @@ impl VulkanRenderer { &tex.sampled_image_descriptor, ); if let Some(addr) = c.color_management_data_address { - let uniform_buffer = DescriptorAddressInfoEXT::default() - .address(addr) - .range(size_of::() as _); - let info = DescriptorGetInfoEXT::default() - .ty(DescriptorType::UNIFORM_BUFFER) - .data(DescriptorDataEXT { - p_uniform_buffer: &uniform_buffer, - }); - unsafe { - db.get_descriptor(&info, uniform_buffer_descriptor_cache); - } writer.write( tex_descriptor_set_layout.offsets[1], - uniform_buffer_descriptor_cache, + get_ub_descriptor!(addr, ColorManagementData), ); } + if let Some(addr) = c.eotf_args_address { + writer.write( + tex_descriptor_set_layout.offsets[2], + get_ub_descriptor!(addr, EotfArgs), + ); + } + if let Some(desc) = inv_eotf_desc { + writer.write(tex_descriptor_set_layout.offsets[3], desc); + } } } let mut infos = ArrayVec::<_, 2>::new(); @@ -655,6 +710,7 @@ impl VulkanRenderer { memory.data_buffer.clear(); memory.uniform_buffer_writer.clear(); memory.color_transforms.map.clear(); + memory.eotf_args_cache.map.clear(); let sync = |memory: &mut Memory| { for pass in RenderPass::variants() { let ops = &mut memory.ops_tmp[pass]; @@ -809,6 +865,12 @@ impl VulkanRenderer { self.device.uniform_buffer_offset_mask, &mut memory.uniform_buffer_writer, ); + let eotf_args_address = memory.eotf_args_cache.get_offset( + &ct.cd, + false, + self.device.uniform_buffer_offset_mask, + &mut memory.uniform_buffer_writer, + ); ops.push(VulkanOp::Tex(VulkanTexOp { tex: tex.clone(), range: lo..hi, @@ -822,6 +884,7 @@ impl VulkanRenderer { instances: 0, tex_cd: ct.cd.clone(), color_management_data_address, + eotf_args_address, resource_descriptor_buffer_offset: 0, })); } @@ -832,21 +895,41 @@ impl VulkanRenderer { Ok(()) } - fn create_blend_cm_data( + fn create_fixed_cm_data( &self, bb: Option<&VulkanImage>, bb_cd: &ColorDescription, fb_cd: &ColorDescription, ) { - zone!("create_blend_cm_data"); + zone!("create_fixed_cm_data"); let memory = &mut *self.memory.borrow_mut(); memory.blend_buffer_color_management_data_address = None; - if bb.is_none() { - return; + memory.blend_buffer_eotf_args_address = None; + memory.blend_buffer_inv_eotf_args_address = None; + memory.fb_inv_eotf_args_address = None; + if bb.is_some() { + memory.blend_buffer_color_management_data_address = memory.color_transforms.get_offset( + &bb_cd.linear, + fb_cd, + self.device.uniform_buffer_offset_mask, + &mut memory.uniform_buffer_writer, + ); + memory.blend_buffer_eotf_args_address = memory.eotf_args_cache.get_offset( + bb_cd, + false, + self.device.uniform_buffer_offset_mask, + &mut memory.uniform_buffer_writer, + ); + memory.blend_buffer_inv_eotf_args_address = memory.eotf_args_cache.get_offset( + bb_cd, + true, + self.device.uniform_buffer_offset_mask, + &mut memory.uniform_buffer_writer, + ); } - memory.blend_buffer_color_management_data_address = memory.color_transforms.get_offset( - &bb_cd.linear, + memory.fb_inv_eotf_args_address = memory.eotf_args_cache.get_offset( fb_cd, + true, self.device.uniform_buffer_offset_mask, &mut memory.uniform_buffer_writer, ); @@ -908,18 +991,25 @@ impl VulkanRenderer { buffer.buffer.allocation.upload(|ptr, _| unsafe { ptr::copy_nonoverlapping(buf.as_ptr(), ptr, buf.len()); })?; + macro_rules! adj { + ($expr:expr) => { + if let Some(addr) = $expr { + *addr += buffer.buffer.address; + } + }; + } for ops in memory.ops.values_mut() { for op in ops { - if let VulkanOp::Tex(c) = op - && let Some(addr) = &mut c.color_management_data_address - { - *addr += buffer.buffer.address; + if let VulkanOp::Tex(c) = op { + adj!(&mut c.color_management_data_address); + adj!(&mut c.eotf_args_address); } } } - if let Some(addr) = &mut memory.blend_buffer_color_management_data_address { - *addr += buffer.buffer.address; - } + adj!(&mut memory.blend_buffer_color_management_data_address); + adj!(&mut memory.blend_buffer_eotf_args_address); + adj!(&mut memory.blend_buffer_inv_eotf_args_address); + adj!(&mut memory.fb_inv_eotf_args_address); memory.used_buffers.push(buffer); Ok(()) } @@ -1938,7 +2028,7 @@ impl VulkanRenderer { let bb = blend_buffer.as_deref(); let buf = self.gfx_command_buffers.allocate()?; self.convert_ops(opts, bb_cd, fb_cd)?; - self.create_blend_cm_data(bb, bb_cd, fb_cd); + self.create_fixed_cm_data(bb, bb_cd, fb_cd); self.create_data_buffer()?; self.create_uniform_buffer()?; self.collect_memory(); @@ -2229,7 +2319,7 @@ impl ColorTransforms { ) -> Option { let ct = self.get_or_create(src, dst)?; if ct.offset.is_none() { - let data = TexColorManagementData { + let data = ColorManagementData { matrix: ct.matrix.to_f32(), }; let offset = writer.write(uniform_buffer_offset_mask, &data); @@ -2238,3 +2328,73 @@ impl ColorTransforms { ct.offset } } + +#[derive(Default)] +struct EotfArgsCache { + map: AHashMap<(EotfCacheKey, bool), EotfArg>, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +enum EotfCacheKey { + Pow(EotfPow), + Bt1886(F32), +} + +struct EotfArg { + offset: DeviceSize, +} + +impl EotfArgsCache { + fn get_offset( + &mut self, + desc: &ColorDescription, + inv: bool, + uniform_buffer_offset_mask: DeviceSize, + writer: &mut GenericBufferWriter, + ) -> Option { + 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((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, + 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, + arg2, + arg3, + arg4, + }; + let offset = writer.write(uniform_buffer_offset_mask, &data); + e.insert(EotfArg { offset }) + } + } + }; + Some(ct.offset) + } +} diff --git a/src/gfx_apis/vulkan/shaders.rs b/src/gfx_apis/vulkan/shaders.rs index 74ef9031..0e441582 100644 --- a/src/gfx_apis/vulkan/shaders.rs +++ b/src/gfx_apis/vulkan/shaders.rs @@ -63,19 +63,33 @@ unsafe impl Packed for TexPushConstants {} #[derive(Copy, Clone, Debug)] #[repr(C, align(16))] -pub struct TexColorManagementData { +pub struct ColorManagementData { pub matrix: [[f32; 4]; 4], } -unsafe impl Packed for TexColorManagementData {} +unsafe impl Packed for ColorManagementData {} #[derive(Copy, Clone, Debug)] #[repr(C, align(16))] -pub struct OutColorManagementData { - pub matrix: [[f32; 4]; 4], +pub struct EotfArgs { + pub arg1: f32, + pub arg2: f32, + pub arg3: f32, + pub arg4: f32, } -unsafe impl Packed for OutColorManagementData {} +unsafe impl Packed for EotfArgs {} + +#[derive(Copy, Clone, Debug)] +#[repr(C, align(16))] +pub struct InvEotfArgs { + pub arg1: f32, + pub arg2: f32, + pub arg3: f32, + pub arg4: f32, +} + +unsafe impl Packed for InvEotfArgs {} #[derive(Copy, Clone, Debug)] #[repr(C)] diff --git a/src/gfx_apis/vulkan/shaders/eotfs.glsl b/src/gfx_apis/vulkan/shaders/eotfs.glsl index 049a0444..5538a96b 100644 --- a/src/gfx_apis/vulkan/shaders/eotfs.glsl +++ b/src/gfx_apis/vulkan/shaders/eotfs.glsl @@ -2,16 +2,37 @@ #define EOTFS_GLSL #include "frag_spec_const.glsl" +#include "tex_set.glsl" #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 #define TF_LOG100 8 #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); @@ -84,13 +105,15 @@ 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); 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; } } @@ -99,13 +122,15 @@ 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); 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/gfx_apis/vulkan/shaders/out.frag b/src/gfx_apis/vulkan/shaders/out.frag index d5df53e0..b374000e 100644 --- a/src/gfx_apis/vulkan/shaders/out.frag +++ b/src/gfx_apis/vulkan/shaders/out.frag @@ -3,18 +3,17 @@ #extension GL_EXT_samplerless_texture_functions : require #extension GL_EXT_scalar_block_layout : require -#include "frag_spec_const.glsl" -#include "eotfs.glsl" -#include "out.common.glsl" +#define TEX_SET 0 + +#include "frag_spec_const.glsl" +#include "out.common.glsl" +#include "tex_set.glsl" +#include "eotfs.glsl" -layout(set = 0, binding = 0) uniform texture2D in_color; -layout(set = 0, binding = 1, row_major, std430) uniform ColorManagementData { - mat4x4 matrix; -} cm_data; layout(location = 0) out vec4 out_color; void main() { - vec4 c = texelFetch(in_color, ivec2(gl_FragCoord.xy), 0); + vec4 c = texelFetch(tex, ivec2(gl_FragCoord.xy), 0); if (eotf != inv_eotf || has_matrix) { vec3 rgb = c.rgb; rgb /= mix(c.a, 1.0, c.a == 0.0); diff --git a/src/gfx_apis/vulkan/shaders/tex.frag b/src/gfx_apis/vulkan/shaders/tex.frag index 6e365a3c..df6ee5ec 100644 --- a/src/gfx_apis/vulkan/shaders/tex.frag +++ b/src/gfx_apis/vulkan/shaders/tex.frag @@ -2,15 +2,14 @@ #extension GL_EXT_scalar_block_layout : require +#define TEX_SET 1 + #include "frag_spec_const.glsl" -#include "eotfs.glsl" #include "tex.common.glsl" +#include "tex_set.glsl" +#include "eotfs.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; diff --git a/src/gfx_apis/vulkan/shaders/tex_set.glsl b/src/gfx_apis/vulkan/shaders/tex_set.glsl new file mode 100644 index 00000000..7a9f51aa --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/tex_set.glsl @@ -0,0 +1,21 @@ +#ifndef TEX_SET_GLSL +#define TEX_SET_GLSL + +layout(set = TEX_SET, binding = 0) uniform texture2D tex; +layout(set = TEX_SET, binding = 1, row_major, std430) uniform ColorManagementData { + mat4x4 matrix; +} cm_data; +layout(set = TEX_SET, binding = 2, row_major, std430) uniform EotfArgs { + float arg1; + float arg2; + float arg3; + float arg4; +} cm_eotf_args; +layout(set = TEX_SET, binding = 3, row_major, std430) uniform InvEotfArgs { + float arg1; + float arg2; + float arg3; + float arg4; +} cm_inv_eotf_args; + +#endif 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..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 @@ -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}, }, @@ -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, @@ -125,8 +132,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 +279,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..7a745389 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,28 @@ 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::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, + 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 +70,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 +117,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..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,13 +117,23 @@ 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), 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 } } @@ -239,13 +252,23 @@ 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), 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] { 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);