From 5da2f9f0b594d148160fa7a57a108959b4e593ec Mon Sep 17 00:00:00 2001 From: mitchellhansen Date: Thu, 11 Jul 2019 22:42:59 -0700 Subject: [PATCH] had a bit of a time finding the problem with the specialization constants being unimplemented. Adding an impl lets rust elicit the pipeline type so I can store it, thank god --- src/main.rs | 29 ++++- src/vkprocessor.rs | 288 ++++++++++++++++++++++++++++++++------------- 2 files changed, 234 insertions(+), 83 deletions(-) diff --git a/src/main.rs b/src/main.rs index 32bfd13a..4043ae20 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,6 +43,8 @@ use vulkano::sync::GpuFuture; use shaderc::CompileOptions; use shade_runner::CompileError; use crate::workpiece::{WorkpieceLoader, Workpiece}; +use winit::{EventsLoop, WindowBuilder}; +use vulkano_win::VkSurfaceBuild; mod slider; mod timer; @@ -82,16 +84,37 @@ Let's take a look at how easy it would be to replace SFML... fn main() { - let font = Font::from_file("resources/fonts/sansation.ttf").unwrap(); + let instance = { + let extensions = vulkano_win::required_extensions(); + Instance::new(None, &extensions, None).unwrap() + }; + + let mut events_loop = EventsLoop::new(); + let mut surface = WindowBuilder::new() + .build_vk_surface(&events_loop, instance.clone()).unwrap(); + let mut window = surface.window(); + + + let mut processor = vkprocessor::VkProcessor::new(&instance, &surface); - let instance = Instance::new(None, &InstanceExtensions::none(), None).unwrap(); - let mut processor = vkprocessor::VkProcessor::new(&instance); processor.compile_kernel(String::from("simple-edge.compute")); processor.load_buffers(String::from("funky-bird.jpg")); processor.run_kernel(); processor.read_image(); processor.save_image(); + + + + + + + + + let font = Font::from_file("resources/fonts/sansation.ttf").unwrap(); + + + let mut window = RenderWindow::new( (900, 900), "Custom drawable", diff --git a/src/vkprocessor.rs b/src/vkprocessor.rs index c148d623..90ec9632 100644 --- a/src/vkprocessor.rs +++ b/src/vkprocessor.rs @@ -3,7 +3,7 @@ use vulkano::command_buffer::AutoCommandBufferBuilder; use vulkano::descriptor::descriptor_set::{PersistentDescriptorSet, StdDescriptorPoolAlloc}; use vulkano::device::{Device, DeviceExtensions, QueuesIter, Queue}; use vulkano::instance::{Instance, InstanceExtensions, PhysicalDevice, QueueFamily}; -use vulkano::pipeline::{ComputePipeline, GraphicsPipeline}; +use vulkano::pipeline::{ComputePipeline, GraphicsPipeline, GraphicsPipelineAbstract}; use vulkano::sync::GpuFuture; use vulkano::sync; use std::time::SystemTime; @@ -15,17 +15,56 @@ use image::{DynamicImage, ImageBuffer}; use image::GenericImageView; use vulkano::descriptor::pipeline_layout::PipelineLayout; use image::GenericImage; -use shade_runner::{ComputeLayout, CompileError, FragLayout}; +use shade_runner::{ComputeLayout, CompileError, FragLayout, FragInput, FragOutput, VertInput, VertOutput, VertLayout}; use vulkano::descriptor::descriptor_set::PersistentDescriptorSetBuf; use shaderc::CompileOptions; -use vulkano::framebuffer::Subpass; -use vulkano::pipeline::shader::GraphicsShaderType; +use vulkano::framebuffer::{Subpass, RenderPass}; +use vulkano::pipeline::shader::{GraphicsShaderType, ShaderModule, GraphicsEntryPoint, SpecializationConstants, SpecializationMapEntry}; +use vulkano::swapchain::{Swapchain, PresentMode, SurfaceTransform, Surface}; +use vulkano::image::swapchain::SwapchainImage; +use winit::{EventsLoop, WindowBuilder, Window}; +use vulkano_win::VkSurfaceBuild; +use vulkano::pipeline::vertex::{SingleBufferDefinition, Vertex}; +use vulkano::descriptor::PipelineLayoutAbstract; +use std::alloc::Layout; + +#[repr(C)] +struct MySpecConstants { + my_integer_constant: i32, + a_boolean: u32, + floating_point: f32, +} + +unsafe impl SpecializationConstants for MySpecConstants { + fn descriptors() -> &'static [SpecializationMapEntry] { + static DESCRIPTORS: [SpecializationMapEntry; 3] = [ + SpecializationMapEntry { + constant_id: 0, + offset: 0, + size: 4, + }, + SpecializationMapEntry { + constant_id: 1, + offset: 4, + size: 4, + }, + SpecializationMapEntry { + constant_id: 2, + offset: 8, + size: 4, + }, + ]; + + &DESCRIPTORS + } +} + pub struct VkProcessor<'a> { pub instance: Arc, pub physical: PhysicalDevice<'a>, - pub pipeline: Option>>>, - pub compute_pipeline: (), + pub pipeline: Option>, + pub compute_pipeline: Option>>>, pub device: Arc, pub queues: QueuesIter, pub queue: Arc, @@ -33,19 +72,33 @@ pub struct VkProcessor<'a> { pub image_buffer: Vec, pub img_buffers: Vec>>, pub settings_buffer: Option>>, + pub swapchain: Option>>, + pub images: Option>>>, pub xy: (u32, u32), } impl<'a> VkProcessor<'a> { - pub fn new(instance : &'a Arc) -> VkProcessor<'a> { + pub fn new(instance : &'a Arc, surface : &'a Arc>) -> VkProcessor<'a> { + let physical = PhysicalDevice::enumerate(instance).next().unwrap(); - let queue_family = physical.queue_families().find(|&q| q.supports_compute()).unwrap(); + + let queue_family = physical.queue_families().find(|&q| { + // We take the first queue that supports drawing to our window. + q.supports_graphics() && + surface.is_supported(q).unwrap_or(false) && + q.supports_compute() + }).unwrap(); + + let device_ext = DeviceExtensions { khr_swapchain: true, ..DeviceExtensions::none() }; + let (device, mut queues) = Device::new(physical, physical.supported_features(), - &DeviceExtensions::none(), + &device_ext, [(queue_family, 0.5)].iter().cloned()).unwrap(); + let queue = queues.next().unwrap(); + VkProcessor { instance: instance.clone(), physical: physical.clone(), @@ -58,6 +111,8 @@ impl<'a> VkProcessor<'a> { image_buffer: Vec::new(), img_buffers: Vec::new(), settings_buffer: Option::None, + swapchain: Option::None, + images: Option::None, xy: (0,0), } @@ -104,7 +159,52 @@ impl<'a> VkProcessor<'a> { self.compute_pipeline = Some(compute_pipeline); } - pub fn compile_shaders(&mut self, filename: String) { + pub fn compile_shaders(&mut self, filename: String, surface : &'a Arc>) { + + // Before we can draw on the surface, we have to create what is called a swapchain. Creating + // a swapchain allocates the color buffers that will contain the image that will ultimately + // be visible on the screen. These images are returned alongside with the swapchain. + let (mut swapchain, images) = { + // Querying the capabilities of the surface. When we create the swapchain we can only + // pass values that are allowed by the capabilities. + let capabilities = surface.capabilities(self.physical).unwrap(); + + let usage = capabilities.supported_usage_flags; + + // The alpha mode indicates how the alpha value of the final image will behave. For example + // you can choose whether the window will be opaque or transparent. + let alpha = capabilities.supported_composite_alpha.iter().next().unwrap(); + + // Choosing the internal format that the images will have. + let format = capabilities.supported_formats[0].0; + + // The dimensions of the window, only used to initially setup the swapchain. + // NOTE: + // On some drivers the swapchain dimensions are specified by `caps.current_extent` and the + // swapchain size must use these dimensions. + // These dimensions are always the same as the window dimensions + // + // However other drivers dont specify a value i.e. `caps.current_extent` is `None` + // These drivers will allow anything but the only sensible value is the window dimensions. + // + // Because for both of these cases, the swapchain needs to be the window dimensions, we just use that. + let initial_dimensions = if let Some(dimensions) = surface.window().get_inner_size() { + // convert to physical pixels + let dimensions: (u32, u32) = dimensions.to_physical(surface.window().get_hidpi_factor()).into(); + [dimensions.0, dimensions.1] + } else { + // The window no longer exists so exit the application. + return; + }; + + // Please take a look at the docs for the meaning of the parameters we didn't mention. + Swapchain::new(self.device.clone(), surface.clone(), capabilities.min_image_count, format, + initial_dimensions, 1, usage, &self.queue, SurfaceTransform::Identity, alpha, + PresentMode::Fifo, true, None).unwrap() + }; + + self.swapchain = Some(swapchain); + self.images = Some(images); let project_root = std::env::current_dir() @@ -113,15 +213,15 @@ impl<'a> VkProcessor<'a> { let mut shader_path = project_root.clone(); shader_path.push(PathBuf::from("resources/shaders/")); - let mut vertex_shader_path = project_root.clone() - .push(PathBuf::from("resources/shaders/")); + let mut vertex_shader_path = project_root.clone(); + vertex_shader_path.push(PathBuf::from("resources/shaders/")); vertex_shader_path.push(PathBuf::from(filename.clone())); vertex_shader_path.push(PathBuf::from(".vertex")); - let mut fragment_shader_path = project_root.clone() - .push(PathBuf::from("resources/shaders/")); - vertex_shader_path.push(PathBuf::from(filename.clone())); - vertex_shader_path.push(PathBuf::from(".fragment")); + let mut fragment_shader_path = project_root.clone(); + fragment_shader_path.push(PathBuf::from("resources/shaders/")); + fragment_shader_path.push(PathBuf::from(filename.clone())); + fragment_shader_path.push(PathBuf::from(".fragment")); let mut options = CompileOptions::new().ok_or(CompileError::CreateCompiler).unwrap(); options.add_macro_definition("SETTING_POS_X", Some("0")); @@ -137,68 +237,127 @@ impl<'a> VkProcessor<'a> { sr::parse(&shader) .expect("failed to parse"); - let x = unsafe { + let x1 : Arc = unsafe { vulkano::pipeline::shader::ShaderModule::from_words(self.device.clone(), &shader.fragment) }.unwrap(); + let x2 = unsafe { + vulkano::pipeline::shader::ShaderModule::from_words(self.device.clone(), &shader.vertex) + }.unwrap(); - let frag_entry_point = unsafe { - &x.graphics_entry_point(CStr::from_bytes_with_nul_unchecked(b"main\0"), + let frag_entry_point : GraphicsEntryPoint = unsafe { + x1.graphics_entry_point(CStr::from_bytes_with_nul_unchecked(b"main\0"), vulkano_entry.frag_input, vulkano_entry.frag_output, - vulkano_entry.frag_layout, GraphicsShaderType::Fragment) + vulkano_entry.frag_layout, + GraphicsShaderType::Fragment) }; - let vert_entry_point = unsafe { - &x.graphics_entry_point(CStr::from_bytes_with_nul_unchecked(b"main\0"), - vulkano_entry.frag_input, - vulkano_entry.frag_output, - vulkano_entry.frag_layout, GraphicsShaderType::Fragment) + let vert_entry_point: GraphicsEntryPoint = unsafe { + x2.graphics_entry_point(CStr::from_bytes_with_nul_unchecked(b"main\0"), + vulkano_entry.vert_input, + vulkano_entry.vert_output, + vulkano_entry.vert_layout, + GraphicsShaderType::Vertex) }; + // 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 = Arc::new(vulkano::single_pass_renderpass!( + self.device.clone(), + attachments: { + // `color` is a custom name we give to the first and only attachment. + color: { + // `load: Clear` means that we ask the GPU to clear the content of this + // attachment at the start of the drawing. + load: Clear, + // `store: 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: Store, + // `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: self.swapchain.clone().unwrap().clone().format(), + // TODO: + samples: 1, + } + }, + 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()); - // Before we draw we have to create what is called a pipeline. This is similar to an OpenGL - // program, but much more specific. - self.pipeline = Option::Some(Arc::new(GraphicsPipeline::start() + +// Before we draw we have to create what is called a pipeline. This is similar to an OpenGL +// program, but much more specific. + let pipeline = GraphicsPipeline::start() // We need to indicate the layout of the vertices. // The type `SingleBufferDefinition` actually contains a template parameter corresponding // to the type of each vertex. But in this code it is automatically inferred. - .vertex_input_single_buffer() +// .vertex_input_single_buffer() // A Vulkan shader can in theory contain multiple entry points, so we have to specify // which one. The `main` word of `main_entry_point` actually corresponds to the name of // the entry point. - .vertex_shader(vert_entry_point, ()) + .vertex_shader(vert_entry_point, MySpecConstants { + my_integer_constant: 0, + a_boolean: 0, + floating_point: 0.0 + }) // The content of the vertex buffer describes a list of triangles. .triangle_list() // Use a resizable viewport set to draw over the entire window .viewports_dynamic_scissors_irrelevant(1) // See `vertex_shader`. - .fragment_shader(frag_entry_point, ()) + .fragment_shader(frag_entry_point, MySpecConstants { + my_integer_constant: 0, + a_boolean: 0, + floating_point: 0.0 + }) // 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. .render_pass(Subpass::from(render_pass.clone(), 0).unwrap()) // Now that our builder is filled, we call `build()` to obtain an actual pipeline. - .build(device.clone()) - .unwrap())); - - + .build(self.device.clone()) + .unwrap(); + self.pipeline = Option::Some(Arc::new(pipeline)); + } - let x = unsafe { - vulkano::pipeline::shader::ShaderModule::from_words(self.device.clone(), &shader.fragment) - }.unwrap(); + pub fn create_renderpass(&mut self) { - let pipeline = Arc::new({ - unsafe { - ComputePipeline::new(self.device.clone(), &x.compute_entry_point( - CStr::from_bytes_with_nul_unchecked(b"main\0"), - vulkano_entry.compute_layout), &(), - ).unwrap() + let render_pass = Arc::new(vulkano::single_pass_renderpass!( + self.device.clone(), + attachments: { + // `color` is a custom name we give to the first and only attachment. + color: { + // `load: Clear` means that we ask the GPU to clear the content of this + // attachment at the start of the drawing. + load: Clear, + // `store: 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: Store, + // `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: self.swapchain.clone().unwrap().clone().format(), + // TODO: + samples: 1, + } + }, + 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: {} } - }); - - self.pipeline = Some(pipeline); + ).unwrap()); } pub fn load_buffers(&mut self, image_filename: String) @@ -268,7 +427,7 @@ impl<'a> VkProcessor<'a> { // Create the data descriptor set for our previously created shader pipeline let mut set = - PersistentDescriptorSet::start(self.pipeline.clone().unwrap().clone(), 0) + PersistentDescriptorSet::start(self.compute_pipeline.clone().unwrap().clone(), 0) .add_buffer(write_buffer.clone()).unwrap() .add_buffer(read_buffer.clone()).unwrap() .add_buffer(settings_buffer.clone()).unwrap(); @@ -280,37 +439,6 @@ impl<'a> VkProcessor<'a> { self.settings_buffer = Some(settings_buffer); } - pub fn create_renderpass(&mut self) { - - let render_pass = Arc::new(vulkano::single_pass_renderpass!( - self.device.clone(), - attachments: { - // `color` is a custom name we give to the first and only attachment. - color: { - // `load: Clear` means that we ask the GPU to clear the content of this - // attachment at the start of the drawing. - load: Clear, - // `store: 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: Store, - // `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: swapchain.format(), - // TODO: - samples: 1, - } - }, - 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()); - } - pub fn run_kernel(&mut self) { println!("Running Kernel..."); @@ -319,7 +447,7 @@ impl<'a> VkProcessor<'a> { let command_buffer = AutoCommandBufferBuilder::primary_one_time_submit(self.device.clone(),self.queue.family()).unwrap() .dispatch([self.xy.0, self.xy.1, 1], - self.pipeline.clone().unwrap().clone(), + self.compute_pipeline.clone().unwrap().clone(), self.set.clone().unwrap().clone(), ()).unwrap() .build().unwrap();