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] 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 764e29ef..d499ac2c 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 aa244dd9..18178daf 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,