Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 113 additions & 22 deletions src/converter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,6 @@ pub struct ColorConverter {
descriptor_set_layout: vk::DescriptorSetLayout,
pipeline_layout: vk::PipelineLayout,
pipeline: vk::Pipeline,
descriptor_pool: vk::DescriptorPool,
descriptor_set: vk::DescriptorSet,

// Sampler for texelFetch on the source image.
sampler: vk::Sampler,
Expand All @@ -212,6 +210,24 @@ pub struct ColorConverter {
output_buffer: vk::Buffer,
output_memory: vk::DeviceMemory,

// Descriptor buffer (holds captured descriptor data).
descriptor_buffer: vk::Buffer,
descriptor_buffer_memory: vk::DeviceMemory,
descriptor_buffer_address: vk::DeviceAddress,
descriptor_buffer_usage: vk::BufferUsageFlags,
descriptor_buffer_ptr: *mut u8,
sampler_capture_size: u32,
image_capture_size: u32,
buffer_capture_size: u32,
// Cached descriptor buffer device and per-frame capture buffers.
ext_device: ash::ext::descriptor_buffer::Device,
sampler_data: Vec<u8>,
image_data: Vec<u8>,
buffer_data: Vec<u8>,
// Descriptor set layout binding offsets for correct payload placement.
binding0_offset: u64,
binding1_offset: u64,

// Command resources.
command_pool: vk::CommandPool,
command_buffer: vk::CommandBuffer,
Expand Down Expand Up @@ -539,20 +555,6 @@ impl ColorConverter {

let device = self.context.device();

// Update descriptor set binding 0 with the source image view.
let image_info = vk::DescriptorImageInfo::default()
.sampler(self.sampler)
.image_view(src_view)
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);

let write = vk::WriteDescriptorSet::default()
.dst_set(self.descriptor_set)
.dst_binding(0)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.image_info(std::slice::from_ref(&image_info));

unsafe { device.update_descriptor_sets(&[write], &[]) };

// Reset and record command buffer.
unsafe {
device
Expand Down Expand Up @@ -626,20 +628,105 @@ impl ColorConverter {
&[],
);

// Bind pipeline and descriptor set.
// Bind pipeline.
device.cmd_bind_pipeline(
self.command_buffer,
vk::PipelineBindPoint::COMPUTE,
self.pipeline,
);

device.cmd_bind_descriptor_sets(
// --- Opaque capture descriptors into descriptor buffer ---
// 1. Capture sampler descriptor data into preallocated buffer.
let sampler_capture_info =
vk::SamplerCaptureDescriptorDataInfoEXT::default().sampler(self.sampler);
self.ext_device
.get_sampler_opaque_capture_descriptor_data(
&sampler_capture_info,
self.sampler_data.as_mut_slice(),
)
.map_err(|e| PixelForgeError::CommandBuffer(format!("sampler capture: {}", e)))?;

// 2. Capture image view descriptor data into preallocated buffer.
let image_capture_info =
vk::ImageViewCaptureDescriptorDataInfoEXT::default().image_view(src_view);
self.ext_device
.get_image_view_opaque_capture_descriptor_data(
&image_capture_info,
self.image_data.as_mut_slice(),
)
.map_err(|e| {
PixelForgeError::CommandBuffer(format!("image view capture: {}", e))
})?;

// 3. Capture output buffer descriptor data into preallocated buffer.
let buffer_capture_info =
vk::BufferCaptureDescriptorDataInfoEXT::default().buffer(self.output_buffer);
self.ext_device
.get_buffer_opaque_capture_descriptor_data(
&buffer_capture_info,
self.buffer_data.as_mut_slice(),
)
.map_err(|e| PixelForgeError::CommandBuffer(format!("buffer capture: {}", e)))?;

Comment thread
hgaiser marked this conversation as resolved.
// 4. Write captured data into descriptor buffer (persistent map, HOST_COHERENT).
//
// The descriptor buffer capture functions return driver-defined descriptor payloads
// in the format expected by the descriptor buffer. For combined image sampler
// descriptors (binding 0), the sampler and image view captures are placed
// consecutively at the binding's offset.
//
// Layout:
// Offset binding0_offset: Sampler + image view capture payloads (binding 0)
// Offset binding1_offset: Buffer capture payload (binding 1)

let sampler_offset = self.binding0_offset as usize;
let image_offset = sampler_offset + self.sampler_capture_size as usize;
let buffer_offset = self.binding1_offset as usize;

// Write sampler capture payload.
std::ptr::copy_nonoverlapping(
self.sampler_data.as_ptr(),
self.descriptor_buffer_ptr.add(sampler_offset),
self.sampler_capture_size as usize,
);

// Write image view capture payload.
std::ptr::copy_nonoverlapping(
self.image_data.as_ptr(),
self.descriptor_buffer_ptr.add(image_offset),
self.image_capture_size as usize,
);

// Write buffer capture payload.
std::ptr::copy_nonoverlapping(
self.buffer_data.as_ptr(),
self.descriptor_buffer_ptr.add(buffer_offset),
self.buffer_capture_size as usize,
);

// --- Bind descriptor buffers ---
let binding_info = vk::DescriptorBufferBindingInfoEXT::default()
.address(self.descriptor_buffer_address)
.usage(self.descriptor_buffer_usage);

self.ext_device.cmd_bind_descriptor_buffers(
self.command_buffer,
std::slice::from_ref(&binding_info),
);
Comment thread
hgaiser marked this conversation as resolved.

Comment thread
hgaiser marked this conversation as resolved.
// Associate set 0 in the pipeline layout with the bound descriptor buffer.
// This is required because descriptor buffers use offset-based binding.
// The base offset is 0 since all payloads are placed at their binding offsets.
let buffer_indices = [0u32];
let offsets = [0 as vk::DeviceSize];

self.ext_device.cmd_set_descriptor_buffer_offsets(
self.command_buffer,
vk::PipelineBindPoint::COMPUTE,
self.pipeline_layout,
0,
&[self.descriptor_set],
&[],
0, // first_set
&buffer_indices,
&offsets,
);
Comment thread
hgaiser marked this conversation as resolved.

// Push constants: width, height, input_format, output_format, color_space, full_range, sdr_white_nits.
Expand Down Expand Up @@ -846,10 +933,14 @@ impl Drop for ColorConverter {
device.destroy_buffer(self.output_buffer, None);
device.free_memory(self.output_memory, None);

// Destroy descriptor buffer and its memory.
device.unmap_memory(self.descriptor_buffer_memory);
device.destroy_buffer(self.descriptor_buffer, None);
device.free_memory(self.descriptor_buffer_memory, None);

// Destroy pipeline resources.
device.destroy_pipeline(self.pipeline, None);
device.destroy_pipeline_layout(self.pipeline_layout, None);
device.destroy_descriptor_pool(self.descriptor_pool, None);
device.destroy_descriptor_set_layout(self.descriptor_set_layout, None);

// Destroy command resources.
Expand Down
154 changes: 110 additions & 44 deletions src/converter/pipeline.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//! Vulkan compute pipeline creation for color conversion.

use super::{ColorConverter, ColorConverterConfig};
use crate::encoder::resources::find_memory_type;
use crate::error::{PixelForgeError, Result};
Expand All @@ -11,7 +10,15 @@ pub fn create_converter(
context: VideoContext,
config: ColorConverterConfig,
) -> Result<ColorConverter> {
if !context.has_descriptor_buffer() {
return Err(PixelForgeError::NoSuitableDevice(
"VK_EXT_descriptor_buffer with capture-replay is required but not available on this device".to_string(),
));
}

let device = context.device();
let instance = context.instance();
let physical_device = context.physical_device();

// Create descriptor set layout.
let bindings = [
Expand All @@ -29,7 +36,9 @@ pub fn create_converter(
.stage_flags(vk::ShaderStageFlags::COMPUTE),
];

let layout_info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings);
let layout_info = vk::DescriptorSetLayoutCreateInfo::default()
.flags(vk::DescriptorSetLayoutCreateFlags::DESCRIPTOR_BUFFER_EXT)
.bindings(&bindings);

let descriptor_set_layout = unsafe { device.create_descriptor_set_layout(&layout_info, None) }
Comment thread
hgaiser marked this conversation as resolved.
.map_err(|e| PixelForgeError::ResourceCreation(e.to_string()))?;
Comment thread
hgaiser marked this conversation as resolved.
Expand Down Expand Up @@ -73,32 +82,6 @@ pub fn create_converter(
// Destroy shader module (no longer needed after pipeline creation)
unsafe { device.destroy_shader_module(shader_module, None) };

// Create descriptor pool.
let pool_sizes = [
vk::DescriptorPoolSize::default()
.ty(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.descriptor_count(1),
vk::DescriptorPoolSize::default()
.ty(vk::DescriptorType::STORAGE_BUFFER)
.descriptor_count(1),
];

let pool_info = vk::DescriptorPoolCreateInfo::default()
.flags(vk::DescriptorPoolCreateFlags::FREE_DESCRIPTOR_SET)
.max_sets(1)
.pool_sizes(&pool_sizes);

let descriptor_pool = unsafe { device.create_descriptor_pool(&pool_info, None) }
.map_err(|e| PixelForgeError::ResourceCreation(e.to_string()))?;

// Allocate descriptor set.
let alloc_info = vk::DescriptorSetAllocateInfo::default()
.descriptor_pool(descriptor_pool)
.set_layouts(std::slice::from_ref(&descriptor_set_layout));

let descriptor_set = unsafe { device.allocate_descriptor_sets(&alloc_info) }
.map_err(|e| PixelForgeError::ResourceCreation(e.to_string()))?[0];

// Calculate output buffer size.
let output_size = config
.output_format
Expand Down Expand Up @@ -127,20 +110,87 @@ pub fn create_converter(
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)?;

// Write only the output buffer descriptor now; the source image descriptor
// is written per-frame in convert() when we know the actual source ImageView.
let output_buffer_info = vk::DescriptorBufferInfo::default()
.buffer(output_buffer)
.offset(0)
.range(output_size as vk::DeviceSize);

let writes = [vk::WriteDescriptorSet::default()
.dst_set(descriptor_set)
.dst_binding(1)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(std::slice::from_ref(&output_buffer_info))];

unsafe { device.update_descriptor_sets(&writes, &[]) };
// Query descriptor buffer properties to determine correct capture sizes.
let mut db_props = vk::PhysicalDeviceDescriptorBufferPropertiesEXT::default();
let mut props = vk::PhysicalDeviceProperties2 {
p_next: &mut db_props as *mut _ as *mut _,
..Default::default()
};
unsafe {
instance.get_physical_device_properties2(physical_device, &mut props);
}
let sampler_cap_size = db_props.sampler_capture_replay_descriptor_data_size;
let image_cap_size = db_props.image_view_capture_replay_descriptor_data_size;
let buffer_cap_size = db_props.buffer_capture_replay_descriptor_data_size;
Comment thread
hgaiser marked this conversation as resolved.

// Query descriptor set layout size and binding offsets for correct buffer sizing.
let ext_device =
ash::ext::descriptor_buffer::Device::load(context.instance(), context.device());
let vk_device = context.device().handle();
let mut layout_size = 0u64;
unsafe {
(ext_device.fp().get_descriptor_set_layout_size_ext)(
vk_device,
descriptor_set_layout,
&mut layout_size,
);
}
let binding0_offset = unsafe {
let mut offset = 0u64;
(ext_device.fp().get_descriptor_set_layout_binding_offset_ext)(
vk_device,
descriptor_set_layout,
0,
&mut offset,
);
offset
};
let binding1_offset = unsafe {
let mut offset = 0u64;
(ext_device.fp().get_descriptor_set_layout_binding_offset_ext)(
vk_device,
descriptor_set_layout,
1,
&mut offset,
);
offset
};

// Descriptor buffer layout:
// Offset 0: Sampler + image view capture payload (binding 0)
// Offset X: Buffer capture payload (binding 1)
// The total size is the layout size which accounts for alignment.
let descriptor_buffer_size: vk::DeviceSize = layout_size as vk::DeviceSize;

Comment thread
hgaiser marked this conversation as resolved.
let (descriptor_buffer, descriptor_buffer_memory) =
crate::encoder::resources::create_buffer_with_device_address(
device,
context.memory_properties(),
descriptor_buffer_size,
vk::BufferUsageFlags::SHADER_DEVICE_ADDRESS
| vk::BufferUsageFlags::RESOURCE_DESCRIPTOR_BUFFER_EXT,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?;

// Get the buffer's device address for binding descriptor buffers.
// cmdBindDescriptorBuffers requires the buffer's device address, not the memory capture address.
let buf_addr_info = vk::BufferDeviceAddressInfo::default().buffer(descriptor_buffer);
let descriptor_buffer_address = unsafe { device.get_buffer_device_address(&buf_addr_info) };

// Persistent map the descriptor buffer (HOST_COHERENT, no flush needed).
let descriptor_buffer_ptr = unsafe {
device
.map_memory(
descriptor_buffer_memory,
0,
vk::WHOLE_SIZE,
vk::MemoryMapFlags::empty(),
)
.map_err(|e| {
PixelForgeError::ResourceCreation(format!("map descriptor buffer: {}", e))
})?
};
let descriptor_buffer_ptr = descriptor_buffer_ptr as *mut u8;

// Create command pool for compute queue.
let pool_info = vk::CommandPoolCreateInfo::default()
Expand Down Expand Up @@ -170,15 +220,31 @@ pub fn create_converter(
descriptor_set_layout,
pipeline_layout,
pipeline,
descriptor_pool,
descriptor_set,
sampler,
cached_src_view: None,
output_buffer,
output_memory,
command_pool,
command_buffer,
fence,
// Descriptor buffer fields.
descriptor_buffer,
descriptor_buffer_memory,
descriptor_buffer_address,
descriptor_buffer_usage: vk::BufferUsageFlags::SHADER_DEVICE_ADDRESS
| vk::BufferUsageFlags::RESOURCE_DESCRIPTOR_BUFFER_EXT,
descriptor_buffer_ptr,
sampler_capture_size: sampler_cap_size as u32,
image_capture_size: image_cap_size as u32,
buffer_capture_size: buffer_cap_size as u32,
// Cached descriptor buffer device and capture buffers.
ext_device,
sampler_data: vec![0u8; sampler_cap_size],
image_data: vec![0u8; image_cap_size],
buffer_data: vec![0u8; buffer_cap_size],
// Layout info for correct offset computation.
binding0_offset,
binding1_offset,
})
}

Expand Down
Loading
Loading