use {
ash::vk,
bytemuck::cast_slice,
clap::Parser,
log::warn,
std::{mem::size_of, sync::Arc},
vk_graph::{
Graph,
driver::{
DriverError,
buffer::{Buffer, BufferInfo},
compute::{ComputePipeline, ComputePipelineInfo},
device::{Device, DeviceInfo},
image::{Image, ImageInfo},
shader::{SamplerInfo, Shader},
},
node::ImageNode,
pool::hash::HashPool,
},
vk_shader_macros::glsl,
vk_sync::AccessType,
};
fn main() -> Result<(), DriverError> {
pretty_env_logger::init();
let mut graph = Graph::default();
let args = Args::parse();
let device_info = DeviceInfo::builder().debug(args.debug);
let device = Device::create(device_info)?;
let size = 4;
let depth_image = fill_depth_image(&device, &mut graph, size)?;
let min_reduced_image = reduce_depth_image(
&device,
&mut graph,
depth_image,
vk::SamplerReductionMode::MIN,
)?;
let max_reduced_image = reduce_depth_image(
&device,
&mut graph,
depth_image,
vk::SamplerReductionMode::MAX,
)?;
let min_result_buf = copy_image_to_buffer(&device, &mut graph, min_reduced_image)?;
let max_result_buf = copy_image_to_buffer(&device, &mut graph, max_reduced_image)?;
let mut fence = graph
.finalize()
.queue_submit(&mut HashPool::new(&device), 0, 0)?;
fence.wait()?;
let min_result_data: &[f32] = cast_slice(Buffer::mapped_slice(&min_result_buf));
let max_result_data: &[f32] = cast_slice(Buffer::mapped_slice(&max_result_buf));
println!("{min_result_data:?}");
println!("{max_result_data:?}");
assert_eq!(min_result_data.len(), 4);
assert_eq!(max_result_data.len(), 4);
assert_eq!(min_result_data[0], 0.0);
assert_eq!(min_result_data[1], 2.0);
assert_eq!(min_result_data[2], 8.0);
assert_eq!(min_result_data[3], 10.0);
assert_eq!(max_result_data[0], 5.0);
assert_eq!(max_result_data[1], 7.0);
assert_eq!(max_result_data[2], 13.0);
assert_eq!(max_result_data[3], 15.0);
Ok(())
}
fn fill_depth_image(
device: &Device,
graph: &mut Graph,
size: u32,
) -> Result<ImageNode, DriverError> {
let info = ImageInfo::image_2d(
size,
size,
vk::Format::D32_SFLOAT,
vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::TRANSFER_DST,
);
let ImageInfo {
format,
image_type,
tiling,
usage,
flags,
..
} = info;
let fmt_props = device.physical.format_properties(format);
if !fmt_props.optimal_tiling_features.contains(
vk::FormatFeatureFlags::SAMPLED_IMAGE
| vk::FormatFeatureFlags::SAMPLED_IMAGE_FILTER_LINEAR
| vk::FormatFeatureFlags::SAMPLED_IMAGE_FILTER_MINMAX,
) {
warn!("Requested image does not support min/max reduction");
return Err(DriverError::Unsupported);
}
assert!(
device
.physical
.sampler_filter_minmax_properties
.single_component_formats
);
let image_fmt_props = device
.physical
.image_format_properties(format, image_type, tiling, usage, flags)?
.ok_or(DriverError::Unsupported)?;
if size > image_fmt_props.max_extent.width || size > image_fmt_props.max_extent.height {
warn!("Requested image is too big");
return Err(DriverError::Unsupported);
}
let depth_data = (0..size.pow(2)).map(|x| x as f32).collect::<Box<_>>();
let depth_data = graph.bind_resource(Buffer::create_from_slice(
device,
vk::BufferUsageFlags::TRANSFER_SRC,
cast_slice(&depth_data),
)?);
let depth_image = graph.bind_resource(Image::create(device, info)?);
graph.copy_buffer_to_image(depth_data, depth_image);
Ok(depth_image)
}
fn reduce_depth_image(
device: &Device,
graph: &mut Graph,
depth_image: ImageNode,
reduction_mode: vk::SamplerReductionMode,
) -> Result<ImageNode, DriverError> {
let depth_info = graph.resource(depth_image).info;
assert_eq!(depth_info.width, depth_info.height);
let reduced_info = ImageInfo::image_2d(
depth_info.width >> 1,
depth_info.height >> 1,
vk::Format::R32_SFLOAT,
vk::ImageUsageFlags::STORAGE | vk::ImageUsageFlags::TRANSFER_SRC,
);
let reduced_image = graph.bind_resource(Image::create(device, reduced_info)?);
graph
.begin_cmd()
.debug_name("Reduce depth image")
.bind_pipeline(ComputePipeline::create(
device,
ComputePipelineInfo::default(),
Shader::new_compute(
glsl!(
r#"
#version 460 core
#pragma shader_stage(compute)
layout(binding = 0) uniform sampler2D depth_image;
layout(binding = 1) writeonly uniform image2D reduced_image;
void main() {
ivec2 reduced_size = imageSize(reduced_image);
vec2 sample_xy = vec2(gl_GlobalInvocationID.xy) + 0.5;
vec4 sample_val = texture(depth_image, sample_xy / vec2(reduced_size));
ivec2 store_xy = ivec2(gl_GlobalInvocationID.xy);
imageStore(reduced_image, store_xy, sample_val);
}
"#
)
.as_slice(),
)
.image_sampler(0, SamplerInfo::LINEAR.reduction_mode(reduction_mode)),
)?)
.shader_resource_access(0, depth_image, AccessType::ComputeShaderReadOther)
.shader_resource_access(1, reduced_image, AccessType::ComputeShaderWrite)
.record_cmd(move |cmd| {
cmd.dispatch(reduced_info.width, reduced_info.height, 1);
});
Ok(reduced_image)
}
fn copy_image_to_buffer(
device: &Device,
graph: &mut Graph,
reduced_image: ImageNode,
) -> Result<Arc<Buffer>, DriverError> {
let reduced_info = graph.resource(reduced_image).info;
let result_len = (reduced_info.width * reduced_info.height) as vk::DeviceSize
* size_of::<f32>() as vk::DeviceSize;
let result_buf = graph.bind_resource(Buffer::create(
device,
BufferInfo::host_mem(result_len, vk::BufferUsageFlags::TRANSFER_DST),
)?);
graph.copy_image_to_buffer(reduced_image, result_buf);
Ok(graph.resource(result_buf).clone())
}
#[derive(Parser)]
struct Args {
#[arg(long)]
debug: bool,
}