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] 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,