From ab81e4bd5150a7d41dae7b3641a178a20620a817 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 8 Sep 2025 17:57:09 +0200 Subject: [PATCH 1/7] vulkan: fix out pipeline cache --- src/gfx_apis/vulkan/renderer.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index 2d63478d..6c1f6455 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -260,6 +260,7 @@ pub(super) struct TexPipelines { pub(super) struct OutPipelineKey { format: vk::Format, eotf: Eotf, + has_color_management_data: bool, } impl VulkanDevice { @@ -508,6 +509,7 @@ impl VulkanRenderer { let key = OutPipelineKey { format, eotf: bb_cd.eotf, + has_color_management_data, }; let pipelines = &self.out_pipelines[fb_cd.eotf]; if let Some(pl) = pipelines.get(&key) { From 05bf029a5533a6d5730ff3e0972fe074e3cc9bbf Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 8 Sep 2025 18:00:06 +0200 Subject: [PATCH 2/7] vulkan: add VulkanEotf --- src/cmm/cmm_eotf.rs | 4 +-- src/gfx_apis/vulkan/eotfs.rs | 64 ++++++++++++++++++++++++++------- src/gfx_apis/vulkan/renderer.rs | 28 ++++++++------- 3 files changed, 67 insertions(+), 29 deletions(-) diff --git a/src/cmm/cmm_eotf.rs b/src/cmm/cmm_eotf.rs index b8980cca..6811928b 100644 --- a/src/cmm/cmm_eotf.rs +++ b/src/cmm/cmm_eotf.rs @@ -1,6 +1,4 @@ -use linearize::Linearize; - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Linearize)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum Eotf { Linear, St2084Pq, diff --git a/src/gfx_apis/vulkan/eotfs.rs b/src/gfx_apis/vulkan/eotfs.rs index db5a6da4..12f799df 100644 --- a/src/gfx_apis/vulkan/eotfs.rs +++ b/src/gfx_apis/vulkan/eotfs.rs @@ -1,4 +1,4 @@ -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; @@ -10,22 +10,60 @@ pub const EOTF_LOG100: u32 = 8; pub const EOTF_LOG316: u32 = 9; pub const EOTF_ST428: u32 = 10; +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Linearize)] +pub enum VulkanEotf { + Linear, + St2084Pq, + Bt1886, + Gamma22, + Gamma28, + St240, + Log100, + Log316, + St428, +} + 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, + Gamma28, + St240, + Log100, + Log316, + St428, + } + } +} + +impl VulkanEotf { + pub fn to_vulkan(self) -> u32 { + match self { + Self::Linear => EOTF_LINEAR, + Self::St2084Pq => EOTF_ST2084_PQ, + Self::Bt1886 => EOTF_GAMMA24, + Self::Gamma22 => EOTF_GAMMA22, + Self::Gamma28 => EOTF_GAMMA28, + Self::St240 => EOTF_ST240, + Self::Log100 => EOTF_LOG100, + Self::Log316 => EOTF_LOG316, + Self::St428 => EOTF_ST428, } } } diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index 6c1f6455..2d87821a 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -3,7 +3,6 @@ use { async_engine::{AsyncEngine, SpawnedFuture}, cmm::{ cmm_description::{ColorDescription, LinearColorDescription, LinearColorDescriptionId}, - cmm_eotf::Eotf, cmm_transform::ColorMatrix, }, cpu_worker::PendingJob, @@ -19,7 +18,7 @@ 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}, @@ -78,8 +77,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>, @@ -246,20 +246,20 @@ 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, } @@ -437,13 +437,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()); @@ -463,7 +464,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) { @@ -508,10 +509,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); } @@ -527,7 +529,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, })?; From a2e089d9e302ccf8326516654cef40da60478ce9 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 8 Sep 2025 18:04:02 +0200 Subject: [PATCH 3/7] vulkan: unify Tex/OutColorManagementData --- src/gfx_apis/vulkan/renderer.rs | 16 ++++++++-------- src/gfx_apis/vulkan/shaders.rs | 12 ++---------- src/gfx_apis/vulkan/shaders/eotfs.glsl | 1 + src/gfx_apis/vulkan/shaders/out.frag | 15 +++++++-------- src/gfx_apis/vulkan/shaders/tex.frag | 9 ++++----- src/gfx_apis/vulkan/shaders/tex_set.glsl | 9 +++++++++ 6 files changed, 31 insertions(+), 31 deletions(-) create mode 100644 src/gfx_apis/vulkan/shaders/tex_set.glsl diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index 2d87821a..b95260d8 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -25,10 +25,10 @@ use { 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, FILL_FRAG, FILL_VERT, FillPushConstants, LEGACY_FILL_FRAG, + LEGACY_FILL_VERT, LEGACY_TEX_FRAG, LEGACY_TEX_VERT, LegacyFillPushConstants, + LegacyTexPushConstants, OUT_FRAG, OUT_VERT, OutPushConstants, TEX_FRAG, TEX_VERT, + TexPushConstants, TexVertex, VulkanShader, }, }, io_uring::IoUring, @@ -352,7 +352,7 @@ 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::() as DeviceSize; VulkanBufferCache::new(self, &allocator, usage, align) }; let render = Rc::new(VulkanRenderer { @@ -575,7 +575,7 @@ impl VulkanRenderer { if let Some(addr) = memory.blend_buffer_color_management_data_address { let uniform_buffer = DescriptorAddressInfoEXT::default() .address(addr) - .range(size_of::() as _); + .range(size_of::() as _); let info = DescriptorGetInfoEXT::default() .ty(DescriptorType::UNIFORM_BUFFER) .data(DescriptorDataEXT { @@ -603,7 +603,7 @@ impl VulkanRenderer { if let Some(addr) = c.color_management_data_address { let uniform_buffer = DescriptorAddressInfoEXT::default() .address(addr) - .range(size_of::() as _); + .range(size_of::() as _); let info = DescriptorGetInfoEXT::default() .ty(DescriptorType::UNIFORM_BUFFER) .data(DescriptorDataEXT { @@ -2233,7 +2233,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); diff --git a/src/gfx_apis/vulkan/shaders.rs b/src/gfx_apis/vulkan/shaders.rs index 74ef9031..de75388d 100644 --- a/src/gfx_apis/vulkan/shaders.rs +++ b/src/gfx_apis/vulkan/shaders.rs @@ -63,19 +63,11 @@ 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 {} - -#[derive(Copy, Clone, Debug)] -#[repr(C, align(16))] -pub struct OutColorManagementData { - pub matrix: [[f32; 4]; 4], -} - -unsafe impl Packed for OutColorManagementData {} +unsafe impl Packed for ColorManagementData {} #[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..d8816785 100644 --- a/src/gfx_apis/vulkan/shaders/eotfs.glsl +++ b/src/gfx_apis/vulkan/shaders/eotfs.glsl @@ -2,6 +2,7 @@ #define EOTFS_GLSL #include "frag_spec_const.glsl" +#include "tex_set.glsl" #define TF_LINEAR 1 #define TF_ST2084_PQ 2 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..eedfe0ff --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/tex_set.glsl @@ -0,0 +1,9 @@ +#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; + +#endif From 6d28bfd4e29d8d1710e1a741898e9acb691d1850 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 8 Sep 2025 18:15:10 +0200 Subject: [PATCH 4/7] vulkan: add get_ub_descriptor macro --- src/gfx_apis/vulkan/renderer.rs | 44 ++++++++++++++++----------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index b95260d8..b8455ef2 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -567,24 +567,35 @@ impl VulkanRenderer { .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! 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) + }; + } + 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), + ); } } let tex_descriptor_set_layout = &self.tex_descriptor_set_layouts[1]; @@ -601,20 +612,9 @@ 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), ); } } From a2d726e50895244d656b67a96f71879ed5c2cfb3 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 8 Sep 2025 18:26:19 +0200 Subject: [PATCH 5/7] vulkan: allow EOTFs to be parametrized --- src/gfx_apis/vulkan/descriptor.rs | 14 ++- src/gfx_apis/vulkan/renderer.rs | 151 +++++++++++++++++++---- src/gfx_apis/vulkan/shaders.rs | 22 ++++ src/gfx_apis/vulkan/shaders/tex_set.glsl | 12 ++ 4 files changed, 174 insertions(+), 25 deletions(-) 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/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index b8455ef2..4531165b 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -25,10 +25,10 @@ use { sampler::VulkanSampler, semaphore::VulkanSemaphore, shaders::{ - ColorManagementData, FILL_FRAG, FILL_VERT, FillPushConstants, LEGACY_FILL_FRAG, - LEGACY_FILL_VERT, LEGACY_TEX_FRAG, LEGACY_TEX_VERT, LegacyFillPushConstants, - LegacyTexPushConstants, OUT_FRAG, OUT_VERT, OutPushConstants, TEX_FRAG, TEX_VERT, - TexPushConstants, TexVertex, VulkanShader, + 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, @@ -176,10 +176,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 +208,7 @@ struct VulkanTexOp { instances: u32, tex_cd: Rc, color_management_data_address: Option, + eotf_args_address: Option, resource_descriptor_buffer_offset: DeviceAddress, } @@ -352,7 +359,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 { @@ -562,11 +571,14 @@ 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() - }); + 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() @@ -586,6 +598,22 @@ impl VulkanRenderer { 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(); @@ -597,9 +625,19 @@ impl VulkanRenderer { 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; @@ -617,6 +655,15 @@ impl VulkanRenderer { 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(); @@ -659,6 +706,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]; @@ -813,6 +861,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, @@ -826,6 +880,7 @@ impl VulkanRenderer { instances: 0, tex_cd: ct.cd.clone(), color_management_data_address, + eotf_args_address, resource_descriptor_buffer_offset: 0, })); } @@ -836,21 +891,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, ); @@ -912,18 +987,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(()) } @@ -1942,7 +2024,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(); @@ -2242,3 +2324,24 @@ impl ColorTransforms { ct.offset } } + +#[derive(Default)] +struct EotfArgsCache { + map: AHashMap<(), EotfArg>, +} + +struct EotfArg { + _offset: DeviceSize, +} + +impl EotfArgsCache { + fn get_offset( + &mut self, + _desc: &ColorDescription, + _inv: bool, + _uniform_buffer_offset_mask: DeviceSize, + _writer: &mut GenericBufferWriter, + ) -> Option { + None + } +} diff --git a/src/gfx_apis/vulkan/shaders.rs b/src/gfx_apis/vulkan/shaders.rs index de75388d..0e441582 100644 --- a/src/gfx_apis/vulkan/shaders.rs +++ b/src/gfx_apis/vulkan/shaders.rs @@ -69,6 +69,28 @@ pub struct ColorManagementData { unsafe impl Packed for ColorManagementData {} +#[derive(Copy, Clone, Debug)] +#[repr(C, align(16))] +pub struct EotfArgs { + pub arg1: f32, + pub arg2: f32, + pub arg3: f32, + pub arg4: f32, +} + +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)] pub struct LegacyTexPushConstants { diff --git a/src/gfx_apis/vulkan/shaders/tex_set.glsl b/src/gfx_apis/vulkan/shaders/tex_set.glsl index eedfe0ff..7a9f51aa 100644 --- a/src/gfx_apis/vulkan/shaders/tex_set.glsl +++ b/src/gfx_apis/vulkan/shaders/tex_set.glsl @@ -5,5 +5,17 @@ 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 From c37567f1cdadf0b5e02a96ffa4a12024b549c7bb Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 8 Sep 2025 18:37:07 +0200 Subject: [PATCH 6/7] 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] { From ef1727a186d2c3258f8bee20959cc6c29c37eab3 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 8 Sep 2025 20:37:47 +0200 Subject: [PATCH 7/7] color-management: parametrize bt1886 --- src/cmm/cmm_eotf.rs | 25 +++- src/gfx_apis/vulkan/eotfs.rs | 8 +- src/gfx_apis/vulkan/renderer.rs | 53 ++++++-- src/gfx_apis/vulkan/shaders/eotfs.glsl | 27 +++- .../wp_image_description_creator_params_v1.rs | 15 ++- .../wp_image_description_info_v1.rs | 6 +- src/theme.rs | 19 ++- src/utils/ordered_float.rs | 121 +++++++++--------- 8 files changed, 189 insertions(+), 85 deletions(-) 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);