From ecb5b5ccb10945efd0b14283141392cd80958882 Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Thu, 1 Aug 2024 13:56:11 +0800 Subject: [PATCH 01/26] ray tracing pipeline --- Cargo.lock | 2 + examples/triangle-util/Cargo.toml | 2 + examples/triangle-util/main.rs | 747 ++++++++++++------ examples/triangle-util/raytrace.rchit | 10 + examples/triangle-util/raytrace.rgen | 43 + examples/triangle-util/raytrace.rmiss | 6 + vulkano/src/acceleration_structure.rs | 2 + vulkano/src/buffer/usage.rs | 4 +- vulkano/src/command_buffer/auto/builder.rs | 2 + .../src/command_buffer/commands/bind_push.rs | 56 +- .../src/command_buffer/commands/pipeline.rs | 104 +++ vulkano/src/command_buffer/commands/sync.rs | 4 +- vulkano/src/command_buffer/mod.rs | 1 + vulkano/src/device/mod.rs | 102 +++ vulkano/src/pipeline/compute.rs | 78 +- vulkano/src/pipeline/graphics/mod.rs | 98 +-- vulkano/src/pipeline/mod.rs | 5 +- vulkano/src/pipeline/ray_tracing/mod.rs | 477 +++++++++++ vulkano/src/pipeline/shader/mod.rs | 91 ++- 19 files changed, 1431 insertions(+), 403 deletions(-) create mode 100644 examples/triangle-util/raytrace.rchit create mode 100644 examples/triangle-util/raytrace.rgen create mode 100644 examples/triangle-util/raytrace.rmiss create mode 100644 vulkano/src/pipeline/ray_tracing/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 60d44b8131..498505678e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2287,6 +2287,8 @@ dependencies = [ name = "triangle-util" version = "0.0.0" dependencies = [ + "ash", + "glam", "vulkano", "vulkano-shaders", "vulkano-util", diff --git a/examples/triangle-util/Cargo.toml b/examples/triangle-util/Cargo.toml index 3dbb029751..11b43a1836 100644 --- a/examples/triangle-util/Cargo.toml +++ b/examples/triangle-util/Cargo.toml @@ -21,3 +21,5 @@ vulkano-util = { workspace = true } # The Vulkan library doesn't provide any functionality to create and handle windows, as # this would be out of scope. In order to open a window, we are going to use the `winit` crate. winit = { workspace = true, default-features = true } +ash = { workspace = true } +glam = { workspace = true } diff --git a/examples/triangle-util/main.rs b/examples/triangle-util/main.rs index 3297b5a058..fab327015f 100644 --- a/examples/triangle-util/main.rs +++ b/examples/triangle-util/main.rs @@ -7,30 +7,50 @@ // that you want to learn Vulkan. This means that for example it won't go into details about what a // vertex or a shader is. -use std::{error::Error, sync::Arc, time::Duration}; +use glam::{Mat4, Vec3}; +use std::{ + error::Error, + mem::size_of, + sync::Arc, + time::{Duration, Instant}, +}; use vulkano::{ - buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage}, + acceleration_structure::{ + AccelerationStructure, AccelerationStructureBuildGeometryInfo, + AccelerationStructureBuildRangeInfo, AccelerationStructureBuildType, + AccelerationStructureCreateInfo, AccelerationStructureGeometries, + AccelerationStructureGeometryInstancesData, AccelerationStructureGeometryInstancesDataType, + AccelerationStructureGeometryTrianglesData, AccelerationStructureInstance, + AccelerationStructureType, BuildAccelerationStructureFlags, BuildAccelerationStructureMode, + }, + buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer}, command_buffer::{ allocator::StandardCommandBufferAllocator, CommandBufferBeginInfo, CommandBufferLevel, - CommandBufferUsage, RecordingCommandBuffer, RenderPassBeginInfo, SubpassBeginInfo, - SubpassContents, + CommandBufferUsage, RecordingCommandBuffer, }, - image::view::ImageView, - memory::allocator::{AllocationCreateInfo, MemoryTypeFilter}, - pipeline::{ - graphics::{ - color_blend::{ColorBlendAttachmentState, ColorBlendState}, - input_assembly::InputAssemblyState, - multisample::MultisampleState, - rasterization::RasterizationState, - vertex_input::{Vertex, VertexDefinition}, - viewport::{Viewport, ViewportState}, - GraphicsPipelineCreateInfo, + descriptor_set::{ + allocator::{DescriptorSetAllocator, StandardDescriptorSetAllocator}, + layout::{ + DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo, + DescriptorType, }, + DescriptorSet, WriteDescriptorSet, + }, + device::{Device, DeviceExtensions, DeviceFeatures, DeviceOwnedVulkanObject}, + format::Format, + image::{view::ImageView, ImageUsage}, + instance::{InstanceCreateInfo, InstanceExtensions}, + memory::allocator::{AllocationCreateInfo, MemoryAllocator, MemoryTypeFilter}, + pipeline::{ + graphics::{vertex_input::Vertex, viewport::Viewport}, layout::PipelineDescriptorSetLayoutCreateInfo, - DynamicState, GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo, + ray_tracing::{ + RayTracingPipeline, RayTracingPipelineCreateInfo, RayTracingShaderGroupCreateInfo, + ShaderBindingTable, + }, + Pipeline, PipelineBindPoint, PipelineLayout, PipelineShaderStageCreateInfo, }, - render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass}, + shader::ShaderStages, sync::GpuFuture, }; use vulkano_util::{ @@ -42,12 +62,74 @@ use winit::{ event_loop::{ControlFlow, EventLoop}, }; +#[derive(BufferContents, Vertex)] +#[repr(C)] +struct MyVertex { + #[format(R32G32B32_SFLOAT)] + position: [f32; 3], +} + +mod raygen { + vulkano_shaders::shader! { + ty: "raygen", + path: "raytrace.rgen", + vulkan_version: "1.2" + } +} + +mod closest_hit { + vulkano_shaders::shader! { + ty: "closesthit", + path: "raytrace.rchit", + vulkan_version: "1.2" + } +} + +mod miss { + vulkano_shaders::shader! { + ty: "miss", + path: "raytrace.rmiss", + vulkan_version: "1.2" + } +} + fn main() -> Result<(), impl Error> { - let context = VulkanoContext::new(VulkanoConfig::default()); + let context = VulkanoContext::new(VulkanoConfig { + device_extensions: DeviceExtensions { + khr_swapchain: true, + khr_acceleration_structure: true, + khr_ray_tracing_pipeline: true, + khr_deferred_host_operations: true, + ..Default::default() + }, + device_features: DeviceFeatures { + acceleration_structure: true, + ray_tracing_pipeline: true, + buffer_device_address: true, + synchronization2: true, + ..Default::default() + }, + instance_create_info: InstanceCreateInfo { + enabled_layers: vec!["VK_LAYER_KHRONOS_validation".to_owned()], + enabled_extensions: InstanceExtensions { + ext_debug_utils: true, + ..InstanceExtensions::empty() + }, + ..Default::default() + }, + ..Default::default() + }); let event_loop = EventLoop::new().unwrap(); // Manages any windows and their rendering. let mut windows_manager = VulkanoWindows::default(); - windows_manager.create_window(&event_loop, &context, &Default::default(), |_| {}); + windows_manager.create_window( + &event_loop, + &context, + &Default::default(), + |swapchain_create_info| { + swapchain_create_info.image_usage |= ImageUsage::STORAGE; + }, + ); let window_renderer = windows_manager.get_primary_renderer_mut().unwrap(); // Some little debug infos. @@ -60,28 +142,24 @@ fn main() -> Result<(), impl Error> { // We now create a buffer that will store the shape of our triangle. We use `#[repr(C)]` here // to force rustc to use a defined layout for our data, as the default representation has *no // guarantees*. - #[derive(BufferContents, Vertex)] - #[repr(C)] - struct Vertex { - #[format(R32G32_SFLOAT)] - position: [f32; 2], - } let vertices = [ - Vertex { - position: [-0.5, -0.25], + MyVertex { + position: [-0.5, -0.25, 0.0], }, - Vertex { - position: [0.0, 0.5], + MyVertex { + position: [0.0, 0.5, 0.0], }, - Vertex { - position: [0.25, -0.1], + MyVertex { + position: [0.25, -0.1, 0.0], }, ]; let vertex_buffer = Buffer::from_iter( context.memory_allocator().clone(), BufferCreateInfo { - usage: BufferUsage::VERTEX_BUFFER, + usage: BufferUsage::VERTEX_BUFFER + | BufferUsage::SHADER_DEVICE_ADDRESS + | BufferUsage::ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY, ..Default::default() }, AllocationCreateInfo { @@ -93,89 +171,122 @@ fn main() -> Result<(), impl Error> { ) .unwrap(); - // The next step is to create the shaders. - // - // The raw shader creation API provided by the vulkano library is unsafe for various reasons, - // so The `shader!` macro provides a way to generate a Rust module from GLSL source - in the - // example below, the source is provided as a string input directly to the shader, but a path - // to a source file can be provided as well. Note that the user must specify the type of shader - // (e.g. "vertex", "fragment", etc.) using the `ty` option of the macro. - // - // The items generated by the `shader!` macro include a `load` function which loads the shader - // using an input logical device. The module also includes type definitions for layout - // structures defined in the shader source, for example uniforms and push constants. - // - // A more detailed overview of what the `shader!` macro generates can be found in the - // vulkano-shaders crate docs. You can view them at https://docs.rs/vulkano-shaders/ - mod vs { - vulkano_shaders::shader! { - ty: "vertex", - src: r" - #version 450 - - layout(location = 0) in vec2 position; - - void main() { - gl_Position = vec4(position, 0.0, 1.0); - } - ", - } - } + let uniform_buffer = Buffer::from_data( + context.memory_allocator().clone(), + BufferCreateInfo { + usage: BufferUsage::UNIFORM_BUFFER, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + raygen::Camera { + projInverse: Default::default(), + viewInverse: Default::default(), + viewProj: Default::default(), + }, + ) + .unwrap(); - mod fs { - vulkano_shaders::shader! { - ty: "fragment", - src: r" - #version 450 + // Before we can start creating and recording command buffers, we need a way of allocating + // them. Vulkano provides a command buffer allocator, which manages raw Vulkan command pools + // underneath and provides a safe interface for them. + let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new( + context.device().clone(), + Default::default(), + )); - layout(location = 0) out vec4 f_color; + let tlas = unsafe { + let mut builder = RecordingCommandBuffer::new( + command_buffer_allocator.clone(), + context.graphics_queue().queue_family_index(), + CommandBufferLevel::Primary, + CommandBufferBeginInfo { + usage: CommandBufferUsage::OneTimeSubmit, + ..Default::default() + }, + ) + .unwrap(); - void main() { - f_color = vec4(1.0, 0.0, 0.0, 1.0); - } - ", - } - } + let blas = build_acceleration_structure_triangles( + vertex_buffer.clone(), + context.memory_allocator().clone(), + context.device().clone(), + &mut builder, + ); + blas.set_debug_utils_object_name("Triangle BLAS".into()) + .unwrap(); + let tlas = build_top_level_acceleration_structure( + blas, + context.memory_allocator().clone(), + context.device().clone(), + &mut builder, + ); + tlas.set_debug_utils_object_name("Triangle TLAS".into()) + .unwrap(); + + builder + .end() + .unwrap() + .execute(context.graphics_queue().clone()) + .unwrap() + .then_signal_fence_and_flush() + .unwrap() + .wait(None) + .unwrap(); - // At this point, OpenGL initialization would be finished. However in Vulkan it is not. OpenGL - // implicitly does a lot of computation whenever you draw. In Vulkan, you have to do all this - // manually. + tlas + }; - // The next step is to create a *render pass*, which is an object that describes where the - // output of the graphics pipeline will go. It describes the layout of the images where the - // colors, depth and/or stencil information will be written. - let render_pass = vulkano::single_pass_renderpass!( + let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new( context.device().clone(), - attachments: { - // `color` is a custom name we give to the first and only attachment. - color: { - // `format: ` indicates the type of the format of the image. This has to be one - // of the types of the `vulkano::format` module (or alternatively one of your - // structs that implements the `FormatDesc` trait). Here we use the same format as - // the swapchain. - format: window_renderer.swapchain_format(), - // `samples: 1` means that we ask the GPU to use one sample to determine the value - // of each pixel in the color attachment. We could use a larger value - // (multisampling) for antialiasing. An example of this can be found in - // msaa-renderpass.rs. - samples: 1, - // `load_op: Clear` means that we ask the GPU to clear the content of this - // attachment at the start of the drawing. - load_op: Clear, - // `store_op: Store` means that we ask the GPU to store the output of the draw in - // the actual image. We could also ask it to discard the result. - store_op: Store, - }, - }, - pass: { - // We use the attachment named `color` as the one and only color attachment. - color: [color], - // No depth-stencil attachment is indicated with empty brackets. - depth_stencil: {}, + Default::default(), + )); + + let descriptor_set_layout_0 = DescriptorSetLayout::new( + context.device().clone(), + DescriptorSetLayoutCreateInfo { + bindings: [ + ( + 0, + DescriptorSetLayoutBinding { + stages: ShaderStages::RAYGEN, + ..DescriptorSetLayoutBinding::descriptor_type( + DescriptorType::AccelerationStructure, + ) + }, + ), + ( + 1, + DescriptorSetLayoutBinding { + stages: ShaderStages::RAYGEN, + ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::UniformBuffer) + }, + ), + ] + .into_iter() + .collect(), + ..Default::default() }, ) .unwrap(); + let descriptor_set_0 = DescriptorSet::new( + descriptor_set_allocator.clone(), + descriptor_set_layout_0, + [ + WriteDescriptorSet::acceleration_structure(0, tlas), + WriteDescriptorSet::buffer(1, uniform_buffer.clone()), + ], + [], + ) + .unwrap(); + descriptor_set_0 + .set_debug_utils_object_name("Descriptor Set 0".into()) + .unwrap(); + // Before we draw, we have to create what is called a **pipeline**. A pipeline describes how // a GPU operation is to be performed. It is similar to an OpenGL program, but it also contains // many settings for customization, all baked into a single object. For drawing, we create @@ -186,23 +297,25 @@ fn main() -> Result<(), impl Error> { // // A Vulkan shader can in theory contain multiple entry points, so we have to specify which // one. - let vs = vs::load(context.device().clone()) + let raygen = raygen::load(context.device().clone()) .unwrap() .entry_point("main") .unwrap(); - let fs = fs::load(context.device().clone()) + let closest_hit = closest_hit::load(context.device().clone()) .unwrap() .entry_point("main") .unwrap(); - // Automatically generate a vertex input state from the vertex shader's input interface, - // that takes a single vertex buffer containing `Vertex` structs. - let vertex_input_state = Vertex::per_vertex().definition(&vs).unwrap(); + let miss = miss::load(context.device().clone()) + .unwrap() + .entry_point("main") + .unwrap(); // Make a list of the shader stages that the pipeline will have. let stages = [ - PipelineShaderStageCreateInfo::new(vs), - PipelineShaderStageCreateInfo::new(fs), + PipelineShaderStageCreateInfo::new(raygen), + PipelineShaderStageCreateInfo::new(miss), + PipelineShaderStageCreateInfo::new(closest_hit), ]; // We must now create a **pipeline layout** object, which describes the locations and types @@ -226,47 +339,44 @@ fn main() -> Result<(), impl Error> { ) .unwrap(); - // We have to indicate which subpass of which render pass this pipeline is going to be used - // in. The pipeline will only be usable from this particular subpass. - let subpass = Subpass::from(render_pass.clone(), 0).unwrap(); + let groups = [ + RayTracingShaderGroupCreateInfo { + // Raygen + general_shader: Some(0), + ..Default::default() + }, + RayTracingShaderGroupCreateInfo { + // Miss + general_shader: Some(1), + ..Default::default() + }, + RayTracingShaderGroupCreateInfo { + // Closest Hit + group_type: ash::vk::RayTracingShaderGroupTypeKHR::TRIANGLES_HIT_GROUP, + closest_hit_shader: Some(2), + ..Default::default() + }, + ]; - // Finally, create the pipeline. - GraphicsPipeline::new( + RayTracingPipeline::new( context.device().clone(), None, - GraphicsPipelineCreateInfo { + RayTracingPipelineCreateInfo { stages: stages.into_iter().collect(), - // How vertex data is read from the vertex buffers into the vertex shader. - vertex_input_state: Some(vertex_input_state), - // How vertices are arranged into primitive shapes. - // The default primitive shape is a triangle. - input_assembly_state: Some(InputAssemblyState::default()), - // How primitives are transformed and clipped to fit the framebuffer. - // We use a resizable viewport, set to draw over the entire window. - viewport_state: Some(ViewportState::default()), - // How polygons are culled and converted into a raster of pixels. - // The default value does not perform any culling. - rasterization_state: Some(RasterizationState::default()), - // How multiple fragment shader samples are converted to a single pixel value. - // The default value does not perform any multisampling. - multisample_state: Some(MultisampleState::default()), - // How pixel values are combined with the values already present in the framebuffer. - // The default value overwrites the old value with the new one, without any - // blending. - color_blend_state: Some(ColorBlendState::with_attachment_states( - subpass.num_color_attachments(), - ColorBlendAttachmentState::default(), - )), - // Dynamic states allows us to specify parts of the pipeline settings when - // recording the command buffer, before we perform drawing. - // Here, we specify that the viewport should be dynamic. - dynamic_state: [DynamicState::Viewport].into_iter().collect(), - subpass: Some(subpass.into()), - ..GraphicsPipelineCreateInfo::layout(layout) + groups: groups.into_iter().collect(), + max_pipeline_ray_recursion_depth: 1, + + ..RayTracingPipelineCreateInfo::layout(layout) }, ) .unwrap() }; + pipeline + .set_debug_utils_object_name("Ray Tracing Pipeline".into()) + .unwrap(); + + let shader_binding_table = + ShaderBindingTable::new(context.memory_allocator().clone(), &pipeline, 1, 1, 0).unwrap(); // Dynamic viewports allow us to recreate just the viewport when the window is resized. // Otherwise we would have to recreate the whole pipeline. @@ -281,19 +391,6 @@ fn main() -> Result<(), impl Error> { // // Since we need to draw to multiple images, we are going to create a different framebuffer for // each image. - let mut framebuffers = window_size_dependent_setup( - window_renderer.swapchain_image_views(), - render_pass.clone(), - &mut viewport, - ); - - // Before we can start creating and recording command buffers, we need a way of allocating - // them. Vulkano provides a command buffer allocator, which manages raw Vulkan command pools - // underneath and provides a safe interface for them. - let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new( - context.device().clone(), - Default::default(), - )); // Initialization is finally finished! @@ -301,6 +398,8 @@ fn main() -> Result<(), impl Error> { // an object that implements the `GpuFuture` trait, which holds the resources for as long as // they are in use by the GPU. + let rotation_start = Instant::now(); + event_loop.run(move |event, elwt| { elwt.set_control_flow(ControlFlow::Poll); @@ -321,27 +420,45 @@ fn main() -> Result<(), impl Error> { event: WindowEvent::RedrawRequested, .. } => { + let window_size_dependent_items = window_size_dependent_setup( + window_renderer.swapchain_image_views()[window_renderer.image_index() as usize] + .clone(), + descriptor_set_allocator.clone(), + context.device().clone(), + ); + // Do not draw the frame when the screen size is zero. On Windows, this can // occur when minimizing the application. - let image_extent: [u32; 2] = window_renderer.window().inner_size().into(); - - if image_extent.contains(&0) { + if window_size_dependent_items.extent.contains(&0) { return; } + let elapsed = rotation_start.elapsed(); + + // NOTE: This teapot was meant for OpenGL where the origin is at the lower left + // instead the origin is at the upper left in Vulkan, so we reverse the Y axis. + let aspect_ratio = window_size_dependent_items.extent[0] as f32 + / window_size_dependent_items.extent[1] as f32; + + let proj = + Mat4::perspective_rh_gl(std::f32::consts::FRAC_PI_2, aspect_ratio, 0.01, 100.0); + let view = Mat4::look_at_rh( + Vec3::new(0.3, 0.3, 1.0), + Vec3::new(0.0, 0.0, 0.0), + Vec3::new(0.0, -1.0, 0.0), + ); + + let uniform_data = raygen::Camera { + projInverse: proj.inverse().to_cols_array_2d(), + viewInverse: view.inverse().to_cols_array_2d(), + viewProj: (proj * view).to_cols_array_2d(), + }; + + *uniform_buffer.write().unwrap() = uniform_data; + // Begin rendering by acquiring the gpu future from the window renderer. let previous_frame_end = window_renderer - .acquire(Some(Duration::from_millis(1)), |swapchain_images| { - // Whenever the window resizes we need to recreate everything dependent - // on the window size. In this example that - // includes the swapchain, the framebuffers - // and the dynamic state viewport. - framebuffers = window_size_dependent_setup( - swapchain_images, - render_pass.clone(), - &mut viewport, - ); - }) + .acquire(Some(Duration::from_millis(1)), |_| {}) .unwrap(); // In order to draw, we have to record a *command buffer*. The command buffer object @@ -365,53 +482,30 @@ fn main() -> Result<(), impl Error> { .unwrap(); builder - // Before we can draw, we have to *enter a render pass*. - .begin_render_pass( - RenderPassBeginInfo { - // A list of values to clear the attachments with. This list contains - // one item for each attachment in the render pass. In this case, there - // is only one attachment, and we clear it with a blue color. - // - // Only attachments that have `AttachmentLoadOp::Clear` are provided - // with clear values, any others should use `None` as the clear value. - clear_values: vec![Some([0.0, 0.0, 1.0, 1.0].into())], - - ..RenderPassBeginInfo::framebuffer( - framebuffers[window_renderer.image_index() as usize].clone(), - ) - }, - SubpassBeginInfo { - // The contents of the first (and only) subpass. - // This can be either `Inline` or `SecondaryCommandBuffers`. - // The latter is a bit more advanced and is not covered here. - contents: SubpassContents::Inline, - ..Default::default() - }, + .bind_descriptor_sets( + PipelineBindPoint::RayTracing, + pipeline.layout().clone(), + 0, + vec![ + descriptor_set_0.clone(), + window_size_dependent_items.image_descriptor_set, + ], ) .unwrap() - // We are now inside the first subpass of the render pass. - // - // TODO: Document state setting and how it affects subsequent draw commands. - .set_viewport(0, [viewport.clone()].into_iter().collect()) - .unwrap() - .bind_pipeline_graphics(pipeline.clone()) - .unwrap() - .bind_vertex_buffers(0, vertex_buffer.clone()) + .bind_pipeline_ray_tracing(pipeline.clone()) .unwrap(); unsafe { builder - // We add a draw command. - .draw(vertex_buffer.len() as u32, 1, 0, 0) + .trace_rays( + shader_binding_table.clone(), + window_size_dependent_items.extent[0], + window_size_dependent_items.extent[1], + 1, + ) .unwrap(); } - builder - // We leave the render pass. Note that if we had multiple subpasses we could - // have called `next_subpass` to jump to the next subpass. - .end_render_pass(Default::default()) - .unwrap(); - // Finish recording the command buffer by calling `end`. let command_buffer = builder.end().unwrap(); @@ -429,6 +523,8 @@ fn main() -> Result<(), impl Error> { // only be presented once the GPU has finished executing the command buffer // that draws the triangle. window_renderer.present(future, false); + + panic!("Done"); } Event::AboutToWait => window_renderer.window().request_redraw(), _ => (), @@ -436,26 +532,223 @@ fn main() -> Result<(), impl Error> { }) } +struct WindowSizeDependentItems { + extent: [u32; 3], + image_descriptor_set: Arc, +} + /// This function is called once during initialization, then again whenever the window is resized. fn window_size_dependent_setup( - swapchain_images: &[Arc], - render_pass: Arc, - viewport: &mut Viewport, -) -> Vec> { - let extent = swapchain_images[0].image().extent(); - viewport.extent = [extent[0] as f32, extent[1] as f32]; - - swapchain_images - .iter() - .map(|swapchain_image| { - Framebuffer::new( - render_pass.clone(), - FramebufferCreateInfo { - attachments: vec![swapchain_image.clone()], + target_image_view: Arc, + descriptor_set_allocator: Arc, + device: Arc, +) -> WindowSizeDependentItems { + let extent = target_image_view.image().extent(); + + let descriptor_set_layout = DescriptorSetLayout::new( + device, + DescriptorSetLayoutCreateInfo { + bindings: [( + 0, + DescriptorSetLayoutBinding { + stages: ShaderStages::RAYGEN, + ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::StorageImage) + }, + )] + .into_iter() + .collect(), + ..Default::default() + }, + ) + .unwrap(); + + let image_descriptor_set = DescriptorSet::new( + descriptor_set_allocator.clone(), + descriptor_set_layout.clone(), + [WriteDescriptorSet::image_view(0, target_image_view.clone())], + [], + ) + .unwrap(); + image_descriptor_set + .set_debug_utils_object_name("Image Descriptor Set".into()) + .unwrap(); + + WindowSizeDependentItems { + extent, + image_descriptor_set, + } +} + +unsafe fn build_acceleration_structure_triangles( + vertex_buffer: Subbuffer<[MyVertex]>, + allocator: Arc, + device: Arc, + command_buffer: &mut RecordingCommandBuffer, +) -> Arc { + let primitive_count = (vertex_buffer.len() / 3) as u32; + let as_geometry_triangles_data = AccelerationStructureGeometryTrianglesData { + // TODO: Modify constructor? + max_vertex: vertex_buffer.len() as _, + vertex_data: Some(vertex_buffer.into_bytes()), + vertex_stride: size_of::() as _, + ..AccelerationStructureGeometryTrianglesData::new(Format::R32G32B32_SFLOAT) + }; + + let as_geometries = + AccelerationStructureGeometries::Triangles(vec![as_geometry_triangles_data]); + + let mut as_build_geometry_info = AccelerationStructureBuildGeometryInfo { + mode: BuildAccelerationStructureMode::Build, + flags: BuildAccelerationStructureFlags::PREFER_FAST_TRACE, + ..AccelerationStructureBuildGeometryInfo::new(as_geometries) + }; + + let as_build_sizes_info = device + .acceleration_structure_build_sizes( + AccelerationStructureBuildType::Device, + &as_build_geometry_info, + &[primitive_count], + ) + .unwrap(); + + let scratch_buffer = Buffer::new_slice::( + allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::SHADER_DEVICE_ADDRESS | BufferUsage::STORAGE_BUFFER, + ..Default::default() + }, + AllocationCreateInfo::default(), + as_build_sizes_info.build_scratch_size, + ) + .unwrap(); + + let as_create_info = AccelerationStructureCreateInfo { + ty: AccelerationStructureType::BottomLevel, + ..AccelerationStructureCreateInfo::new( + Buffer::new_slice::( + allocator, + BufferCreateInfo { + usage: BufferUsage::ACCELERATION_STRUCTURE_STORAGE + | BufferUsage::SHADER_DEVICE_ADDRESS, ..Default::default() }, + AllocationCreateInfo::default(), + as_build_sizes_info.acceleration_structure_size, ) - .unwrap() - }) - .collect::>() + .unwrap(), + ) + }; + let acceleration = unsafe { AccelerationStructure::new(device, as_create_info).unwrap() }; + + as_build_geometry_info.dst_acceleration_structure = Some(acceleration.clone()); + as_build_geometry_info.scratch_data = Some(scratch_buffer); + + let as_build_range_info = AccelerationStructureBuildRangeInfo { + primitive_count, + ..Default::default() + }; + + command_buffer + .build_acceleration_structure( + as_build_geometry_info, + Some(as_build_range_info).into_iter().collect(), + ) + .unwrap(); + + acceleration +} + +unsafe fn build_top_level_acceleration_structure( + acceleration_structure: Arc, + allocator: Arc, + device: Arc, + command_buffer: &mut RecordingCommandBuffer, +) -> Arc { + let primitive_count = 1; + let as_instance = AccelerationStructureInstance { + acceleration_structure_reference: acceleration_structure.device_address().into(), // TODO: Need to hold AS + ..Default::default() + }; + + let instance_buffer = Buffer::from_iter( + allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::SHADER_DEVICE_ADDRESS + | BufferUsage::ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + [as_instance], + ) + .unwrap(); + + let as_geometry_instances_data = AccelerationStructureGeometryInstancesData::new( + AccelerationStructureGeometryInstancesDataType::Values(Some(instance_buffer)), + ); + + let as_geometries = AccelerationStructureGeometries::Instances(as_geometry_instances_data); + + let mut as_build_geometry_info = AccelerationStructureBuildGeometryInfo { + mode: BuildAccelerationStructureMode::Build, + flags: BuildAccelerationStructureFlags::PREFER_FAST_TRACE, + ..AccelerationStructureBuildGeometryInfo::new(as_geometries) + }; + + let as_build_sizes_info = device + .acceleration_structure_build_sizes( + AccelerationStructureBuildType::Device, + &as_build_geometry_info, + &[primitive_count], + ) + .unwrap(); + + let scratch_buffer = Buffer::new_slice::( + allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::SHADER_DEVICE_ADDRESS | BufferUsage::STORAGE_BUFFER, + ..Default::default() + }, + AllocationCreateInfo::default(), + as_build_sizes_info.build_scratch_size, + ) + .unwrap(); + + let as_create_info = AccelerationStructureCreateInfo { + ty: AccelerationStructureType::TopLevel, + ..AccelerationStructureCreateInfo::new( + Buffer::new_slice::( + allocator, + BufferCreateInfo { + usage: BufferUsage::ACCELERATION_STRUCTURE_STORAGE + | BufferUsage::SHADER_DEVICE_ADDRESS, + ..Default::default() + }, + AllocationCreateInfo::default(), + as_build_sizes_info.acceleration_structure_size, + ) + .unwrap(), + ) + }; + let acceleration = unsafe { AccelerationStructure::new(device, as_create_info).unwrap() }; + + as_build_geometry_info.dst_acceleration_structure = Some(acceleration.clone()); + as_build_geometry_info.scratch_data = Some(scratch_buffer); + + let as_build_range_info = AccelerationStructureBuildRangeInfo { + primitive_count, + ..Default::default() + }; + + command_buffer + .build_acceleration_structure( + as_build_geometry_info, + Some(as_build_range_info).into_iter().collect(), + ) + .unwrap(); + + acceleration } diff --git a/examples/triangle-util/raytrace.rchit b/examples/triangle-util/raytrace.rchit new file mode 100644 index 0000000000..52c407b96a --- /dev/null +++ b/examples/triangle-util/raytrace.rchit @@ -0,0 +1,10 @@ +#version 460 +#extension GL_EXT_ray_tracing : require + +layout(location = 0) rayPayloadInEXT vec3 hitValue; +hitAttributeEXT vec2 attribs; + +void main() { + vec3 barycentrics = vec3(1.0 - attribs.x - attribs.y, attribs.x, attribs.y); + hitValue = barycentrics; +} diff --git a/examples/triangle-util/raytrace.rgen b/examples/triangle-util/raytrace.rgen new file mode 100644 index 0000000000..8a9416e201 --- /dev/null +++ b/examples/triangle-util/raytrace.rgen @@ -0,0 +1,43 @@ +#version 460 +#extension GL_EXT_ray_tracing : require + +struct Camera { + mat4 viewProj; // Camera view * projection + mat4 viewInverse; // Camera inverse view matrix + mat4 projInverse; // Camera inverse projection matrix +}; + +layout(location = 0) rayPayloadEXT vec3 hitValue; + +layout(set = 0, binding = 0) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = 1) uniform _Camera { Camera camera; }; +layout(set = 1, binding = 0, rgba32f) uniform image2D image; + +void main() { + const vec2 pixelCenter = vec2(gl_LaunchIDEXT.xy) + vec2(0.5); + const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy); + vec2 d = inUV * 2.0 - 1.0; + + vec4 origin = camera.viewInverse * vec4(0, 0, 0, 1); + vec4 target = camera.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = camera.viewInverse * vec4(normalize(target.xyz), 0); + + uint rayFlags = gl_RayFlagsOpaqueEXT; + float tMin = 0.001; + float tMax = 10000.0; + + traceRayEXT(topLevelAS, // acceleration structure + rayFlags, // rayFlags + 0xFF, // cullMask + 0, // sbtRecordOffset + 0, // sbtRecordStride + 0, // missIndex + origin.xyz, // ray origin + tMin, // ray min range + direction.xyz, // ray direction + tMax, // ray max range + 0 // payload (location = 0) + ); + + imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(hitValue, 1.0)); +} diff --git a/examples/triangle-util/raytrace.rmiss b/examples/triangle-util/raytrace.rmiss new file mode 100644 index 0000000000..1c584d5420 --- /dev/null +++ b/examples/triangle-util/raytrace.rmiss @@ -0,0 +1,6 @@ +#version 460 +#extension GL_EXT_ray_tracing : require + +layout(location = 0) rayPayloadInEXT vec3 hitValue; + +void main() { hitValue = vec3(0.0, 0.0, 0.2); } diff --git a/vulkano/src/acceleration_structure.rs b/vulkano/src/acceleration_structure.rs index d0d91cab8b..1f9d3f75ab 100644 --- a/vulkano/src/acceleration_structure.rs +++ b/vulkano/src/acceleration_structure.rs @@ -631,6 +631,8 @@ impl AccelerationStructureBuildGeometryInfo { triangles: ash::vk::AccelerationStructureGeometryTrianglesDataKHR { vertex_format: vertex_format.into(), vertex_data: ash::vk::DeviceOrHostAddressConstKHR { + // TODO: RayTracing: This panics if the usage + // does not contain `BufferUsage::SHADER_DEVICE_ADDRESS`. device_address: vertex_data.as_ref().map_or( 0, |vertex_data| { diff --git a/vulkano/src/buffer/usage.rs b/vulkano/src/buffer/usage.rs index 7b57595561..72f9eb0a19 100644 --- a/vulkano/src/buffer/usage.rs +++ b/vulkano/src/buffer/usage.rs @@ -97,13 +97,13 @@ vulkan_bitflags! { RequiresAllOf([DeviceExtension(khr_acceleration_structure)]), ]), - /* TODO: enable + // TODO: document SHADER_BINDING_TABLE = SHADER_BINDING_TABLE_KHR RequiresOneOf([ RequiresAllOf([DeviceExtension(khr_ray_tracing_pipeline)]), RequiresAllOf([DeviceExtension(nv_ray_tracing)]), - ]),*/ + ]), /* TODO: enable // TODO: document diff --git a/vulkano/src/command_buffer/auto/builder.rs b/vulkano/src/command_buffer/auto/builder.rs index 43d80e896a..70e43510f2 100644 --- a/vulkano/src/command_buffer/auto/builder.rs +++ b/vulkano/src/command_buffer/auto/builder.rs @@ -26,6 +26,7 @@ use crate::{ vertex_input::VertexInputState, viewport::{Scissor, Viewport}, }, + ray_tracing::RayTracingPipeline, ComputePipeline, DynamicState, GraphicsPipeline, PipelineBindPoint, PipelineLayout, }, query::{QueryControlFlags, QueryPool, QueryType}, @@ -1174,6 +1175,7 @@ pub(in crate::command_buffer) struct CommandBufferBuilderState { pub(in crate::command_buffer) index_buffer: Option, pub(in crate::command_buffer) pipeline_compute: Option>, pub(in crate::command_buffer) pipeline_graphics: Option>, + pub(in crate::command_buffer) pipeline_ray_tracing: Option>, pub(in crate::command_buffer) vertex_buffers: HashMap>, pub(in crate::command_buffer) push_constants: RangeSet, pub(in crate::command_buffer) push_constants_pipeline_layout: Option>, diff --git a/vulkano/src/command_buffer/commands/bind_push.rs b/vulkano/src/command_buffer/commands/bind_push.rs index 369e3198c0..ff13f94e7b 100644 --- a/vulkano/src/command_buffer/commands/bind_push.rs +++ b/vulkano/src/command_buffer/commands/bind_push.rs @@ -10,8 +10,8 @@ use crate::{ device::{DeviceOwned, QueueFlags}, memory::is_aligned, pipeline::{ - graphics::vertex_input::VertexBuffersCollection, ComputePipeline, GraphicsPipeline, - PipelineBindPoint, PipelineLayout, + graphics::vertex_input::VertexBuffersCollection, ray_tracing::RayTracingPipeline, + ComputePipeline, GraphicsPipeline, PipelineBindPoint, PipelineLayout, }, DeviceSize, Requires, RequiresAllOf, RequiresOneOf, ValidationError, Version, VulkanObject, }; @@ -225,6 +225,32 @@ impl RecordingCommandBuffer { self } + pub fn bind_pipeline_ray_tracing( + &mut self, + pipeline: Arc, + ) -> Result<&mut Self, Box> { + // TODO: RayTracing: Validation + unsafe { Ok(self.bind_pipeline_ray_tracing_unchecked(pipeline)) } + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn bind_pipeline_ray_tracing_unchecked( + &mut self, + pipeline: Arc, + ) -> &mut Self { + self.builder_state.pipeline_ray_tracing = Some(pipeline.clone()); + // TODO: RayTracing: Do we need to reset dynamic states? + self.add_command( + "bind_pipeline_ray_tracing", + Default::default(), + move |out: &mut RawRecordingCommandBuffer| { + out.bind_pipeline_ray_tracing_unchecked(&pipeline); + }, + ); + + self + } + /// Binds vertex buffers for future draw calls. pub fn bind_vertex_buffers( &mut self, @@ -507,6 +533,9 @@ impl RawRecordingCommandBuffer { })); } } + PipelineBindPoint::RayTracing => { + // TODO: RayTracing + } } if first_set + descriptor_sets.len() as u32 > pipeline_layout.set_layouts().len() as u32 { @@ -878,6 +907,26 @@ impl RawRecordingCommandBuffer { self } + pub unsafe fn bind_pipeline_ray_tracing(&mut self, pipeline: &RayTracingPipeline) -> &mut Self { + // TODO: RayTracing: Validation + self.bind_pipeline_ray_tracing_unchecked(pipeline) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn bind_pipeline_ray_tracing_unchecked( + &mut self, + pipeline: &RayTracingPipeline, + ) -> &mut Self { + let fns = self.device().fns(); + (fns.v1_0.cmd_bind_pipeline)( + self.handle(), + ash::vk::PipelineBindPoint::RAY_TRACING_KHR, + pipeline.handle(), + ); + + self + } + #[inline] pub unsafe fn bind_vertex_buffers( &mut self, @@ -1255,6 +1304,9 @@ impl RawRecordingCommandBuffer { })); } } + PipelineBindPoint::RayTracing => { + // TODO: RayTracing + } } // VUID-vkCmdPushDescriptorSetKHR-commonparent diff --git a/vulkano/src/command_buffer/commands/pipeline.rs b/vulkano/src/command_buffer/commands/pipeline.rs index 5ae954e0a8..55925bd45c 100644 --- a/vulkano/src/command_buffer/commands/pipeline.rs +++ b/vulkano/src/command_buffer/commands/pipeline.rs @@ -22,6 +22,7 @@ use crate::{ subpass::PipelineSubpassType, vertex_input::{RequiredVertexInputsVUIDs, VertexInputRate}, }, + ray_tracing::ShaderBindingTable, DynamicState, GraphicsPipeline, Pipeline, PipelineLayout, }, query::QueryType, @@ -1592,6 +1593,53 @@ impl RecordingCommandBuffer { self } + pub unsafe fn trace_rays( + &mut self, + shader_binding_table: ShaderBindingTable, + width: u32, + height: u32, + depth: u32, + ) -> Result<&mut Self, Box> { + // TODO: RayTrace: Validation + + Ok(self.trace_rays_unchecked(shader_binding_table, width, height, depth)) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn trace_rays_unchecked( + &mut self, + shader_binding_table: ShaderBindingTable, + width: u32, + height: u32, + depth: u32, + ) -> &mut Self { + // TODO: RayTracing: as_deref() + let pipeline = self.builder_state.pipeline_ray_tracing.as_deref().unwrap(); + + let mut used_resources = Vec::new(); + self.add_descriptor_sets_resources(&mut used_resources, pipeline); + self.add_shader_binding_table_buffer_resources( + &mut used_resources, + shader_binding_table.buffer(), + ); + + self.add_command("ray_trace", used_resources, move |out| { + out.trace_rays_unchecked(&shader_binding_table, width, height, depth); + }); + + self + } + + fn validate_trace_rays( + &self, + shader_binding_table: &ShaderBindingTable, + width: u32, + height: u32, + depth: u32, + ) -> Result<(), Box> { + todo!() + } + fn validate_pipeline_descriptor_sets( &self, vuid_type: VUIDType, @@ -3696,6 +3744,21 @@ impl RecordingCommandBuffer { }, )); } + + fn add_shader_binding_table_buffer_resources( + &self, + used_resources: &mut Vec<(ResourceUseRef2, Resource)>, + sbt_buffer: &Subbuffer<[u8]>, + ) { + used_resources.push(( + ResourceInCommand::ShaderBindingTableBuffer.into(), + Resource::Buffer { + buffer: sbt_buffer.clone(), + range: 0..sbt_buffer.size(), + memory_access: PipelineStageAccessFlags::RayTracingShader_ShaderBindingTableRead, + }, + )); + } } impl RawRecordingCommandBuffer { @@ -4929,6 +4992,47 @@ impl RawRecordingCommandBuffer { self } + + pub unsafe fn trace_rays( + &mut self, + shader_binding_table: &ShaderBindingTable, + width: u32, + height: u32, + depth: u32, + ) -> Result<&mut Self, Box> { + // self.validate_trace_ray()?; + // TODO: RayTracing: Validation + + Ok(self.trace_rays_unchecked(shader_binding_table, width, height, depth)) + } + + fn validate_trace_rays(&self) -> Result<(), Box> { + todo!() + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn trace_rays_unchecked( + &mut self, + shader_binding_table: &ShaderBindingTable, + width: u32, + height: u32, + depth: u32, + ) -> &mut Self { + let fns = self.device().fns(); + + (fns.khr_ray_tracing_pipeline.cmd_trace_rays_khr)( + self.handle(), + shader_binding_table.raygen(), + shader_binding_table.miss(), + shader_binding_table.hit(), + shader_binding_table.callable(), + width, + height, + depth, + ); + + self + } } #[derive(Clone, Copy)] diff --git a/vulkano/src/command_buffer/commands/sync.rs b/vulkano/src/command_buffer/commands/sync.rs index 9e547d7645..b97813ba6a 100644 --- a/vulkano/src/command_buffer/commands/sync.rs +++ b/vulkano/src/command_buffer/commands/sync.rs @@ -205,7 +205,9 @@ impl RawRecordingCommandBuffer { _ne: _, } = dependency_info; - if self.device().enabled_features().synchronization2 { + if self.device().enabled_features().synchronization2 + || self.device().api_version() >= Version::V1_3 + { let memory_barriers_vk: SmallVec<[_; 2]> = memory_barriers .iter() .map(|barrier| { diff --git a/vulkano/src/command_buffer/mod.rs b/vulkano/src/command_buffer/mod.rs index 361a79598d..757ffe9831 100644 --- a/vulkano/src/command_buffer/mod.rs +++ b/vulkano/src/command_buffer/mod.rs @@ -1265,6 +1265,7 @@ pub enum ResourceInCommand { SecondaryCommandBuffer { index: u32 }, Source, VertexBuffer { binding: u32 }, + ShaderBindingTableBuffer, } #[doc(hidden)] diff --git a/vulkano/src/device/mod.rs b/vulkano/src/device/mod.rs index 1b853da08e..a099b81287 100644 --- a/vulkano/src/device/mod.rs +++ b/vulkano/src/device/mod.rs @@ -116,6 +116,7 @@ use crate::{ instance::{Instance, InstanceOwned, InstanceOwnedDebugWrapper}, macros::{impl_id_counter, vulkan_bitflags}, memory::{allocator::DeviceLayout, ExternalMemoryHandleType, MemoryRequirements}, + pipeline::ray_tracing::RayTracingPipeline, sync::Sharing, Requires, RequiresAllOf, RequiresOneOf, Validated, ValidationError, Version, VulkanError, VulkanObject, @@ -1608,6 +1609,63 @@ impl Device { Ok(()) } + + pub fn get_ray_tracing_shader_group_handles( + &self, + ray_tracing_pipeline: &RayTracingPipeline, + first_group: u32, + group_count: u32, + ) -> Result> { + if !self.enabled_features().ray_tracing_pipeline + || self + .physical_device() + .properties() + .shader_group_handle_size + .is_none() + { + Err(Box::new(ValidationError { + problem: "device property `shader_group_handle_size` is empty".into(), + requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::DeviceFeature( + "ray_tracing_pipeline", + )])]), + ..Default::default() + }))?; + }; + + if (first_group + group_count) as usize > ray_tracing_pipeline.groups().len() { + Err(Box::new(ValidationError { + problem: "the sum of `first_group` and `group_count` must be less than or equal\ + to the number of shader groups in pipeline" + .into(), + vuids: &["VUID-vkGetRayTracingShaderGroupHandlesKHR-firstGroup-02419"], + ..Default::default() + }))? + } + // TODO: VUID-vkGetRayTracingShaderGroupHandlesKHR-pipeline-07828 + + let handle_size = self + .physical_device() + .properties() + .shader_group_handle_size + .unwrap(); + + let mut data = vec![0u8; (handle_size * group_count) as usize]; + let fns = self.fns(); + unsafe { + (fns.khr_ray_tracing_pipeline + .get_ray_tracing_shader_group_handles_khr)( + self.handle, + ray_tracing_pipeline.handle(), + first_group, + group_count, + data.len(), + data.as_mut_ptr().cast(), + ) + .result() + .map_err(VulkanError::from)?; + } + Ok(ShaderGroupHandlesData { data, handle_size }) + } } impl Debug for Device { @@ -2297,6 +2355,50 @@ pub struct MemoryFdProperties { pub memory_type_bits: u32, } +#[derive(Clone, Debug)] +pub struct ShaderGroupHandlesData { + data: Vec, + handle_size: u32, +} + +impl ShaderGroupHandlesData { + pub fn handle_size(&self) -> u32 { + self.handle_size + } +} + +pub struct ShaderGroupHandlesDataIter<'a> { + data: &'a [u8], + handle_size: usize, + index: usize, +} + +impl<'a> Iterator for ShaderGroupHandlesDataIter<'a> { + type Item = &'a [u8]; + + fn next(&mut self) -> Option { + if self.index >= self.data.len() { + None + } else { + let end = self.index + self.handle_size; + let slice = &self.data[self.index..end]; + self.index = end; + Some(slice) + } + } +} +impl<'a> ExactSizeIterator for ShaderGroupHandlesDataIter<'a> {} + +impl ShaderGroupHandlesData { + pub fn iter(&self) -> ShaderGroupHandlesDataIter<'_> { + ShaderGroupHandlesDataIter { + data: &self.data, + handle_size: self.handle_size as usize, + index: 0, + } + } +} + #[cfg(test)] mod tests { use crate::device::{ diff --git a/vulkano/src/pipeline/compute.rs b/vulkano/src/pipeline/compute.rs index dc54319ee1..bff4fa6982 100644 --- a/vulkano/src/pipeline/compute.rs +++ b/vulkano/src/pipeline/compute.rs @@ -19,11 +19,11 @@ use crate::{ instance::InstanceOwnedDebugWrapper, macros::impl_id_counter, pipeline::{cache::PipelineCache, layout::PipelineLayout, Pipeline, PipelineBindPoint}, - shader::{spirv::ExecutionModel, DescriptorBindingRequirements, ShaderStage}, + shader::{spirv::ExecutionModel, DescriptorBindingRequirements}, Validated, ValidationError, VulkanError, VulkanObject, }; use ahash::HashMap; -use std::{ffi::CString, fmt::Debug, mem::MaybeUninit, num::NonZeroU64, ptr, sync::Arc}; +use std::{fmt::Debug, mem::MaybeUninit, num::NonZeroU64, ptr, sync::Arc}; /// A pipeline object that describes to the Vulkan implementation how it should perform compute /// operations. @@ -54,7 +54,7 @@ impl ComputePipeline { cache: Option>, create_info: ComputePipelineCreateInfo, ) -> Result, Validated> { - Self::validate_new(&device, cache.as_ref().map(AsRef::as_ref), &create_info)?; + Self::validate_new(&device, cache.as_deref(), &create_info)?; unsafe { Ok(Self::new_unchecked(device, cache, create_info)?) } } @@ -88,76 +88,8 @@ impl ComputePipeline { _ne: _, } = &create_info; - let stage_vk; - let name_vk; - let specialization_info_vk; - let specialization_map_entries_vk: Vec<_>; - let mut specialization_data_vk: Vec; - let required_subgroup_size_create_info; - - { - let &PipelineShaderStageCreateInfo { - flags, - ref entry_point, - ref required_subgroup_size, - _ne: _, - } = stage; - - let entry_point_info = entry_point.info(); - name_vk = CString::new(entry_point_info.name.as_str()).unwrap(); - - specialization_data_vk = Vec::new(); - specialization_map_entries_vk = entry_point - .module() - .specialization_info() - .iter() - .map(|(&constant_id, value)| { - let data = value.as_bytes(); - let offset = specialization_data_vk.len() as u32; - let size = data.len(); - specialization_data_vk.extend(data); - - ash::vk::SpecializationMapEntry { - constant_id, - offset, - size, - } - }) - .collect(); - - specialization_info_vk = ash::vk::SpecializationInfo { - map_entry_count: specialization_map_entries_vk.len() as u32, - p_map_entries: specialization_map_entries_vk.as_ptr(), - data_size: specialization_data_vk.len(), - p_data: specialization_data_vk.as_ptr().cast(), - ..Default::default() - }; - required_subgroup_size_create_info = - required_subgroup_size.map(|required_subgroup_size| { - ash::vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo { - required_subgroup_size, - ..Default::default() - } - }); - stage_vk = ash::vk::PipelineShaderStageCreateInfo { - p_next: required_subgroup_size_create_info.as_ref().map_or( - ptr::null(), - |required_subgroup_size_create_info| { - <*const _>::cast(required_subgroup_size_create_info) - }, - ), - flags: flags.into(), - stage: ShaderStage::from(entry_point_info.execution_model).into(), - module: entry_point.module().handle(), - p_name: name_vk.as_ptr(), - p_specialization_info: if specialization_info_vk.data_size == 0 { - ptr::null() - } else { - &specialization_info_vk - }, - ..Default::default() - }; - } + let owned_stage_vk = stage.to_owned_vulkan(); + let stage_vk = owned_stage_vk.to_vulkan(); let create_infos_vk = ash::vk::ComputePipelineCreateInfo { flags: flags.into(), diff --git a/vulkano/src/pipeline/graphics/mod.rs b/vulkano/src/pipeline/graphics/mod.rs index 23ee8abb9e..b6010c2d9c 100644 --- a/vulkano/src/pipeline/graphics/mod.rs +++ b/vulkano/src/pipeline/graphics/mod.rs @@ -185,7 +185,7 @@ impl GraphicsPipeline { cache: Option>, create_info: GraphicsPipelineCreateInfo, ) -> Result, Validated> { - Self::validate_new(&device, cache.as_ref().map(AsRef::as_ref), &create_info)?; + Self::validate_new(&device, cache.as_deref(), &create_info)?; unsafe { Ok(Self::new_unchecked(device, cache, create_info)?) } } @@ -239,99 +239,9 @@ impl GraphicsPipeline { Option>, } - let (mut stages_vk, mut per_stage_vk): (SmallVec<[_; 5]>, SmallVec<[_; 5]>) = stages - .iter() - .map(|stage| { - let &PipelineShaderStageCreateInfo { - flags, - ref entry_point, - ref required_subgroup_size, - _ne: _, - } = stage; - - let entry_point_info = entry_point.info(); - let stage = ShaderStage::from(entry_point_info.execution_model); - - let mut specialization_data_vk: Vec = Vec::new(); - let specialization_map_entries_vk: Vec<_> = entry_point - .module() - .specialization_info() - .iter() - .map(|(&constant_id, value)| { - let data = value.as_bytes(); - let offset = specialization_data_vk.len() as u32; - let size = data.len(); - specialization_data_vk.extend(data); - - ash::vk::SpecializationMapEntry { - constant_id, - offset, - size, - } - }) - .collect(); - let required_subgroup_size_create_info = - required_subgroup_size.map(|required_subgroup_size| { - ash::vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo { - required_subgroup_size, - ..Default::default() - } - }); - ( - ash::vk::PipelineShaderStageCreateInfo { - flags: flags.into(), - stage: stage.into(), - module: entry_point.module().handle(), - p_name: ptr::null(), - p_specialization_info: ptr::null(), - ..Default::default() - }, - PerPipelineShaderStageCreateInfo { - name_vk: CString::new(entry_point_info.name.as_str()).unwrap(), // TODO Borrow CStr for local data? - specialization_info_vk: ash::vk::SpecializationInfo { - map_entry_count: specialization_map_entries_vk.len() as u32, - p_map_entries: ptr::null(), - data_size: specialization_data_vk.len(), - p_data: ptr::null(), - ..Default::default() - }, - specialization_map_entries_vk, - specialization_data_vk, - required_subgroup_size_create_info, - }, - ) - }) - .unzip(); - - for ( - stage_vk, - PerPipelineShaderStageCreateInfo { - name_vk, - specialization_info_vk, - specialization_map_entries_vk, - specialization_data_vk, - required_subgroup_size_create_info, - }, - ) in stages_vk.iter_mut().zip(per_stage_vk.iter_mut()) - { - *stage_vk = ash::vk::PipelineShaderStageCreateInfo { - p_next: required_subgroup_size_create_info.as_ref().map_or( - ptr::null(), - |required_subgroup_size_create_info| { - <*const _>::cast(required_subgroup_size_create_info) - }, - ), - p_name: name_vk.as_ptr(), - p_specialization_info: specialization_info_vk, - ..*stage_vk - }; - - *specialization_info_vk = ash::vk::SpecializationInfo { - p_map_entries: specialization_map_entries_vk.as_ptr(), - p_data: specialization_data_vk.as_ptr().cast(), - ..*specialization_info_vk - }; - } + let owned_stages_vk: SmallVec<[_; 5]> = + stages.iter().map(|s| s.to_owned_vulkan()).collect(); + let stages_vk: SmallVec<[_; 5]> = owned_stages_vk.iter().map(|s| s.to_vulkan()).collect(); let mut vertex_input_state_vk = None; let mut vertex_binding_descriptions_vk: SmallVec<[_; 8]> = SmallVec::new(); diff --git a/vulkano/src/pipeline/mod.rs b/vulkano/src/pipeline/mod.rs index 1e094b6d24..e51dd762b1 100644 --- a/vulkano/src/pipeline/mod.rs +++ b/vulkano/src/pipeline/mod.rs @@ -23,6 +23,7 @@ pub mod cache; pub mod compute; pub mod graphics; pub mod layout; +pub mod ray_tracing; pub(crate) mod shader; /// A trait for operations shared between pipeline types. @@ -60,13 +61,13 @@ vulkan_enum! { // TODO: document Graphics = GRAPHICS, - /* TODO: enable + // TODO: document RayTracing = RAY_TRACING_KHR RequiresOneOf([ RequiresAllOf([DeviceExtension(khr_ray_tracing_pipeline)]), RequiresAllOf([DeviceExtension(nv_ray_tracing)]), - ]),*/ + ]), /* TODO: enable // TODO: document diff --git a/vulkano/src/pipeline/ray_tracing/mod.rs b/vulkano/src/pipeline/ray_tracing/mod.rs new file mode 100644 index 0000000000..def8a8e68d --- /dev/null +++ b/vulkano/src/pipeline/ray_tracing/mod.rs @@ -0,0 +1,477 @@ +use std::{collections::hash_map::Entry, mem::MaybeUninit, num::NonZeroU64, ptr, sync::Arc}; + +use ahash::{HashMap, HashSet}; +use ash::vk::StridedDeviceAddressRegionKHR; +use smallvec::SmallVec; + +use crate::{ + buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer}, + device::{Device, DeviceOwned, DeviceOwnedDebugWrapper, DeviceOwnedVulkanObject}, + instance::InstanceOwnedDebugWrapper, + macros::impl_id_counter, + memory::{ + allocator::{align_up, AllocationCreateInfo, MemoryAllocator, MemoryTypeFilter}, + DeviceAlignment, + }, + shader::DescriptorBindingRequirements, + Validated, ValidationError, VulkanError, VulkanObject, +}; + +use super::{ + cache::PipelineCache, DynamicState, Pipeline, PipelineBindPoint, PipelineCreateFlags, + PipelineLayout, PipelineShaderStageCreateInfo, +}; + +#[derive(Debug)] +pub struct RayTracingPipeline { + handle: ash::vk::Pipeline, + device: InstanceOwnedDebugWrapper>, + id: NonZeroU64, + + flags: PipelineCreateFlags, + layout: DeviceOwnedDebugWrapper>, + + descriptor_binding_requirements: HashMap<(u32, u32), DescriptorBindingRequirements>, + num_used_descriptor_sets: u32, + + groups: SmallVec<[RayTracingShaderGroupCreateInfo; 5]>, + stages: SmallVec<[PipelineShaderStageCreateInfo; 5]>, +} + +impl RayTracingPipeline { + /// Creates a new `RayTracingPipeline`. + #[inline] + pub fn new( + device: Arc, + cache: Option>, + create_info: RayTracingPipelineCreateInfo, + ) -> Result, Validated> { + // Self::validate_new(&device, cache.as_deref(), &create_info)?; + + unsafe { Ok(Self::new_unchecked(device, cache, create_info)?) } + } + + fn validate_new( + device: &Device, + cache: Option<&PipelineCache>, + create_info: &RayTracingPipelineCreateInfo, + ) -> Result<(), Box> { + todo!() + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn new_unchecked( + device: Arc, + cache: Option>, + create_info: RayTracingPipelineCreateInfo, + ) -> Result, VulkanError> { + let RayTracingPipelineCreateInfo { + flags, + stages, + groups, + max_pipeline_ray_recursion_depth, + dynamic_state, + layout, + base_pipeline, + .. + } = &create_info; + + let owned_stages_vk: SmallVec<[_; 5]> = + stages.iter().map(|s| s.to_owned_vulkan()).collect(); + let stages_vk: SmallVec<[_; 5]> = owned_stages_vk.iter().map(|s| s.to_vulkan()).collect(); + + let groups_vk: SmallVec<[_; 5]> = groups + .iter() + .map(|g| ash::vk::RayTracingShaderGroupCreateInfoKHR { + ty: g.group_type, + general_shader: g.general_shader.unwrap_or(ash::vk::SHADER_UNUSED_KHR), + closest_hit_shader: g.closest_hit_shader.unwrap_or(ash::vk::SHADER_UNUSED_KHR), + any_hit_shader: g.any_hit_shader.unwrap_or(ash::vk::SHADER_UNUSED_KHR), + intersection_shader: g.intersection_shader.unwrap_or(ash::vk::SHADER_UNUSED_KHR), + // TODO: RayTracing: p_shader_group_capture_replay_handle + ..Default::default() + }) + .collect(); + + let dynamic_state_list_vk: SmallVec<[_; 4]> = + dynamic_state.iter().copied().map(Into::into).collect(); + let dynamic_state_vk = + (!dynamic_state_list_vk.is_empty()).then(|| ash::vk::PipelineDynamicStateCreateInfo { + flags: ash::vk::PipelineDynamicStateCreateFlags::empty(), + dynamic_state_count: dynamic_state_list_vk.len() as u32, + p_dynamic_states: dynamic_state_list_vk.as_ptr(), + ..Default::default() + }); + + let create_infos_vk = ash::vk::RayTracingPipelineCreateInfoKHR { + flags: (*flags).into(), + stage_count: stages_vk.len() as u32, + p_stages: stages_vk.as_ptr(), + group_count: groups_vk.len() as u32, + p_groups: groups_vk.as_ptr(), + max_pipeline_ray_recursion_depth: *max_pipeline_ray_recursion_depth, + layout: layout.handle(), + base_pipeline_handle: base_pipeline + .as_deref() + .map_or(ash::vk::Pipeline::null(), |p| p.handle()), + base_pipeline_index: 0, + p_dynamic_state: dynamic_state_vk.as_ref().map_or(ptr::null(), |d| d), + // TODO: RayTracing: library + ..Default::default() + }; + + let handle = { + let fns = device.fns(); + let mut output = MaybeUninit::uninit(); + + (fns.khr_ray_tracing_pipeline + .create_ray_tracing_pipelines_khr)( + device.handle(), + ash::vk::DeferredOperationKHR::null(), // TODO: RayTracing: deferred_operation + cache.map_or(ash::vk::PipelineCache::null(), |c| c.handle()), + 1, + &create_infos_vk, + ptr::null(), + output.as_mut_ptr(), + ) + .result() + .map_err(VulkanError::from)?; + output.assume_init() + }; + + Ok(Self::from_handle(device, handle, create_info)) + } + + pub unsafe fn from_handle( + device: Arc, + handle: ash::vk::Pipeline, + create_info: RayTracingPipelineCreateInfo, + ) -> Arc { + let RayTracingPipelineCreateInfo { + flags, + stages, + groups, + layout, + .. + } = create_info; + + let mut descriptor_binding_requirements: HashMap< + (u32, u32), + DescriptorBindingRequirements, + > = HashMap::default(); + for stage in &stages { + for (&loc, reqs) in stage + .entry_point + .info() + .descriptor_binding_requirements + .iter() + { + match descriptor_binding_requirements.entry(loc) { + Entry::Occupied(entry) => { + entry.into_mut().merge(reqs).expect("Could not produce an intersection of the shader descriptor requirements"); + } + Entry::Vacant(entry) => { + entry.insert(reqs.clone()); + } + } + } + } + let num_used_descriptor_sets = descriptor_binding_requirements + .keys() + .map(|loc| loc.0) + .max() + .map(|x| x + 1) + .unwrap_or(0); + Arc::new(Self { + handle, + device: InstanceOwnedDebugWrapper(device), + id: Self::next_id(), + + flags, + layout: DeviceOwnedDebugWrapper(layout), + + descriptor_binding_requirements, + num_used_descriptor_sets, + + groups, + stages, + }) + } + + pub fn groups(&self) -> &[RayTracingShaderGroupCreateInfo] { + &self.groups + } + + pub fn stages(&self) -> &[PipelineShaderStageCreateInfo] { + &self.stages + } + + pub fn device(&self) -> &Arc { + &self.device + } +} + +impl Pipeline for RayTracingPipeline { + #[inline] + fn bind_point(&self) -> PipelineBindPoint { + PipelineBindPoint::RayTracing + } + + #[inline] + fn layout(&self) -> &Arc { + &self.layout + } + + #[inline] + fn num_used_descriptor_sets(&self) -> u32 { + self.num_used_descriptor_sets + } + + #[inline] + fn descriptor_binding_requirements( + &self, + ) -> &HashMap<(u32, u32), DescriptorBindingRequirements> { + &self.descriptor_binding_requirements + } +} + +impl_id_counter!(RayTracingPipeline); + +unsafe impl VulkanObject for RayTracingPipeline { + type Handle = ash::vk::Pipeline; + + #[inline] + fn handle(&self) -> Self::Handle { + self.handle + } +} + +unsafe impl DeviceOwned for RayTracingPipeline { + #[inline] + fn device(&self) -> &Arc { + self.device() + } +} + +impl Drop for RayTracingPipeline { + #[inline] + fn drop(&mut self) { + unsafe { + let fns = self.device.fns(); + (fns.v1_0.destroy_pipeline)(self.device.handle(), self.handle, ptr::null()); + } + } +} + +/// Parameters to create a new `RayTracingPipeline`. +#[derive(Clone, Debug)] +pub struct RayTracingPipelineCreateInfo { + /// Additional properties of the pipeline. + /// + /// The default value is empty. + pub flags: PipelineCreateFlags, + + /// The compute shader stage to use. + /// + /// There is no default value. + pub stages: SmallVec<[PipelineShaderStageCreateInfo; 5]>, + + pub groups: SmallVec<[RayTracingShaderGroupCreateInfo; 5]>, + + pub max_pipeline_ray_recursion_depth: u32, + + pub dynamic_state: HashSet, + + /// The pipeline layout to use. + /// + /// There is no default value. + pub layout: Arc, + + /// The pipeline to use as a base when creating this pipeline. + /// + /// If this is `Some`, then `flags` must contain [`PipelineCreateFlags::DERIVATIVE`], + /// and the `flags` of the provided pipeline must contain + /// [`PipelineCreateFlags::ALLOW_DERIVATIVES`]. + /// + /// The default value is `None`. + pub base_pipeline: Option>, + + pub _ne: crate::NonExhaustive, +} + +impl RayTracingPipelineCreateInfo { + pub fn layout(layout: Arc) -> Self { + Self { + flags: PipelineCreateFlags::empty(), + stages: SmallVec::new(), + groups: SmallVec::new(), + max_pipeline_ray_recursion_depth: 0, + dynamic_state: Default::default(), + + layout, + + base_pipeline: None, + _ne: crate::NonExhaustive(()), + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct RayTracingShaderGroupCreateInfo { + pub group_type: ash::vk::RayTracingShaderGroupTypeKHR, // TODO: Custom type + pub general_shader: Option, + pub closest_hit_shader: Option, + pub any_hit_shader: Option, + pub intersection_shader: Option, +} + +#[derive(Debug, Clone)] +pub struct ShaderBindingTable { + raygen: StridedDeviceAddressRegionKHR, + miss: StridedDeviceAddressRegionKHR, + hit: StridedDeviceAddressRegionKHR, + callable: StridedDeviceAddressRegionKHR, + buffer: Subbuffer<[u8]>, +} + +impl ShaderBindingTable { + pub fn raygen(&self) -> &StridedDeviceAddressRegionKHR { + &self.raygen + } + + pub fn miss(&self) -> &StridedDeviceAddressRegionKHR { + &self.miss + } + + pub fn hit(&self) -> &StridedDeviceAddressRegionKHR { + &self.hit + } + + pub fn callable(&self) -> &StridedDeviceAddressRegionKHR { + &self.callable + } + + pub(crate) fn buffer(&self) -> &Subbuffer<[u8]> { + &self.buffer + } + + pub fn new( + allocator: Arc, + ray_tracing_pipeline: &RayTracingPipeline, + miss_shader_count: u64, + hit_shader_count: u64, + callable_shader_count: u64, + ) -> Result> { + let handle_data = ray_tracing_pipeline + .device() + .get_ray_tracing_shader_group_handles( + &ray_tracing_pipeline, + 0, + ray_tracing_pipeline.groups().len() as u32, + )?; + + let properties = ray_tracing_pipeline.device().physical_device().properties(); + let handle_size_aligned = align_up( + handle_data.handle_size() as u64, + DeviceAlignment::new(properties.shader_group_handle_alignment.unwrap() as u64) + .expect("unexpected shader_group_handle_alignment"), + ); + + let shader_group_base_alignment = + DeviceAlignment::new(properties.shader_group_base_alignment.unwrap() as u64) + .expect("unexpected shader_group_base_alignment"); + + let raygen_stride = align_up(handle_size_aligned, shader_group_base_alignment); + + let mut raygen = StridedDeviceAddressRegionKHR { + stride: raygen_stride, + size: raygen_stride, + device_address: 0, + }; + let mut miss = StridedDeviceAddressRegionKHR { + stride: handle_size_aligned, + size: align_up( + handle_size_aligned * miss_shader_count, + shader_group_base_alignment, + ), + device_address: 0, + }; + let mut hit = StridedDeviceAddressRegionKHR { + stride: handle_size_aligned, + size: align_up( + handle_size_aligned * hit_shader_count, + shader_group_base_alignment, + ), + device_address: 0, + }; + let mut callable = StridedDeviceAddressRegionKHR { + stride: handle_size_aligned, + size: align_up( + handle_size_aligned * callable_shader_count, + shader_group_base_alignment, + ), + device_address: 0, + }; + + let sbt_buffer = Buffer::new_slice::( + allocator, + BufferCreateInfo { + usage: BufferUsage::TRANSFER_SRC + | BufferUsage::SHADER_DEVICE_ADDRESS + | BufferUsage::SHADER_BINDING_TABLE, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::HOST_SEQUENTIAL_WRITE + | MemoryTypeFilter::PREFER_DEVICE, + ..Default::default() + }, + raygen.size + miss.size + hit.size + callable.size, + ) + .expect("todo: raytracing: better error type"); + sbt_buffer + .buffer() + .set_debug_utils_object_name("Shader Binding Table Buffer".into()) + .unwrap(); + + raygen.device_address = sbt_buffer.buffer().device_address().unwrap().get(); + miss.device_address = raygen.device_address + raygen.size; + hit.device_address = miss.device_address + miss.size; + callable.device_address = hit.device_address + hit.size; + + { + let mut sbt_buffer_write = sbt_buffer.write().unwrap(); + + let mut handle_iter = handle_data.iter(); + + let handle_size = handle_data.handle_size() as usize; + sbt_buffer_write[..handle_size].copy_from_slice(handle_iter.next().unwrap()); + let mut offset = raygen.size as usize; + for _ in 0..miss_shader_count { + sbt_buffer_write[offset..offset + handle_size] + .copy_from_slice(handle_iter.next().unwrap()); + offset += miss.stride as usize; + } + offset = (raygen.size + miss.size) as usize; + for _ in 0..hit_shader_count { + sbt_buffer_write[offset..offset + handle_size] + .copy_from_slice(handle_iter.next().unwrap()); + offset += hit.stride as usize; + } + offset = (raygen.size + miss.size + hit.size) as usize; + for _ in 0..callable_shader_count { + sbt_buffer_write[offset..offset + handle_size] + .copy_from_slice(handle_iter.next().unwrap()); + offset += callable.stride as usize; + } + } + + Ok(Self { + raygen, + miss, + hit, + callable, + buffer: sbt_buffer, + }) + } +} diff --git a/vulkano/src/pipeline/shader/mod.rs b/vulkano/src/pipeline/shader/mod.rs index 5a1423aef3..c2cc50a88e 100644 --- a/vulkano/src/pipeline/shader/mod.rs +++ b/vulkano/src/pipeline/shader/mod.rs @@ -1,11 +1,13 @@ +use std::{ffi::CString, ptr, sync::Arc}; + use crate::{ device::Device, macros::vulkan_bitflags, shader::{ spirv::{BuiltIn, Decoration, ExecutionMode, Id, Instruction}, - EntryPoint, ShaderStage, + EntryPoint, ShaderStage, SpecializedShaderModule, }, - Requires, RequiresAllOf, RequiresOneOf, ValidationError, + Requires, RequiresAllOf, RequiresOneOf, ValidationError, VulkanObject, }; pub(crate) mod inout_interface; @@ -494,6 +496,91 @@ impl PipelineShaderStageCreateInfo { Ok(()) } + + pub(crate) fn to_owned_vulkan(&self) -> OwnedVkPipelineShaderStageCreateInfo { + let &PipelineShaderStageCreateInfo { + flags, + ref entry_point, + ref required_subgroup_size, + _ne: _, + } = self; + + let entry_point_info = entry_point.info(); + let name = CString::new(entry_point_info.name.as_str()).unwrap(); + + let mut specialization_data = Vec::new(); + let specialization_map_entries: Vec = entry_point + .module() + .specialization_info() + .iter() + .map(|(&constant_id, value)| { + let data = value.as_bytes(); + let offset = specialization_data.len() as u32; + specialization_data.extend(data); + + ash::vk::SpecializationMapEntry { + constant_id, + offset, + size: data.len(), + } + }) + .collect(); + + OwnedVkPipelineShaderStageCreateInfo { + name, + specialization_data, + specialization_map_entries, + required_subgroup_size: *required_subgroup_size, + flags, + stage: ShaderStage::from(entry_point_info.execution_model), + specialized_shader_module: entry_point.module().clone(), + } + } +} + +pub(crate) struct OwnedVkPipelineShaderStageCreateInfo { + name: CString, + specialization_data: Vec, + specialization_map_entries: Vec, + required_subgroup_size: Option, + flags: PipelineShaderStageCreateFlags, + stage: ShaderStage, + specialized_shader_module: Arc, +} + +impl OwnedVkPipelineShaderStageCreateInfo { + pub(crate) fn to_vulkan(&self) -> ash::vk::PipelineShaderStageCreateInfo<'_> { + let specialization_info = ash::vk::SpecializationInfo { + map_entry_count: self.specialization_map_entries.len() as u32, + p_map_entries: self.specialization_map_entries.as_ptr() as *const _, + data_size: self.specialization_data.len(), + p_data: self.specialization_data.as_ptr().cast(), + ..Default::default() + }; + + let required_subgroup_size_info = self.required_subgroup_size.map(|size| { + ash::vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo { + required_subgroup_size: size, + ..Default::default() + } + }); + + ash::vk::PipelineShaderStageCreateInfo { + p_next: required_subgroup_size_info + .as_ref() + .map_or(ptr::null(), |info| info as *const _ as *const _), + flags: self.flags.into(), + stage: self.stage.into(), + module: self.specialized_shader_module.handle(), + p_name: self.name.as_ptr(), + p_specialization_info: if specialization_info.data_size == 0 { + ptr::null() + } else { + &specialization_info + }, + ..Default::default() + } + } } vulkan_bitflags! { From e74ea4de0c575b974ca9b5e4940fe4ac8b3ae67b Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Thu, 26 Sep 2024 00:02:31 +0800 Subject: [PATCH 02/26] fix invalid pointers --- vulkano/src/pipeline/graphics/mod.rs | 12 +----- vulkano/src/pipeline/shader/mod.rs | 62 +++++++++++++++------------- 2 files changed, 34 insertions(+), 40 deletions(-) diff --git a/vulkano/src/pipeline/graphics/mod.rs b/vulkano/src/pipeline/graphics/mod.rs index b6010c2d9c..64c9d70376 100644 --- a/vulkano/src/pipeline/graphics/mod.rs +++ b/vulkano/src/pipeline/graphics/mod.rs @@ -123,8 +123,7 @@ use crate::{ use ahash::{HashMap, HashSet}; use smallvec::SmallVec; use std::{ - collections::hash_map::Entry, ffi::CString, fmt::Debug, mem::MaybeUninit, num::NonZeroU64, ptr, - sync::Arc, + collections::hash_map::Entry, fmt::Debug, mem::MaybeUninit, num::NonZeroU64, ptr, sync::Arc, }; pub mod color_blend; @@ -230,15 +229,6 @@ impl GraphicsPipeline { _ne: _, } = &create_info; - struct PerPipelineShaderStageCreateInfo { - name_vk: CString, - specialization_info_vk: ash::vk::SpecializationInfo<'static>, - specialization_map_entries_vk: Vec, - specialization_data_vk: Vec, - required_subgroup_size_create_info: - Option>, - } - let owned_stages_vk: SmallVec<[_; 5]> = stages.iter().map(|s| s.to_owned_vulkan()).collect(); let stages_vk: SmallVec<[_; 5]> = owned_stages_vk.iter().map(|s| s.to_vulkan()).collect(); diff --git a/vulkano/src/pipeline/shader/mod.rs b/vulkano/src/pipeline/shader/mod.rs index c2cc50a88e..f709f9a499 100644 --- a/vulkano/src/pipeline/shader/mod.rs +++ b/vulkano/src/pipeline/shader/mod.rs @@ -508,7 +508,7 @@ impl PipelineShaderStageCreateInfo { let entry_point_info = entry_point.info(); let name = CString::new(entry_point_info.name.as_str()).unwrap(); - let mut specialization_data = Vec::new(); + let mut specialization_data: Vec = Vec::new(); let specialization_map_entries: Vec = entry_point .module() .specialization_info() @@ -526,58 +526,62 @@ impl PipelineShaderStageCreateInfo { }) .collect(); + let specialization_info = ash::vk::SpecializationInfo { + map_entry_count: specialization_map_entries.len() as u32, + p_map_entries: specialization_map_entries.as_ptr() as *const _, + data_size: specialization_data.len(), + p_data: specialization_data.as_ptr().cast(), + ..Default::default() + }; + + let required_subgroup_size_info = required_subgroup_size.map(|size| { + ash::vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo { + required_subgroup_size: size, + ..Default::default() + } + }); + OwnedVkPipelineShaderStageCreateInfo { name, - specialization_data, - specialization_map_entries, - required_subgroup_size: *required_subgroup_size, + _specialization_data: specialization_data, + _specialization_map_entries: specialization_map_entries, flags, stage: ShaderStage::from(entry_point_info.execution_model), specialized_shader_module: entry_point.module().clone(), + specialization_info: Some(specialization_info), + required_subgroup_size_info, } } } pub(crate) struct OwnedVkPipelineShaderStageCreateInfo { name: CString, - specialization_data: Vec, - specialization_map_entries: Vec, - required_subgroup_size: Option, + _specialization_data: Vec, + _specialization_map_entries: Vec, flags: PipelineShaderStageCreateFlags, stage: ShaderStage, specialized_shader_module: Arc, + // Includes pointers to _specialization_data and _specialization_map_entries. + specialization_info: Option>, + required_subgroup_size_info: + Option>, } impl OwnedVkPipelineShaderStageCreateInfo { - pub(crate) fn to_vulkan(&self) -> ash::vk::PipelineShaderStageCreateInfo<'_> { - let specialization_info = ash::vk::SpecializationInfo { - map_entry_count: self.specialization_map_entries.len() as u32, - p_map_entries: self.specialization_map_entries.as_ptr() as *const _, - data_size: self.specialization_data.len(), - p_data: self.specialization_data.as_ptr().cast(), - ..Default::default() - }; - - let required_subgroup_size_info = self.required_subgroup_size.map(|size| { - ash::vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo { - required_subgroup_size: size, - ..Default::default() - } - }); - + pub fn to_vulkan(&self) -> ash::vk::PipelineShaderStageCreateInfo<'_> { ash::vk::PipelineShaderStageCreateInfo { - p_next: required_subgroup_size_info + p_next: self + .required_subgroup_size_info .as_ref() .map_or(ptr::null(), |info| info as *const _ as *const _), flags: self.flags.into(), stage: self.stage.into(), module: self.specialized_shader_module.handle(), p_name: self.name.as_ptr(), - p_specialization_info: if specialization_info.data_size == 0 { - ptr::null() - } else { - &specialization_info - }, + p_specialization_info: self + .specialization_info + .as_ref() + .map_or(ptr::null(), |info| info as *const _), ..Default::default() } } From d5d46a6c59148b35d2fe899b4a154152f3fe8a64 Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Wed, 20 Nov 2024 00:03:36 +0800 Subject: [PATCH 03/26] sync --- examples/triangle-util/main.rs | 312 ++++++------------------ vulkano/src/pipeline/ray_tracing/mod.rs | 1 + 2 files changed, 76 insertions(+), 237 deletions(-) diff --git a/examples/triangle-util/main.rs b/examples/triangle-util/main.rs index 764e29ef24..d499ac2c2c 100644 --- a/examples/triangle-util/main.rs +++ b/examples/triangle-util/main.rs @@ -14,19 +14,20 @@ use vulkano::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, RenderPassBeginInfo, SubpassBeginInfo, SubpassContents, }, + device::DeviceOwnedVulkanObject, image::view::ImageView, memory::allocator::{AllocationCreateInfo, MemoryTypeFilter}, pipeline::{ graphics::{ - color_blend::{ColorBlendAttachmentState, ColorBlendState}, - input_assembly::InputAssemblyState, - multisample::MultisampleState, - rasterization::RasterizationState, vertex_input::{Vertex, VertexDefinition}, viewport::{Viewport, ViewportState}, GraphicsPipelineCreateInfo, }, layout::PipelineDescriptorSetLayoutCreateInfo, + ray_tracing::{ + RayTracingPipeline, RayTracingPipelineCreateInfo, RayTracingShaderGroupCreateInfo, + ShaderBindingTable, + }, DynamicState, GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo, }, render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass}, @@ -59,10 +60,31 @@ struct App { } struct RenderContext { - render_pass: Arc, - framebuffers: Vec>, - pipeline: Arc, - viewport: Viewport, + pipeline: Arc, +} + +mod raygen { + vulkano_shaders::shader! { + ty: "raygen", + path: "raytrace.rgen", + vulkan_version: "1.2" + } +} + +mod closest_hit { + vulkano_shaders::shader! { + ty: "closesthit", + path: "raytrace.rchit", + vulkan_version: "1.2" + } +} + +mod miss { + vulkano_shaders::shader! { + ty: "miss", + path: "raytrace.rmiss", + vulkan_version: "1.2" + } } impl App { @@ -135,204 +157,89 @@ impl ApplicationHandler for App { let window_renderer = self.windows.get_primary_renderer_mut().unwrap(); let window_size = window_renderer.window().inner_size(); - // The next step is to create the shaders. - // - // The raw shader creation API provided by the vulkano library is unsafe for various - // reasons, so The `shader!` macro provides a way to generate a Rust module from GLSL - // source - in the example below, the source is provided as a string input directly to the - // shader, but a path to a source file can be provided as well. Note that the user must - // specify the type of shader (e.g. "vertex", "fragment", etc.) using the `ty` option of - // the macro. - // - // The items generated by the `shader!` macro include a `load` function which loads the - // shader using an input logical device. The module also includes type definitions for - // layout structures defined in the shader source, for example uniforms and push constants. - // - // A more detailed overview of what the `shader!` macro generates can be found in the - // vulkano-shaders crate docs. You can view them at https://docs.rs/vulkano-shaders/ - mod vs { - vulkano_shaders::shader! { - ty: "vertex", - src: r" - #version 450 - - layout(location = 0) in vec2 position; - - void main() { - gl_Position = vec4(position, 0.0, 1.0); - } - ", - } - } - - mod fs { - vulkano_shaders::shader! { - ty: "fragment", - src: r" - #version 450 - - layout(location = 0) out vec4 f_color; - - void main() { - f_color = vec4(1.0, 0.0, 0.0, 1.0); - } - ", - } - } - - // The next step is to create a *render pass*, which is an object that describes where the - // output of the graphics pipeline will go. It describes the layout of the images where the - // colors, depth and/or stencil information will be written. - let render_pass = vulkano::single_pass_renderpass!( - self.context.device().clone(), - attachments: { - // `color` is a custom name we give to the first and only attachment. - color: { - // `format: ` indicates the type of the format of the image. This has to be - // one of the types of the `vulkano::format` module (or alternatively one of - // your structs that implements the `FormatDesc` trait). Here we use the same - // format as the swapchain. - format: window_renderer.swapchain_format(), - // `samples: 1` means that we ask the GPU to use one sample to determine the - // value of each pixel in the color attachment. We could use a larger value - // (multisampling) for antialiasing. An example of this can be found in - // msaa-renderpass.rs. - samples: 1, - // `load_op: Clear` means that we ask the GPU to clear the content of this - // attachment at the start of the drawing. - load_op: Clear, - // `store_op: Store` means that we ask the GPU to store the output of the draw - // in the actual image. We could also ask it to discard the result. - store_op: Store, - }, - }, - pass: { - // We use the attachment named `color` as the one and only color attachment. - color: [color], - // No depth-stencil attachment is indicated with empty brackets. - depth_stencil: {}, - }, - ) - .unwrap(); - - // The render pass we created above only describes the layout of our framebuffers. Before - // we can draw we also need to create the actual framebuffers. - // - // Since we need to draw to multiple images, we are going to create a different framebuffer - // for each image. - let framebuffers = - window_size_dependent_setup(window_renderer.swapchain_image_views(), &render_pass); - // Before we draw, we have to create what is called a **pipeline**. A pipeline describes // how a GPU operation is to be performed. It is similar to an OpenGL program, but it also // contains many settings for customization, all baked into a single object. For drawing, // we create a **graphics** pipeline, but there are also other types of pipeline. let pipeline = { - // First, we load the shaders that the pipeline will use: the vertex shader and the - // fragment shader. - // - // A Vulkan shader can in theory contain multiple entry points, so we have to specify - // which one. - let vs = vs::load(self.context.device().clone()) + let raygen = raygen::load(self.context.device().clone()) .unwrap() .entry_point("main") .unwrap(); - let fs = fs::load(self.context.device().clone()) + let closest_hit = closest_hit::load(self.context.device().clone()) .unwrap() .entry_point("main") .unwrap(); - // Automatically generate a vertex input state from the vertex shader's input - // interface, that takes a single vertex buffer containing `Vertex` structs. - let vertex_input_state = MyVertex::per_vertex().definition(&vs).unwrap(); + let miss = miss::load(self.context.device().clone()) + .unwrap() + .entry_point("main") + .unwrap(); // Make a list of the shader stages that the pipeline will have. let stages = [ - PipelineShaderStageCreateInfo::new(vs), - PipelineShaderStageCreateInfo::new(fs), + PipelineShaderStageCreateInfo::new(raygen), + PipelineShaderStageCreateInfo::new(miss), + PipelineShaderStageCreateInfo::new(closest_hit), + ]; + + let groups = [ + RayTracingShaderGroupCreateInfo { + // Raygen + general_shader: Some(0), + ..Default::default() + }, + RayTracingShaderGroupCreateInfo { + // Miss + general_shader: Some(1), + ..Default::default() + }, + RayTracingShaderGroupCreateInfo { + // Closest Hit + group_type: ash::vk::RayTracingShaderGroupTypeKHR::TRIANGLES_HIT_GROUP, + closest_hit_shader: Some(2), + ..Default::default() + }, ]; - // We must now create a **pipeline layout** object, which describes the locations and - // types of descriptor sets and push constants used by the shaders in the pipeline. - // - // Multiple pipelines can share a common layout object, which is more efficient. The - // shaders in a pipeline must use a subset of the resources described in its pipeline - // layout, but the pipeline layout is allowed to contain resources that are not present - // in the shaders; they can be used by shaders in other pipelines that share the same - // layout. Thus, it is a good idea to design shaders so that many pipelines have common - // resource locations, which allows them to share pipeline layouts. let layout = PipelineLayout::new( self.context.device().clone(), // Since we only have one pipeline in this example, and thus one pipeline layout, - // we automatically generate the creation info for it from the resources used in - // the shaders. In a real application, you would specify this information manually - // so that you can re-use one layout in multiple pipelines. + // we automatically generate the creation info for it from the resources used in the + // shaders. In a real application, you would specify this information manually so that + // you can re-use one layout in multiple pipelines. PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages) .into_pipeline_layout_create_info(self.context.device().clone()) .unwrap(), ) .unwrap(); - // We have to indicate which subpass of which render pass this pipeline is going to be - // used in. The pipeline will only be usable from this particular subpass. - let subpass = Subpass::from(render_pass.clone(), 0).unwrap(); - - // Finally, create the pipeline. - GraphicsPipeline::new( + RayTracingPipeline::new( self.context.device().clone(), None, - GraphicsPipelineCreateInfo { + RayTracingPipelineCreateInfo { stages: stages.into_iter().collect(), - // How vertex data is read from the vertex buffers into the vertex shader. - vertex_input_state: Some(vertex_input_state), - // How vertices are arranged into primitive shapes. The default primitive shape - // is a triangle. - input_assembly_state: Some(InputAssemblyState::default()), - // How primitives are transformed and clipped to fit the framebuffer. We use a - // resizable viewport, set to draw over the entire window. - viewport_state: Some(ViewportState::default()), - // How polygons are culled and converted into a raster of pixels. The default - // value does not perform any culling. - rasterization_state: Some(RasterizationState::default()), - // How multiple fragment shader samples are converted to a single pixel value. - // The default value does not perform any multisampling. - multisample_state: Some(MultisampleState::default()), - // How pixel values are combined with the values already present in the - // framebuffer. The default value overwrites the old value with the new one, - // without any blending. - color_blend_state: Some(ColorBlendState::with_attachment_states( - subpass.num_color_attachments(), - ColorBlendAttachmentState::default(), - )), - // Dynamic states allows us to specify parts of the pipeline settings when - // recording the command buffer, before we perform drawing. Here, we specify - // that the viewport should be dynamic. - dynamic_state: [DynamicState::Viewport].into_iter().collect(), - subpass: Some(subpass.into()), - ..GraphicsPipelineCreateInfo::layout(layout) + groups: groups.into_iter().collect(), + max_pipeline_ray_recursion_depth: 1, + + ..RayTracingPipelineCreateInfo::layout(layout) }, ) .unwrap() }; + pipeline + .set_debug_utils_object_name("Ray Tracing Pipeline".into()) + .unwrap(); - // Dynamic viewports allow us to recreate just the viewport when the window is resized. - // Otherwise we would have to recreate the whole pipeline. - let viewport = Viewport { - offset: [0.0, 0.0], - extent: window_size.into(), - depth_range: 0.0..=1.0, - }; + let shader_binding_table = + ShaderBindingTable::new(self.context.memory_allocator().clone(), &pipeline, 1, 1, 0) + .unwrap(); // In the `window_event` handler below we are going to submit commands to the GPU. // Submitting a command produces an object that implements the `GpuFuture` trait, which // holds the resources for as long as they are in use by the GPU. - self.rcx = Some(RenderContext { - render_pass, - framebuffers, - pipeline, - viewport, - }); + self.rcx = Some(RenderContext { pipeline }); } fn window_event( @@ -362,15 +269,7 @@ impl ApplicationHandler for App { // Begin rendering by acquiring the gpu future from the window renderer. let previous_frame_end = window_renderer - .acquire(Some(Duration::from_millis(1000)), |swapchain_images| { - // Whenever the window resizes we need to recreate everything dependent - // on the window size. In this example that - // includes the swapchain, the framebuffers - // and the dynamic state viewport. - rcx.framebuffers = - window_size_dependent_setup(swapchain_images, &rcx.render_pass); - rcx.viewport.extent = window_size.into(); - }) + .acquire(Some(Duration::from_millis(1000)), |_swapchain_images| {}) .unwrap(); // In order to draw, we have to record a *command buffer*. The command buffer @@ -389,50 +288,9 @@ impl ApplicationHandler for App { ) .unwrap(); - builder - // Before we can draw, we have to *enter a render pass*. - .begin_render_pass( - RenderPassBeginInfo { - // A list of values to clear the attachments with. This list contains - // one item for each attachment in the render pass. In this case, there - // is only one attachment, and we clear it with a blue color. - // - // Only attachments that have `AttachmentLoadOp::Clear` are provided - // with clear values, any others should use `None` as the clear value. - clear_values: vec![Some([0.0, 0.0, 1.0, 1.0].into())], - - ..RenderPassBeginInfo::framebuffer( - rcx.framebuffers[window_renderer.image_index() as usize].clone(), - ) - }, - SubpassBeginInfo { - // The contents of the first (and only) subpass. This can be either - // `Inline` or `SecondaryCommandBuffers`. The latter is a bit more - // advanced and is not covered here. - contents: SubpassContents::Inline, - ..Default::default() - }, - ) - .unwrap() - // We are now inside the first subpass of the render pass. - // - // TODO: Document state setting and how it affects subsequent draw commands. - .set_viewport(0, [rcx.viewport.clone()].into_iter().collect()) - .unwrap() - .bind_pipeline_graphics(rcx.pipeline.clone()) - .unwrap() - .bind_vertex_buffers(0, self.vertex_buffer.clone()) - .unwrap(); - // We add a draw command. unsafe { builder.draw(self.vertex_buffer.len() as u32, 1, 0, 0) }.unwrap(); - builder - // We leave the render pass. Note that if we had multiple subpasses we could - // have called `next_subpass` to jump to the next subpass. - .end_render_pass(Default::default()) - .unwrap(); - // Finish recording the command buffer by calling `end`. let command_buffer = builder.build().unwrap(); @@ -469,23 +327,3 @@ struct MyVertex { #[format(R32G32_SFLOAT)] position: [f32; 2], } - -/// This function is called once during initialization, then again whenever the window is resized. -fn window_size_dependent_setup( - swapchain_images: &[Arc], - render_pass: &Arc, -) -> Vec> { - swapchain_images - .iter() - .map(|swapchain_image| { - Framebuffer::new( - render_pass.clone(), - FramebufferCreateInfo { - attachments: vec![swapchain_image.clone()], - ..Default::default() - }, - ) - .unwrap() - }) - .collect::>() -} diff --git a/vulkano/src/pipeline/ray_tracing/mod.rs b/vulkano/src/pipeline/ray_tracing/mod.rs index aa244dd9ae..18178daf83 100644 --- a/vulkano/src/pipeline/ray_tracing/mod.rs +++ b/vulkano/src/pipeline/ray_tracing/mod.rs @@ -580,6 +580,7 @@ impl ShaderBindingTable { offset += callable.stride as usize; } } + // TODO: RayTracing: Add unit test for copy algorithm Ok(Self { raygen, From 193ae3d811dec55f6e25b886464dc64176f8d520 Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Sun, 24 Nov 2024 00:09:10 +0800 Subject: [PATCH 04/26] triangle-raytracing --- Cargo.lock | 11 + examples/triangle-raytracing/Cargo.toml | 19 + examples/triangle-raytracing/main.rs | 401 +++++++++++++ .../raytrace.rchit | 0 .../raytrace.rgen | 0 .../raytrace.rmiss | 0 examples/triangle-raytracing/scene.rs | 541 ++++++++++++++++++ examples/triangle-util/main.rs | 312 +++++++--- .../src/command_buffer/commands/bind_push.rs | 29 +- .../src/command_buffer/commands/pipeline.rs | 39 +- vulkano-taskgraph/src/resource.rs | 12 +- .../src/command_buffer/commands/pipeline.rs | 14 - vulkano/src/pipeline/ray_tracing/mod.rs | 10 +- 13 files changed, 1282 insertions(+), 106 deletions(-) create mode 100644 examples/triangle-raytracing/Cargo.toml create mode 100644 examples/triangle-raytracing/main.rs rename examples/{triangle-util => triangle-raytracing}/raytrace.rchit (100%) rename examples/{triangle-util => triangle-raytracing}/raytrace.rgen (100%) rename examples/{triangle-util => triangle-raytracing}/raytrace.rmiss (100%) create mode 100644 examples/triangle-raytracing/scene.rs diff --git a/Cargo.lock b/Cargo.lock index c68bb49db4..8fa53b60ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1732,6 +1732,17 @@ dependencies = [ "winit", ] +[[package]] +name = "triangle-raytracing" +version = "0.0.0" +dependencies = [ + "ash", + "vulkano", + "vulkano-shaders", + "vulkano-taskgraph", + "winit", +] + [[package]] name = "triangle-util" version = "0.0.0" diff --git a/examples/triangle-raytracing/Cargo.toml b/examples/triangle-raytracing/Cargo.toml new file mode 100644 index 0000000000..f1666e0892 --- /dev/null +++ b/examples/triangle-raytracing/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "triangle-raytracing" +version = "0.0.0" +edition = "2021" +publish = false + +[[bin]] +name = "triangle-raytracing" +path = "main.rs" +test = false +bench = false +doc = false + +[dependencies] +vulkano = { workspace = true, default-features = true } +vulkano-shaders = { workspace = true } +vulkano-taskgraph = { workspace = true } +winit = { workspace = true, default-features = true } +ash = { workspace = true } diff --git a/examples/triangle-raytracing/main.rs b/examples/triangle-raytracing/main.rs new file mode 100644 index 0000000000..ad3d361cf1 --- /dev/null +++ b/examples/triangle-raytracing/main.rs @@ -0,0 +1,401 @@ +// TODO: document + +use scene::SceneTask; +use std::{error::Error, sync::Arc}; +use vulkano::{ + command_buffer::allocator::StandardCommandBufferAllocator, + descriptor_set::{ + allocator::StandardDescriptorSetAllocator, + layout::{ + DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo, + DescriptorType, + }, + }, + device::{ + physical::PhysicalDeviceType, Device, DeviceCreateInfo, DeviceExtensions, Queue, + QueueCreateInfo, QueueFlags, + }, + format::NumericFormat, + image::ImageUsage, + instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, InstanceExtensions}, + memory::allocator::StandardMemoryAllocator, + pipeline::{layout::PipelineLayoutCreateInfo, PipelineLayout}, + shader::ShaderStages, + swapchain::{ColorSpace, Surface, Swapchain, SwapchainCreateInfo}, + Validated, Version, VulkanError, VulkanLibrary, +}; +use vulkano_taskgraph::{ + graph::{CompileInfo, ExecutableTaskGraph, ExecuteError, NodeId, TaskGraph}, + resource::{AccessType, Flight, ImageLayoutType, Resources}, + resource_map, Id, QueueFamilyType, +}; +use winit::{ + application::ApplicationHandler, + event::WindowEvent, + event_loop::{ActiveEventLoop, EventLoop}, + window::{Window, WindowId}, +}; + +mod scene; + +const MAX_FRAMES_IN_FLIGHT: u32 = 2; + +fn main() -> Result<(), impl Error> { + let event_loop = EventLoop::new().unwrap(); + let mut app = App::new(&event_loop); + + event_loop.run_app(&mut app) +} + +struct App { + instance: Arc, + device: Arc, + queue: Arc, + resources: Arc, + flight_id: Id, + rcx: Option, +} + +pub struct RenderContext { + window: Arc, + swapchain_id: Id, + pipeline_layout: Arc, + recreate_swapchain: bool, + task_graph: ExecutableTaskGraph, + scene_node_id: NodeId, + virtual_swapchain_id: Id, +} + +impl App { + fn new(event_loop: &EventLoop<()>) -> Self { + let library = VulkanLibrary::new().unwrap(); + let required_extensions = Surface::required_extensions(event_loop).unwrap(); + let instance = Instance::new( + library, + InstanceCreateInfo { + flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, + enabled_extensions: InstanceExtensions { + ext_debug_utils: true, + ..required_extensions + }, + ..Default::default() + }, + ) + .unwrap(); + + let device_extensions = DeviceExtensions { + khr_swapchain: true, + khr_ray_tracing_pipeline: true, + khr_ray_tracing_maintenance1: true, + khr_synchronization2: true, + khr_deferred_host_operations: true, + khr_acceleration_structure: true, + ..DeviceExtensions::empty() + }; + let (physical_device, queue_family_index) = instance + .enumerate_physical_devices() + .unwrap() + .filter(|p| p.api_version() >= Version::V1_3) + .filter(|p| p.supported_extensions().contains(&device_extensions)) + .filter_map(|p| { + p.queue_family_properties() + .iter() + .enumerate() + .position(|(i, q)| { + q.queue_flags + .contains(QueueFlags::GRAPHICS | QueueFlags::COMPUTE) + && p.presentation_support(i as u32, event_loop).unwrap() + }) + .map(|i| (p, i as u32)) + }) + .min_by_key(|(p, _)| match p.properties().device_type { + PhysicalDeviceType::DiscreteGpu => 0, + PhysicalDeviceType::IntegratedGpu => 1, + PhysicalDeviceType::VirtualGpu => 2, + PhysicalDeviceType::Cpu => 3, + PhysicalDeviceType::Other => 4, + _ => 5, + }) + .unwrap(); + + println!( + "Using device: {} (type: {:?})", + physical_device.properties().device_name, + physical_device.properties().device_type, + ); + + let (device, mut queues) = Device::new( + physical_device, + DeviceCreateInfo { + enabled_extensions: device_extensions, + queue_create_infos: vec![QueueCreateInfo { + queue_family_index, + ..Default::default() + }], + ..Default::default() + }, + ) + .unwrap(); + + let queue = queues.next().unwrap(); + + let resources = Resources::new(&device, &Default::default()); + + let flight_id = resources.create_flight(MAX_FRAMES_IN_FLIGHT).unwrap(); + + App { + instance, + device, + queue, + resources, + flight_id, + rcx: None, + } + } +} + +impl ApplicationHandler for App { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let window = Arc::new( + event_loop + .create_window(Window::default_attributes()) + .unwrap(), + ); + let surface = Surface::from_window(self.instance.clone(), window.clone()).unwrap(); + let window_size = window.inner_size(); + + let swapchain_format; + let swapchain_id = { + let surface_capabilities = self + .device + .physical_device() + .surface_capabilities(&surface, Default::default()) + .unwrap(); + (swapchain_format, _) = self + .device + .physical_device() + .surface_formats(&surface, Default::default()) + .unwrap() + .into_iter() + .find(|&(format, color_space)| { + format.numeric_format_color() == Some(NumericFormat::SRGB) + && color_space == ColorSpace::SrgbNonLinear + }) + .unwrap(); + + self.resources + .create_swapchain( + self.flight_id, + surface, + SwapchainCreateInfo { + min_image_count: surface_capabilities.min_image_count.max(3), + image_format: swapchain_format, + image_extent: window.inner_size().into(), + image_usage: ImageUsage::COLOR_ATTACHMENT, + composite_alpha: surface_capabilities + .supported_composite_alpha + .into_iter() + .next() + .unwrap(), + ..Default::default() + }, + ) + .unwrap() + }; + + let pipeline_layout = PipelineLayout::new( + self.device.clone(), + PipelineLayoutCreateInfo { + set_layouts: vec![ + DescriptorSetLayout::new( + self.device.clone(), + DescriptorSetLayoutCreateInfo { + bindings: [ + ( + 0, + DescriptorSetLayoutBinding { + stages: ShaderStages::RAYGEN, + ..DescriptorSetLayoutBinding::descriptor_type( + DescriptorType::AccelerationStructure, + ) + }, + ), + ( + 1, + DescriptorSetLayoutBinding { + stages: ShaderStages::RAYGEN, + ..DescriptorSetLayoutBinding::descriptor_type( + DescriptorType::UniformBuffer, + ) + }, + ), + ] + .into_iter() + .collect(), + ..Default::default() + }, + ) + .unwrap(), + DescriptorSetLayout::new( + self.device.clone(), + DescriptorSetLayoutCreateInfo { + bindings: [( + 0, + DescriptorSetLayoutBinding { + stages: ShaderStages::RAYGEN, + ..DescriptorSetLayoutBinding::descriptor_type( + DescriptorType::StorageImage, + ) + }, + )] + .into_iter() + .collect(), + ..Default::default() + }, + ) + .unwrap(), + ], + push_constant_ranges: vec![], + ..Default::default() + }, + ) + .unwrap(); + + let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new( + self.device.clone(), + Default::default(), + )); + + let memory_allocator = Arc::new(StandardMemoryAllocator::new( + self.device.clone(), + Default::default(), + )); + + let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new( + self.device.clone(), + Default::default(), + )); + + let mut task_graph = TaskGraph::new(&self.resources, 3, 2); + + let virtual_swapchain_id = task_graph.add_swapchain(&SwapchainCreateInfo::default()); + + let scene_node_id = task_graph + .create_task_node( + "Scene", + QueueFamilyType::Graphics, + SceneTask::new( + self, + pipeline_layout.clone(), + swapchain_id, + virtual_swapchain_id, + descriptor_set_allocator, + memory_allocator, + command_buffer_allocator, + ), + ) + .image_access( + virtual_swapchain_id.current_image_id(), + AccessType::RayTracingShaderStorageWrite, + ImageLayoutType::Optimal, + ) + .build(); + + let task_graph = unsafe { + task_graph.compile(&CompileInfo { + queues: &[&self.queue], + present_queue: Some(&self.queue), + flight_id: self.flight_id, + ..Default::default() + }) + } + .unwrap(); + + self.rcx = Some(RenderContext { + window, + swapchain_id, + virtual_swapchain_id, + pipeline_layout, + recreate_swapchain: false, + task_graph, + scene_node_id, + }); + } + + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + _window_id: WindowId, + event: WindowEvent, + ) { + let rcx = self.rcx.as_mut().unwrap(); + + match event { + WindowEvent::CloseRequested => { + event_loop.exit(); + } + WindowEvent::Resized(_) => { + rcx.recreate_swapchain = true; + } + WindowEvent::RedrawRequested => { + let window_size = rcx.window.inner_size(); + + if window_size.width == 0 || window_size.height == 0 { + return; + } + + let flight = self.resources.flight(self.flight_id).unwrap(); + + if rcx.recreate_swapchain { + rcx.swapchain_id = self + .resources + .recreate_swapchain(rcx.swapchain_id, |create_info| SwapchainCreateInfo { + image_extent: window_size.into(), + ..create_info + }) + .expect("failed to recreate swapchain"); + + rcx.task_graph + .task_node_mut(rcx.scene_node_id) + .unwrap() + .task_mut() + .downcast_mut::() + .unwrap() + .handle_resize(&self.resources, rcx.swapchain_id); + + rcx.recreate_swapchain = false; + } + + flight.wait(None).unwrap(); + + let resource_map = resource_map!( + &rcx.task_graph, + rcx.virtual_swapchain_id => rcx.swapchain_id, + ) + .unwrap(); + + match unsafe { + rcx.task_graph + .execute(resource_map, rcx, || rcx.window.pre_present_notify()) + } { + Ok(()) => {} + Err(ExecuteError::Swapchain { + error: Validated::Error(VulkanError::OutOfDate), + .. + }) => { + rcx.recreate_swapchain = true; + } + Err(e) => { + panic!("failed to execute next frame: {e:?}"); + } + } + } + _ => {} + } + } + + fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { + let rcx = self.rcx.as_mut().unwrap(); + rcx.window.request_redraw(); + } +} diff --git a/examples/triangle-util/raytrace.rchit b/examples/triangle-raytracing/raytrace.rchit similarity index 100% rename from examples/triangle-util/raytrace.rchit rename to examples/triangle-raytracing/raytrace.rchit diff --git a/examples/triangle-util/raytrace.rgen b/examples/triangle-raytracing/raytrace.rgen similarity index 100% rename from examples/triangle-util/raytrace.rgen rename to examples/triangle-raytracing/raytrace.rgen diff --git a/examples/triangle-util/raytrace.rmiss b/examples/triangle-raytracing/raytrace.rmiss similarity index 100% rename from examples/triangle-util/raytrace.rmiss rename to examples/triangle-raytracing/raytrace.rmiss diff --git a/examples/triangle-raytracing/scene.rs b/examples/triangle-raytracing/scene.rs new file mode 100644 index 0000000000..c2bcc44421 --- /dev/null +++ b/examples/triangle-raytracing/scene.rs @@ -0,0 +1,541 @@ +use std::sync::Arc; + +use vulkano::{ + acceleration_structure::{ + AccelerationStructure, AccelerationStructureBuildGeometryInfo, + AccelerationStructureBuildRangeInfo, AccelerationStructureBuildType, + AccelerationStructureCreateInfo, AccelerationStructureGeometries, + AccelerationStructureGeometryInstancesData, AccelerationStructureGeometryInstancesDataType, + AccelerationStructureGeometryTrianglesData, AccelerationStructureInstance, + AccelerationStructureType, BuildAccelerationStructureFlags, BuildAccelerationStructureMode, + }, + buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer}, + command_buffer::{ + allocator::CommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, + PrimaryCommandBufferAbstract, + }, + descriptor_set::{ + allocator::StandardDescriptorSetAllocator, sys::RawDescriptorSet, WriteDescriptorSet, + }, + device::{Device, Queue}, + format::Format, + image::view::ImageView, + memory::allocator::{AllocationCreateInfo, MemoryAllocator, MemoryTypeFilter}, + pipeline::{ + graphics::vertex_input::Vertex, + ray_tracing::{ + RayTracingPipeline, RayTracingPipelineCreateInfo, RayTracingShaderGroupCreateInfo, + ShaderBindingTable, + }, + PipelineBindPoint, PipelineLayout, PipelineShaderStageCreateInfo, + }, + swapchain::Swapchain, + sync::GpuFuture, +}; +use vulkano_taskgraph::{ + command_buffer::RecordingCommandBuffer, resource::Resources, Id, Task, TaskContext, TaskResult, +}; + +use crate::{App, RenderContext}; + +mod raygen { + vulkano_shaders::shader! { + ty: "raygen", + path: "raytrace.rgen", + vulkan_version: "1.2" + } +} + +mod closest_hit { + vulkano_shaders::shader! { + ty: "closesthit", + path: "raytrace.rchit", + vulkan_version: "1.2" + } +} + +mod miss { + vulkano_shaders::shader! { + ty: "miss", + path: "raytrace.rmiss", + vulkan_version: "1.2" + } +} + +#[derive(BufferContents, Vertex)] +#[repr(C)] +struct MyVertex { + #[format(R32G32B32_SFLOAT)] + position: [f32; 3], +} + +pub struct SceneTask { + descriptor_set_0: Arc, + swapchain_image_sets: Vec<(Arc, Arc)>, + pipeline_layout: Arc, + descriptor_set_allocator: Arc, + virtual_swapchain_id: Id, + shader_binding_table: ShaderBindingTable, + pipeline: Arc, + blas: Arc, + tlas: Arc, + uniform_buffer: Subbuffer, +} + +impl SceneTask { + pub fn new( + app: &App, + pipeline_layout: Arc, + swapchain_id: Id, + virtual_swapchain_id: Id, + descriptor_set_allocator: Arc, + memory_allocator: Arc, + command_buffer_allocator: Arc, + ) -> Self { + let pipeline = { + let raygen = raygen::load(app.device.clone()) + .unwrap() + .entry_point("main") + .unwrap(); + let closest_hit = closest_hit::load(app.device.clone()) + .unwrap() + .entry_point("main") + .unwrap(); + + let miss = miss::load(app.device.clone()) + .unwrap() + .entry_point("main") + .unwrap(); + + // Make a list of the shader stages that the pipeline will have. + let stages = [ + PipelineShaderStageCreateInfo::new(raygen), + PipelineShaderStageCreateInfo::new(miss), + PipelineShaderStageCreateInfo::new(closest_hit), + ]; + + let groups = [ + RayTracingShaderGroupCreateInfo { + // Raygen + general_shader: Some(0), + ..Default::default() + }, + RayTracingShaderGroupCreateInfo { + // Miss + general_shader: Some(1), + ..Default::default() + }, + RayTracingShaderGroupCreateInfo { + // Closest Hit + group_type: ash::vk::RayTracingShaderGroupTypeKHR::TRIANGLES_HIT_GROUP, + closest_hit_shader: Some(2), + ..Default::default() + }, + ]; + + RayTracingPipeline::new( + app.device.clone(), + None, + RayTracingPipelineCreateInfo { + stages: stages.into_iter().collect(), + groups: groups.into_iter().collect(), + max_pipeline_ray_recursion_depth: 1, + + ..RayTracingPipelineCreateInfo::layout(pipeline_layout.clone()) + }, + ) + .unwrap() + }; + + let vertices = [ + MyVertex { + position: [-0.5, -0.25, 0.0], + }, + MyVertex { + position: [0.0, 0.5, 0.0], + }, + MyVertex { + position: [0.25, -0.1, 0.0], + }, + ]; + let vertex_buffer = Buffer::from_iter( + memory_allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::VERTEX_BUFFER + | BufferUsage::SHADER_DEVICE_ADDRESS + | BufferUsage::ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + vertices, + ) + .unwrap(); + + let blas = unsafe { + build_acceleration_structure_triangles( + vertex_buffer, + memory_allocator.clone(), + command_buffer_allocator.clone(), + app.device.clone(), + app.queue.clone(), + ) + }; + + let tlas = unsafe { + build_top_level_acceleration_structure( + blas.clone(), + memory_allocator.clone(), + command_buffer_allocator.clone(), + app.device.clone(), + app.queue.clone(), + ) + }; + + let uniform_buffer = Buffer::from_data( + memory_allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::UNIFORM_BUFFER, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + raygen::Camera { + projInverse: Default::default(), + viewInverse: Default::default(), + viewProj: Default::default(), + }, + ) + .unwrap(); + + let descriptor_set_0 = RawDescriptorSet::new( + descriptor_set_allocator.clone(), + &pipeline_layout.set_layouts()[0], + 0, + ) + .unwrap(); + + unsafe { + let writes = &[ + WriteDescriptorSet::acceleration_structure(0, tlas.clone()), + WriteDescriptorSet::buffer(1, uniform_buffer.clone()), + ]; + descriptor_set_0.update(writes, &[]).unwrap(); + } + + let swapchain_image_sets = window_size_dependent_setup( + &app.resources, + swapchain_id, + &pipeline_layout, + &descriptor_set_allocator, + ); + + let shader_binding_table = + ShaderBindingTable::new(memory_allocator.clone(), &pipeline, 1, 1, 0).unwrap(); + + SceneTask { + descriptor_set_0: Arc::new(descriptor_set_0), + swapchain_image_sets, + descriptor_set_allocator, + pipeline_layout, + virtual_swapchain_id, + shader_binding_table, + pipeline, + blas, + tlas, + uniform_buffer, + } + } + + pub fn handle_resize(&mut self, resources: &Resources, swapchain_id: Id) { + self.swapchain_image_sets = window_size_dependent_setup( + resources, + swapchain_id, + &self.pipeline_layout, + &self.descriptor_set_allocator, + ); + } +} + +impl Task for SceneTask { + type World = RenderContext; + + unsafe fn execute( + &self, + cbf: &mut RecordingCommandBuffer<'_>, + tcx: &mut TaskContext<'_>, + rcx: &Self::World, + ) -> TaskResult { + let swapchain_state = tcx.swapchain(self.virtual_swapchain_id)?; + let image_index = swapchain_state.current_image_index().unwrap(); + + cbf.as_raw().bind_descriptor_sets( + PipelineBindPoint::Graphics, + &rcx.pipeline_layout, + 0, + &[ + &self.descriptor_set_0, + &self.swapchain_image_sets[image_index as usize].1, + ], + &[], + )?; + + cbf.bind_pipeline_ray_tracing(&self.pipeline)?; + + let extent = self.swapchain_image_sets[0].0.image().extent(); + + unsafe { cbf.trace_rays(&self.shader_binding_table, extent[0], extent[1], 1) }?; + + for (image_view, descriptor_set) in self.swapchain_image_sets.iter() { + cbf.destroy_object(descriptor_set.clone()); + cbf.destroy_object(image_view.clone()); + } + cbf.destroy_object(self.descriptor_set_0.clone()); + + Ok(()) + } +} + +/// This function is called once during initialization, then again whenever the window is resized. +fn window_size_dependent_setup( + resources: &Resources, + swapchain_id: Id, + pipeline_layout: &Arc, + descriptor_set_allocator: &Arc, +) -> Vec<(Arc, Arc)> { + let swapchain_state = resources.swapchain(swapchain_id).unwrap(); + let images = swapchain_state.images(); + + let swapchain_image_sets = images + .iter() + .map(|image| { + let descriptor_set = RawDescriptorSet::new( + descriptor_set_allocator.clone(), + &pipeline_layout.set_layouts()[1], + 0, + ) + .unwrap(); + let image_view = ImageView::new_default(image.clone()).unwrap(); + let writes = &[WriteDescriptorSet::image_view(0, image_view.clone())]; + unsafe { descriptor_set.update(writes, &[]) }.unwrap(); + (image_view, Arc::new(descriptor_set)) + }) + .collect(); + + swapchain_image_sets +} + +unsafe fn build_acceleration_structure_triangles( + vertex_buffer: Subbuffer<[MyVertex]>, + memory_allocator: Arc, + command_buffer_allocator: Arc, + device: Arc, + queue: Arc, +) -> Arc { + let mut builder = AutoCommandBufferBuilder::primary( + command_buffer_allocator, + queue.queue_family_index(), + CommandBufferUsage::OneTimeSubmit, + ) + .unwrap(); + + let primitive_count = (vertex_buffer.len() / 3) as u32; + let as_geometry_triangles_data = AccelerationStructureGeometryTrianglesData { + // TODO: Modify constructor? + max_vertex: vertex_buffer.len() as _, + vertex_data: Some(vertex_buffer.into_bytes()), + vertex_stride: size_of::() as _, + ..AccelerationStructureGeometryTrianglesData::new(Format::R32G32B32_SFLOAT) + }; + + let as_geometries = + AccelerationStructureGeometries::Triangles(vec![as_geometry_triangles_data]); + + let mut as_build_geometry_info = AccelerationStructureBuildGeometryInfo { + mode: BuildAccelerationStructureMode::Build, + flags: BuildAccelerationStructureFlags::PREFER_FAST_TRACE, + ..AccelerationStructureBuildGeometryInfo::new(as_geometries) + }; + + let as_build_sizes_info = device + .acceleration_structure_build_sizes( + AccelerationStructureBuildType::Device, + &as_build_geometry_info, + &[primitive_count], + ) + .unwrap(); + + let scratch_buffer = Buffer::new_slice::( + memory_allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::SHADER_DEVICE_ADDRESS | BufferUsage::STORAGE_BUFFER, + ..Default::default() + }, + AllocationCreateInfo::default(), + as_build_sizes_info.build_scratch_size, + ) + .unwrap(); + + let as_create_info = AccelerationStructureCreateInfo { + ty: AccelerationStructureType::BottomLevel, + ..AccelerationStructureCreateInfo::new( + Buffer::new_slice::( + memory_allocator, + BufferCreateInfo { + usage: BufferUsage::ACCELERATION_STRUCTURE_STORAGE + | BufferUsage::SHADER_DEVICE_ADDRESS, + ..Default::default() + }, + AllocationCreateInfo::default(), + as_build_sizes_info.acceleration_structure_size, + ) + .unwrap(), + ) + }; + let acceleration = unsafe { AccelerationStructure::new(device, as_create_info).unwrap() }; + + as_build_geometry_info.dst_acceleration_structure = Some(acceleration.clone()); + as_build_geometry_info.scratch_data = Some(scratch_buffer); + + let as_build_range_info = AccelerationStructureBuildRangeInfo { + primitive_count, + ..Default::default() + }; + + builder + .build_acceleration_structure( + as_build_geometry_info, + [as_build_range_info].into_iter().collect(), + ) + .unwrap(); + + builder + .build() + .unwrap() + .execute(queue) + .unwrap() + .then_signal_fence_and_flush() + .unwrap() + .wait(None) + .unwrap(); + + acceleration +} + +unsafe fn build_top_level_acceleration_structure( + acceleration_structure: Arc, + allocator: Arc, + command_buffer_allocator: Arc, + device: Arc, + queue: Arc, +) -> Arc { + let mut builder = AutoCommandBufferBuilder::primary( + command_buffer_allocator, + queue.queue_family_index(), + CommandBufferUsage::OneTimeSubmit, + ) + .unwrap(); + + let primitive_count = 1; + let as_instance = AccelerationStructureInstance { + acceleration_structure_reference: acceleration_structure.device_address().into(), // TODO: Need to hold AS + ..Default::default() + }; + + let instance_buffer = Buffer::from_iter( + allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::SHADER_DEVICE_ADDRESS + | BufferUsage::ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + [as_instance], + ) + .unwrap(); + + let as_geometry_instances_data = AccelerationStructureGeometryInstancesData::new( + AccelerationStructureGeometryInstancesDataType::Values(Some(instance_buffer)), + ); + + let as_geometries = AccelerationStructureGeometries::Instances(as_geometry_instances_data); + + let mut as_build_geometry_info = AccelerationStructureBuildGeometryInfo { + mode: BuildAccelerationStructureMode::Build, + flags: BuildAccelerationStructureFlags::PREFER_FAST_TRACE, + ..AccelerationStructureBuildGeometryInfo::new(as_geometries) + }; + + let as_build_sizes_info = device + .acceleration_structure_build_sizes( + AccelerationStructureBuildType::Device, + &as_build_geometry_info, + &[primitive_count], + ) + .unwrap(); + + let scratch_buffer = Buffer::new_slice::( + allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::SHADER_DEVICE_ADDRESS | BufferUsage::STORAGE_BUFFER, + ..Default::default() + }, + AllocationCreateInfo::default(), + as_build_sizes_info.build_scratch_size, + ) + .unwrap(); + + let as_create_info = AccelerationStructureCreateInfo { + ty: AccelerationStructureType::TopLevel, + ..AccelerationStructureCreateInfo::new( + Buffer::new_slice::( + allocator, + BufferCreateInfo { + usage: BufferUsage::ACCELERATION_STRUCTURE_STORAGE + | BufferUsage::SHADER_DEVICE_ADDRESS, + ..Default::default() + }, + AllocationCreateInfo::default(), + as_build_sizes_info.acceleration_structure_size, + ) + .unwrap(), + ) + }; + let acceleration = unsafe { AccelerationStructure::new(device, as_create_info).unwrap() }; + + as_build_geometry_info.dst_acceleration_structure = Some(acceleration.clone()); + as_build_geometry_info.scratch_data = Some(scratch_buffer); + + let as_build_range_info = AccelerationStructureBuildRangeInfo { + primitive_count, + ..Default::default() + }; + + builder + .build_acceleration_structure( + as_build_geometry_info, + [as_build_range_info].into_iter().collect(), + ) + .unwrap(); + builder + .build() + .unwrap() + .execute(queue) + .unwrap() + .then_signal_fence_and_flush() + .unwrap() + .wait(None) + .unwrap(); + + acceleration +} diff --git a/examples/triangle-util/main.rs b/examples/triangle-util/main.rs index d499ac2c2c..764e29ef24 100644 --- a/examples/triangle-util/main.rs +++ b/examples/triangle-util/main.rs @@ -14,20 +14,19 @@ use vulkano::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, RenderPassBeginInfo, SubpassBeginInfo, SubpassContents, }, - device::DeviceOwnedVulkanObject, image::view::ImageView, memory::allocator::{AllocationCreateInfo, MemoryTypeFilter}, pipeline::{ graphics::{ + color_blend::{ColorBlendAttachmentState, ColorBlendState}, + input_assembly::InputAssemblyState, + multisample::MultisampleState, + rasterization::RasterizationState, vertex_input::{Vertex, VertexDefinition}, viewport::{Viewport, ViewportState}, GraphicsPipelineCreateInfo, }, layout::PipelineDescriptorSetLayoutCreateInfo, - ray_tracing::{ - RayTracingPipeline, RayTracingPipelineCreateInfo, RayTracingShaderGroupCreateInfo, - ShaderBindingTable, - }, DynamicState, GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo, }, render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass}, @@ -60,31 +59,10 @@ struct App { } struct RenderContext { - pipeline: Arc, -} - -mod raygen { - vulkano_shaders::shader! { - ty: "raygen", - path: "raytrace.rgen", - vulkan_version: "1.2" - } -} - -mod closest_hit { - vulkano_shaders::shader! { - ty: "closesthit", - path: "raytrace.rchit", - vulkan_version: "1.2" - } -} - -mod miss { - vulkano_shaders::shader! { - ty: "miss", - path: "raytrace.rmiss", - vulkan_version: "1.2" - } + render_pass: Arc, + framebuffers: Vec>, + pipeline: Arc, + viewport: Viewport, } impl App { @@ -157,89 +135,204 @@ impl ApplicationHandler for App { let window_renderer = self.windows.get_primary_renderer_mut().unwrap(); let window_size = window_renderer.window().inner_size(); + // The next step is to create the shaders. + // + // The raw shader creation API provided by the vulkano library is unsafe for various + // reasons, so The `shader!` macro provides a way to generate a Rust module from GLSL + // source - in the example below, the source is provided as a string input directly to the + // shader, but a path to a source file can be provided as well. Note that the user must + // specify the type of shader (e.g. "vertex", "fragment", etc.) using the `ty` option of + // the macro. + // + // The items generated by the `shader!` macro include a `load` function which loads the + // shader using an input logical device. The module also includes type definitions for + // layout structures defined in the shader source, for example uniforms and push constants. + // + // A more detailed overview of what the `shader!` macro generates can be found in the + // vulkano-shaders crate docs. You can view them at https://docs.rs/vulkano-shaders/ + mod vs { + vulkano_shaders::shader! { + ty: "vertex", + src: r" + #version 450 + + layout(location = 0) in vec2 position; + + void main() { + gl_Position = vec4(position, 0.0, 1.0); + } + ", + } + } + + mod fs { + vulkano_shaders::shader! { + ty: "fragment", + src: r" + #version 450 + + layout(location = 0) out vec4 f_color; + + void main() { + f_color = vec4(1.0, 0.0, 0.0, 1.0); + } + ", + } + } + + // The next step is to create a *render pass*, which is an object that describes where the + // output of the graphics pipeline will go. It describes the layout of the images where the + // colors, depth and/or stencil information will be written. + let render_pass = vulkano::single_pass_renderpass!( + self.context.device().clone(), + attachments: { + // `color` is a custom name we give to the first and only attachment. + color: { + // `format: ` indicates the type of the format of the image. This has to be + // one of the types of the `vulkano::format` module (or alternatively one of + // your structs that implements the `FormatDesc` trait). Here we use the same + // format as the swapchain. + format: window_renderer.swapchain_format(), + // `samples: 1` means that we ask the GPU to use one sample to determine the + // value of each pixel in the color attachment. We could use a larger value + // (multisampling) for antialiasing. An example of this can be found in + // msaa-renderpass.rs. + samples: 1, + // `load_op: Clear` means that we ask the GPU to clear the content of this + // attachment at the start of the drawing. + load_op: Clear, + // `store_op: Store` means that we ask the GPU to store the output of the draw + // in the actual image. We could also ask it to discard the result. + store_op: Store, + }, + }, + pass: { + // We use the attachment named `color` as the one and only color attachment. + color: [color], + // No depth-stencil attachment is indicated with empty brackets. + depth_stencil: {}, + }, + ) + .unwrap(); + + // The render pass we created above only describes the layout of our framebuffers. Before + // we can draw we also need to create the actual framebuffers. + // + // Since we need to draw to multiple images, we are going to create a different framebuffer + // for each image. + let framebuffers = + window_size_dependent_setup(window_renderer.swapchain_image_views(), &render_pass); + // Before we draw, we have to create what is called a **pipeline**. A pipeline describes // how a GPU operation is to be performed. It is similar to an OpenGL program, but it also // contains many settings for customization, all baked into a single object. For drawing, // we create a **graphics** pipeline, but there are also other types of pipeline. let pipeline = { - let raygen = raygen::load(self.context.device().clone()) + // First, we load the shaders that the pipeline will use: the vertex shader and the + // fragment shader. + // + // A Vulkan shader can in theory contain multiple entry points, so we have to specify + // which one. + let vs = vs::load(self.context.device().clone()) .unwrap() .entry_point("main") .unwrap(); - let closest_hit = closest_hit::load(self.context.device().clone()) + let fs = fs::load(self.context.device().clone()) .unwrap() .entry_point("main") .unwrap(); - let miss = miss::load(self.context.device().clone()) - .unwrap() - .entry_point("main") - .unwrap(); + // Automatically generate a vertex input state from the vertex shader's input + // interface, that takes a single vertex buffer containing `Vertex` structs. + let vertex_input_state = MyVertex::per_vertex().definition(&vs).unwrap(); // Make a list of the shader stages that the pipeline will have. let stages = [ - PipelineShaderStageCreateInfo::new(raygen), - PipelineShaderStageCreateInfo::new(miss), - PipelineShaderStageCreateInfo::new(closest_hit), - ]; - - let groups = [ - RayTracingShaderGroupCreateInfo { - // Raygen - general_shader: Some(0), - ..Default::default() - }, - RayTracingShaderGroupCreateInfo { - // Miss - general_shader: Some(1), - ..Default::default() - }, - RayTracingShaderGroupCreateInfo { - // Closest Hit - group_type: ash::vk::RayTracingShaderGroupTypeKHR::TRIANGLES_HIT_GROUP, - closest_hit_shader: Some(2), - ..Default::default() - }, + PipelineShaderStageCreateInfo::new(vs), + PipelineShaderStageCreateInfo::new(fs), ]; + // We must now create a **pipeline layout** object, which describes the locations and + // types of descriptor sets and push constants used by the shaders in the pipeline. + // + // Multiple pipelines can share a common layout object, which is more efficient. The + // shaders in a pipeline must use a subset of the resources described in its pipeline + // layout, but the pipeline layout is allowed to contain resources that are not present + // in the shaders; they can be used by shaders in other pipelines that share the same + // layout. Thus, it is a good idea to design shaders so that many pipelines have common + // resource locations, which allows them to share pipeline layouts. let layout = PipelineLayout::new( self.context.device().clone(), // Since we only have one pipeline in this example, and thus one pipeline layout, - // we automatically generate the creation info for it from the resources used in the - // shaders. In a real application, you would specify this information manually so that - // you can re-use one layout in multiple pipelines. + // we automatically generate the creation info for it from the resources used in + // the shaders. In a real application, you would specify this information manually + // so that you can re-use one layout in multiple pipelines. PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages) .into_pipeline_layout_create_info(self.context.device().clone()) .unwrap(), ) .unwrap(); - RayTracingPipeline::new( + // We have to indicate which subpass of which render pass this pipeline is going to be + // used in. The pipeline will only be usable from this particular subpass. + let subpass = Subpass::from(render_pass.clone(), 0).unwrap(); + + // Finally, create the pipeline. + GraphicsPipeline::new( self.context.device().clone(), None, - RayTracingPipelineCreateInfo { + GraphicsPipelineCreateInfo { stages: stages.into_iter().collect(), - groups: groups.into_iter().collect(), - max_pipeline_ray_recursion_depth: 1, - - ..RayTracingPipelineCreateInfo::layout(layout) + // How vertex data is read from the vertex buffers into the vertex shader. + vertex_input_state: Some(vertex_input_state), + // How vertices are arranged into primitive shapes. The default primitive shape + // is a triangle. + input_assembly_state: Some(InputAssemblyState::default()), + // How primitives are transformed and clipped to fit the framebuffer. We use a + // resizable viewport, set to draw over the entire window. + viewport_state: Some(ViewportState::default()), + // How polygons are culled and converted into a raster of pixels. The default + // value does not perform any culling. + rasterization_state: Some(RasterizationState::default()), + // How multiple fragment shader samples are converted to a single pixel value. + // The default value does not perform any multisampling. + multisample_state: Some(MultisampleState::default()), + // How pixel values are combined with the values already present in the + // framebuffer. The default value overwrites the old value with the new one, + // without any blending. + color_blend_state: Some(ColorBlendState::with_attachment_states( + subpass.num_color_attachments(), + ColorBlendAttachmentState::default(), + )), + // Dynamic states allows us to specify parts of the pipeline settings when + // recording the command buffer, before we perform drawing. Here, we specify + // that the viewport should be dynamic. + dynamic_state: [DynamicState::Viewport].into_iter().collect(), + subpass: Some(subpass.into()), + ..GraphicsPipelineCreateInfo::layout(layout) }, ) .unwrap() }; - pipeline - .set_debug_utils_object_name("Ray Tracing Pipeline".into()) - .unwrap(); - let shader_binding_table = - ShaderBindingTable::new(self.context.memory_allocator().clone(), &pipeline, 1, 1, 0) - .unwrap(); + // Dynamic viewports allow us to recreate just the viewport when the window is resized. + // Otherwise we would have to recreate the whole pipeline. + let viewport = Viewport { + offset: [0.0, 0.0], + extent: window_size.into(), + depth_range: 0.0..=1.0, + }; // In the `window_event` handler below we are going to submit commands to the GPU. // Submitting a command produces an object that implements the `GpuFuture` trait, which // holds the resources for as long as they are in use by the GPU. - self.rcx = Some(RenderContext { pipeline }); + self.rcx = Some(RenderContext { + render_pass, + framebuffers, + pipeline, + viewport, + }); } fn window_event( @@ -269,7 +362,15 @@ impl ApplicationHandler for App { // Begin rendering by acquiring the gpu future from the window renderer. let previous_frame_end = window_renderer - .acquire(Some(Duration::from_millis(1000)), |_swapchain_images| {}) + .acquire(Some(Duration::from_millis(1000)), |swapchain_images| { + // Whenever the window resizes we need to recreate everything dependent + // on the window size. In this example that + // includes the swapchain, the framebuffers + // and the dynamic state viewport. + rcx.framebuffers = + window_size_dependent_setup(swapchain_images, &rcx.render_pass); + rcx.viewport.extent = window_size.into(); + }) .unwrap(); // In order to draw, we have to record a *command buffer*. The command buffer @@ -288,9 +389,50 @@ impl ApplicationHandler for App { ) .unwrap(); + builder + // Before we can draw, we have to *enter a render pass*. + .begin_render_pass( + RenderPassBeginInfo { + // A list of values to clear the attachments with. This list contains + // one item for each attachment in the render pass. In this case, there + // is only one attachment, and we clear it with a blue color. + // + // Only attachments that have `AttachmentLoadOp::Clear` are provided + // with clear values, any others should use `None` as the clear value. + clear_values: vec![Some([0.0, 0.0, 1.0, 1.0].into())], + + ..RenderPassBeginInfo::framebuffer( + rcx.framebuffers[window_renderer.image_index() as usize].clone(), + ) + }, + SubpassBeginInfo { + // The contents of the first (and only) subpass. This can be either + // `Inline` or `SecondaryCommandBuffers`. The latter is a bit more + // advanced and is not covered here. + contents: SubpassContents::Inline, + ..Default::default() + }, + ) + .unwrap() + // We are now inside the first subpass of the render pass. + // + // TODO: Document state setting and how it affects subsequent draw commands. + .set_viewport(0, [rcx.viewport.clone()].into_iter().collect()) + .unwrap() + .bind_pipeline_graphics(rcx.pipeline.clone()) + .unwrap() + .bind_vertex_buffers(0, self.vertex_buffer.clone()) + .unwrap(); + // We add a draw command. unsafe { builder.draw(self.vertex_buffer.len() as u32, 1, 0, 0) }.unwrap(); + builder + // We leave the render pass. Note that if we had multiple subpasses we could + // have called `next_subpass` to jump to the next subpass. + .end_render_pass(Default::default()) + .unwrap(); + // Finish recording the command buffer by calling `end`. let command_buffer = builder.build().unwrap(); @@ -327,3 +469,23 @@ struct MyVertex { #[format(R32G32_SFLOAT)] position: [f32; 2], } + +/// This function is called once during initialization, then again whenever the window is resized. +fn window_size_dependent_setup( + swapchain_images: &[Arc], + render_pass: &Arc, +) -> Vec> { + swapchain_images + .iter() + .map(|swapchain_image| { + Framebuffer::new( + render_pass.clone(), + FramebufferCreateInfo { + attachments: vec![swapchain_image.clone()], + ..Default::default() + }, + ) + .unwrap() + }) + .collect::>() +} diff --git a/vulkano-taskgraph/src/command_buffer/commands/bind_push.rs b/vulkano-taskgraph/src/command_buffer/commands/bind_push.rs index a15a0a997f..17a98b5029 100644 --- a/vulkano-taskgraph/src/command_buffer/commands/bind_push.rs +++ b/vulkano-taskgraph/src/command_buffer/commands/bind_push.rs @@ -9,7 +9,9 @@ use vulkano::{ self, buffer::{Buffer, BufferContents, IndexType}, device::DeviceOwned, - pipeline::{ComputePipeline, GraphicsPipeline, PipelineLayout}, + pipeline::{ + ray_tracing::RayTracingPipeline, ComputePipeline, GraphicsPipeline, PipelineLayout, + }, DeviceSize, Version, VulkanObject, }; @@ -115,6 +117,31 @@ impl RecordingCommandBuffer<'_> { self } + pub unsafe fn bind_pipeline_ray_tracing( + &mut self, + pipeline: &Arc, + ) -> Result<&mut Self> { + Ok(unsafe { self.bind_pipeline_ray_tracing_unchecked(pipeline) }) + } + + pub unsafe fn bind_pipeline_ray_tracing_unchecked( + &mut self, + pipeline: &Arc, + ) -> &mut Self { + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_bind_pipeline)( + self.handle(), + vk::PipelineBindPoint::RAY_TRACING_KHR, + pipeline.handle(), + ) + }; + + self.death_row.push(pipeline.clone()); + + self + } + /// Binds vertex buffers for future draw calls. pub unsafe fn bind_vertex_buffers( &mut self, diff --git a/vulkano-taskgraph/src/command_buffer/commands/pipeline.rs b/vulkano-taskgraph/src/command_buffer/commands/pipeline.rs index e87d112b6c..40d2f34878 100644 --- a/vulkano-taskgraph/src/command_buffer/commands/pipeline.rs +++ b/vulkano-taskgraph/src/command_buffer/commands/pipeline.rs @@ -7,7 +7,10 @@ use vulkano::command_buffer::{ DispatchIndirectCommand, DrawIndexedIndirectCommand, DrawIndirectCommand, DrawMeshTasksIndirectCommand, }; -use vulkano::{buffer::Buffer, device::DeviceOwned, DeviceSize, Version, VulkanObject}; +use vulkano::{ + buffer::Buffer, device::DeviceOwned, pipeline::ray_tracing::ShaderBindingTable, DeviceSize, + Version, VulkanObject, +}; /// # Commands to execute a bound pipeline /// @@ -658,4 +661,38 @@ impl RecordingCommandBuffer<'_> { self } + + pub unsafe fn trace_rays( + &mut self, + shader_binding_table: &ShaderBindingTable, + width: u32, + height: u32, + depth: u32, + ) -> Result<&mut Self> { + Ok(unsafe { self.trace_rays_unchecked(shader_binding_table, width, height, depth) }) + } + + pub unsafe fn trace_rays_unchecked( + &mut self, + shader_binding_table: &ShaderBindingTable, + width: u32, + height: u32, + depth: u32, + ) -> &mut Self { + let fns = self.device().fns(); + unsafe { + (fns.khr_ray_tracing_pipeline.cmd_trace_rays_khr)( + self.handle(), + shader_binding_table.raygen(), + shader_binding_table.miss(), + shader_binding_table.hit(), + shader_binding_table.callable(), + width, + height, + depth, + ) + }; + + self + } } diff --git a/vulkano-taskgraph/src/resource.rs b/vulkano-taskgraph/src/resource.rs index 3613453bda..c1ac038cec 100644 --- a/vulkano-taskgraph/src/resource.rs +++ b/vulkano-taskgraph/src/resource.rs @@ -1641,12 +1641,12 @@ access_types! { // } // TODO: - // RayTracingShaderStorageWrite { - // stage_mask: RAY_TRACING_SHADER, - // access_mask: SHADER_STORAGE_WRITE, - // image_layout: General, - // valid_for: BUFFER | IMAGE, - // } + RayTracingShaderStorageWrite { + stage_mask: RAY_TRACING_SHADER, + access_mask: SHADER_STORAGE_WRITE, + image_layout: General, + valid_for: BUFFER | IMAGE, + } // TODO: // RayTracingShaderBindingTableRead { diff --git a/vulkano/src/command_buffer/commands/pipeline.rs b/vulkano/src/command_buffer/commands/pipeline.rs index 17a09cb3de..007c58f56b 100644 --- a/vulkano/src/command_buffer/commands/pipeline.rs +++ b/vulkano/src/command_buffer/commands/pipeline.rs @@ -1630,16 +1630,6 @@ impl AutoCommandBufferBuilder { self } - fn validate_trace_rays( - &self, - shader_binding_table: &ShaderBindingTable, - width: u32, - height: u32, - depth: u32, - ) -> Result<(), Box> { - todo!() - } - fn validate_pipeline_descriptor_sets( &self, vuid_type: VUIDType, @@ -5024,10 +5014,6 @@ impl RecordingCommandBuffer { Ok(self.trace_rays_unchecked(shader_binding_table, width, height, depth)) } - fn validate_trace_rays(&self) -> Result<(), Box> { - todo!() - } - #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] pub unsafe fn trace_rays_unchecked( &mut self, diff --git a/vulkano/src/pipeline/ray_tracing/mod.rs b/vulkano/src/pipeline/ray_tracing/mod.rs index 18178daf83..809d354c6e 100644 --- a/vulkano/src/pipeline/ray_tracing/mod.rs +++ b/vulkano/src/pipeline/ray_tracing/mod.rs @@ -14,7 +14,7 @@ use crate::{ DeviceAlignment, }, shader::DescriptorBindingRequirements, - Validated, ValidationError, VulkanError, VulkanObject, + Validated, VulkanError, VulkanObject, }; use super::{ @@ -52,14 +52,6 @@ impl RayTracingPipeline { unsafe { Ok(Self::new_unchecked(device, cache, create_info)?) } } - fn validate_new( - device: &Device, - cache: Option<&PipelineCache>, - create_info: &RayTracingPipelineCreateInfo, - ) -> Result<(), Box> { - todo!() - } - #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] pub unsafe fn new_unchecked( device: Arc, From db68cff068610e23fcfb37ddb04312f1b582fd86 Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Sun, 24 Nov 2024 14:46:14 +0800 Subject: [PATCH 05/26] working example --- Cargo.lock | 1 + examples/triangle-raytracing/Cargo.toml | 1 + examples/triangle-raytracing/main.rs | 80 +++++++++++++++---------- examples/triangle-raytracing/scene.rs | 27 +++++++-- 4 files changed, 73 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8fa53b60ae..ece1cb6559 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1737,6 +1737,7 @@ name = "triangle-raytracing" version = "0.0.0" dependencies = [ "ash", + "glam", "vulkano", "vulkano-shaders", "vulkano-taskgraph", diff --git a/examples/triangle-raytracing/Cargo.toml b/examples/triangle-raytracing/Cargo.toml index f1666e0892..f13090a9f9 100644 --- a/examples/triangle-raytracing/Cargo.toml +++ b/examples/triangle-raytracing/Cargo.toml @@ -17,3 +17,4 @@ vulkano-shaders = { workspace = true } vulkano-taskgraph = { workspace = true } winit = { workspace = true, default-features = true } ash = { workspace = true } +glam = { workspace = true } diff --git a/examples/triangle-raytracing/main.rs b/examples/triangle-raytracing/main.rs index ad3d361cf1..604ea8c018 100644 --- a/examples/triangle-raytracing/main.rs +++ b/examples/triangle-raytracing/main.rs @@ -12,16 +12,15 @@ use vulkano::{ }, }, device::{ - physical::PhysicalDeviceType, Device, DeviceCreateInfo, DeviceExtensions, Queue, - QueueCreateInfo, QueueFlags, + physical::PhysicalDeviceType, Device, DeviceCreateInfo, DeviceExtensions, DeviceFeatures, + Queue, QueueCreateInfo, QueueFlags, }, - format::NumericFormat, - image::ImageUsage, + image::{ImageFormatInfo, ImageUsage}, instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, InstanceExtensions}, memory::allocator::StandardMemoryAllocator, pipeline::{layout::PipelineLayoutCreateInfo, PipelineLayout}, shader::ShaderStages, - swapchain::{ColorSpace, Surface, Swapchain, SwapchainCreateInfo}, + swapchain::{Surface, Swapchain, SwapchainCreateInfo}, Validated, Version, VulkanError, VulkanLibrary, }; use vulkano_taskgraph::{ @@ -59,7 +58,6 @@ struct App { pub struct RenderContext { window: Arc, swapchain_id: Id, - pipeline_layout: Arc, recreate_swapchain: bool, task_graph: ExecutableTaskGraph, scene_node_id: NodeId, @@ -76,8 +74,10 @@ impl App { flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, enabled_extensions: InstanceExtensions { ext_debug_utils: true, + ext_swapchain_colorspace: true, ..required_extensions }, + enabled_layers: vec!["VK_LAYER_KHRONOS_validation".to_owned()], ..Default::default() }, ) @@ -118,12 +118,6 @@ impl App { }) .unwrap(); - println!( - "Using device: {} (type: {:?})", - physical_device.properties().device_name, - physical_device.properties().device_type, - ); - let (device, mut queues) = Device::new( physical_device, DeviceCreateInfo { @@ -132,6 +126,13 @@ impl App { queue_family_index, ..Default::default() }], + enabled_features: DeviceFeatures { + acceleration_structure: true, + ray_tracing_pipeline: true, + buffer_device_address: true, + synchronization2: true, + ..Default::default() + }, ..Default::default() }, ) @@ -164,24 +165,45 @@ impl ApplicationHandler for App { let surface = Surface::from_window(self.instance.clone(), window.clone()).unwrap(); let window_size = window.inner_size(); - let swapchain_format; + let physical_device = self.device.physical_device(); + let supported_surface_formats = physical_device + .surface_formats(&surface, Default::default()) + .unwrap(); + + // For each supported format, check if it is supported for storage images + let supported_storage_formats = supported_surface_formats + .into_iter() + .filter(|(format, _)| { + physical_device + .image_format_properties(ImageFormatInfo { + format: *format, + usage: ImageUsage::STORAGE, + ..Default::default() + }) + .unwrap() + .is_some() + }) + .collect::>(); + println!("Supported storage formats: {:?}", supported_storage_formats); + + println!( + "Using device: {} (type: {:?})", + physical_device.properties().device_name, + physical_device.properties().device_type, + ); + let swapchain_id = { let surface_capabilities = self .device .physical_device() .surface_capabilities(&surface, Default::default()) .unwrap(); - (swapchain_format, _) = self - .device - .physical_device() - .surface_formats(&surface, Default::default()) - .unwrap() - .into_iter() - .find(|&(format, color_space)| { - format.numeric_format_color() == Some(NumericFormat::SRGB) - && color_space == ColorSpace::SrgbNonLinear - }) + + let (swapchain_format, swapchain_color_space) = supported_storage_formats + .get(0) + .map(|(format, color_space)| (*format, *color_space)) .unwrap(); + println!("Using swapchain format: {:?}", swapchain_format); self.resources .create_swapchain( @@ -191,7 +213,8 @@ impl ApplicationHandler for App { min_image_count: surface_capabilities.min_image_count.max(3), image_format: swapchain_format, image_extent: window.inner_size().into(), - image_usage: ImageUsage::COLOR_ATTACHMENT, + image_usage: ImageUsage::STORAGE, + image_color_space: swapchain_color_space, composite_alpha: surface_capabilities .supported_composite_alpha .into_iter() @@ -266,10 +289,7 @@ impl ApplicationHandler for App { Default::default(), )); - let memory_allocator = Arc::new(StandardMemoryAllocator::new( - self.device.clone(), - Default::default(), - )); + let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(self.device.clone())); let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new( self.device.clone(), @@ -297,7 +317,7 @@ impl ApplicationHandler for App { .image_access( virtual_swapchain_id.current_image_id(), AccessType::RayTracingShaderStorageWrite, - ImageLayoutType::Optimal, + ImageLayoutType::General, ) .build(); @@ -315,7 +335,6 @@ impl ApplicationHandler for App { window, swapchain_id, virtual_swapchain_id, - pipeline_layout, recreate_swapchain: false, task_graph, scene_node_id, @@ -389,6 +408,7 @@ impl ApplicationHandler for App { panic!("failed to execute next frame: {e:?}"); } } + // panic!() } _ => {} } diff --git a/examples/triangle-raytracing/scene.rs b/examples/triangle-raytracing/scene.rs index c2bcc44421..f682b33594 100644 --- a/examples/triangle-raytracing/scene.rs +++ b/examples/triangle-raytracing/scene.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use glam::{Mat4, Vec3}; use vulkano::{ acceleration_structure::{ AccelerationStructure, AccelerationStructureBuildGeometryInfo, @@ -17,7 +18,7 @@ use vulkano::{ descriptor_set::{ allocator::StandardDescriptorSetAllocator, sys::RawDescriptorSet, WriteDescriptorSet, }, - device::{Device, Queue}, + device::{Device, DeviceOwnedVulkanObject, Queue}, format::Format, image::view::ImageView, memory::allocator::{AllocationCreateInfo, MemoryAllocator, MemoryTypeFilter}, @@ -146,6 +147,9 @@ impl SceneTask { ) .unwrap() }; + pipeline + .set_debug_utils_object_name("Ray Tracing Pipeline".into()) + .unwrap(); let vertices = [ MyVertex { @@ -195,6 +199,13 @@ impl SceneTask { ) }; + let proj = Mat4::perspective_rh_gl(std::f32::consts::FRAC_PI_2, 4.0 / 3.0, 0.01, 100.0); + let view = Mat4::look_at_rh( + Vec3::new(0.0, 0.0, 1.0), + Vec3::new(0.0, 0.0, 0.0), + Vec3::new(0.0, -1.0, 0.0), + ); + let uniform_buffer = Buffer::from_data( memory_allocator.clone(), BufferCreateInfo { @@ -207,9 +218,10 @@ impl SceneTask { ..Default::default() }, raygen::Camera { - projInverse: Default::default(), - viewInverse: Default::default(), - viewProj: Default::default(), + viewInverse: view.inverse().to_cols_array_2d(), + // 90 degree FOV perspective projection + projInverse: proj.inverse().to_cols_array_2d(), + viewProj: (proj * view).to_cols_array_2d(), }, ) .unwrap(); @@ -276,8 +288,8 @@ impl Task for SceneTask { let image_index = swapchain_state.current_image_index().unwrap(); cbf.as_raw().bind_descriptor_sets( - PipelineBindPoint::Graphics, - &rcx.pipeline_layout, + PipelineBindPoint::RayTracing, // Changed from Graphics to RayTracing + &self.pipeline_layout, // Changed from rcx.pipeline_layout to self.pipeline_layout 0, &[ &self.descriptor_set_0, @@ -296,6 +308,9 @@ impl Task for SceneTask { cbf.destroy_object(descriptor_set.clone()); cbf.destroy_object(image_view.clone()); } + cbf.destroy_object(self.blas.clone()); + cbf.destroy_object(self.tlas.clone()); + cbf.destroy_object(self.uniform_buffer.clone().into()); cbf.destroy_object(self.descriptor_set_0.clone()); Ok(()) From e1d7da7faeea907c814c82e9a5b5c58154bf3698 Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Sun, 24 Nov 2024 21:32:50 +0800 Subject: [PATCH 06/26] refactor --- examples/triangle-raytracing/main.rs | 3 +- examples/triangle-raytracing/scene.rs | 144 +++++++++----------------- 2 files changed, 49 insertions(+), 98 deletions(-) diff --git a/examples/triangle-raytracing/main.rs b/examples/triangle-raytracing/main.rs index 604ea8c018..0a81bbb3c2 100644 --- a/examples/triangle-raytracing/main.rs +++ b/examples/triangle-raytracing/main.rs @@ -213,7 +213,7 @@ impl ApplicationHandler for App { min_image_count: surface_capabilities.min_image_count.max(3), image_format: swapchain_format, image_extent: window.inner_size().into(), - image_usage: ImageUsage::STORAGE, + image_usage: ImageUsage::STORAGE | ImageUsage::COLOR_ATTACHMENT, image_color_space: swapchain_color_space, composite_alpha: surface_capabilities .supported_composite_alpha @@ -408,7 +408,6 @@ impl ApplicationHandler for App { panic!("failed to execute next frame: {e:?}"); } } - // panic!() } _ => {} } diff --git a/examples/triangle-raytracing/scene.rs b/examples/triangle-raytracing/scene.rs index f682b33594..88da3b026d 100644 --- a/examples/triangle-raytracing/scene.rs +++ b/examples/triangle-raytracing/scene.rs @@ -1,4 +1,5 @@ use std::sync::Arc; +use std::{iter, mem::size_of}; use glam::{Mat4, Vec3}; use vulkano::{ @@ -219,7 +220,6 @@ impl SceneTask { }, raygen::Camera { viewInverse: view.inverse().to_cols_array_2d(), - // 90 degree FOV perspective projection projInverse: proj.inverse().to_cols_array_2d(), viewProj: (proj * view).to_cols_array_2d(), }, @@ -282,7 +282,7 @@ impl Task for SceneTask { &self, cbf: &mut RecordingCommandBuffer<'_>, tcx: &mut TaskContext<'_>, - rcx: &Self::World, + _rcx: &Self::World, ) -> TaskResult { let swapchain_state = tcx.swapchain(self.virtual_swapchain_id)?; let image_index = swapchain_state.current_image_index().unwrap(); @@ -346,8 +346,10 @@ fn window_size_dependent_setup( swapchain_image_sets } -unsafe fn build_acceleration_structure_triangles( - vertex_buffer: Subbuffer<[MyVertex]>, +unsafe fn build_acceleration_structure_common( + geometries: AccelerationStructureGeometries, + primitive_count: u32, + ty: AccelerationStructureType, memory_allocator: Arc, command_buffer_allocator: Arc, device: Arc, @@ -360,22 +362,10 @@ unsafe fn build_acceleration_structure_triangles( ) .unwrap(); - let primitive_count = (vertex_buffer.len() / 3) as u32; - let as_geometry_triangles_data = AccelerationStructureGeometryTrianglesData { - // TODO: Modify constructor? - max_vertex: vertex_buffer.len() as _, - vertex_data: Some(vertex_buffer.into_bytes()), - vertex_stride: size_of::() as _, - ..AccelerationStructureGeometryTrianglesData::new(Format::R32G32B32_SFLOAT) - }; - - let as_geometries = - AccelerationStructureGeometries::Triangles(vec![as_geometry_triangles_data]); - let mut as_build_geometry_info = AccelerationStructureBuildGeometryInfo { mode: BuildAccelerationStructureMode::Build, flags: BuildAccelerationStructureFlags::PREFER_FAST_TRACE, - ..AccelerationStructureBuildGeometryInfo::new(as_geometries) + ..AccelerationStructureBuildGeometryInfo::new(geometries) }; let as_build_sizes_info = device @@ -398,7 +388,7 @@ unsafe fn build_acceleration_structure_triangles( .unwrap(); let as_create_info = AccelerationStructureCreateInfo { - ty: AccelerationStructureType::BottomLevel, + ty, ..AccelerationStructureCreateInfo::new( Buffer::new_slice::( memory_allocator, @@ -413,6 +403,7 @@ unsafe fn build_acceleration_structure_triangles( .unwrap(), ) }; + let acceleration = unsafe { AccelerationStructure::new(device, as_create_info).unwrap() }; as_build_geometry_info.dst_acceleration_structure = Some(acceleration.clone()); @@ -426,7 +417,7 @@ unsafe fn build_acceleration_structure_triangles( builder .build_acceleration_structure( as_build_geometry_info, - [as_build_range_info].into_iter().collect(), + iter::once(as_build_range_info).collect(), ) .unwrap(); @@ -443,23 +434,43 @@ unsafe fn build_acceleration_structure_triangles( acceleration } -unsafe fn build_top_level_acceleration_structure( - acceleration_structure: Arc, - allocator: Arc, +unsafe fn build_acceleration_structure_triangles( + vertex_buffer: Subbuffer<[MyVertex]>, + memory_allocator: Arc, command_buffer_allocator: Arc, device: Arc, queue: Arc, ) -> Arc { - let mut builder = AutoCommandBufferBuilder::primary( + let primitive_count = (vertex_buffer.len() / 3) as u32; + let as_geometry_triangles_data = AccelerationStructureGeometryTrianglesData { + max_vertex: vertex_buffer.len() as _, + vertex_data: Some(vertex_buffer.into_bytes()), + vertex_stride: size_of::() as _, + ..AccelerationStructureGeometryTrianglesData::new(Format::R32G32B32_SFLOAT) + }; + + let geometries = AccelerationStructureGeometries::Triangles(vec![as_geometry_triangles_data]); + + build_acceleration_structure_common( + geometries, + primitive_count, + AccelerationStructureType::BottomLevel, + memory_allocator, command_buffer_allocator, - queue.queue_family_index(), - CommandBufferUsage::OneTimeSubmit, + device, + queue, ) - .unwrap(); +} - let primitive_count = 1; +unsafe fn build_top_level_acceleration_structure( + acceleration_structure: Arc, + allocator: Arc, + command_buffer_allocator: Arc, + device: Arc, + queue: Arc, +) -> Arc { let as_instance = AccelerationStructureInstance { - acceleration_structure_reference: acceleration_structure.device_address().into(), // TODO: Need to hold AS + acceleration_structure_reference: acceleration_structure.device_address().into(), ..Default::default() }; @@ -483,74 +494,15 @@ unsafe fn build_top_level_acceleration_structure( AccelerationStructureGeometryInstancesDataType::Values(Some(instance_buffer)), ); - let as_geometries = AccelerationStructureGeometries::Instances(as_geometry_instances_data); - - let mut as_build_geometry_info = AccelerationStructureBuildGeometryInfo { - mode: BuildAccelerationStructureMode::Build, - flags: BuildAccelerationStructureFlags::PREFER_FAST_TRACE, - ..AccelerationStructureBuildGeometryInfo::new(as_geometries) - }; - - let as_build_sizes_info = device - .acceleration_structure_build_sizes( - AccelerationStructureBuildType::Device, - &as_build_geometry_info, - &[primitive_count], - ) - .unwrap(); + let geometries = AccelerationStructureGeometries::Instances(as_geometry_instances_data); - let scratch_buffer = Buffer::new_slice::( - allocator.clone(), - BufferCreateInfo { - usage: BufferUsage::SHADER_DEVICE_ADDRESS | BufferUsage::STORAGE_BUFFER, - ..Default::default() - }, - AllocationCreateInfo::default(), - as_build_sizes_info.build_scratch_size, + build_acceleration_structure_common( + geometries, + 1, + AccelerationStructureType::TopLevel, + allocator, + command_buffer_allocator, + device, + queue, ) - .unwrap(); - - let as_create_info = AccelerationStructureCreateInfo { - ty: AccelerationStructureType::TopLevel, - ..AccelerationStructureCreateInfo::new( - Buffer::new_slice::( - allocator, - BufferCreateInfo { - usage: BufferUsage::ACCELERATION_STRUCTURE_STORAGE - | BufferUsage::SHADER_DEVICE_ADDRESS, - ..Default::default() - }, - AllocationCreateInfo::default(), - as_build_sizes_info.acceleration_structure_size, - ) - .unwrap(), - ) - }; - let acceleration = unsafe { AccelerationStructure::new(device, as_create_info).unwrap() }; - - as_build_geometry_info.dst_acceleration_structure = Some(acceleration.clone()); - as_build_geometry_info.scratch_data = Some(scratch_buffer); - - let as_build_range_info = AccelerationStructureBuildRangeInfo { - primitive_count, - ..Default::default() - }; - - builder - .build_acceleration_structure( - as_build_geometry_info, - [as_build_range_info].into_iter().collect(), - ) - .unwrap(); - builder - .build() - .unwrap() - .execute(queue) - .unwrap() - .then_signal_fence_and_flush() - .unwrap() - .wait(None) - .unwrap(); - - acceleration } From 73a582e811b38e043dede4cab9f614b006c558ab Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Mon, 25 Nov 2024 21:12:46 +0800 Subject: [PATCH 07/26] refactor SBT --- examples/triangle-raytracing/main.rs | 1 - examples/triangle-raytracing/scene.rs | 33 ++- .../src/command_buffer/commands/pipeline.rs | 20 +- .../src/command_buffer/commands/pipeline.rs | 43 ++-- vulkano/src/pipeline/ray_tracing/mod.rs | 193 ++++++++++++------ 5 files changed, 173 insertions(+), 117 deletions(-) diff --git a/examples/triangle-raytracing/main.rs b/examples/triangle-raytracing/main.rs index 0a81bbb3c2..f94b1c0a87 100644 --- a/examples/triangle-raytracing/main.rs +++ b/examples/triangle-raytracing/main.rs @@ -163,7 +163,6 @@ impl ApplicationHandler for App { .unwrap(), ); let surface = Surface::from_window(self.instance.clone(), window.clone()).unwrap(); - let window_size = window.inner_size(); let physical_device = self.device.physical_device(); let supported_surface_formats = physical_device diff --git a/examples/triangle-raytracing/scene.rs b/examples/triangle-raytracing/scene.rs index 88da3b026d..c7cd2901bc 100644 --- a/examples/triangle-raytracing/scene.rs +++ b/examples/triangle-raytracing/scene.rs @@ -117,21 +117,11 @@ impl SceneTask { ]; let groups = [ - RayTracingShaderGroupCreateInfo { - // Raygen - general_shader: Some(0), - ..Default::default() - }, - RayTracingShaderGroupCreateInfo { - // Miss - general_shader: Some(1), - ..Default::default() - }, - RayTracingShaderGroupCreateInfo { - // Closest Hit - group_type: ash::vk::RayTracingShaderGroupTypeKHR::TRIANGLES_HIT_GROUP, + RayTracingShaderGroupCreateInfo::General { general_shader: 0 }, + RayTracingShaderGroupCreateInfo::General { general_shader: 1 }, + RayTracingShaderGroupCreateInfo::TrianglesHit { closest_hit_shader: Some(2), - ..Default::default() + any_hit_shader: None, }, ]; @@ -249,7 +239,7 @@ impl SceneTask { ); let shader_binding_table = - ShaderBindingTable::new(memory_allocator.clone(), &pipeline, 1, 1, 0).unwrap(); + ShaderBindingTable::new(memory_allocator.clone(), &pipeline).unwrap(); SceneTask { descriptor_set_0: Arc::new(descriptor_set_0), @@ -288,8 +278,8 @@ impl Task for SceneTask { let image_index = swapchain_state.current_image_index().unwrap(); cbf.as_raw().bind_descriptor_sets( - PipelineBindPoint::RayTracing, // Changed from Graphics to RayTracing - &self.pipeline_layout, // Changed from rcx.pipeline_layout to self.pipeline_layout + PipelineBindPoint::RayTracing, + &self.pipeline_layout, 0, &[ &self.descriptor_set_0, @@ -302,7 +292,14 @@ impl Task for SceneTask { let extent = self.swapchain_image_sets[0].0.image().extent(); - unsafe { cbf.trace_rays(&self.shader_binding_table, extent[0], extent[1], 1) }?; + unsafe { + cbf.trace_rays( + self.shader_binding_table.addresses(), + extent[0], + extent[1], + 1, + ) + }?; for (image_view, descriptor_set) in self.swapchain_image_sets.iter() { cbf.destroy_object(descriptor_set.clone()); diff --git a/vulkano-taskgraph/src/command_buffer/commands/pipeline.rs b/vulkano-taskgraph/src/command_buffer/commands/pipeline.rs index 40d2f34878..342cddbe6c 100644 --- a/vulkano-taskgraph/src/command_buffer/commands/pipeline.rs +++ b/vulkano-taskgraph/src/command_buffer/commands/pipeline.rs @@ -8,8 +8,8 @@ use vulkano::command_buffer::{ DrawMeshTasksIndirectCommand, }; use vulkano::{ - buffer::Buffer, device::DeviceOwned, pipeline::ray_tracing::ShaderBindingTable, DeviceSize, - Version, VulkanObject, + buffer::Buffer, device::DeviceOwned, pipeline::ray_tracing::ShaderBindingTableAddresses, + DeviceSize, Version, VulkanObject, }; /// # Commands to execute a bound pipeline @@ -664,17 +664,19 @@ impl RecordingCommandBuffer<'_> { pub unsafe fn trace_rays( &mut self, - shader_binding_table: &ShaderBindingTable, + shader_binding_table_addresses: &ShaderBindingTableAddresses, width: u32, height: u32, depth: u32, ) -> Result<&mut Self> { - Ok(unsafe { self.trace_rays_unchecked(shader_binding_table, width, height, depth) }) + Ok(unsafe { + self.trace_rays_unchecked(shader_binding_table_addresses, width, height, depth) + }) } pub unsafe fn trace_rays_unchecked( &mut self, - shader_binding_table: &ShaderBindingTable, + shader_binding_table_addresses: &ShaderBindingTableAddresses, width: u32, height: u32, depth: u32, @@ -683,10 +685,10 @@ impl RecordingCommandBuffer<'_> { unsafe { (fns.khr_ray_tracing_pipeline.cmd_trace_rays_khr)( self.handle(), - shader_binding_table.raygen(), - shader_binding_table.miss(), - shader_binding_table.hit(), - shader_binding_table.callable(), + &shader_binding_table_addresses.raygen, + &shader_binding_table_addresses.miss, + &shader_binding_table_addresses.hit, + &shader_binding_table_addresses.callable, width, height, depth, diff --git a/vulkano/src/command_buffer/commands/pipeline.rs b/vulkano/src/command_buffer/commands/pipeline.rs index 007c58f56b..5f6ba23fb2 100644 --- a/vulkano/src/command_buffer/commands/pipeline.rs +++ b/vulkano/src/command_buffer/commands/pipeline.rs @@ -22,7 +22,7 @@ use crate::{ subpass::PipelineSubpassType, vertex_input::{RequiredVertexInputsVUIDs, VertexInputRate}, }, - ray_tracing::ShaderBindingTable, + ray_tracing::ShaderBindingTableAddresses, DynamicState, GraphicsPipeline, Pipeline, PipelineLayout, }, query::QueryType, @@ -1595,20 +1595,20 @@ impl AutoCommandBufferBuilder { pub unsafe fn trace_rays( &mut self, - shader_binding_table: ShaderBindingTable, + shader_binding_table_addresses: ShaderBindingTableAddresses, width: u32, height: u32, depth: u32, ) -> Result<&mut Self, Box> { // TODO: RayTrace: Validation - Ok(self.trace_rays_unchecked(shader_binding_table, width, height, depth)) + Ok(self.trace_rays_unchecked(shader_binding_table_addresses, width, height, depth)) } #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] pub unsafe fn trace_rays_unchecked( &mut self, - shader_binding_table: ShaderBindingTable, + shader_binding_table_addresses: ShaderBindingTableAddresses, width: u32, height: u32, depth: u32, @@ -1618,13 +1618,9 @@ impl AutoCommandBufferBuilder { let mut used_resources = Vec::new(); self.add_descriptor_sets_resources(&mut used_resources, pipeline); - self.add_shader_binding_table_buffer_resources( - &mut used_resources, - shader_binding_table.buffer(), - ); self.add_command("ray_trace", used_resources, move |out| { - out.trace_rays_unchecked(&shader_binding_table, width, height, depth); + out.trace_rays_unchecked(&shader_binding_table_addresses, width, height, depth); }); self @@ -3752,21 +3748,6 @@ impl AutoCommandBufferBuilder { }, )); } - - fn add_shader_binding_table_buffer_resources( - &self, - used_resources: &mut Vec<(ResourceUseRef2, Resource)>, - sbt_buffer: &Subbuffer<[u8]>, - ) { - used_resources.push(( - ResourceInCommand::ShaderBindingTableBuffer.into(), - Resource::Buffer { - buffer: sbt_buffer.clone(), - range: 0..sbt_buffer.size(), - memory_access: PipelineStageAccessFlags::RayTracingShader_ShaderBindingTableRead, - }, - )); - } } impl RecordingCommandBuffer { @@ -5003,7 +4984,7 @@ impl RecordingCommandBuffer { pub unsafe fn trace_rays( &mut self, - shader_binding_table: &ShaderBindingTable, + shader_binding_table_addresses: &ShaderBindingTableAddresses, width: u32, height: u32, depth: u32, @@ -5011,13 +4992,13 @@ impl RecordingCommandBuffer { // self.validate_trace_ray()?; // TODO: RayTracing: Validation - Ok(self.trace_rays_unchecked(shader_binding_table, width, height, depth)) + Ok(self.trace_rays_unchecked(shader_binding_table_addresses, width, height, depth)) } #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] pub unsafe fn trace_rays_unchecked( &mut self, - shader_binding_table: &ShaderBindingTable, + shader_binding_table_addresses: &ShaderBindingTableAddresses, width: u32, height: u32, depth: u32, @@ -5026,10 +5007,10 @@ impl RecordingCommandBuffer { (fns.khr_ray_tracing_pipeline.cmd_trace_rays_khr)( self.handle(), - shader_binding_table.raygen(), - shader_binding_table.miss(), - shader_binding_table.hit(), - shader_binding_table.callable(), + &shader_binding_table_addresses.raygen, + &shader_binding_table_addresses.miss, + &shader_binding_table_addresses.hit, + &shader_binding_table_addresses.callable, width, height, depth, diff --git a/vulkano/src/pipeline/ray_tracing/mod.rs b/vulkano/src/pipeline/ray_tracing/mod.rs index 809d354c6e..3b716a49d6 100644 --- a/vulkano/src/pipeline/ray_tracing/mod.rs +++ b/vulkano/src/pipeline/ray_tracing/mod.rs @@ -6,14 +6,14 @@ use smallvec::SmallVec; use crate::{ buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer}, - device::{Device, DeviceOwned, DeviceOwnedDebugWrapper, DeviceOwnedVulkanObject}, + device::{Device, DeviceOwned, DeviceOwnedDebugWrapper}, instance::InstanceOwnedDebugWrapper, macros::impl_id_counter, memory::{ allocator::{align_up, AllocationCreateInfo, MemoryAllocator, MemoryTypeFilter}, DeviceAlignment, }, - shader::DescriptorBindingRequirements, + shader::{spirv::ExecutionModel, DescriptorBindingRequirements}, Validated, VulkanError, VulkanObject, }; @@ -153,6 +153,10 @@ impl RayTracingPipeline { pub fn device(&self) -> &Arc { &self.device } + + pub fn flags(&self) -> PipelineCreateFlags { + self.flags + } } impl Pipeline for RayTracingPipeline { @@ -387,30 +391,54 @@ impl RayTracingPipelineCreateInfo { } } -#[derive(Clone, Debug, Default)] -pub struct RayTracingShaderGroupCreateInfo { - pub group_type: ash::vk::RayTracingShaderGroupTypeKHR, // TODO: Custom type - pub general_shader: Option, - pub closest_hit_shader: Option, - pub any_hit_shader: Option, - pub intersection_shader: Option, +/// Enum representing different types of Ray Tracing Shader Groups. +#[derive(Debug, Clone)] +pub enum RayTracingShaderGroupCreateInfo { + General { + general_shader: u32, + }, + ProceduralHit { + closest_hit_shader: Option, + any_hit_shader: Option, + intersection_shader: u32, + }, + TrianglesHit { + closest_hit_shader: Option, + any_hit_shader: Option, + }, } impl RayTracingShaderGroupCreateInfo { pub(crate) fn to_vk(&self) -> ash::vk::RayTracingShaderGroupCreateInfoKHR<'static> { - // We are not using pointers in the struct, so 'static is used. - ash::vk::RayTracingShaderGroupCreateInfoKHR::default() - .ty(self.group_type) - .general_shader(self.general_shader.unwrap_or(ash::vk::SHADER_UNUSED_KHR)) - .closest_hit_shader( - self.closest_hit_shader - .unwrap_or(ash::vk::SHADER_UNUSED_KHR), - ) - .any_hit_shader(self.any_hit_shader.unwrap_or(ash::vk::SHADER_UNUSED_KHR)) - .intersection_shader( - self.intersection_shader - .unwrap_or(ash::vk::SHADER_UNUSED_KHR), - ) + match self { + RayTracingShaderGroupCreateInfo::General { general_shader } => { + ash::vk::RayTracingShaderGroupCreateInfoKHR::default() + .ty(ash::vk::RayTracingShaderGroupTypeKHR::GENERAL) + .general_shader(*general_shader) + .closest_hit_shader(ash::vk::SHADER_UNUSED_KHR) + .any_hit_shader(ash::vk::SHADER_UNUSED_KHR) + .intersection_shader(ash::vk::SHADER_UNUSED_KHR) + } + RayTracingShaderGroupCreateInfo::ProceduralHit { + closest_hit_shader, + any_hit_shader, + intersection_shader, + } => ash::vk::RayTracingShaderGroupCreateInfoKHR::default() + .ty(ash::vk::RayTracingShaderGroupTypeKHR::PROCEDURAL_HIT_GROUP) + .general_shader(ash::vk::SHADER_UNUSED_KHR) + .closest_hit_shader(closest_hit_shader.unwrap_or(ash::vk::SHADER_UNUSED_KHR)) + .any_hit_shader(any_hit_shader.unwrap_or(ash::vk::SHADER_UNUSED_KHR)) + .intersection_shader(*intersection_shader), + RayTracingShaderGroupCreateInfo::TrianglesHit { + closest_hit_shader, + any_hit_shader, + } => ash::vk::RayTracingShaderGroupCreateInfoKHR::default() + .ty(ash::vk::RayTracingShaderGroupTypeKHR::TRIANGLES_HIT_GROUP) + .general_shader(ash::vk::SHADER_UNUSED_KHR) + .closest_hit_shader(closest_hit_shader.unwrap_or(ash::vk::SHADER_UNUSED_KHR)) + .any_hit_shader(any_hit_shader.unwrap_or(ash::vk::SHADER_UNUSED_KHR)) + .intersection_shader(ash::vk::SHADER_UNUSED_KHR), + } } } @@ -432,43 +460,95 @@ pub struct RayTracingPipelineCreateInfoFields2Vk<'a> { pub struct RayTracingPipelineCreateInfoFields3Vk { pub(crate) stages_fields2_vk: SmallVec<[PipelineShaderStageCreateInfoFields2Vk; 5]>, } + +#[derive(Debug, Clone)] +pub struct ShaderBindingTableAddresses { + pub raygen: StridedDeviceAddressRegionKHR, + pub miss: StridedDeviceAddressRegionKHR, + pub hit: StridedDeviceAddressRegionKHR, + pub callable: StridedDeviceAddressRegionKHR, +} + #[derive(Debug, Clone)] pub struct ShaderBindingTable { - raygen: StridedDeviceAddressRegionKHR, - miss: StridedDeviceAddressRegionKHR, - hit: StridedDeviceAddressRegionKHR, - callable: StridedDeviceAddressRegionKHR, - buffer: Subbuffer<[u8]>, + addresses: ShaderBindingTableAddresses, + _buffer: Subbuffer<[u8]>, } impl ShaderBindingTable { - pub fn raygen(&self) -> &StridedDeviceAddressRegionKHR { - &self.raygen - } - - pub fn miss(&self) -> &StridedDeviceAddressRegionKHR { - &self.miss - } - - pub fn hit(&self) -> &StridedDeviceAddressRegionKHR { - &self.hit - } - - pub fn callable(&self) -> &StridedDeviceAddressRegionKHR { - &self.callable - } - - pub(crate) fn buffer(&self) -> &Subbuffer<[u8]> { - &self.buffer + pub fn addresses(&self) -> &ShaderBindingTableAddresses { + &self.addresses } pub fn new( allocator: Arc, ray_tracing_pipeline: &RayTracingPipeline, - miss_shader_count: u64, - hit_shader_count: u64, - callable_shader_count: u64, ) -> Result> { + let mut miss_shader_count: u64 = 0; + let mut hit_shader_count: u64 = 0; + let mut callable_shader_count: u64 = 0; + + let get_shader_type = |shader: u32| { + ray_tracing_pipeline.stages()[shader as usize] + .entry_point + .info() + .execution_model + }; + + for group in ray_tracing_pipeline.groups() { + match group { + RayTracingShaderGroupCreateInfo::General { general_shader } => { + match get_shader_type(*general_shader) { + ExecutionModel::RayGenerationKHR => {} + ExecutionModel::MissKHR => miss_shader_count += 1, + ExecutionModel::CallableKHR => callable_shader_count += 1, + _ => { + panic!("Unexpected shader type in general shader group"); + } + } + } + RayTracingShaderGroupCreateInfo::ProceduralHit { + intersection_shader, + any_hit_shader, + closest_hit_shader, + } => { + if get_shader_type(*intersection_shader) != ExecutionModel::IntersectionKHR { + panic!("Unexpected shader type in procedural hit shader group"); + } + if let Some(any_hit_shader) = any_hit_shader { + if get_shader_type(*any_hit_shader) != ExecutionModel::AnyHitKHR { + panic!("Unexpected shader type in procedural hit shader group"); + } + } + if let Some(closest_hit_shader) = closest_hit_shader { + if get_shader_type(*closest_hit_shader) != ExecutionModel::ClosestHitKHR { + panic!("Unexpected shader type in procedural hit shader group"); + } + } + hit_shader_count += 1; + } + RayTracingShaderGroupCreateInfo::TrianglesHit { + any_hit_shader, + closest_hit_shader, + } => { + if any_hit_shader.is_none() && closest_hit_shader.is_none() { + panic!("Triangles hit shader group must have at least one hit shader"); + } + if let Some(any_hit_shader) = any_hit_shader { + if get_shader_type(*any_hit_shader) != ExecutionModel::AnyHitKHR { + panic!("Unexpected shader type in triangles hit shader group"); + } + } + if let Some(closest_hit_shader) = closest_hit_shader { + if get_shader_type(*closest_hit_shader) != ExecutionModel::ClosestHitKHR { + panic!("Unexpected shader type in triangles hit shader group"); + } + } + hit_shader_count += 1; + } + } + } + let handle_data = ray_tracing_pipeline .device() .get_ray_tracing_shader_group_handles( @@ -536,10 +616,6 @@ impl ShaderBindingTable { raygen.size + miss.size + hit.size + callable.size, ) .expect("todo: raytracing: better error type"); - sbt_buffer - .buffer() - .set_debug_utils_object_name("Shader Binding Table Buffer".into()) - .unwrap(); raygen.device_address = sbt_buffer.buffer().device_address().unwrap().get(); miss.device_address = raygen.device_address + raygen.size; @@ -572,14 +648,15 @@ impl ShaderBindingTable { offset += callable.stride as usize; } } - // TODO: RayTracing: Add unit test for copy algorithm Ok(Self { - raygen, - miss, - hit, - callable, - buffer: sbt_buffer, + addresses: ShaderBindingTableAddresses { + raygen, + miss, + hit, + callable, + }, + _buffer: sbt_buffer, }) } } From e82e140fd880dfc3ed3da3c8b04893fe658d6877 Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Mon, 25 Nov 2024 23:08:23 +0800 Subject: [PATCH 08/26] example for AutoCommandBufferBuilder --- Cargo.lock | 11 + examples/triangle-raytracing-auto/Cargo.toml | 19 + examples/triangle-raytracing-auto/main.rs | 407 +++++++++++++++ .../triangle-raytracing-auto/raytrace.rchit | 10 + .../triangle-raytracing-auto/raytrace.rgen | 43 ++ .../triangle-raytracing-auto/raytrace.rmiss | 6 + examples/triangle-raytracing-auto/scene.rs | 467 ++++++++++++++++++ .../src/command_buffer/commands/bind_push.rs | 25 + 8 files changed, 988 insertions(+) create mode 100644 examples/triangle-raytracing-auto/Cargo.toml create mode 100644 examples/triangle-raytracing-auto/main.rs create mode 100644 examples/triangle-raytracing-auto/raytrace.rchit create mode 100644 examples/triangle-raytracing-auto/raytrace.rgen create mode 100644 examples/triangle-raytracing-auto/raytrace.rmiss create mode 100644 examples/triangle-raytracing-auto/scene.rs diff --git a/Cargo.lock b/Cargo.lock index ece1cb6559..7288b14370 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1744,6 +1744,17 @@ dependencies = [ "winit", ] +[[package]] +name = "triangle-raytracing-auto" +version = "0.0.0" +dependencies = [ + "ash", + "glam", + "vulkano", + "vulkano-shaders", + "winit", +] + [[package]] name = "triangle-util" version = "0.0.0" diff --git a/examples/triangle-raytracing-auto/Cargo.toml b/examples/triangle-raytracing-auto/Cargo.toml new file mode 100644 index 0000000000..ffe7a58f8e --- /dev/null +++ b/examples/triangle-raytracing-auto/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "triangle-raytracing-auto" +version = "0.0.0" +edition = "2021" +publish = false + +[[bin]] +name = "triangle-raytracing-auto" +path = "main.rs" +test = false +bench = false +doc = false + +[dependencies] +vulkano = { workspace = true, default-features = true } +vulkano-shaders = { workspace = true } +winit = { workspace = true, default-features = true } +ash = { workspace = true } +glam = { workspace = true } diff --git a/examples/triangle-raytracing-auto/main.rs b/examples/triangle-raytracing-auto/main.rs new file mode 100644 index 0000000000..05193ac6d0 --- /dev/null +++ b/examples/triangle-raytracing-auto/main.rs @@ -0,0 +1,407 @@ +// TODO: document + +use scene::SceneTask; +use std::{error::Error, sync::Arc}; +use vulkano::{ + command_buffer::{ + allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, + }, + descriptor_set::{ + allocator::StandardDescriptorSetAllocator, + layout::{ + DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo, + DescriptorType, + }, + }, + device::{ + physical::PhysicalDeviceType, Device, DeviceCreateInfo, DeviceExtensions, DeviceFeatures, + Queue, QueueCreateInfo, QueueFlags, + }, + image::{ImageFormatInfo, ImageUsage}, + instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, InstanceExtensions}, + memory::allocator::StandardMemoryAllocator, + pipeline::{layout::PipelineLayoutCreateInfo, PipelineLayout}, + shader::ShaderStages, + swapchain::{ + acquire_next_image, Surface, Swapchain, SwapchainCreateInfo, SwapchainPresentInfo, + }, + sync::{self, GpuFuture}, + Version, VulkanLibrary, +}; +use winit::{ + application::ApplicationHandler, + event::WindowEvent, + event_loop::{ActiveEventLoop, EventLoop}, + window::{Window, WindowId}, +}; + +mod scene; + +fn main() -> Result<(), impl Error> { + let event_loop = EventLoop::new().unwrap(); + let mut app = App::new(&event_loop); + + event_loop.run_app(&mut app) +} + +struct App { + instance: Arc, + device: Arc, + queue: Arc, + rcx: Option, + command_buffer_allocator: Arc, +} + +pub struct RenderContext { + window: Arc, + swapchain: Arc, + recreate_swapchain: bool, + scene_task: SceneTask, + previous_frame_end: Option>, +} + +impl App { + fn new(event_loop: &EventLoop<()>) -> Self { + let library = VulkanLibrary::new().unwrap(); + let required_extensions = Surface::required_extensions(event_loop).unwrap(); + let instance = Instance::new( + library, + InstanceCreateInfo { + flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, + enabled_extensions: InstanceExtensions { + ext_debug_utils: true, + ext_swapchain_colorspace: true, + ..required_extensions + }, + enabled_layers: vec!["VK_LAYER_KHRONOS_validation".to_owned()], + ..Default::default() + }, + ) + .unwrap(); + + let device_extensions = DeviceExtensions { + khr_swapchain: true, + khr_ray_tracing_pipeline: true, + khr_ray_tracing_maintenance1: true, + khr_synchronization2: true, + khr_deferred_host_operations: true, + khr_acceleration_structure: true, + ..DeviceExtensions::empty() + }; + let (physical_device, queue_family_index) = instance + .enumerate_physical_devices() + .unwrap() + .filter(|p| p.api_version() >= Version::V1_3) + .filter(|p| p.supported_extensions().contains(&device_extensions)) + .filter_map(|p| { + p.queue_family_properties() + .iter() + .enumerate() + .position(|(i, q)| { + q.queue_flags + .contains(QueueFlags::GRAPHICS | QueueFlags::COMPUTE) + && p.presentation_support(i as u32, event_loop).unwrap() + }) + .map(|i| (p, i as u32)) + }) + .min_by_key(|(p, _)| match p.properties().device_type { + PhysicalDeviceType::DiscreteGpu => 0, + PhysicalDeviceType::IntegratedGpu => 1, + PhysicalDeviceType::VirtualGpu => 2, + PhysicalDeviceType::Cpu => 3, + PhysicalDeviceType::Other => 4, + _ => 5, + }) + .unwrap(); + + let (device, mut queues) = Device::new( + physical_device, + DeviceCreateInfo { + enabled_extensions: device_extensions, + queue_create_infos: vec![QueueCreateInfo { + queue_family_index, + ..Default::default() + }], + enabled_features: DeviceFeatures { + acceleration_structure: true, + ray_tracing_pipeline: true, + buffer_device_address: true, + synchronization2: true, + ..Default::default() + }, + ..Default::default() + }, + ) + .unwrap(); + + let queue = queues.next().unwrap(); + + let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new( + device.clone(), + Default::default(), + )); + + App { + instance, + device, + queue, + rcx: None, + command_buffer_allocator, + } + } +} + +impl ApplicationHandler for App { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let window = Arc::new( + event_loop + .create_window(Window::default_attributes()) + .unwrap(), + ); + let surface = Surface::from_window(self.instance.clone(), window.clone()).unwrap(); + + let physical_device = self.device.physical_device(); + let supported_surface_formats = physical_device + .surface_formats(&surface, Default::default()) + .unwrap(); + + // For each supported format, check if it is supported for storage images + let supported_storage_formats = supported_surface_formats + .into_iter() + .filter(|(format, _)| { + physical_device + .image_format_properties(ImageFormatInfo { + format: *format, + usage: ImageUsage::STORAGE, + ..Default::default() + }) + .unwrap() + .is_some() + }) + .collect::>(); + + println!( + "Using device: {} (type: {:?})", + physical_device.properties().device_name, + physical_device.properties().device_type, + ); + + let (swapchain, images) = { + let surface_capabilities = self + .device + .physical_device() + .surface_capabilities(&surface, Default::default()) + .unwrap(); + + let (swapchain_format, swapchain_color_space) = supported_storage_formats + .get(0) + .map(|(format, color_space)| (*format, *color_space)) + .unwrap(); + Swapchain::new( + self.device.clone(), + surface.clone(), + SwapchainCreateInfo { + min_image_count: surface_capabilities.min_image_count.max(2), + image_format: swapchain_format, + image_color_space: swapchain_color_space, + image_extent: window.inner_size().into(), + image_usage: ImageUsage::COLOR_ATTACHMENT | ImageUsage::STORAGE, + composite_alpha: surface_capabilities + .supported_composite_alpha + .into_iter() + .next() + .unwrap(), + ..Default::default() + }, + ) + .unwrap() + }; + + let pipeline_layout = PipelineLayout::new( + self.device.clone(), + PipelineLayoutCreateInfo { + set_layouts: vec![ + DescriptorSetLayout::new( + self.device.clone(), + DescriptorSetLayoutCreateInfo { + bindings: [ + ( + 0, + DescriptorSetLayoutBinding { + stages: ShaderStages::RAYGEN, + ..DescriptorSetLayoutBinding::descriptor_type( + DescriptorType::AccelerationStructure, + ) + }, + ), + ( + 1, + DescriptorSetLayoutBinding { + stages: ShaderStages::RAYGEN, + ..DescriptorSetLayoutBinding::descriptor_type( + DescriptorType::UniformBuffer, + ) + }, + ), + ] + .into_iter() + .collect(), + ..Default::default() + }, + ) + .unwrap(), + DescriptorSetLayout::new( + self.device.clone(), + DescriptorSetLayoutCreateInfo { + bindings: [( + 0, + DescriptorSetLayoutBinding { + stages: ShaderStages::RAYGEN, + ..DescriptorSetLayoutBinding::descriptor_type( + DescriptorType::StorageImage, + ) + }, + )] + .into_iter() + .collect(), + ..Default::default() + }, + ) + .unwrap(), + ], + push_constant_ranges: vec![], + ..Default::default() + }, + ) + .unwrap(); + + let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new( + self.device.clone(), + Default::default(), + )); + + let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(self.device.clone())); + + let scene_task = SceneTask::new( + &self, + &images, + pipeline_layout, + descriptor_set_allocator.clone(), + memory_allocator.clone(), + self.command_buffer_allocator.clone(), + ); + self.rcx = Some(RenderContext { + window, + swapchain, + recreate_swapchain: false, + previous_frame_end: None, + scene_task, + }); + } + + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + _window_id: WindowId, + event: WindowEvent, + ) { + let rcx = self.rcx.as_mut().unwrap(); + + match event { + WindowEvent::CloseRequested => { + event_loop.exit(); + } + WindowEvent::Resized(_) => { + rcx.recreate_swapchain = true; + } + WindowEvent::RedrawRequested => { + let window_size = rcx.window.inner_size(); + + if window_size.width == 0 || window_size.height == 0 { + return; + } + + // Cleanup previous frame + if let Some(previous_frame_end) = rcx.previous_frame_end.as_mut() { + previous_frame_end.cleanup_finished(); + } + + // Recreate swapchain if needed + if rcx.recreate_swapchain { + let (new_swapchain, new_images) = + match rcx.swapchain.recreate(SwapchainCreateInfo { + image_extent: window_size.into(), + ..rcx.swapchain.create_info() + }) { + Ok(r) => r, + Err(e) => panic!("Failed to recreate swapchain: {e:?}"), + }; + + rcx.swapchain = new_swapchain; + rcx.scene_task.handle_resize(&new_images); + rcx.recreate_swapchain = false; + } + + // Acquire next image + let (image_index, suboptimal, acquire_future) = + match acquire_next_image(rcx.swapchain.clone(), None) { + Ok(r) => r, + Err(e) => { + eprintln!("Failed to acquire next image: {e:?}"); + rcx.recreate_swapchain = true; + return; + } + }; + + if suboptimal { + rcx.recreate_swapchain = true; + } + + let mut builder = AutoCommandBufferBuilder::primary( + self.command_buffer_allocator.clone(), + self.queue.queue_family_index(), + CommandBufferUsage::OneTimeSubmit, + ) + .unwrap(); + + rcx.scene_task.record_commands(image_index, &mut builder); + + let command_buffer = builder.build().unwrap(); + + let future = rcx + .previous_frame_end + .take() + .unwrap_or_else(|| { + Box::new(sync::now(self.device.clone())) as Box + }) + .join(acquire_future) + .then_execute(self.queue.clone(), command_buffer) + .unwrap() + .then_swapchain_present( + self.queue.clone(), + SwapchainPresentInfo::swapchain_image_index( + rcx.swapchain.clone(), + image_index, + ), + ) + .then_signal_fence_and_flush(); + + match future { + Ok(future) => { + rcx.previous_frame_end = Some(Box::new(future) as Box); + } + Err(e) => { + println!("Failed to flush future: {e:?}"); + rcx.previous_frame_end = Some(Box::new(sync::now(self.device.clone()))); + } + } + } + _ => {} + } + } + + fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { + let rcx = self.rcx.as_mut().unwrap(); + rcx.window.request_redraw(); + } +} diff --git a/examples/triangle-raytracing-auto/raytrace.rchit b/examples/triangle-raytracing-auto/raytrace.rchit new file mode 100644 index 0000000000..52c407b96a --- /dev/null +++ b/examples/triangle-raytracing-auto/raytrace.rchit @@ -0,0 +1,10 @@ +#version 460 +#extension GL_EXT_ray_tracing : require + +layout(location = 0) rayPayloadInEXT vec3 hitValue; +hitAttributeEXT vec2 attribs; + +void main() { + vec3 barycentrics = vec3(1.0 - attribs.x - attribs.y, attribs.x, attribs.y); + hitValue = barycentrics; +} diff --git a/examples/triangle-raytracing-auto/raytrace.rgen b/examples/triangle-raytracing-auto/raytrace.rgen new file mode 100644 index 0000000000..8a9416e201 --- /dev/null +++ b/examples/triangle-raytracing-auto/raytrace.rgen @@ -0,0 +1,43 @@ +#version 460 +#extension GL_EXT_ray_tracing : require + +struct Camera { + mat4 viewProj; // Camera view * projection + mat4 viewInverse; // Camera inverse view matrix + mat4 projInverse; // Camera inverse projection matrix +}; + +layout(location = 0) rayPayloadEXT vec3 hitValue; + +layout(set = 0, binding = 0) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = 1) uniform _Camera { Camera camera; }; +layout(set = 1, binding = 0, rgba32f) uniform image2D image; + +void main() { + const vec2 pixelCenter = vec2(gl_LaunchIDEXT.xy) + vec2(0.5); + const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy); + vec2 d = inUV * 2.0 - 1.0; + + vec4 origin = camera.viewInverse * vec4(0, 0, 0, 1); + vec4 target = camera.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = camera.viewInverse * vec4(normalize(target.xyz), 0); + + uint rayFlags = gl_RayFlagsOpaqueEXT; + float tMin = 0.001; + float tMax = 10000.0; + + traceRayEXT(topLevelAS, // acceleration structure + rayFlags, // rayFlags + 0xFF, // cullMask + 0, // sbtRecordOffset + 0, // sbtRecordStride + 0, // missIndex + origin.xyz, // ray origin + tMin, // ray min range + direction.xyz, // ray direction + tMax, // ray max range + 0 // payload (location = 0) + ); + + imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(hitValue, 1.0)); +} diff --git a/examples/triangle-raytracing-auto/raytrace.rmiss b/examples/triangle-raytracing-auto/raytrace.rmiss new file mode 100644 index 0000000000..1c584d5420 --- /dev/null +++ b/examples/triangle-raytracing-auto/raytrace.rmiss @@ -0,0 +1,6 @@ +#version 460 +#extension GL_EXT_ray_tracing : require + +layout(location = 0) rayPayloadInEXT vec3 hitValue; + +void main() { hitValue = vec3(0.0, 0.0, 0.2); } diff --git a/examples/triangle-raytracing-auto/scene.rs b/examples/triangle-raytracing-auto/scene.rs new file mode 100644 index 0000000000..0d2c719abd --- /dev/null +++ b/examples/triangle-raytracing-auto/scene.rs @@ -0,0 +1,467 @@ +use std::sync::Arc; +use std::{iter, mem::size_of}; + +use crate::App; +use glam::{Mat4, Vec3}; +use vulkano::command_buffer::PrimaryAutoCommandBuffer; +use vulkano::descriptor_set::DescriptorSet; +use vulkano::{ + acceleration_structure::{ + AccelerationStructure, AccelerationStructureBuildGeometryInfo, + AccelerationStructureBuildRangeInfo, AccelerationStructureBuildType, + AccelerationStructureCreateInfo, AccelerationStructureGeometries, + AccelerationStructureGeometryInstancesData, AccelerationStructureGeometryInstancesDataType, + AccelerationStructureGeometryTrianglesData, AccelerationStructureInstance, + AccelerationStructureType, BuildAccelerationStructureFlags, BuildAccelerationStructureMode, + }, + buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer}, + command_buffer::{ + allocator::CommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, + PrimaryCommandBufferAbstract, + }, + descriptor_set::{allocator::StandardDescriptorSetAllocator, WriteDescriptorSet}, + device::{Device, DeviceOwnedVulkanObject, Queue}, + format::Format, + image::{view::ImageView, Image}, + memory::allocator::{AllocationCreateInfo, MemoryAllocator, MemoryTypeFilter}, + pipeline::{ + graphics::vertex_input::Vertex, + ray_tracing::{ + RayTracingPipeline, RayTracingPipelineCreateInfo, RayTracingShaderGroupCreateInfo, + ShaderBindingTable, + }, + PipelineBindPoint, PipelineLayout, PipelineShaderStageCreateInfo, + }, + sync::GpuFuture, +}; + +mod raygen { + vulkano_shaders::shader! { + ty: "raygen", + path: "raytrace.rgen", + vulkan_version: "1.2" + } +} + +mod closest_hit { + vulkano_shaders::shader! { + ty: "closesthit", + path: "raytrace.rchit", + vulkan_version: "1.2" + } +} + +mod miss { + vulkano_shaders::shader! { + ty: "miss", + path: "raytrace.rmiss", + vulkan_version: "1.2" + } +} + +#[derive(BufferContents, Vertex)] +#[repr(C)] +struct MyVertex { + #[format(R32G32B32_SFLOAT)] + position: [f32; 3], +} + +pub struct SceneTask { + descriptor_set_0: Arc, + swapchain_image_sets: Vec<(Arc, Arc)>, + pipeline_layout: Arc, + descriptor_set_allocator: Arc, + shader_binding_table: ShaderBindingTable, + pipeline: Arc, + _blas: Arc, + _tlas: Arc, +} + +impl SceneTask { + pub fn new( + app: &App, + images: &[Arc], + pipeline_layout: Arc, + descriptor_set_allocator: Arc, + memory_allocator: Arc, + command_buffer_allocator: Arc, + ) -> Self { + let pipeline = { + let raygen = raygen::load(app.device.clone()) + .unwrap() + .entry_point("main") + .unwrap(); + let closest_hit = closest_hit::load(app.device.clone()) + .unwrap() + .entry_point("main") + .unwrap(); + + let miss = miss::load(app.device.clone()) + .unwrap() + .entry_point("main") + .unwrap(); + + // Make a list of the shader stages that the pipeline will have. + let stages = [ + PipelineShaderStageCreateInfo::new(raygen), + PipelineShaderStageCreateInfo::new(miss), + PipelineShaderStageCreateInfo::new(closest_hit), + ]; + + let groups = [ + RayTracingShaderGroupCreateInfo::General { general_shader: 0 }, + RayTracingShaderGroupCreateInfo::General { general_shader: 1 }, + RayTracingShaderGroupCreateInfo::TrianglesHit { + closest_hit_shader: Some(2), + any_hit_shader: None, + }, + ]; + + RayTracingPipeline::new( + app.device.clone(), + None, + RayTracingPipelineCreateInfo { + stages: stages.into_iter().collect(), + groups: groups.into_iter().collect(), + max_pipeline_ray_recursion_depth: 1, + + ..RayTracingPipelineCreateInfo::layout(pipeline_layout.clone()) + }, + ) + .unwrap() + }; + pipeline + .set_debug_utils_object_name("Ray Tracing Pipeline".into()) + .unwrap(); + + let vertices = [ + MyVertex { + position: [-0.5, -0.25, 0.0], + }, + MyVertex { + position: [0.0, 0.5, 0.0], + }, + MyVertex { + position: [0.25, -0.1, 0.0], + }, + ]; + let vertex_buffer = Buffer::from_iter( + memory_allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::VERTEX_BUFFER + | BufferUsage::SHADER_DEVICE_ADDRESS + | BufferUsage::ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + vertices, + ) + .unwrap(); + + let blas = unsafe { + build_acceleration_structure_triangles( + vertex_buffer, + memory_allocator.clone(), + command_buffer_allocator.clone(), + app.device.clone(), + app.queue.clone(), + ) + }; + + let tlas = unsafe { + build_top_level_acceleration_structure( + blas.clone(), + memory_allocator.clone(), + command_buffer_allocator.clone(), + app.device.clone(), + app.queue.clone(), + ) + }; + + let proj = Mat4::perspective_rh_gl(std::f32::consts::FRAC_PI_2, 4.0 / 3.0, 0.01, 100.0); + let view = Mat4::look_at_rh( + Vec3::new(0.0, 0.0, 1.0), + Vec3::new(0.0, 0.0, 0.0), + Vec3::new(0.0, -1.0, 0.0), + ); + + let uniform_buffer = Buffer::from_data( + memory_allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::UNIFORM_BUFFER, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + raygen::Camera { + viewInverse: view.inverse().to_cols_array_2d(), + projInverse: proj.inverse().to_cols_array_2d(), + viewProj: (proj * view).to_cols_array_2d(), + }, + ) + .unwrap(); + + let descriptor_set_0 = DescriptorSet::new( + descriptor_set_allocator.clone(), + pipeline_layout.set_layouts()[0].clone(), + [ + WriteDescriptorSet::acceleration_structure(0, tlas.clone()), + WriteDescriptorSet::buffer(1, uniform_buffer.clone()), + ], + [], + ) + .unwrap(); + + let swapchain_image_sets = + window_size_dependent_setup(images, &pipeline_layout, &descriptor_set_allocator); + + let shader_binding_table = + ShaderBindingTable::new(memory_allocator.clone(), &pipeline).unwrap(); + + SceneTask { + descriptor_set_0, + swapchain_image_sets, + descriptor_set_allocator, + pipeline_layout, + shader_binding_table, + pipeline, + _blas: blas, + _tlas: tlas, + } + } + + pub fn handle_resize(&mut self, images: &[Arc]) { + self.swapchain_image_sets = window_size_dependent_setup( + images, + &self.pipeline_layout, + &self.descriptor_set_allocator, + ); + } + + pub fn record_commands( + &self, + image_index: u32, + builder: &mut AutoCommandBufferBuilder, + ) { + builder + .bind_descriptor_sets( + PipelineBindPoint::RayTracing, + self.pipeline_layout.clone(), + 0, + vec![ + self.descriptor_set_0.clone(), + self.swapchain_image_sets[image_index as usize].1.clone(), + ], + ) + .unwrap(); + + builder + .bind_pipeline_ray_tracing(self.pipeline.clone()) + .unwrap(); + + let extent = self.swapchain_image_sets[0].0.image().extent(); + + unsafe { + builder + .trace_rays( + self.shader_binding_table.addresses().clone(), + extent[0], + extent[1], + 1, + ) + .unwrap(); + } + } +} + +/// This function is called once during initialization, then again whenever the window is resized. +fn window_size_dependent_setup( + images: &[Arc], + pipeline_layout: &Arc, + descriptor_set_allocator: &Arc, +) -> Vec<(Arc, Arc)> { + let swapchain_image_sets = images + .iter() + .map(|image| { + let image_view = ImageView::new_default(image.clone()).unwrap(); + let descriptor_set = DescriptorSet::new( + descriptor_set_allocator.clone(), + pipeline_layout.set_layouts()[1].clone(), + [WriteDescriptorSet::image_view(0, image_view.clone())], + [], + ) + .unwrap(); + (image_view, descriptor_set) + }) + .collect(); + + swapchain_image_sets +} + +unsafe fn build_acceleration_structure_common( + geometries: AccelerationStructureGeometries, + primitive_count: u32, + ty: AccelerationStructureType, + memory_allocator: Arc, + command_buffer_allocator: Arc, + device: Arc, + queue: Arc, +) -> Arc { + let mut builder = AutoCommandBufferBuilder::primary( + command_buffer_allocator, + queue.queue_family_index(), + CommandBufferUsage::OneTimeSubmit, + ) + .unwrap(); + + let mut as_build_geometry_info = AccelerationStructureBuildGeometryInfo { + mode: BuildAccelerationStructureMode::Build, + flags: BuildAccelerationStructureFlags::PREFER_FAST_TRACE, + ..AccelerationStructureBuildGeometryInfo::new(geometries) + }; + + let as_build_sizes_info = device + .acceleration_structure_build_sizes( + AccelerationStructureBuildType::Device, + &as_build_geometry_info, + &[primitive_count], + ) + .unwrap(); + + let scratch_buffer = Buffer::new_slice::( + memory_allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::SHADER_DEVICE_ADDRESS | BufferUsage::STORAGE_BUFFER, + ..Default::default() + }, + AllocationCreateInfo::default(), + as_build_sizes_info.build_scratch_size, + ) + .unwrap(); + + let as_create_info = AccelerationStructureCreateInfo { + ty, + ..AccelerationStructureCreateInfo::new( + Buffer::new_slice::( + memory_allocator, + BufferCreateInfo { + usage: BufferUsage::ACCELERATION_STRUCTURE_STORAGE + | BufferUsage::SHADER_DEVICE_ADDRESS, + ..Default::default() + }, + AllocationCreateInfo::default(), + as_build_sizes_info.acceleration_structure_size, + ) + .unwrap(), + ) + }; + + let acceleration = unsafe { AccelerationStructure::new(device, as_create_info).unwrap() }; + + as_build_geometry_info.dst_acceleration_structure = Some(acceleration.clone()); + as_build_geometry_info.scratch_data = Some(scratch_buffer); + + let as_build_range_info = AccelerationStructureBuildRangeInfo { + primitive_count, + ..Default::default() + }; + + builder + .build_acceleration_structure( + as_build_geometry_info, + iter::once(as_build_range_info).collect(), + ) + .unwrap(); + + builder + .build() + .unwrap() + .execute(queue) + .unwrap() + .then_signal_fence_and_flush() + .unwrap() + .wait(None) + .unwrap(); + + acceleration +} + +unsafe fn build_acceleration_structure_triangles( + vertex_buffer: Subbuffer<[MyVertex]>, + memory_allocator: Arc, + command_buffer_allocator: Arc, + device: Arc, + queue: Arc, +) -> Arc { + let primitive_count = (vertex_buffer.len() / 3) as u32; + let as_geometry_triangles_data = AccelerationStructureGeometryTrianglesData { + max_vertex: vertex_buffer.len() as _, + vertex_data: Some(vertex_buffer.into_bytes()), + vertex_stride: size_of::() as _, + ..AccelerationStructureGeometryTrianglesData::new(Format::R32G32B32_SFLOAT) + }; + + let geometries = AccelerationStructureGeometries::Triangles(vec![as_geometry_triangles_data]); + + build_acceleration_structure_common( + geometries, + primitive_count, + AccelerationStructureType::BottomLevel, + memory_allocator, + command_buffer_allocator, + device, + queue, + ) +} + +unsafe fn build_top_level_acceleration_structure( + acceleration_structure: Arc, + allocator: Arc, + command_buffer_allocator: Arc, + device: Arc, + queue: Arc, +) -> Arc { + let as_instance = AccelerationStructureInstance { + acceleration_structure_reference: acceleration_structure.device_address().into(), + ..Default::default() + }; + + let instance_buffer = Buffer::from_iter( + allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::SHADER_DEVICE_ADDRESS + | BufferUsage::ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + [as_instance], + ) + .unwrap(); + + let as_geometry_instances_data = AccelerationStructureGeometryInstancesData::new( + AccelerationStructureGeometryInstancesDataType::Values(Some(instance_buffer)), + ); + + let geometries = AccelerationStructureGeometries::Instances(as_geometry_instances_data); + + build_acceleration_structure_common( + geometries, + 1, + AccelerationStructureType::TopLevel, + allocator, + command_buffer_allocator, + device, + queue, + ) +} diff --git a/vulkano/src/command_buffer/commands/bind_push.rs b/vulkano/src/command_buffer/commands/bind_push.rs index b64d4b8a7a..57fc86f3df 100644 --- a/vulkano/src/command_buffer/commands/bind_push.rs +++ b/vulkano/src/command_buffer/commands/bind_push.rs @@ -378,6 +378,31 @@ impl AutoCommandBufferBuilder { self } + pub fn bind_pipeline_ray_tracing( + &mut self, + pipeline: Arc, + ) -> Result<&mut Self, Box> { + // TODO: RayTracing: Validation + Ok(unsafe { self.bind_pipeline_ray_tracing_unchecked(pipeline) }) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn bind_pipeline_ray_tracing_unchecked( + &mut self, + pipeline: Arc, + ) -> &mut Self { + self.builder_state.pipeline_ray_tracing = Some(pipeline.clone()); + self.add_command( + "bind_pipeline_ray_tracing", + Default::default(), + move |out: &mut RecordingCommandBuffer| { + out.bind_pipeline_ray_tracing_unchecked(&pipeline); + }, + ); + + self + } + /// Binds vertex buffers for future draw calls. pub fn bind_vertex_buffers( &mut self, From 616cca9e08f9d02f9305ebe9ac3e970a303157ac Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Tue, 26 Nov 2024 02:14:20 +0800 Subject: [PATCH 09/26] bind & trace_rays validation --- vulkano-taskgraph/src/resource.rs | 92 +++++++++---------- .../src/command_buffer/commands/bind_push.rs | 73 +++++++++++++-- vulkano/src/device/mod.rs | 4 + 3 files changed, 113 insertions(+), 56 deletions(-) diff --git a/vulkano-taskgraph/src/resource.rs b/vulkano-taskgraph/src/resource.rs index c1ac038cec..8fb33129bb 100644 --- a/vulkano-taskgraph/src/resource.rs +++ b/vulkano-taskgraph/src/resource.rs @@ -1600,47 +1600,41 @@ access_types! { // valid_for: IMAGE, // } - // TODO: - // RayTracingShaderUniformRead { - // stage_mask: RAY_TRACING_SHADER, - // access_mask: UNIFORM_READ, - // image_layout: Undefined, - // valid_for: BUFFER, - // } + RayTracingShaderUniformRead { + stage_mask: RAY_TRACING_SHADER, + access_mask: UNIFORM_READ, + image_layout: Undefined, + valid_for: BUFFER, + } - // TODO: - // RayTracingShaderColorInputAttachmentRead { - // stage_mask: RAY_TRACING_SHADER, - // access_mask: INPUT_ATTACHMENT_READ, - // image_layout: ShaderReadOnlyOptimal, - // valid_for: IMAGE, - // } + RayTracingShaderColorInputAttachmentRead { + stage_mask: RAY_TRACING_SHADER, + access_mask: INPUT_ATTACHMENT_READ, + image_layout: ShaderReadOnlyOptimal, + valid_for: IMAGE, + } - // TODO: - // RayTracingShaderDepthStencilInputAttachmentRead { - // stage_mask: RAY_TRACING_SHADER, - // access_mask: INPUT_ATTACHMENT_READ, - // image_layout: DepthStencilReadOnlyOptimal, - // valid_for: IMAGE, - // } + RayTracingShaderDepthStencilInputAttachmentRead { + stage_mask: RAY_TRACING_SHADER, + access_mask: INPUT_ATTACHMENT_READ, + image_layout: DepthStencilReadOnlyOptimal, + valid_for: IMAGE, + } - // TODO: - // RayTracingShaderSampledRead { - // stage_mask: RAY_TRACING_SHADER, - // access_mask: SHADER_SAMPLED_READ, - // image_layout: ShaderReadOnlyOptimal, - // valid_for: BUFFER | IMAGE, - // } + RayTracingShaderSampledRead { + stage_mask: RAY_TRACING_SHADER, + access_mask: SHADER_SAMPLED_READ, + image_layout: ShaderReadOnlyOptimal, + valid_for: BUFFER | IMAGE, + } - // TODO: - // RayTracingShaderStorageRead { - // stage_mask: RAY_TRACING_SHADER, - // access_mask: SHADER_STORAGE_READ, - // image_layout: General, - // valid_for: BUFFER | IMAGE, - // } + RayTracingShaderStorageRead { + stage_mask: RAY_TRACING_SHADER, + access_mask: SHADER_STORAGE_READ, + image_layout: General, + valid_for: BUFFER | IMAGE, + } - // TODO: RayTracingShaderStorageWrite { stage_mask: RAY_TRACING_SHADER, access_mask: SHADER_STORAGE_WRITE, @@ -1648,21 +1642,19 @@ access_types! { valid_for: BUFFER | IMAGE, } - // TODO: - // RayTracingShaderBindingTableRead { - // stage_mask: RAY_TRACING_SHADER, - // access_mask: SHADER_BINDING_TABLE_READ, - // image_layout: Undefined, - // valid_for: BUFFER, - // } + RayTracingShaderBindingTableRead { + stage_mask: RAY_TRACING_SHADER, + access_mask: SHADER_BINDING_TABLE_READ, + image_layout: Undefined, + valid_for: BUFFER, + } - // TODO: - // RayTracingShaderAccelerationStructureRead { - // stage_mask: RAY_TRACING_SHADER, - // access_mask: ACCELERATION_STRUCTURE_READ, - // image_layout: Undefined, - // valid_for: BUFFER, - // } + RayTracingShaderAccelerationStructureRead { + stage_mask: RAY_TRACING_SHADER, + access_mask: ACCELERATION_STRUCTURE_READ, + image_layout: Undefined, + valid_for: BUFFER, + } TaskShaderUniformRead { stage_mask: TASK_SHADER, diff --git a/vulkano/src/command_buffer/commands/bind_push.rs b/vulkano/src/command_buffer/commands/bind_push.rs index 57fc86f3df..5dfdea2926 100644 --- a/vulkano/src/command_buffer/commands/bind_push.rs +++ b/vulkano/src/command_buffer/commands/bind_push.rs @@ -382,7 +382,7 @@ impl AutoCommandBufferBuilder { &mut self, pipeline: Arc, ) -> Result<&mut Self, Box> { - // TODO: RayTracing: Validation + self.inner.validate_bind_pipeline_ray_tracing(&pipeline)?; Ok(unsafe { self.bind_pipeline_ray_tracing_unchecked(pipeline) }) } @@ -820,7 +820,23 @@ impl RecordingCommandBuffer { } } PipelineBindPoint::RayTracing => { - // TODO: RayTracing: Validation + if !queue_family_properties + .queue_flags + .intersects(QueueFlags::COMPUTE) + { + return Err(Box::new(ValidationError { + context: "pipeline_bind_point".into(), + problem: "is `PipelineBindPoint::RayTracing`, but \ + the queue family of the command buffer does not support \ + compute operations" + .into(), + vuids: &[ + "VUID-vkCmdBindDescriptorSets-pipelineBindPoint-02391", + "VUID-vkCmdBindDescriptorSets-commandBuffer-cmdpool", + ], + ..Default::default() + })); + } } } @@ -1046,9 +1062,38 @@ impl RecordingCommandBuffer { self } - pub unsafe fn bind_pipeline_ray_tracing(&mut self, pipeline: &RayTracingPipeline) -> &mut Self { - // TODO: RayTracing: Validation - self.bind_pipeline_ray_tracing_unchecked(pipeline) + pub unsafe fn bind_pipeline_ray_tracing( + &mut self, + pipeline: &RayTracingPipeline, + ) -> Result<&mut Self, Box> { + self.validate_bind_pipeline_ray_tracing(pipeline)?; + Ok(self.bind_pipeline_ray_tracing_unchecked(pipeline)) + } + + fn validate_bind_pipeline_ray_tracing( + &self, + pipeline: &RayTracingPipeline, + ) -> Result<(), Box> { + if !self + .queue_family_properties() + .queue_flags + .intersects(QueueFlags::COMPUTE) + { + return Err(Box::new(ValidationError { + problem: "the queue family of the command buffer does not support \ + compute operations" + .into(), + vuids: &["VUID-vkCmdBindPipeline-pipelineBindPoint-02391"], + ..Default::default() + })); + } + + // VUID-vkCmdBindPipeline-commonparent + assert_eq!(self.device(), pipeline.device()); + + // TODO: VUID-vkCmdBindPipeline-pipelineBindPoint-06721 + + Ok(()) } #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] @@ -1444,7 +1489,23 @@ impl RecordingCommandBuffer { } } PipelineBindPoint::RayTracing => { - // TODO: RayTracing + if !queue_family_properties + .queue_flags + .intersects(QueueFlags::COMPUTE) + { + return Err(Box::new(ValidationError { + context: "self".into(), + problem: + "`pipeline_bind_point` is `PipelineBindPoint::RayTracing`, and the \ + queue family does not support compute operations" + .into(), + vuids: &[ + "VUID-vkCmdPushDescriptorSetKHR-pipelineBindPoint-02391", + "VUID-vkCmdPushDescriptorSetKHR-commandBuffer-cmdpool", + ], + ..Default::default() + })); + } } } diff --git a/vulkano/src/device/mod.rs b/vulkano/src/device/mod.rs index caf6a3eee6..a1fde8e53b 100644 --- a/vulkano/src/device/mod.rs +++ b/vulkano/src/device/mod.rs @@ -2199,6 +2199,10 @@ pub struct ShaderGroupHandlesData { } impl ShaderGroupHandlesData { + pub fn data(&self) -> &[u8] { + &self.data + } + pub fn handle_size(&self) -> u32 { self.handle_size } From fe75b1c0e9ebcb38641e2e7a92d90e2f6a0f6873 Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Tue, 26 Nov 2024 02:14:52 +0800 Subject: [PATCH 10/26] trace_rays validation --- .../src/command_buffer/commands/pipeline.rs | 92 ++++++++++++++++++- 1 file changed, 87 insertions(+), 5 deletions(-) diff --git a/vulkano/src/command_buffer/commands/pipeline.rs b/vulkano/src/command_buffer/commands/pipeline.rs index 5f6ba23fb2..30f7dea92c 100644 --- a/vulkano/src/command_buffer/commands/pipeline.rs +++ b/vulkano/src/command_buffer/commands/pipeline.rs @@ -1600,7 +1600,8 @@ impl AutoCommandBufferBuilder { height: u32, depth: u32, ) -> Result<&mut Self, Box> { - // TODO: RayTrace: Validation + self.inner + .validate_trace_rays(&shader_binding_table_addresses, width, height, depth)?; Ok(self.trace_rays_unchecked(shader_binding_table_addresses, width, height, depth)) } @@ -1613,13 +1614,12 @@ impl AutoCommandBufferBuilder { height: u32, depth: u32, ) -> &mut Self { - // TODO: RayTracing: as_deref() let pipeline = self.builder_state.pipeline_ray_tracing.as_deref().unwrap(); let mut used_resources = Vec::new(); self.add_descriptor_sets_resources(&mut used_resources, pipeline); - self.add_command("ray_trace", used_resources, move |out| { + self.add_command("trace_rays", used_resources, move |out| { out.trace_rays_unchecked(&shader_binding_table_addresses, width, height, depth); }); @@ -4989,12 +4989,94 @@ impl RecordingCommandBuffer { height: u32, depth: u32, ) -> Result<&mut Self, Box> { - // self.validate_trace_ray()?; - // TODO: RayTracing: Validation + self.validate_trace_rays(shader_binding_table_addresses, width, height, depth)?; Ok(self.trace_rays_unchecked(shader_binding_table_addresses, width, height, depth)) } + fn validate_trace_rays( + &self, + _shader_binding_table_addresses: &ShaderBindingTableAddresses, + width: u32, + height: u32, + depth: u32, + ) -> Result<(), Box> { + if !self.device().enabled_features().ray_tracing_pipeline { + return Err(Box::new(ValidationError { + requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::DeviceFeature( + "ray_tracing_pipeline", + )])]), + ..Default::default() + })); + } + + if !self + .queue_family_properties() + .queue_flags + .intersects(QueueFlags::COMPUTE) + { + return Err(Box::new(ValidationError { + problem: "the queue family of the command buffer does not support \ + compute operations" + .into(), + vuids: &["VUID-vkCmdTraceRaysKHR-commandBuffer-cmdpool"], + ..Default::default() + })); + } + + let device_properties = self.device().physical_device().properties(); + + let max_width = device_properties.max_compute_work_group_count[0] + * device_properties.max_compute_work_group_size[0]; + + if width > max_width { + return Err(Box::new(ValidationError { + context: "width".into(), + problem: "exceeds maxComputeWorkGroupCount[0] * maxComputeWorkGroupSize[0]".into(), + vuids: &["VUID-vkCmdTraceRaysKHR-width-03638"], + ..Default::default() + })); + } + + let max_height = device_properties.max_compute_work_group_count[1] + * device_properties.max_compute_work_group_size[1]; + + if height > max_height { + return Err(Box::new(ValidationError { + context: "height".into(), + problem: "exceeds maxComputeWorkGroupCount[1] * maxComputeWorkGroupSize[1]".into(), + vuids: &["VUID-vkCmdTraceRaysKHR-height-03639"], + ..Default::default() + })); + } + + let max_depth = device_properties.max_compute_work_group_count[2] + * device_properties.max_compute_work_group_size[2]; + + if depth > max_depth { + return Err(Box::new(ValidationError { + context: "depth".into(), + problem: "exceeds maxComputeWorkGroupCount[2] * maxComputeWorkGroupSize[2]".into(), + vuids: &["VUID-vkCmdTraceRaysKHR-depth-03640"], + ..Default::default() + })); + } + + let total_invocations = width as u64 * height as u64 * depth as u64; + let max_invocations = device_properties.max_ray_dispatch_invocation_count.unwrap() as u64; + + if total_invocations > max_invocations { + return Err(Box::new(ValidationError { + context: "width * height * depth".into(), + problem: "exceeds maxRayDispatchInvocationCount".into(), + vuids: &["VUID-vkCmdTraceRaysKHR-width-03641"], + ..Default::default() + })); + } + + Ok(()) + } + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] pub unsafe fn trace_rays_unchecked( &mut self, From b105ca5c3b27d0beb3510ed8a8df3ef608de2c7a Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Wed, 27 Nov 2024 23:28:44 +0800 Subject: [PATCH 11/26] doc for triangle-raytracing-auto --- examples/triangle-raytracing-auto/main.rs | 22 +- examples/triangle-raytracing-auto/scene.rs | 43 ++- examples/triangle-raytracing/scene.rs | 5 +- .../src/command_buffer/commands/pipeline.rs | 18 +- vulkano/src/pipeline/ray_tracing/mod.rs | 311 +++++++++++++++--- 5 files changed, 308 insertions(+), 91 deletions(-) diff --git a/examples/triangle-raytracing-auto/main.rs b/examples/triangle-raytracing-auto/main.rs index 05193ac6d0..3fa6fb7b41 100644 --- a/examples/triangle-raytracing-auto/main.rs +++ b/examples/triangle-raytracing-auto/main.rs @@ -1,6 +1,4 @@ -// TODO: document - -use scene::SceneTask; +use scene::Scene; use std::{error::Error, sync::Arc}; use vulkano::{ command_buffer::{ @@ -56,7 +54,7 @@ pub struct RenderContext { window: Arc, swapchain: Arc, recreate_swapchain: bool, - scene_task: SceneTask, + scene: Scene, previous_frame_end: Option>, } @@ -69,11 +67,9 @@ impl App { InstanceCreateInfo { flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, enabled_extensions: InstanceExtensions { - ext_debug_utils: true, ext_swapchain_colorspace: true, ..required_extensions }, - enabled_layers: vec!["VK_LAYER_KHRONOS_validation".to_owned()], ..Default::default() }, ) @@ -205,7 +201,10 @@ impl ApplicationHandler for App { image_format: swapchain_format, image_color_space: swapchain_color_space, image_extent: window.inner_size().into(), - image_usage: ImageUsage::COLOR_ATTACHMENT | ImageUsage::STORAGE, + // To simplify the example, we will directly write to the swapchain images + // from the ray tracing shader. This requires the images to support storage + // usage. + image_usage: ImageUsage::STORAGE, composite_alpha: surface_capabilities .supported_composite_alpha .into_iter() @@ -269,7 +268,6 @@ impl ApplicationHandler for App { ) .unwrap(), ], - push_constant_ranges: vec![], ..Default::default() }, ) @@ -282,7 +280,7 @@ impl ApplicationHandler for App { let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(self.device.clone())); - let scene_task = SceneTask::new( + let scene = Scene::new( &self, &images, pipeline_layout, @@ -295,7 +293,7 @@ impl ApplicationHandler for App { swapchain, recreate_swapchain: false, previous_frame_end: None, - scene_task, + scene, }); } @@ -338,7 +336,7 @@ impl ApplicationHandler for App { }; rcx.swapchain = new_swapchain; - rcx.scene_task.handle_resize(&new_images); + rcx.scene.handle_resize(&new_images); rcx.recreate_swapchain = false; } @@ -364,7 +362,7 @@ impl ApplicationHandler for App { ) .unwrap(); - rcx.scene_task.record_commands(image_index, &mut builder); + rcx.scene.record_commands(image_index, &mut builder); let command_buffer = builder.build().unwrap(); diff --git a/examples/triangle-raytracing-auto/scene.rs b/examples/triangle-raytracing-auto/scene.rs index 0d2c719abd..b9d2bc87c3 100644 --- a/examples/triangle-raytracing-auto/scene.rs +++ b/examples/triangle-raytracing-auto/scene.rs @@ -20,7 +20,7 @@ use vulkano::{ PrimaryCommandBufferAbstract, }, descriptor_set::{allocator::StandardDescriptorSetAllocator, WriteDescriptorSet}, - device::{Device, DeviceOwnedVulkanObject, Queue}, + device::{Device, Queue}, format::Format, image::{view::ImageView, Image}, memory::allocator::{AllocationCreateInfo, MemoryAllocator, MemoryTypeFilter}, @@ -66,18 +66,20 @@ struct MyVertex { position: [f32; 3], } -pub struct SceneTask { +pub struct Scene { descriptor_set_0: Arc, swapchain_image_sets: Vec<(Arc, Arc)>, pipeline_layout: Arc, descriptor_set_allocator: Arc, shader_binding_table: ShaderBindingTable, pipeline: Arc, + // The bottom-level acceleration structure is required to be kept alive + // as we reference it in the top-level acceleration structure. _blas: Arc, _tlas: Arc, } -impl SceneTask { +impl Scene { pub fn new( app: &App, images: &[Arc], @@ -108,6 +110,8 @@ impl SceneTask { PipelineShaderStageCreateInfo::new(closest_hit), ]; + // Define the shader groups that will eventually turn into the shader binding table. + // The numbers are the indices of the stages in the `stages` array. let groups = [ RayTracingShaderGroupCreateInfo::General { general_shader: 0 }, RayTracingShaderGroupCreateInfo::General { general_shader: 1 }, @@ -130,9 +134,6 @@ impl SceneTask { ) .unwrap() }; - pipeline - .set_debug_utils_object_name("Ray Tracing Pipeline".into()) - .unwrap(); let vertices = [ MyVertex { @@ -162,6 +163,11 @@ impl SceneTask { ) .unwrap(); + // Build the bottom-level acceleration structure and then the top-level acceleration structure. + // Acceleration structures are used to accelerate ray tracing. + // The bottom-level acceleration structure contains the geometry data. + // The top-level acceleration structure contains the instances of the bottom-level acceleration structures. + // In our shader, we will trace rays against the top-level acceleration structure. let blas = unsafe { build_acceleration_structure_triangles( vertex_buffer, @@ -225,7 +231,7 @@ impl SceneTask { let shader_binding_table = ShaderBindingTable::new(memory_allocator.clone(), &pipeline).unwrap(); - SceneTask { + Scene { descriptor_set_0, swapchain_image_sets, descriptor_set_allocator, @@ -305,6 +311,10 @@ fn window_size_dependent_setup( swapchain_image_sets } +/// A helper function to build a acceleration structure and wait for its completion. +/// # SAFETY +/// - If you are referencing a bottom-level acceleration structure in a top-level acceleration structure, +/// you must ensure that the bottom-level acceleration structure is kept alive. unsafe fn build_acceleration_structure_common( geometries: AccelerationStructureGeometries, primitive_count: u32, @@ -314,13 +324,6 @@ unsafe fn build_acceleration_structure_common( device: Arc, queue: Arc, ) -> Arc { - let mut builder = AutoCommandBufferBuilder::primary( - command_buffer_allocator, - queue.queue_family_index(), - CommandBufferUsage::OneTimeSubmit, - ) - .unwrap(); - let mut as_build_geometry_info = AccelerationStructureBuildGeometryInfo { mode: BuildAccelerationStructureMode::Build, flags: BuildAccelerationStructureFlags::PREFER_FAST_TRACE, @@ -335,6 +338,8 @@ unsafe fn build_acceleration_structure_common( ) .unwrap(); + // We build a new scratch buffer for each acceleration structure for simplicity. + // You may want to reuse scratch buffers if you need to build many acceleration structures. let scratch_buffer = Buffer::new_slice::( memory_allocator.clone(), BufferCreateInfo { @@ -373,6 +378,16 @@ unsafe fn build_acceleration_structure_common( ..Default::default() }; + // For simplicity, we build a single command buffer + // that builds the acceleration structure, then waits + // for its execution to complete. + let mut builder = AutoCommandBufferBuilder::primary( + command_buffer_allocator, + queue.queue_family_index(), + CommandBufferUsage::OneTimeSubmit, + ) + .unwrap(); + builder .build_acceleration_structure( as_build_geometry_info, diff --git a/examples/triangle-raytracing/scene.rs b/examples/triangle-raytracing/scene.rs index c7cd2901bc..1c061d1fd1 100644 --- a/examples/triangle-raytracing/scene.rs +++ b/examples/triangle-raytracing/scene.rs @@ -19,7 +19,7 @@ use vulkano::{ descriptor_set::{ allocator::StandardDescriptorSetAllocator, sys::RawDescriptorSet, WriteDescriptorSet, }, - device::{Device, DeviceOwnedVulkanObject, Queue}, + device::{Device, Queue}, format::Format, image::view::ImageView, memory::allocator::{AllocationCreateInfo, MemoryAllocator, MemoryTypeFilter}, @@ -138,9 +138,6 @@ impl SceneTask { ) .unwrap() }; - pipeline - .set_debug_utils_object_name("Ray Tracing Pipeline".into()) - .unwrap(); let vertices = [ MyVertex { diff --git a/vulkano/src/command_buffer/commands/pipeline.rs b/vulkano/src/command_buffer/commands/pipeline.rs index 30f7dea92c..ad87f53b4c 100644 --- a/vulkano/src/command_buffer/commands/pipeline.rs +++ b/vulkano/src/command_buffer/commands/pipeline.rs @@ -5026,8 +5026,12 @@ impl RecordingCommandBuffer { let device_properties = self.device().physical_device().properties(); - let max_width = device_properties.max_compute_work_group_count[0] - * device_properties.max_compute_work_group_size[0]; + let width = width as u64; + let height = height as u64; + let depth = depth as u64; + + let max_width = device_properties.max_compute_work_group_count[0] as u64 + * device_properties.max_compute_work_group_size[0] as u64; if width > max_width { return Err(Box::new(ValidationError { @@ -5038,8 +5042,8 @@ impl RecordingCommandBuffer { })); } - let max_height = device_properties.max_compute_work_group_count[1] - * device_properties.max_compute_work_group_size[1]; + let max_height = device_properties.max_compute_work_group_count[1] as u64 + * device_properties.max_compute_work_group_size[1] as u64; if height > max_height { return Err(Box::new(ValidationError { @@ -5050,8 +5054,8 @@ impl RecordingCommandBuffer { })); } - let max_depth = device_properties.max_compute_work_group_count[2] - * device_properties.max_compute_work_group_size[2]; + let max_depth = device_properties.max_compute_work_group_count[2] as u64 + * device_properties.max_compute_work_group_size[2] as u64; if depth > max_depth { return Err(Box::new(ValidationError { @@ -5062,7 +5066,7 @@ impl RecordingCommandBuffer { })); } - let total_invocations = width as u64 * height as u64 * depth as u64; + let total_invocations = width * height * depth; let max_invocations = device_properties.max_ray_dispatch_invocation_count.unwrap() as u64; if total_invocations > max_invocations { diff --git a/vulkano/src/pipeline/ray_tracing/mod.rs b/vulkano/src/pipeline/ray_tracing/mod.rs index 3b716a49d6..ef92b95456 100644 --- a/vulkano/src/pipeline/ray_tracing/mod.rs +++ b/vulkano/src/pipeline/ray_tracing/mod.rs @@ -14,7 +14,7 @@ use crate::{ DeviceAlignment, }, shader::{spirv::ExecutionModel, DescriptorBindingRequirements}, - Validated, VulkanError, VulkanObject, + Validated, ValidationError, VulkanError, VulkanObject, }; use super::{ @@ -23,6 +23,9 @@ use super::{ PipelineShaderStageCreateInfoFields1Vk, PipelineShaderStageCreateInfoFields2Vk, }; +/// Defines how the implementation should perform ray tracing operations. +/// +/// This object uses the `VK_KHR_ray_tracing_pipeline` extension. #[derive(Debug)] pub struct RayTracingPipeline { handle: ash::vk::Pipeline, @@ -47,11 +50,26 @@ impl RayTracingPipeline { cache: Option>, create_info: RayTracingPipelineCreateInfo, ) -> Result, Validated> { - // Self::validate_new(&device, cache.as_deref(), &create_info)?; + Self::validate_new(&device, cache.as_deref(), &create_info)?; unsafe { Ok(Self::new_unchecked(device, cache, create_info)?) } } + fn validate_new( + device: &Arc, + cache: Option<&PipelineCache>, + create_info: &RayTracingPipelineCreateInfo, + ) -> Result<(), Validated> { + if let Some(cache) = &cache { + assert_eq!(device, cache.device()); + } + create_info + .validate(device) + .map_err(|err| err.add_context("create_info"))?; + + Ok(()) + } + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] pub unsafe fn new_unchecked( device: Arc, @@ -219,15 +237,23 @@ pub struct RayTracingPipelineCreateInfo { /// The default value is empty. pub flags: PipelineCreateFlags, - /// The compute shader stage to use. + /// The ray tracing shader stages to use. /// /// There is no default value. pub stages: SmallVec<[PipelineShaderStageCreateInfo; 5]>, pub groups: SmallVec<[RayTracingShaderGroupCreateInfo; 5]>, + /// The maximum recursion depth of the pipeline. + /// + /// The default value is 1. pub max_pipeline_ray_recursion_depth: u32, + /// The dynamic state to use. + /// + /// May only contain `DynamicState::RayTracingPipelineStackSize`. + /// + /// The default value is empty. pub dynamic_state: HashSet, /// The pipeline layout to use. @@ -253,7 +279,7 @@ impl RayTracingPipelineCreateInfo { flags: PipelineCreateFlags::empty(), stages: SmallVec::new(), groups: SmallVec::new(), - max_pipeline_ray_recursion_depth: 0, + max_pipeline_ray_recursion_depth: 1, dynamic_state: Default::default(), layout, @@ -263,6 +289,127 @@ impl RayTracingPipelineCreateInfo { } } + fn validate(&self, device: &Arc) -> Result<(), Box> { + let &Self { + flags, + ref stages, + ref groups, + ref layout, + ref base_pipeline, + ref dynamic_state, + max_pipeline_ray_recursion_depth, + _ne: _, + } = self; + + flags.validate_device(device).map_err(|err| { + err.add_context("flags") + .set_vuids(&["VUID-VkRayTracingPipelineCreateInfoKHR-flags-parameter"]) + })?; + + if flags.intersects(PipelineCreateFlags::DERIVATIVE) { + let base_pipeline = base_pipeline.as_ref().ok_or_else(|| { + Box::new(ValidationError { + problem: "`flags` contains `PipelineCreateFlags::DERIVATIVE`, but \ + `base_pipeline` is `None`" + .into(), + vuids: &["VUID-VkRayTracingPipelineCreateInfoKHR-flags-07984 +"], + ..Default::default() + }) + })?; + + if !base_pipeline + .flags() + .intersects(PipelineCreateFlags::ALLOW_DERIVATIVES) + { + return Err(Box::new(ValidationError { + context: "base_pipeline.flags()".into(), + problem: "does not contain `PipelineCreateFlags::ALLOW_DERIVATIVES`".into(), + vuids: &["VUID-vkCreateRayTracingPipelinesKHR-flags-03416"], + ..Default::default() + })); + } + } else if base_pipeline.is_some() { + return Err(Box::new(ValidationError { + problem: "`flags` does not contain `PipelineCreateFlags::DERIVATIVE`, but \ + `base_pipeline` is `Some`" + .into(), + ..Default::default() + })); + } + + for stage in stages { + stage.validate(device).map_err(|err| { + err.add_context("stages") + .set_vuids(&["VUID-VkRayTracingPipelineCreateInfoKHR-pStages-parameter"]) + })?; + + let entry_point_info = stage.entry_point.info(); + + layout + .ensure_compatible_with_shader( + entry_point_info + .descriptor_binding_requirements + .iter() + .map(|(k, v)| (*k, v)), + entry_point_info.push_constant_requirements.as_ref(), + ) + .map_err(|err| { + Box::new(ValidationError { + context: "stage.entry_point".into(), + vuids: &[ + "VUID-VkRayTracingPipelineCreateInfoKHR-layout-07987", + "VUID-VkRayTracingPipelineCreateInfoKHR-layout-07988", + "VUID-VkRayTracingPipelineCreateInfoKHR-layout-07990", + "VUID-VkRayTracingPipelineCreateInfoKHR-layout-07991", + ], + ..ValidationError::from_error(err) + }) + })?; + } + + for group in groups { + group.validate(stages).map_err(|err| { + err.add_context("groups") + .set_vuids(&["VUID-VkRayTracingPipelineCreateInfoKHR-pGroups-parameter"]) + })?; + } + + // TODO: Enable + // if dynamic_state + // .iter() + // .any(|&state| state != DynamicState::RayTracingPipelineStackSize) + // { + // return Err(Box::new(ValidationError { + // problem: + // format!("`dynamic_state` contains a dynamic state other than RayTracingPipelineStackSize: {:?}", dynamic_state).into(), + // vuids: &["VUID-VkRayTracingPipelineCreateInfoKHR-pDynamicStates-03602"], + // ..Default::default() + // })); + // } + if dynamic_state.len() > 0 { + todo!("Dynamic state for ray tracing pipelines is not yet supported"); + } + + let max_ray_recursion_depth = device + .physical_device() + .properties() + .max_ray_recursion_depth + .unwrap(); + if max_pipeline_ray_recursion_depth > max_ray_recursion_depth { + return Err(Box::new(ValidationError { + problem: format!( + "`max_pipeline_ray_recursion_depth` is greater than the device's max value of {}", + max_ray_recursion_depth + ).into(), + vuids: &["VUID-VkRayTracingPipelineCreateInfoKHR-maxPipelineRayRecursionDepth-03589"], + ..Default::default() + })); + } + + Ok(()) + } + pub(crate) fn to_vk<'a>( &self, fields1_vk: &'a RayTracingPipelineCreateInfoFields1Vk<'_>, @@ -392,6 +539,9 @@ impl RayTracingPipelineCreateInfo { } /// Enum representing different types of Ray Tracing Shader Groups. +/// +/// Contains the index of the shader to use for each type of shader group. +/// The index corresponds to the position of the shader in the `stages` field of the `RayTracingPipelineCreateInfo`. #[derive(Debug, Clone)] pub enum RayTracingShaderGroupCreateInfo { General { @@ -409,6 +559,96 @@ pub enum RayTracingShaderGroupCreateInfo { } impl RayTracingShaderGroupCreateInfo { + fn validate( + &self, + stages: &[PipelineShaderStageCreateInfo], + ) -> Result<(), Box> { + let get_shader_type = + |shader: u32| stages[shader as usize].entry_point.info().execution_model; + + match self { + RayTracingShaderGroupCreateInfo::General { general_shader } => { + match get_shader_type(*general_shader) { + ExecutionModel::RayGenerationKHR + | ExecutionModel::MissKHR + | ExecutionModel::CallableKHR => Ok(()), + _ => Err(Box::new(ValidationError { + problem: "general shader in GENERAL group must be a RayGeneration, Miss, or Callable shader".into(), + vuids: &["VUID-VkRayTracingShaderGroupCreateInfoKHR-type-03474"], + ..Default::default() + })), + }?; + } + RayTracingShaderGroupCreateInfo::ProceduralHit { + intersection_shader, + any_hit_shader, + closest_hit_shader, + } => { + if get_shader_type(*intersection_shader) != ExecutionModel::IntersectionKHR { + return Err(Box::new(ValidationError { + problem: "intersection shader in PROCEDURAL_HIT_GROUP must be an Intersection shader".into(), + vuids: &["VUID-VkRayTracingShaderGroupCreateInfoKHR-type-03476"], + ..Default::default() + })); + } + + if let Some(any_hit_shader) = any_hit_shader { + if get_shader_type(*any_hit_shader) != ExecutionModel::AnyHitKHR { + return Err(Box::new(ValidationError { + problem: "any hit shader must be an AnyHit shader".into(), + vuids: &[ + "VUID-VkRayTracingShaderGroupCreateInfoKHR-anyHitShader-03479", + ], + ..Default::default() + })); + } + } + + if let Some(closest_hit_shader) = closest_hit_shader { + if get_shader_type(*closest_hit_shader) != ExecutionModel::ClosestHitKHR { + return Err(Box::new(ValidationError { + problem: "closest hit shader must be a ClosestHit shader".into(), + vuids: &[ + "VUID-VkRayTracingShaderGroupCreateInfoKHR-closestHitShader-03478", + ], + ..Default::default() + })); + } + } + } + RayTracingShaderGroupCreateInfo::TrianglesHit { + any_hit_shader, + closest_hit_shader, + } => { + if let Some(any_hit_shader) = any_hit_shader { + if get_shader_type(*any_hit_shader) != ExecutionModel::AnyHitKHR { + return Err(Box::new(ValidationError { + problem: "any hit shader must be an AnyHit shader".into(), + vuids: &[ + "VUID-VkRayTracingShaderGroupCreateInfoKHR-anyHitShader-03479", + ], + ..Default::default() + })); + } + } + + if let Some(closest_hit_shader) = closest_hit_shader { + if get_shader_type(*closest_hit_shader) != ExecutionModel::ClosestHitKHR { + return Err(Box::new(ValidationError { + problem: "closest hit shader must be a ClosestHit shader".into(), + vuids: &[ + "VUID-VkRayTracingShaderGroupCreateInfoKHR-closestHitShader-03478", + ], + ..Default::default() + })); + } + } + } + } + + Ok(()) + } + pub(crate) fn to_vk(&self) -> ash::vk::RayTracingShaderGroupCreateInfoKHR<'static> { match self { RayTracingShaderGroupCreateInfo::General { general_shader } => { @@ -461,6 +701,7 @@ pub struct RayTracingPipelineCreateInfoFields3Vk { pub(crate) stages_fields2_vk: SmallVec<[PipelineShaderStageCreateInfoFields2Vk; 5]>, } +/// An object that holds the addresses of the shader groups in a shader binding table. #[derive(Debug, Clone)] pub struct ShaderBindingTableAddresses { pub raygen: StridedDeviceAddressRegionKHR, @@ -469,6 +710,7 @@ pub struct ShaderBindingTableAddresses { pub callable: StridedDeviceAddressRegionKHR, } +/// An object that holds the shader binding table buffer and its addresses. #[derive(Debug, Clone)] pub struct ShaderBindingTable { addresses: ShaderBindingTableAddresses, @@ -480,6 +722,7 @@ impl ShaderBindingTable { &self.addresses } + /// Automatically creates a shader binding table from a ray tracing pipeline. pub fn new( allocator: Arc, ray_tracing_pipeline: &RayTracingPipeline, @@ -488,17 +731,14 @@ impl ShaderBindingTable { let mut hit_shader_count: u64 = 0; let mut callable_shader_count: u64 = 0; - let get_shader_type = |shader: u32| { - ray_tracing_pipeline.stages()[shader as usize] - .entry_point - .info() - .execution_model - }; - for group in ray_tracing_pipeline.groups() { match group { RayTracingShaderGroupCreateInfo::General { general_shader } => { - match get_shader_type(*general_shader) { + match ray_tracing_pipeline.stages()[*general_shader as usize] + .entry_point + .info() + .execution_model + { ExecutionModel::RayGenerationKHR => {} ExecutionModel::MissKHR => miss_shader_count += 1, ExecutionModel::CallableKHR => callable_shader_count += 1, @@ -507,43 +747,8 @@ impl ShaderBindingTable { } } } - RayTracingShaderGroupCreateInfo::ProceduralHit { - intersection_shader, - any_hit_shader, - closest_hit_shader, - } => { - if get_shader_type(*intersection_shader) != ExecutionModel::IntersectionKHR { - panic!("Unexpected shader type in procedural hit shader group"); - } - if let Some(any_hit_shader) = any_hit_shader { - if get_shader_type(*any_hit_shader) != ExecutionModel::AnyHitKHR { - panic!("Unexpected shader type in procedural hit shader group"); - } - } - if let Some(closest_hit_shader) = closest_hit_shader { - if get_shader_type(*closest_hit_shader) != ExecutionModel::ClosestHitKHR { - panic!("Unexpected shader type in procedural hit shader group"); - } - } - hit_shader_count += 1; - } - RayTracingShaderGroupCreateInfo::TrianglesHit { - any_hit_shader, - closest_hit_shader, - } => { - if any_hit_shader.is_none() && closest_hit_shader.is_none() { - panic!("Triangles hit shader group must have at least one hit shader"); - } - if let Some(any_hit_shader) = any_hit_shader { - if get_shader_type(*any_hit_shader) != ExecutionModel::AnyHitKHR { - panic!("Unexpected shader type in triangles hit shader group"); - } - } - if let Some(closest_hit_shader) = closest_hit_shader { - if get_shader_type(*closest_hit_shader) != ExecutionModel::ClosestHitKHR { - panic!("Unexpected shader type in triangles hit shader group"); - } - } + RayTracingShaderGroupCreateInfo::ProceduralHit { .. } + | RayTracingShaderGroupCreateInfo::TrianglesHit { .. } => { hit_shader_count += 1; } } @@ -560,13 +765,11 @@ impl ShaderBindingTable { let properties = ray_tracing_pipeline.device().physical_device().properties(); let handle_size_aligned = align_up( handle_data.handle_size() as u64, - DeviceAlignment::new(properties.shader_group_handle_alignment.unwrap() as u64) - .expect("unexpected shader_group_handle_alignment"), + DeviceAlignment::new(properties.shader_group_handle_alignment.unwrap() as u64).unwrap(), ); let shader_group_base_alignment = - DeviceAlignment::new(properties.shader_group_base_alignment.unwrap() as u64) - .expect("unexpected shader_group_base_alignment"); + DeviceAlignment::new(properties.shader_group_base_alignment.unwrap() as u64).unwrap(); let raygen_stride = align_up(handle_size_aligned, shader_group_base_alignment); @@ -615,7 +818,7 @@ impl ShaderBindingTable { }, raygen.size + miss.size + hit.size + callable.size, ) - .expect("todo: raytracing: better error type"); + .expect("todo: raytracing: better error type for buffer errors"); raygen.device_address = sbt_buffer.buffer().device_address().unwrap().get(); miss.device_address = raygen.device_address + raygen.size; From 8392a7f8c284af300fb680a58156444d7666014c Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Wed, 27 Nov 2024 23:31:40 +0800 Subject: [PATCH 12/26] move mod.rs --- vulkano/src/pipeline/{ray_tracing/mod.rs => ray_tracing.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename vulkano/src/pipeline/{ray_tracing/mod.rs => ray_tracing.rs} (100%) diff --git a/vulkano/src/pipeline/ray_tracing/mod.rs b/vulkano/src/pipeline/ray_tracing.rs similarity index 100% rename from vulkano/src/pipeline/ray_tracing/mod.rs rename to vulkano/src/pipeline/ray_tracing.rs From 6c6326975050cc9633faacd9f8fd097b90c3dc1e Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Wed, 27 Nov 2024 23:52:04 +0800 Subject: [PATCH 13/26] fmt & clippy --- examples/triangle-raytracing-auto/main.rs | 4 +-- examples/triangle-raytracing-auto/scene.rs | 24 +++++++-------- examples/triangle-raytracing/main.rs | 2 +- examples/triangle-raytracing/scene.rs | 7 ++--- vulkano/src/pipeline/ray_tracing.rs | 35 +++++++++++----------- 5 files changed, 34 insertions(+), 38 deletions(-) diff --git a/examples/triangle-raytracing-auto/main.rs b/examples/triangle-raytracing-auto/main.rs index 3fa6fb7b41..d39d912a35 100644 --- a/examples/triangle-raytracing-auto/main.rs +++ b/examples/triangle-raytracing-auto/main.rs @@ -190,7 +190,7 @@ impl ApplicationHandler for App { .unwrap(); let (swapchain_format, swapchain_color_space) = supported_storage_formats - .get(0) + .first() .map(|(format, color_space)| (*format, *color_space)) .unwrap(); Swapchain::new( @@ -281,7 +281,7 @@ impl ApplicationHandler for App { let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(self.device.clone())); let scene = Scene::new( - &self, + self, &images, pipeline_layout, descriptor_set_allocator.clone(), diff --git a/examples/triangle-raytracing-auto/scene.rs b/examples/triangle-raytracing-auto/scene.rs index b9d2bc87c3..1f0d29e58b 100644 --- a/examples/triangle-raytracing-auto/scene.rs +++ b/examples/triangle-raytracing-auto/scene.rs @@ -1,10 +1,6 @@ -use std::sync::Arc; -use std::{iter, mem::size_of}; - use crate::App; use glam::{Mat4, Vec3}; -use vulkano::command_buffer::PrimaryAutoCommandBuffer; -use vulkano::descriptor_set::DescriptorSet; +use std::{iter, mem::size_of, sync::Arc}; use vulkano::{ acceleration_structure::{ AccelerationStructure, AccelerationStructureBuildGeometryInfo, @@ -17,9 +13,11 @@ use vulkano::{ buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer}, command_buffer::{ allocator::CommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, - PrimaryCommandBufferAbstract, + PrimaryAutoCommandBuffer, PrimaryCommandBufferAbstract, + }, + descriptor_set::{ + allocator::StandardDescriptorSetAllocator, DescriptorSet, WriteDescriptorSet, }, - descriptor_set::{allocator::StandardDescriptorSetAllocator, WriteDescriptorSet}, device::{Device, Queue}, format::Format, image::{view::ImageView, Image}, @@ -163,11 +161,12 @@ impl Scene { ) .unwrap(); - // Build the bottom-level acceleration structure and then the top-level acceleration structure. - // Acceleration structures are used to accelerate ray tracing. + // Build the bottom-level acceleration structure and then the top-level acceleration + // structure. Acceleration structures are used to accelerate ray tracing. // The bottom-level acceleration structure contains the geometry data. - // The top-level acceleration structure contains the instances of the bottom-level acceleration structures. - // In our shader, we will trace rays against the top-level acceleration structure. + // The top-level acceleration structure contains the instances of the bottom-level + // acceleration structures. In our shader, we will trace rays against the top-level + // acceleration structure. let blas = unsafe { build_acceleration_structure_triangles( vertex_buffer, @@ -313,7 +312,8 @@ fn window_size_dependent_setup( /// A helper function to build a acceleration structure and wait for its completion. /// # SAFETY -/// - If you are referencing a bottom-level acceleration structure in a top-level acceleration structure, +/// - If you are referencing a bottom-level acceleration structure in a top-level acceleration +/// structure, /// you must ensure that the bottom-level acceleration structure is kept alive. unsafe fn build_acceleration_structure_common( geometries: AccelerationStructureGeometries, diff --git a/examples/triangle-raytracing/main.rs b/examples/triangle-raytracing/main.rs index f94b1c0a87..f82387d1c3 100644 --- a/examples/triangle-raytracing/main.rs +++ b/examples/triangle-raytracing/main.rs @@ -199,7 +199,7 @@ impl ApplicationHandler for App { .unwrap(); let (swapchain_format, swapchain_color_space) = supported_storage_formats - .get(0) + .first() .map(|(format, color_space)| (*format, *color_space)) .unwrap(); println!("Using swapchain format: {:?}", swapchain_format); diff --git a/examples/triangle-raytracing/scene.rs b/examples/triangle-raytracing/scene.rs index 1c061d1fd1..577d1d463d 100644 --- a/examples/triangle-raytracing/scene.rs +++ b/examples/triangle-raytracing/scene.rs @@ -1,7 +1,6 @@ -use std::sync::Arc; -use std::{iter, mem::size_of}; - +use crate::{App, RenderContext}; use glam::{Mat4, Vec3}; +use std::{iter, mem::size_of, sync::Arc}; use vulkano::{ acceleration_structure::{ AccelerationStructure, AccelerationStructureBuildGeometryInfo, @@ -38,8 +37,6 @@ use vulkano_taskgraph::{ command_buffer::RecordingCommandBuffer, resource::Resources, Id, Task, TaskContext, TaskResult, }; -use crate::{App, RenderContext}; - mod raygen { vulkano_shaders::shader! { ty: "raygen", diff --git a/vulkano/src/pipeline/ray_tracing.rs b/vulkano/src/pipeline/ray_tracing.rs index ef92b95456..553fa48bd8 100644 --- a/vulkano/src/pipeline/ray_tracing.rs +++ b/vulkano/src/pipeline/ray_tracing.rs @@ -1,9 +1,8 @@ -use std::{collections::hash_map::Entry, mem::MaybeUninit, num::NonZeroU64, ptr, sync::Arc}; - -use ahash::{HashMap, HashSet}; -use ash::vk::StridedDeviceAddressRegionKHR; -use smallvec::SmallVec; - +use super::{ + cache::PipelineCache, DynamicState, Pipeline, PipelineBindPoint, PipelineCreateFlags, + PipelineLayout, PipelineShaderStageCreateInfo, PipelineShaderStageCreateInfoExtensionsVk, + PipelineShaderStageCreateInfoFields1Vk, PipelineShaderStageCreateInfoFields2Vk, +}; use crate::{ buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer}, device::{Device, DeviceOwned, DeviceOwnedDebugWrapper}, @@ -16,12 +15,10 @@ use crate::{ shader::{spirv::ExecutionModel, DescriptorBindingRequirements}, Validated, ValidationError, VulkanError, VulkanObject, }; - -use super::{ - cache::PipelineCache, DynamicState, Pipeline, PipelineBindPoint, PipelineCreateFlags, - PipelineLayout, PipelineShaderStageCreateInfo, PipelineShaderStageCreateInfoExtensionsVk, - PipelineShaderStageCreateInfoFields1Vk, PipelineShaderStageCreateInfoFields2Vk, -}; +use ahash::{HashMap, HashSet}; +use ash::vk::StridedDeviceAddressRegionKHR; +use smallvec::SmallVec; +use std::{collections::hash_map::Entry, mem::MaybeUninit, num::NonZeroU64, ptr, sync::Arc}; /// Defines how the implementation should perform ray tracing operations. /// @@ -382,12 +379,13 @@ impl RayTracingPipelineCreateInfo { // { // return Err(Box::new(ValidationError { // problem: - // format!("`dynamic_state` contains a dynamic state other than RayTracingPipelineStackSize: {:?}", dynamic_state).into(), - // vuids: &["VUID-VkRayTracingPipelineCreateInfoKHR-pDynamicStates-03602"], + // format!("`dynamic_state` contains a dynamic state other than + // RayTracingPipelineStackSize: {:?}", dynamic_state).into(), vuids: + // &["VUID-VkRayTracingPipelineCreateInfoKHR-pDynamicStates-03602"], // ..Default::default() // })); // } - if dynamic_state.len() > 0 { + if !dynamic_state.is_empty() { todo!("Dynamic state for ray tracing pipelines is not yet supported"); } @@ -446,7 +444,7 @@ impl RayTracingPipelineCreateInfo { val_vk = val_vk.dynamic_state(dynamic_state_vk); } - return val_vk; + val_vk } pub(crate) fn to_vk_fields1<'a>( @@ -541,7 +539,8 @@ impl RayTracingPipelineCreateInfo { /// Enum representing different types of Ray Tracing Shader Groups. /// /// Contains the index of the shader to use for each type of shader group. -/// The index corresponds to the position of the shader in the `stages` field of the `RayTracingPipelineCreateInfo`. +/// The index corresponds to the position of the shader in the `stages` field of the +/// `RayTracingPipelineCreateInfo`. #[derive(Debug, Clone)] pub enum RayTracingShaderGroupCreateInfo { General { @@ -757,7 +756,7 @@ impl ShaderBindingTable { let handle_data = ray_tracing_pipeline .device() .get_ray_tracing_shader_group_handles( - &ray_tracing_pipeline, + ray_tracing_pipeline, 0, ray_tracing_pipeline.groups().len() as u32, )?; From b7e593e088cbf484634e433e990f1c34c0674e0d Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Wed, 27 Nov 2024 23:56:08 +0800 Subject: [PATCH 14/26] fix clippy ci --- vulkano/src/pipeline/ray_tracing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vulkano/src/pipeline/ray_tracing.rs b/vulkano/src/pipeline/ray_tracing.rs index 553fa48bd8..795b41ce61 100644 --- a/vulkano/src/pipeline/ray_tracing.rs +++ b/vulkano/src/pipeline/ray_tracing.rs @@ -527,7 +527,7 @@ impl RayTracingPipelineCreateInfo { } } - pub(crate) fn to_vk_fields3<'a>(&self) -> RayTracingPipelineCreateInfoFields3Vk { + pub(crate) fn to_vk_fields3(&self) -> RayTracingPipelineCreateInfoFields3Vk { let Self { stages, .. } = self; let stages_fields2_vk = stages.iter().map(|stage| stage.to_vk_fields2()).collect(); From 4b908abeb6e17d4824588a89921645a653aee55a Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Fri, 29 Nov 2024 15:45:13 +0800 Subject: [PATCH 15/26] add unit tests for sbt builder --- examples/triangle-raytracing-auto/scene.rs | 2 +- vulkano/src/device/mod.rs | 4 +- vulkano/src/pipeline/ray_tracing.rs | 143 +++++++++++++++++---- 3 files changed, 122 insertions(+), 27 deletions(-) diff --git a/examples/triangle-raytracing-auto/scene.rs b/examples/triangle-raytracing-auto/scene.rs index 1f0d29e58b..24b167ac6b 100644 --- a/examples/triangle-raytracing-auto/scene.rs +++ b/examples/triangle-raytracing-auto/scene.rs @@ -314,7 +314,7 @@ fn window_size_dependent_setup( /// # SAFETY /// - If you are referencing a bottom-level acceleration structure in a top-level acceleration /// structure, -/// you must ensure that the bottom-level acceleration structure is kept alive. +/// you must ensure that the bottom-level acceleration structure is kept alive. unsafe fn build_acceleration_structure_common( geometries: AccelerationStructureGeometries, primitive_count: u32, diff --git a/vulkano/src/device/mod.rs b/vulkano/src/device/mod.rs index a1fde8e53b..0157c3efac 100644 --- a/vulkano/src/device/mod.rs +++ b/vulkano/src/device/mod.rs @@ -2194,8 +2194,8 @@ impl Deref for DeviceOwnedDebugWrapper { #[derive(Clone, Debug)] pub struct ShaderGroupHandlesData { - data: Vec, - handle_size: u32, + pub(crate) data: Vec, + pub(crate) handle_size: u32, } impl ShaderGroupHandlesData { diff --git a/vulkano/src/pipeline/ray_tracing.rs b/vulkano/src/pipeline/ray_tracing.rs index 795b41ce61..28d4d6bee8 100644 --- a/vulkano/src/pipeline/ray_tracing.rs +++ b/vulkano/src/pipeline/ray_tracing.rs @@ -5,7 +5,7 @@ use super::{ }; use crate::{ buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer}, - device::{Device, DeviceOwned, DeviceOwnedDebugWrapper}, + device::{Device, DeviceOwned, DeviceOwnedDebugWrapper, ShaderGroupHandlesData}, instance::InstanceOwnedDebugWrapper, macros::impl_id_counter, memory::{ @@ -826,29 +826,17 @@ impl ShaderBindingTable { { let mut sbt_buffer_write = sbt_buffer.write().unwrap(); - - let mut handle_iter = handle_data.iter(); - - let handle_size = handle_data.handle_size() as usize; - sbt_buffer_write[..handle_size].copy_from_slice(handle_iter.next().unwrap()); - let mut offset = raygen.size as usize; - for _ in 0..miss_shader_count { - sbt_buffer_write[offset..offset + handle_size] - .copy_from_slice(handle_iter.next().unwrap()); - offset += miss.stride as usize; - } - offset = (raygen.size + miss.size) as usize; - for _ in 0..hit_shader_count { - sbt_buffer_write[offset..offset + handle_size] - .copy_from_slice(handle_iter.next().unwrap()); - offset += hit.stride as usize; - } - offset = (raygen.size + miss.size + hit.size) as usize; - for _ in 0..callable_shader_count { - sbt_buffer_write[offset..offset + handle_size] - .copy_from_slice(handle_iter.next().unwrap()); - offset += callable.stride as usize; - } + copy_shader_handles( + &handle_data, + &mut sbt_buffer_write, + raygen.size as usize, + miss.size as usize, + miss.stride as usize, + hit.size as usize, + hit.stride as usize, + callable.size as usize, + callable.stride as usize, + ); } Ok(Self { @@ -862,3 +850,110 @@ impl ShaderBindingTable { }) } } + +fn copy_shader_handles( + handle_data: &ShaderGroupHandlesData, + output: &mut [u8], + raygen_size: usize, + miss_size: usize, + miss_stride: usize, + hit_size: usize, + hit_stride: usize, + callable_size: usize, + callable_stride: usize, +) { + let handle_size = handle_data.handle_size() as usize; + let mut handle_iter = handle_data.iter(); + + // Copy raygen shader handle + output[..handle_size].copy_from_slice(handle_iter.next().unwrap()); + + // Copy miss shader handles + let mut offset = raygen_size; + while offset < raygen_size + miss_size { + output[offset..offset + handle_size].copy_from_slice(handle_iter.next().unwrap()); + offset += miss_stride; + } + + // Copy hit shader handles + assert_eq!(offset, raygen_size + miss_size); + while offset < raygen_size + miss_size + hit_size { + output[offset..offset + handle_size].copy_from_slice(handle_iter.next().unwrap()); + offset += hit_stride; + } + + // Copy callable shader handles + assert_eq!(offset, raygen_size + miss_size + hit_size); + while offset < raygen_size + miss_size + hit_size + callable_size { + output[offset..offset + handle_size].copy_from_slice(handle_iter.next().unwrap()); + offset += callable_stride; + } + + assert_eq!(offset, raygen_size + miss_size + hit_size + callable_size); +} + +#[cfg(test)] +mod tests { + use crate::device::ShaderGroupHandlesData; + + use super::*; + + #[test] + fn test_copy_shader_handles_single_raygen() { + let handle_data = ShaderGroupHandlesData { + data: vec![1, 2, 3, 4], + handle_size: 4, + }; + let mut output = vec![0; 4]; + + copy_shader_handles(&handle_data, &mut output, 4, 0, 0, 0, 0, 0, 0); + + assert_eq!(output, vec![1, 2, 3, 4]); + } + + #[test] + fn test_copy_shader_handles_with_stride() { + let handle_data = ShaderGroupHandlesData { + data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + handle_size: 4, + }; + let mut output = vec![0; 24]; + + // raygen (4 bytes) + 2 miss shaders with 8-byte stride + copy_shader_handles(&handle_data, &mut output, 4, 16, 8, 4, 4, 0, 0); + + assert_eq!( + output, + vec![ + 1, 2, 3, 4, // raygen + 5, 6, 7, 8, // first miss shader + 0, 0, 0, 0, // padding + 9, 10, 11, 12, // second miss shader + 0, 0, 0, 0, // padding + 13, 14, 15, 16 // hit shader + ] + ); + } + + #[test] + fn test_copy_shader_handles_all_types() { + let handle_data = ShaderGroupHandlesData { + data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + handle_size: 4, + }; + let mut output = vec![0; 24]; + + // raygen (4 bytes) + miss (4 bytes) + hit (4 bytes) + callable (4 bytes) + copy_shader_handles(&handle_data, &mut output, 4, 4, 4, 4, 4, 4, 4); + + assert_eq!( + output[..16], + vec![ + 1, 2, 3, 4, // raygen + 5, 6, 7, 8, // miss + 9, 10, 11, 12, // hit + 13, 14, 15, 16 // callable + ] + ); + } +} From a6a8739f7baf097619734a0a2907aea7fe30c3d2 Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Fri, 29 Nov 2024 15:48:28 +0800 Subject: [PATCH 16/26] fmt --- examples/triangle-raytracing-auto/scene.rs | 3 +-- vulkano/src/pipeline/ray_tracing.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/triangle-raytracing-auto/scene.rs b/examples/triangle-raytracing-auto/scene.rs index 24b167ac6b..37784788d4 100644 --- a/examples/triangle-raytracing-auto/scene.rs +++ b/examples/triangle-raytracing-auto/scene.rs @@ -313,8 +313,7 @@ fn window_size_dependent_setup( /// A helper function to build a acceleration structure and wait for its completion. /// # SAFETY /// - If you are referencing a bottom-level acceleration structure in a top-level acceleration -/// structure, -/// you must ensure that the bottom-level acceleration structure is kept alive. +/// structure, you must ensure that the bottom-level acceleration structure is kept alive. unsafe fn build_acceleration_structure_common( geometries: AccelerationStructureGeometries, primitive_count: u32, diff --git a/vulkano/src/pipeline/ray_tracing.rs b/vulkano/src/pipeline/ray_tracing.rs index 28d4d6bee8..4f99a8a190 100644 --- a/vulkano/src/pipeline/ray_tracing.rs +++ b/vulkano/src/pipeline/ray_tracing.rs @@ -894,9 +894,8 @@ fn copy_shader_handles( #[cfg(test)] mod tests { - use crate::device::ShaderGroupHandlesData; - use super::*; + use crate::device::ShaderGroupHandlesData; #[test] fn test_copy_shader_handles_single_raygen() { From 6d68913f7177dc5c755412e5e5ef70531c53f425 Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:17:50 +0800 Subject: [PATCH 17/26] undo sbt copy refactor --- vulkano/src/device/mod.rs | 4 +- vulkano/src/pipeline/ray_tracing.rs | 142 +++++----------------------- 2 files changed, 26 insertions(+), 120 deletions(-) diff --git a/vulkano/src/device/mod.rs b/vulkano/src/device/mod.rs index 0157c3efac..a1fde8e53b 100644 --- a/vulkano/src/device/mod.rs +++ b/vulkano/src/device/mod.rs @@ -2194,8 +2194,8 @@ impl Deref for DeviceOwnedDebugWrapper { #[derive(Clone, Debug)] pub struct ShaderGroupHandlesData { - pub(crate) data: Vec, - pub(crate) handle_size: u32, + data: Vec, + handle_size: u32, } impl ShaderGroupHandlesData { diff --git a/vulkano/src/pipeline/ray_tracing.rs b/vulkano/src/pipeline/ray_tracing.rs index 4f99a8a190..795b41ce61 100644 --- a/vulkano/src/pipeline/ray_tracing.rs +++ b/vulkano/src/pipeline/ray_tracing.rs @@ -5,7 +5,7 @@ use super::{ }; use crate::{ buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer}, - device::{Device, DeviceOwned, DeviceOwnedDebugWrapper, ShaderGroupHandlesData}, + device::{Device, DeviceOwned, DeviceOwnedDebugWrapper}, instance::InstanceOwnedDebugWrapper, macros::impl_id_counter, memory::{ @@ -826,17 +826,29 @@ impl ShaderBindingTable { { let mut sbt_buffer_write = sbt_buffer.write().unwrap(); - copy_shader_handles( - &handle_data, - &mut sbt_buffer_write, - raygen.size as usize, - miss.size as usize, - miss.stride as usize, - hit.size as usize, - hit.stride as usize, - callable.size as usize, - callable.stride as usize, - ); + + let mut handle_iter = handle_data.iter(); + + let handle_size = handle_data.handle_size() as usize; + sbt_buffer_write[..handle_size].copy_from_slice(handle_iter.next().unwrap()); + let mut offset = raygen.size as usize; + for _ in 0..miss_shader_count { + sbt_buffer_write[offset..offset + handle_size] + .copy_from_slice(handle_iter.next().unwrap()); + offset += miss.stride as usize; + } + offset = (raygen.size + miss.size) as usize; + for _ in 0..hit_shader_count { + sbt_buffer_write[offset..offset + handle_size] + .copy_from_slice(handle_iter.next().unwrap()); + offset += hit.stride as usize; + } + offset = (raygen.size + miss.size + hit.size) as usize; + for _ in 0..callable_shader_count { + sbt_buffer_write[offset..offset + handle_size] + .copy_from_slice(handle_iter.next().unwrap()); + offset += callable.stride as usize; + } } Ok(Self { @@ -850,109 +862,3 @@ impl ShaderBindingTable { }) } } - -fn copy_shader_handles( - handle_data: &ShaderGroupHandlesData, - output: &mut [u8], - raygen_size: usize, - miss_size: usize, - miss_stride: usize, - hit_size: usize, - hit_stride: usize, - callable_size: usize, - callable_stride: usize, -) { - let handle_size = handle_data.handle_size() as usize; - let mut handle_iter = handle_data.iter(); - - // Copy raygen shader handle - output[..handle_size].copy_from_slice(handle_iter.next().unwrap()); - - // Copy miss shader handles - let mut offset = raygen_size; - while offset < raygen_size + miss_size { - output[offset..offset + handle_size].copy_from_slice(handle_iter.next().unwrap()); - offset += miss_stride; - } - - // Copy hit shader handles - assert_eq!(offset, raygen_size + miss_size); - while offset < raygen_size + miss_size + hit_size { - output[offset..offset + handle_size].copy_from_slice(handle_iter.next().unwrap()); - offset += hit_stride; - } - - // Copy callable shader handles - assert_eq!(offset, raygen_size + miss_size + hit_size); - while offset < raygen_size + miss_size + hit_size + callable_size { - output[offset..offset + handle_size].copy_from_slice(handle_iter.next().unwrap()); - offset += callable_stride; - } - - assert_eq!(offset, raygen_size + miss_size + hit_size + callable_size); -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::device::ShaderGroupHandlesData; - - #[test] - fn test_copy_shader_handles_single_raygen() { - let handle_data = ShaderGroupHandlesData { - data: vec![1, 2, 3, 4], - handle_size: 4, - }; - let mut output = vec![0; 4]; - - copy_shader_handles(&handle_data, &mut output, 4, 0, 0, 0, 0, 0, 0); - - assert_eq!(output, vec![1, 2, 3, 4]); - } - - #[test] - fn test_copy_shader_handles_with_stride() { - let handle_data = ShaderGroupHandlesData { - data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], - handle_size: 4, - }; - let mut output = vec![0; 24]; - - // raygen (4 bytes) + 2 miss shaders with 8-byte stride - copy_shader_handles(&handle_data, &mut output, 4, 16, 8, 4, 4, 0, 0); - - assert_eq!( - output, - vec![ - 1, 2, 3, 4, // raygen - 5, 6, 7, 8, // first miss shader - 0, 0, 0, 0, // padding - 9, 10, 11, 12, // second miss shader - 0, 0, 0, 0, // padding - 13, 14, 15, 16 // hit shader - ] - ); - } - - #[test] - fn test_copy_shader_handles_all_types() { - let handle_data = ShaderGroupHandlesData { - data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], - handle_size: 4, - }; - let mut output = vec![0; 24]; - - // raygen (4 bytes) + miss (4 bytes) + hit (4 bytes) + callable (4 bytes) - copy_shader_handles(&handle_data, &mut output, 4, 4, 4, 4, 4, 4, 4); - - assert_eq!( - output[..16], - vec![ - 1, 2, 3, 4, // raygen - 5, 6, 7, 8, // miss - 9, 10, 11, 12, // hit - 13, 14, 15, 16 // callable - ] - ); - } -} From 81f2fb0b05cb40d374ab547ebca0b734b3555f56 Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:20:56 +0800 Subject: [PATCH 18/26] fix clippy --- vulkano/src/device/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vulkano/src/device/mod.rs b/vulkano/src/device/mod.rs index a1fde8e53b..66c50fdf17 100644 --- a/vulkano/src/device/mod.rs +++ b/vulkano/src/device/mod.rs @@ -2228,7 +2228,7 @@ impl<'a> Iterator for ShaderGroupHandlesDataIter<'a> { } } } -impl<'a> ExactSizeIterator for ShaderGroupHandlesDataIter<'a> {} +impl ExactSizeIterator for ShaderGroupHandlesDataIter<'_> {} impl ShaderGroupHandlesData { pub fn iter(&self) -> ShaderGroupHandlesDataIter<'_> { From ced0dc23ed133b56bfa8f775b767c2f0da828da0 Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Fri, 13 Dec 2024 09:48:41 +0800 Subject: [PATCH 19/26] rename example & cleanup --- Cargo.lock | 2 -- .../{triangle-raytracing-auto => ray-tracing-auto}/Cargo.toml | 0 examples/{triangle-raytracing-auto => ray-tracing-auto}/main.rs | 0 .../raytrace.rchit | 0 .../raytrace.rgen | 0 .../raytrace.rmiss | 0 .../{triangle-raytracing-auto => ray-tracing-auto}/scene.rs | 0 examples/{triangle-raytracing => ray-tracing}/Cargo.toml | 0 examples/{triangle-raytracing => ray-tracing}/main.rs | 1 - examples/{triangle-raytracing => ray-tracing}/raytrace.rchit | 0 examples/{triangle-raytracing => ray-tracing}/raytrace.rgen | 0 examples/{triangle-raytracing => ray-tracing}/raytrace.rmiss | 0 examples/{triangle-raytracing => ray-tracing}/scene.rs | 0 examples/triangle-util/Cargo.toml | 2 -- 14 files changed, 5 deletions(-) rename examples/{triangle-raytracing-auto => ray-tracing-auto}/Cargo.toml (100%) rename examples/{triangle-raytracing-auto => ray-tracing-auto}/main.rs (100%) rename examples/{triangle-raytracing-auto => ray-tracing-auto}/raytrace.rchit (100%) rename examples/{triangle-raytracing-auto => ray-tracing-auto}/raytrace.rgen (100%) rename examples/{triangle-raytracing-auto => ray-tracing-auto}/raytrace.rmiss (100%) rename examples/{triangle-raytracing-auto => ray-tracing-auto}/scene.rs (100%) rename examples/{triangle-raytracing => ray-tracing}/Cargo.toml (100%) rename examples/{triangle-raytracing => ray-tracing}/main.rs (99%) rename examples/{triangle-raytracing => ray-tracing}/raytrace.rchit (100%) rename examples/{triangle-raytracing => ray-tracing}/raytrace.rgen (100%) rename examples/{triangle-raytracing => ray-tracing}/raytrace.rmiss (100%) rename examples/{triangle-raytracing => ray-tracing}/scene.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 7288b14370..b46151f304 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1759,8 +1759,6 @@ dependencies = [ name = "triangle-util" version = "0.0.0" dependencies = [ - "ash", - "glam", "vulkano", "vulkano-shaders", "vulkano-util", diff --git a/examples/triangle-raytracing-auto/Cargo.toml b/examples/ray-tracing-auto/Cargo.toml similarity index 100% rename from examples/triangle-raytracing-auto/Cargo.toml rename to examples/ray-tracing-auto/Cargo.toml diff --git a/examples/triangle-raytracing-auto/main.rs b/examples/ray-tracing-auto/main.rs similarity index 100% rename from examples/triangle-raytracing-auto/main.rs rename to examples/ray-tracing-auto/main.rs diff --git a/examples/triangle-raytracing-auto/raytrace.rchit b/examples/ray-tracing-auto/raytrace.rchit similarity index 100% rename from examples/triangle-raytracing-auto/raytrace.rchit rename to examples/ray-tracing-auto/raytrace.rchit diff --git a/examples/triangle-raytracing-auto/raytrace.rgen b/examples/ray-tracing-auto/raytrace.rgen similarity index 100% rename from examples/triangle-raytracing-auto/raytrace.rgen rename to examples/ray-tracing-auto/raytrace.rgen diff --git a/examples/triangle-raytracing-auto/raytrace.rmiss b/examples/ray-tracing-auto/raytrace.rmiss similarity index 100% rename from examples/triangle-raytracing-auto/raytrace.rmiss rename to examples/ray-tracing-auto/raytrace.rmiss diff --git a/examples/triangle-raytracing-auto/scene.rs b/examples/ray-tracing-auto/scene.rs similarity index 100% rename from examples/triangle-raytracing-auto/scene.rs rename to examples/ray-tracing-auto/scene.rs diff --git a/examples/triangle-raytracing/Cargo.toml b/examples/ray-tracing/Cargo.toml similarity index 100% rename from examples/triangle-raytracing/Cargo.toml rename to examples/ray-tracing/Cargo.toml diff --git a/examples/triangle-raytracing/main.rs b/examples/ray-tracing/main.rs similarity index 99% rename from examples/triangle-raytracing/main.rs rename to examples/ray-tracing/main.rs index f82387d1c3..38be51ca5b 100644 --- a/examples/triangle-raytracing/main.rs +++ b/examples/ray-tracing/main.rs @@ -77,7 +77,6 @@ impl App { ext_swapchain_colorspace: true, ..required_extensions }, - enabled_layers: vec!["VK_LAYER_KHRONOS_validation".to_owned()], ..Default::default() }, ) diff --git a/examples/triangle-raytracing/raytrace.rchit b/examples/ray-tracing/raytrace.rchit similarity index 100% rename from examples/triangle-raytracing/raytrace.rchit rename to examples/ray-tracing/raytrace.rchit diff --git a/examples/triangle-raytracing/raytrace.rgen b/examples/ray-tracing/raytrace.rgen similarity index 100% rename from examples/triangle-raytracing/raytrace.rgen rename to examples/ray-tracing/raytrace.rgen diff --git a/examples/triangle-raytracing/raytrace.rmiss b/examples/ray-tracing/raytrace.rmiss similarity index 100% rename from examples/triangle-raytracing/raytrace.rmiss rename to examples/ray-tracing/raytrace.rmiss diff --git a/examples/triangle-raytracing/scene.rs b/examples/ray-tracing/scene.rs similarity index 100% rename from examples/triangle-raytracing/scene.rs rename to examples/ray-tracing/scene.rs diff --git a/examples/triangle-util/Cargo.toml b/examples/triangle-util/Cargo.toml index d4d99abf9b..f5d7a10104 100644 --- a/examples/triangle-util/Cargo.toml +++ b/examples/triangle-util/Cargo.toml @@ -21,5 +21,3 @@ vulkano-util = { workspace = true } # The Vulkan library doesn't provide any functionality to create and handle windows, as # this would be out of scope. In order to open a window, we are going to use the `winit` crate. winit = { workspace = true, default-features = true } -ash = { workspace = true } -glam = { workspace = true } From 17dd7cce6350589fb0df5c3e6be777e7f8cf584e Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Fri, 13 Dec 2024 10:55:58 +0800 Subject: [PATCH 20/26] rmiss -> miss --- examples/ray-tracing-auto/{raytrace.rmiss => raytrace.miss} | 0 examples/ray-tracing-auto/scene.rs | 2 +- examples/ray-tracing/{raytrace.rmiss => raytrace.miss} | 0 examples/ray-tracing/scene.rs | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) rename examples/ray-tracing-auto/{raytrace.rmiss => raytrace.miss} (100%) rename examples/ray-tracing/{raytrace.rmiss => raytrace.miss} (100%) diff --git a/examples/ray-tracing-auto/raytrace.rmiss b/examples/ray-tracing-auto/raytrace.miss similarity index 100% rename from examples/ray-tracing-auto/raytrace.rmiss rename to examples/ray-tracing-auto/raytrace.miss diff --git a/examples/ray-tracing-auto/scene.rs b/examples/ray-tracing-auto/scene.rs index 37784788d4..019b61e631 100644 --- a/examples/ray-tracing-auto/scene.rs +++ b/examples/ray-tracing-auto/scene.rs @@ -52,7 +52,7 @@ mod closest_hit { mod miss { vulkano_shaders::shader! { ty: "miss", - path: "raytrace.rmiss", + path: "raytrace.miss", vulkan_version: "1.2" } } diff --git a/examples/ray-tracing/raytrace.rmiss b/examples/ray-tracing/raytrace.miss similarity index 100% rename from examples/ray-tracing/raytrace.rmiss rename to examples/ray-tracing/raytrace.miss diff --git a/examples/ray-tracing/scene.rs b/examples/ray-tracing/scene.rs index 577d1d463d..b01eec3ed0 100644 --- a/examples/ray-tracing/scene.rs +++ b/examples/ray-tracing/scene.rs @@ -56,7 +56,7 @@ mod closest_hit { mod miss { vulkano_shaders::shader! { ty: "miss", - path: "raytrace.rmiss", + path: "raytrace.miss", vulkan_version: "1.2" } } From c517d7d95eed0101e83c8bcf3baa33ce6d966120 Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Fri, 13 Dec 2024 10:59:16 +0800 Subject: [PATCH 21/26] refactor & add doc --- vulkano/src/device/mod.rs | 65 +++++++++++++++------------- vulkano/src/pipeline/ray_tracing.rs | 67 ++++++++++++++++++++++++++++- 2 files changed, 102 insertions(+), 30 deletions(-) diff --git a/vulkano/src/device/mod.rs b/vulkano/src/device/mod.rs index 66c50fdf17..6d22b516fa 100644 --- a/vulkano/src/device/mod.rs +++ b/vulkano/src/device/mod.rs @@ -1306,12 +1306,33 @@ impl Device { Ok(()) } - pub fn get_ray_tracing_shader_group_handles( + pub fn ray_tracing_shader_group_handles( &self, ray_tracing_pipeline: &RayTracingPipeline, first_group: u32, group_count: u32, ) -> Result> { + self.validate_ray_tracing_pipeline_properties( + ray_tracing_pipeline, + first_group, + group_count, + )?; + + unsafe { + Ok(self.ray_tracing_shader_group_handles_unchecked( + ray_tracing_pipeline, + first_group, + group_count, + )?) + } + } + + fn validate_ray_tracing_pipeline_properties( + &self, + ray_tracing_pipeline: &RayTracingPipeline, + first_group: u32, + group_count: u32, + ) -> Result<(), Box> { if !self.enabled_features().ray_tracing_pipeline || self .physical_device() @@ -1339,6 +1360,16 @@ impl Device { } // TODO: VUID-vkGetRayTracingShaderGroupHandlesKHR-pipeline-07828 + Ok(()) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn ray_tracing_shader_group_handles_unchecked( + &self, + ray_tracing_pipeline: &RayTracingPipeline, + first_group: u32, + group_count: u32, + ) -> Result { let handle_size = self .physical_device() .properties() @@ -2192,6 +2223,7 @@ impl Deref for DeviceOwnedDebugWrapper { } } +/// Holds the data returned by [`Device::ray_tracing_shader_group_handles`]. #[derive(Clone, Debug)] pub struct ShaderGroupHandlesData { data: Vec, @@ -2208,35 +2240,10 @@ impl ShaderGroupHandlesData { } } -pub struct ShaderGroupHandlesDataIter<'a> { - data: &'a [u8], - handle_size: usize, - index: usize, -} - -impl<'a> Iterator for ShaderGroupHandlesDataIter<'a> { - type Item = &'a [u8]; - - fn next(&mut self) -> Option { - if self.index >= self.data.len() { - None - } else { - let end = self.index + self.handle_size; - let slice = &self.data[self.index..end]; - self.index = end; - Some(slice) - } - } -} -impl ExactSizeIterator for ShaderGroupHandlesDataIter<'_> {} - impl ShaderGroupHandlesData { - pub fn iter(&self) -> ShaderGroupHandlesDataIter<'_> { - ShaderGroupHandlesDataIter { - data: &self.data, - handle_size: self.handle_size as usize, - index: 0, - } + /// Returns an iterator over the handles in the data. + pub fn iter(&self) -> impl ExactSizeIterator { + self.data().chunks_exact(self.handle_size as usize) } } diff --git a/vulkano/src/pipeline/ray_tracing.rs b/vulkano/src/pipeline/ray_tracing.rs index 795b41ce61..ffac791c65 100644 --- a/vulkano/src/pipeline/ray_tracing.rs +++ b/vulkano/src/pipeline/ray_tracing.rs @@ -1,3 +1,42 @@ +//! Ray tracing pipeline functionality for GPU-accelerated ray tracing. +//! +//! # Overview +//! Ray tracing pipelines enable high-performance ray tracing by defining a set of shader stages that +//! handle ray generation, intersection testing, and shading calculations. The pipeline consists of +//! different shader stages organized into shader groups. +//! +//! # Shader Types +//! +//! ## Ray Generation Shader (rgen) +//! - Entry point for ray tracing +//! - Generates and traces primary rays +//! - Controls the overall ray tracing process +//! +//! ## Intersection Shaders +//! - **Built-in Triangle Intersection**: Handles standard triangle geometry intersection +//! - **Custom Intersection (intersection)**: Implements custom geometry intersection testing +//! +//! ## Hit Shaders +//! - **Closest Hit (chit)**: Executes when a ray finds its closest intersection +//! - **Any Hit (ahit)**: Optional shader that runs on every potential intersection +//! +//! ## Miss Shader (miss) +//! - Executes when a ray doesn't intersect any geometry +//! - Typically handles environment mapping or background colors +//! +//! ## Callable Shader (rcall) +//! - Utility shader that can be called from other shader stages +//! - Enables code reuse across different shader stages +//! +//! # Pipeline Organization +//! Shaders are organized into groups: +//! - General groups: Contains ray generation, miss, or callable shaders +//! - Triangle hit groups: Contains closest-hit and optional any-hit shaders +//! - Procedural hit groups: Contains intersection, closest-hit, and optional any-hit shaders +//! +//! The ray tracing pipeline uses a Shader Binding Table (SBT) to organize and access +//! these shader groups during execution. + use super::{ cache::PipelineCache, DynamicState, Pipeline, PipelineBindPoint, PipelineCreateFlags, PipelineLayout, PipelineShaderStageCreateInfo, PipelineShaderStageCreateInfoExtensionsVk, @@ -239,6 +278,9 @@ pub struct RayTracingPipelineCreateInfo { /// There is no default value. pub stages: SmallVec<[PipelineShaderStageCreateInfo; 5]>, + /// The shader groups to use. They reference the shader stages in `stages`. + /// + /// The default value is empty. pub groups: SmallVec<[RayTracingShaderGroupCreateInfo; 5]>, /// The maximum recursion depth of the pipeline. @@ -543,16 +585,39 @@ impl RayTracingPipelineCreateInfo { /// `RayTracingPipelineCreateInfo`. #[derive(Debug, Clone)] pub enum RayTracingShaderGroupCreateInfo { + /// General shader group type, typically used for ray generation and miss shaders. + /// + /// Contains a single shader stage that can be: + /// - Ray generation shader (rgen) + /// - Miss shader (rmiss) + /// - Callable shader (rcall) General { + /// Index of the general shader stage general_shader: u32, }, + + /// Procedural hit shader group type, used for custom intersection testing. + /// + /// Used when implementing custom intersection shapes or volumes. + /// Requires an intersection shader and can optionally include closest hit + /// and any hit shaders. ProceduralHit { + /// Optional index of the closest hit shader stage closest_hit_shader: Option, + /// Optional index of the any hit shader stage any_hit_shader: Option, + /// Index of the intersection shader stage intersection_shader: u32, }, + + /// Triangle hit shader group type, used for built-in triangle intersection. + /// + /// Used for standard triangle geometry intersection testing. + /// Can optionally include closest hit and any hit shaders. TrianglesHit { + /// Optional index of the closest hit shader stage closest_hit_shader: Option, + /// Optional index of the any hit shader stage any_hit_shader: Option, }, } @@ -755,7 +820,7 @@ impl ShaderBindingTable { let handle_data = ray_tracing_pipeline .device() - .get_ray_tracing_shader_group_handles( + .ray_tracing_shader_group_handles( ray_tracing_pipeline, 0, ray_tracing_pipeline.groups().len() as u32, From 9d738242e2e06b2ef2002ab20e9e4431a55f6f2a Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Fri, 13 Dec 2024 11:01:47 +0800 Subject: [PATCH 22/26] fmt --- vulkano/src/pipeline/ray_tracing.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vulkano/src/pipeline/ray_tracing.rs b/vulkano/src/pipeline/ray_tracing.rs index ffac791c65..c88eb98438 100644 --- a/vulkano/src/pipeline/ray_tracing.rs +++ b/vulkano/src/pipeline/ray_tracing.rs @@ -1,9 +1,9 @@ //! Ray tracing pipeline functionality for GPU-accelerated ray tracing. //! //! # Overview -//! Ray tracing pipelines enable high-performance ray tracing by defining a set of shader stages that -//! handle ray generation, intersection testing, and shading calculations. The pipeline consists of -//! different shader stages organized into shader groups. +//! Ray tracing pipelines enable high-performance ray tracing by defining a set of shader stages +//! that handle ray generation, intersection testing, and shading calculations. The pipeline +//! consists of different shader stages organized into shader groups. //! //! # Shader Types //! From 2d8804b6ef1cb7aea5ff9dc24b47f074ebbeee96 Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Fri, 13 Dec 2024 11:18:11 +0800 Subject: [PATCH 23/26] update dep & remove abbreviates --- vulkano/src/pipeline/ray_tracing.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/vulkano/src/pipeline/ray_tracing.rs b/vulkano/src/pipeline/ray_tracing.rs index c88eb98438..3c6e90fc12 100644 --- a/vulkano/src/pipeline/ray_tracing.rs +++ b/vulkano/src/pipeline/ray_tracing.rs @@ -7,24 +7,24 @@ //! //! # Shader Types //! -//! ## Ray Generation Shader (rgen) +//! ## Ray Generation Shader //! - Entry point for ray tracing //! - Generates and traces primary rays //! - Controls the overall ray tracing process //! //! ## Intersection Shaders //! - **Built-in Triangle Intersection**: Handles standard triangle geometry intersection -//! - **Custom Intersection (intersection)**: Implements custom geometry intersection testing +//! - **Custom Intersection**: Implements custom geometry intersection testing //! //! ## Hit Shaders -//! - **Closest Hit (chit)**: Executes when a ray finds its closest intersection -//! - **Any Hit (ahit)**: Optional shader that runs on every potential intersection +//! - **Closest Hit**: Executes when a ray finds its closest intersection +//! - **Any Hit**: Optional shader that runs on every potential intersection //! -//! ## Miss Shader (miss) +//! ## Miss Shader //! - Executes when a ray doesn't intersect any geometry //! - Typically handles environment mapping or background colors //! -//! ## Callable Shader (rcall) +//! ## Callable Shader //! - Utility shader that can be called from other shader stages //! - Enables code reuse across different shader stages //! @@ -54,8 +54,8 @@ use crate::{ shader::{spirv::ExecutionModel, DescriptorBindingRequirements}, Validated, ValidationError, VulkanError, VulkanObject, }; -use ahash::{HashMap, HashSet}; use ash::vk::StridedDeviceAddressRegionKHR; +use foldhash::{HashMap, HashSet}; use smallvec::SmallVec; use std::{collections::hash_map::Entry, mem::MaybeUninit, num::NonZeroU64, ptr, sync::Arc}; @@ -588,9 +588,9 @@ pub enum RayTracingShaderGroupCreateInfo { /// General shader group type, typically used for ray generation and miss shaders. /// /// Contains a single shader stage that can be: - /// - Ray generation shader (rgen) - /// - Miss shader (rmiss) - /// - Callable shader (rcall) + /// - Ray generation shader + /// - Miss shader + /// - Callable shader General { /// Index of the general shader stage general_shader: u32, From 9f6b795a614548c173c5bb3d02b847ec2b5bae36 Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Fri, 13 Dec 2024 22:09:14 +0800 Subject: [PATCH 24/26] refactor: - add doc - add check for stages/groups - pub modification - `pub use` StridedDeviceAddressRegionKHR in lib.rs --- vulkano/src/lib.rs | 3 ++ vulkano/src/pipeline/ray_tracing.rs | 62 +++++++++++++++++++++-------- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/vulkano/src/lib.rs b/vulkano/src/lib.rs index 288e8c1009..07eb7b18ae 100644 --- a/vulkano/src/lib.rs +++ b/vulkano/src/lib.rs @@ -179,6 +179,9 @@ pub use ash::vk::DeviceAddress; /// A [`DeviceAddress`] that is known not to equal zero. pub type NonNullDeviceAddress = NonZeroU64; +/// Represents a region of device addresses with a stride. +pub use ash::vk::StridedDeviceAddressRegionKHR as StridedDeviceAddressRegion; + /// Holds 24 bits in the least significant bits of memory, /// and 8 bytes in the most significant bits of that memory, /// occupying a single [`u32`] in total. diff --git a/vulkano/src/pipeline/ray_tracing.rs b/vulkano/src/pipeline/ray_tracing.rs index 3c6e90fc12..abee90419c 100644 --- a/vulkano/src/pipeline/ray_tracing.rs +++ b/vulkano/src/pipeline/ray_tracing.rs @@ -52,9 +52,8 @@ use crate::{ DeviceAlignment, }, shader::{spirv::ExecutionModel, DescriptorBindingRequirements}, - Validated, ValidationError, VulkanError, VulkanObject, + StridedDeviceAddressRegion, Validated, ValidationError, VulkanError, VulkanObject, }; -use ash::vk::StridedDeviceAddressRegionKHR; use foldhash::{HashMap, HashSet}; use smallvec::SmallVec; use std::{collections::hash_map::Entry, mem::MaybeUninit, num::NonZeroU64, ptr, sync::Arc}; @@ -140,6 +139,12 @@ impl RayTracingPipeline { Ok(Self::from_handle(device, handle, create_info)) } + /// Creates a new `RayTracingPipeline` from a raw object handle. + /// + /// # Safety + /// + /// - `handle` must be a valid Vulkan object handle created from `device`. + /// - `create_info` must match the info used to create the object. pub unsafe fn from_handle( device: Arc, handle: ash::vk::Pipeline, @@ -196,18 +201,22 @@ impl RayTracingPipeline { }) } + // Returns the shader groups that the pipeline was created with. pub fn groups(&self) -> &[RayTracingShaderGroupCreateInfo] { &self.groups } + // Returns the shader stages that the pipeline was created with. pub fn stages(&self) -> &[PipelineShaderStageCreateInfo] { &self.stages } + /// Returns the `Device` that the pipeline was created with. pub fn device(&self) -> &Arc { &self.device } + /// Returns the flags that the pipeline was created with. pub fn flags(&self) -> PipelineCreateFlags { self.flags } @@ -275,12 +284,12 @@ pub struct RayTracingPipelineCreateInfo { /// The ray tracing shader stages to use. /// - /// There is no default value. + /// The default value is empty, which must be overridden. pub stages: SmallVec<[PipelineShaderStageCreateInfo; 5]>, /// The shader groups to use. They reference the shader stages in `stages`. /// - /// The default value is empty. + /// The default value is empty, which must be overridden. pub groups: SmallVec<[RayTracingShaderGroupCreateInfo; 5]>, /// The maximum recursion depth of the pipeline. @@ -377,6 +386,13 @@ impl RayTracingPipelineCreateInfo { })); } + if stages.is_empty() { + return Err(Box::new(ValidationError { + problem: "`stages` is empty".into(), + vuids: &["VUID-VkRayTracingPipelineCreateInfoKHR-pLibraryInfo-07999"], + ..Default::default() + })); + } for stage in stages { stage.validate(device).map_err(|err| { err.add_context("stages") @@ -407,6 +423,13 @@ impl RayTracingPipelineCreateInfo { })?; } + if groups.is_empty() { + return Err(Box::new(ValidationError { + problem: "`groups` is empty".into(), + vuids: &["VUID-VkRayTracingPipelineCreateInfoKHR-flags-08700"], + ..Default::default() + })); + } for group in groups { group.validate(stages).map_err(|err| { err.add_context("groups") @@ -746,32 +769,36 @@ impl RayTracingShaderGroupCreateInfo { } } -pub struct RayTracingPipelineCreateInfoFields1Vk<'a> { +pub(crate) struct RayTracingPipelineCreateInfoFields1Vk<'a> { pub(crate) stages_vk: SmallVec<[ash::vk::PipelineShaderStageCreateInfo<'a>; 5]>, pub(crate) groups_vk: SmallVec<[ash::vk::RayTracingShaderGroupCreateInfoKHR<'static>; 5]>, pub(crate) dynamic_state_vk: Option>, } -pub struct RayTracingPipelineCreateInfoFields1ExtensionsVk { +pub(crate) struct RayTracingPipelineCreateInfoFields1ExtensionsVk { pub(crate) stages_extensions_vk: SmallVec<[PipelineShaderStageCreateInfoExtensionsVk; 5]>, } -pub struct RayTracingPipelineCreateInfoFields2Vk<'a> { +pub(crate) struct RayTracingPipelineCreateInfoFields2Vk<'a> { pub(crate) stages_fields1_vk: SmallVec<[PipelineShaderStageCreateInfoFields1Vk<'a>; 5]>, pub(crate) dynamic_states_vk: SmallVec<[ash::vk::DynamicState; 4]>, } -pub struct RayTracingPipelineCreateInfoFields3Vk { +pub(crate) struct RayTracingPipelineCreateInfoFields3Vk { pub(crate) stages_fields2_vk: SmallVec<[PipelineShaderStageCreateInfoFields2Vk; 5]>, } -/// An object that holds the addresses of the shader groups in a shader binding table. +/// An object that holds the strided addresses of the shader groups in a shader binding table. #[derive(Debug, Clone)] pub struct ShaderBindingTableAddresses { - pub raygen: StridedDeviceAddressRegionKHR, - pub miss: StridedDeviceAddressRegionKHR, - pub hit: StridedDeviceAddressRegionKHR, - pub callable: StridedDeviceAddressRegionKHR, + /// The address of the ray generation shader group handle. + pub raygen: StridedDeviceAddressRegion, + /// The address of the miss shader group handles. + pub miss: StridedDeviceAddressRegion, + /// The address of the hit shader group handles. + pub hit: StridedDeviceAddressRegion, + /// The address of the callable shader group handles. + pub callable: StridedDeviceAddressRegion, } /// An object that holds the shader binding table buffer and its addresses. @@ -782,6 +809,7 @@ pub struct ShaderBindingTable { } impl ShaderBindingTable { + /// Returns the addresses of the shader groups in the shader binding table. pub fn addresses(&self) -> &ShaderBindingTableAddresses { &self.addresses } @@ -837,12 +865,12 @@ impl ShaderBindingTable { let raygen_stride = align_up(handle_size_aligned, shader_group_base_alignment); - let mut raygen = StridedDeviceAddressRegionKHR { + let mut raygen = StridedDeviceAddressRegion { stride: raygen_stride, size: raygen_stride, device_address: 0, }; - let mut miss = StridedDeviceAddressRegionKHR { + let mut miss = StridedDeviceAddressRegion { stride: handle_size_aligned, size: align_up( handle_size_aligned * miss_shader_count, @@ -850,7 +878,7 @@ impl ShaderBindingTable { ), device_address: 0, }; - let mut hit = StridedDeviceAddressRegionKHR { + let mut hit = StridedDeviceAddressRegion { stride: handle_size_aligned, size: align_up( handle_size_aligned * hit_shader_count, @@ -858,7 +886,7 @@ impl ShaderBindingTable { ), device_address: 0, }; - let mut callable = StridedDeviceAddressRegionKHR { + let mut callable = StridedDeviceAddressRegion { stride: handle_size_aligned, size: align_up( handle_size_aligned * callable_shader_count, From 560eccb29830e2df7868da62f8ab50a956c3248d Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Sat, 14 Dec 2024 11:16:47 +0800 Subject: [PATCH 25/26] implement StridedDeviceAddressRegion --- .../src/command_buffer/commands/pipeline.rs | 17 +++++++++++------ .../src/command_buffer/commands/pipeline.rs | 13 +++++++++---- vulkano/src/lib.rs | 18 +++++++++++++++++- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/vulkano-taskgraph/src/command_buffer/commands/pipeline.rs b/vulkano-taskgraph/src/command_buffer/commands/pipeline.rs index 342cddbe6c..8cac98bdaa 100644 --- a/vulkano-taskgraph/src/command_buffer/commands/pipeline.rs +++ b/vulkano-taskgraph/src/command_buffer/commands/pipeline.rs @@ -682,18 +682,23 @@ impl RecordingCommandBuffer<'_> { depth: u32, ) -> &mut Self { let fns = self.device().fns(); + + let raygen = shader_binding_table_addresses.raygen.to_vk(); + let miss = shader_binding_table_addresses.miss.to_vk(); + let hit = shader_binding_table_addresses.hit.to_vk(); + let callable = shader_binding_table_addresses.callable.to_vk(); unsafe { (fns.khr_ray_tracing_pipeline.cmd_trace_rays_khr)( self.handle(), - &shader_binding_table_addresses.raygen, - &shader_binding_table_addresses.miss, - &shader_binding_table_addresses.hit, - &shader_binding_table_addresses.callable, + &raygen, + &miss, + &hit, + &callable, width, height, depth, - ) - }; + ); + } self } diff --git a/vulkano/src/command_buffer/commands/pipeline.rs b/vulkano/src/command_buffer/commands/pipeline.rs index ad87f53b4c..d88364d988 100644 --- a/vulkano/src/command_buffer/commands/pipeline.rs +++ b/vulkano/src/command_buffer/commands/pipeline.rs @@ -5091,12 +5091,17 @@ impl RecordingCommandBuffer { ) -> &mut Self { let fns = self.device().fns(); + let raygen = shader_binding_table_addresses.raygen.to_vk(); + let miss = shader_binding_table_addresses.miss.to_vk(); + let hit = shader_binding_table_addresses.hit.to_vk(); + let callable = shader_binding_table_addresses.callable.to_vk(); + (fns.khr_ray_tracing_pipeline.cmd_trace_rays_khr)( self.handle(), - &shader_binding_table_addresses.raygen, - &shader_binding_table_addresses.miss, - &shader_binding_table_addresses.hit, - &shader_binding_table_addresses.callable, + &raygen, + &miss, + &hit, + &callable, width, height, depth, diff --git a/vulkano/src/lib.rs b/vulkano/src/lib.rs index 07eb7b18ae..0bc52f8114 100644 --- a/vulkano/src/lib.rs +++ b/vulkano/src/lib.rs @@ -180,7 +180,23 @@ pub use ash::vk::DeviceAddress; pub type NonNullDeviceAddress = NonZeroU64; /// Represents a region of device addresses with a stride. -pub use ash::vk::StridedDeviceAddressRegionKHR as StridedDeviceAddressRegion; +#[derive(Debug, Copy, Clone, Default)] +pub struct StridedDeviceAddressRegion { + pub device_address: DeviceAddress, + pub stride: DeviceSize, + pub size: DeviceSize, +} + +impl StridedDeviceAddressRegion { + #[doc(hidden)] + pub fn to_vk(&self) -> ash::vk::StridedDeviceAddressRegionKHR { + ash::vk::StridedDeviceAddressRegionKHR { + device_address: self.device_address, + stride: self.stride, + size: self.size, + } + } +} /// Holds 24 bits in the least significant bits of memory, /// and 8 bytes in the most significant bits of that memory, From 1fb506ececf2c0d757b76ff86141699b160af156 Mon Sep 17 00:00:00 2001 From: ComfyFluffy <24245520+ComfyFluffy@users.noreply.github.com> Date: Sat, 14 Dec 2024 13:48:03 +0800 Subject: [PATCH 26/26] rename examples --- Cargo.lock | 46 ++++++++++++++-------------- examples/ray-tracing-auto/Cargo.toml | 4 +-- examples/ray-tracing/Cargo.toml | 4 +-- examples/ray-tracing/main.rs | 2 -- 4 files changed, 27 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a401b2707..64630b654e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1320,6 +1320,29 @@ dependencies = [ "objc2-quartz-core", ] +[[package]] +name = "ray-tracing" +version = "0.0.0" +dependencies = [ + "ash", + "glam", + "vulkano", + "vulkano-shaders", + "vulkano-taskgraph", + "winit", +] + +[[package]] +name = "ray-tracing-auto" +version = "0.0.0" +dependencies = [ + "ash", + "glam", + "vulkano", + "vulkano-shaders", + "winit", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -1738,29 +1761,6 @@ dependencies = [ "winit", ] -[[package]] -name = "triangle-raytracing" -version = "0.0.0" -dependencies = [ - "ash", - "glam", - "vulkano", - "vulkano-shaders", - "vulkano-taskgraph", - "winit", -] - -[[package]] -name = "triangle-raytracing-auto" -version = "0.0.0" -dependencies = [ - "ash", - "glam", - "vulkano", - "vulkano-shaders", - "winit", -] - [[package]] name = "triangle-util" version = "0.0.0" diff --git a/examples/ray-tracing-auto/Cargo.toml b/examples/ray-tracing-auto/Cargo.toml index ffe7a58f8e..292c75270c 100644 --- a/examples/ray-tracing-auto/Cargo.toml +++ b/examples/ray-tracing-auto/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "triangle-raytracing-auto" +name = "ray-tracing-auto" version = "0.0.0" edition = "2021" publish = false [[bin]] -name = "triangle-raytracing-auto" +name = "ray-tracing-auto" path = "main.rs" test = false bench = false diff --git a/examples/ray-tracing/Cargo.toml b/examples/ray-tracing/Cargo.toml index f13090a9f9..005d984195 100644 --- a/examples/ray-tracing/Cargo.toml +++ b/examples/ray-tracing/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "triangle-raytracing" +name = "ray-tracing" version = "0.0.0" edition = "2021" publish = false [[bin]] -name = "triangle-raytracing" +name = "ray-tracing" path = "main.rs" test = false bench = false diff --git a/examples/ray-tracing/main.rs b/examples/ray-tracing/main.rs index 38be51ca5b..18f26d2def 100644 --- a/examples/ray-tracing/main.rs +++ b/examples/ray-tracing/main.rs @@ -182,7 +182,6 @@ impl ApplicationHandler for App { .is_some() }) .collect::>(); - println!("Supported storage formats: {:?}", supported_storage_formats); println!( "Using device: {} (type: {:?})", @@ -201,7 +200,6 @@ impl ApplicationHandler for App { .first() .map(|(format, color_space)| (*format, *color_space)) .unwrap(); - println!("Using swapchain format: {:?}", swapchain_format); self.resources .create_swapchain(