// Copyright (c) 2016 The vulkano developers // Licensed under the Apache License, Version 2.0 // or the MIT // license , // at your option. All files in the project carrying such // notice may not be copied, modified, or distributed except // according to those terms. use crate::check_errors; use crate::descriptor_set::layout::DescriptorSetLayout; use crate::device::Device; use crate::device::DeviceOwned; use crate::pipeline::cache::PipelineCache; use crate::pipeline::layout::PipelineLayout; use crate::pipeline::layout::PipelineLayoutCreationError; use crate::pipeline::layout::PipelineLayoutSupersetError; use crate::pipeline::shader::EntryPointAbstract; use crate::pipeline::shader::SpecializationConstants; use crate::Error; use crate::OomError; use crate::SafeDeref; use crate::VulkanObject; use std::error; use std::fmt; use std::marker::PhantomData; use std::mem; use std::mem::MaybeUninit; use std::ptr; use std::sync::Arc; /// A pipeline object that describes to the Vulkan implementation how it should perform compute /// operations. /// /// The template parameter contains the descriptor set to use with this pipeline. /// /// All compute pipeline objects implement the `ComputePipelineAbstract` trait. You can turn any /// `Arc` into an `Arc` if necessary. /// /// Pass an optional `Arc` to a `PipelineCache` to enable pipeline caching. The vulkan /// implementation will handle the `PipelineCache` and check if it is available. /// Check the documentation of the `PipelineCache` for more information. pub struct ComputePipeline { inner: Inner, pipeline_layout: Arc, } struct Inner { pipeline: ash::vk::Pipeline, device: Arc, } impl ComputePipeline { /// Builds a new `ComputePipeline`. pub fn new( device: Arc, shader: &Cs, spec_constants: &Css, cache: Option>, ) -> Result where Cs: EntryPointAbstract, Css: SpecializationConstants, { unsafe { let descriptor_set_layouts = shader .descriptor_set_layout_descs() .iter() .map(|desc| { Ok(Arc::new(DescriptorSetLayout::new( device.clone(), desc.clone(), )?)) }) .collect::, OomError>>()?; let pipeline_layout = Arc::new(PipelineLayout::new( device.clone(), descriptor_set_layouts, shader.push_constant_range().iter().cloned(), )?); ComputePipeline::with_unchecked_pipeline_layout( device, shader, spec_constants, pipeline_layout, cache, ) } } /// Builds a new `ComputePipeline` with a specific pipeline layout. /// /// An error will be returned if the pipeline layout isn't a superset of what the shader /// uses. pub fn with_pipeline_layout( device: Arc, shader: &Cs, spec_constants: &Css, pipeline_layout: Arc, cache: Option>, ) -> Result where Cs: EntryPointAbstract, Css: SpecializationConstants, { if Css::descriptors() != shader.spec_constants() { return Err(ComputePipelineCreationError::IncompatibleSpecializationConstants); } unsafe { pipeline_layout.ensure_superset_of( shader.descriptor_set_layout_descs(), shader.push_constant_range(), )?; ComputePipeline::with_unchecked_pipeline_layout( device, shader, spec_constants, pipeline_layout, cache, ) } } /// Same as `with_pipeline_layout`, but doesn't check whether the pipeline layout is a /// superset of what the shader expects. pub unsafe fn with_unchecked_pipeline_layout( device: Arc, shader: &Cs, spec_constants: &Css, pipeline_layout: Arc, cache: Option>, ) -> Result where Cs: EntryPointAbstract, Css: SpecializationConstants, { let fns = device.fns(); let pipeline = { let spec_descriptors = Css::descriptors(); let specialization = ash::vk::SpecializationInfo { map_entry_count: spec_descriptors.len() as u32, p_map_entries: spec_descriptors.as_ptr() as *const _, data_size: mem::size_of_val(spec_constants), p_data: spec_constants as *const Css as *const _, }; let stage = ash::vk::PipelineShaderStageCreateInfo { flags: ash::vk::PipelineShaderStageCreateFlags::empty(), stage: ash::vk::ShaderStageFlags::COMPUTE, module: shader.module().internal_object(), p_name: shader.name().as_ptr(), p_specialization_info: if specialization.data_size == 0 { ptr::null() } else { &specialization }, ..Default::default() }; let infos = ash::vk::ComputePipelineCreateInfo { flags: ash::vk::PipelineCreateFlags::empty(), stage, layout: pipeline_layout.internal_object(), base_pipeline_handle: ash::vk::Pipeline::null(), base_pipeline_index: 0, ..Default::default() }; let cache_handle = match cache { Some(ref cache) => cache.internal_object(), None => ash::vk::PipelineCache::null(), }; let mut output = MaybeUninit::uninit(); check_errors(fns.v1_0.create_compute_pipelines( device.internal_object(), cache_handle, 1, &infos, ptr::null(), output.as_mut_ptr(), ))?; output.assume_init() }; Ok(ComputePipeline { inner: Inner { device: device.clone(), pipeline: pipeline, }, pipeline_layout: pipeline_layout, }) } } impl fmt::Debug for ComputePipeline { #[inline] fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(fmt, "", self.inner.pipeline) } } impl ComputePipeline { /// Returns the `Device` this compute pipeline was created with. #[inline] pub fn device(&self) -> &Arc { &self.inner.device } } /// Trait implemented on all compute pipelines. pub unsafe trait ComputePipelineAbstract: DeviceOwned { /// Returns an opaque object that represents the inside of the compute pipeline. fn inner(&self) -> ComputePipelineSys; /// Returns the pipeline layout used in this compute pipeline. fn layout(&self) -> &Arc; } unsafe impl ComputePipelineAbstract for ComputePipeline { #[inline] fn inner(&self) -> ComputePipelineSys { ComputePipelineSys(self.inner.pipeline, PhantomData) } #[inline] fn layout(&self) -> &Arc { &self.pipeline_layout } } unsafe impl ComputePipelineAbstract for T where T: SafeDeref, T::Target: ComputePipelineAbstract, { #[inline] fn inner(&self) -> ComputePipelineSys { (**self).inner() } #[inline] fn layout(&self) -> &Arc { (**self).layout() } } /// Opaque object that represents the inside of the compute pipeline. Can be made into a trait /// object. #[derive(Debug, Copy, Clone)] pub struct ComputePipelineSys<'a>(ash::vk::Pipeline, PhantomData<&'a ()>); unsafe impl<'a> VulkanObject for ComputePipelineSys<'a> { type Object = ash::vk::Pipeline; #[inline] fn internal_object(&self) -> ash::vk::Pipeline { self.0 } } unsafe impl DeviceOwned for ComputePipeline { #[inline] fn device(&self) -> &Arc { self.device() } } unsafe impl VulkanObject for ComputePipeline { type Object = ash::vk::Pipeline; #[inline] fn internal_object(&self) -> ash::vk::Pipeline { self.inner.pipeline } } impl Drop for Inner { #[inline] fn drop(&mut self) { unsafe { let fns = self.device.fns(); fns.v1_0 .destroy_pipeline(self.device.internal_object(), self.pipeline, ptr::null()); } } } /// Error that can happen when creating a compute pipeline. #[derive(Clone, Debug, PartialEq, Eq)] pub enum ComputePipelineCreationError { /// Not enough memory. OomError(OomError), /// Error while creating the pipeline layout object. PipelineLayoutCreationError(PipelineLayoutCreationError), /// The pipeline layout is not compatible with what the shader expects. IncompatiblePipelineLayout(PipelineLayoutSupersetError), /// The provided specialization constants are not compatible with what the shader expects. IncompatibleSpecializationConstants, } impl error::Error for ComputePipelineCreationError { #[inline] fn source(&self) -> Option<&(dyn error::Error + 'static)> { match *self { ComputePipelineCreationError::OomError(ref err) => Some(err), ComputePipelineCreationError::PipelineLayoutCreationError(ref err) => Some(err), ComputePipelineCreationError::IncompatiblePipelineLayout(ref err) => Some(err), ComputePipelineCreationError::IncompatibleSpecializationConstants => None, } } } impl fmt::Display for ComputePipelineCreationError { #[inline] fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!( fmt, "{}", match *self { ComputePipelineCreationError::OomError(_) => "not enough memory available", ComputePipelineCreationError::PipelineLayoutCreationError(_) => { "error while creating the pipeline layout object" } ComputePipelineCreationError::IncompatiblePipelineLayout(_) => { "the pipeline layout is not compatible with what the shader expects" } ComputePipelineCreationError::IncompatibleSpecializationConstants => { "the provided specialization constants are not compatible with what the shader expects" } } ) } } impl From for ComputePipelineCreationError { #[inline] fn from(err: OomError) -> ComputePipelineCreationError { ComputePipelineCreationError::OomError(err) } } impl From for ComputePipelineCreationError { #[inline] fn from(err: PipelineLayoutCreationError) -> ComputePipelineCreationError { ComputePipelineCreationError::PipelineLayoutCreationError(err) } } impl From for ComputePipelineCreationError { #[inline] fn from(err: PipelineLayoutSupersetError) -> ComputePipelineCreationError { ComputePipelineCreationError::IncompatiblePipelineLayout(err) } } impl From for ComputePipelineCreationError { #[inline] fn from(err: Error) -> ComputePipelineCreationError { match err { err @ Error::OutOfHostMemory => { ComputePipelineCreationError::OomError(OomError::from(err)) } err @ Error::OutOfDeviceMemory => { ComputePipelineCreationError::OomError(OomError::from(err)) } _ => panic!("unexpected error: {:?}", err), } } } #[cfg(test)] mod tests { use crate::buffer::BufferUsage; use crate::buffer::CpuAccessibleBuffer; use crate::command_buffer::AutoCommandBufferBuilder; use crate::command_buffer::CommandBufferUsage; use crate::descriptor_set::layout::DescriptorBufferDesc; use crate::descriptor_set::layout::DescriptorDesc; use crate::descriptor_set::layout::DescriptorDescTy; use crate::descriptor_set::layout::DescriptorSetDesc; use crate::descriptor_set::PersistentDescriptorSet; use crate::pipeline::shader::ShaderModule; use crate::pipeline::shader::ShaderStages; use crate::pipeline::shader::SpecializationConstants; use crate::pipeline::shader::SpecializationMapEntry; use crate::pipeline::ComputePipeline; use crate::pipeline::ComputePipelineAbstract; use crate::sync::now; use crate::sync::GpuFuture; use std::ffi::CStr; use std::sync::Arc; // TODO: test for basic creation // TODO: test for pipeline layout error #[test] fn spec_constants() { // This test checks whether specialization constants work. // It executes a single compute shader (one invocation) that writes the value of a spec. // constant to a buffer. The buffer content is then checked for the right value. let (device, queue) = gfx_dev_and_queue!(); let module = unsafe { /* #version 450 layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; layout(constant_id = 83) const int VALUE = 0xdeadbeef; layout(set = 0, binding = 0) buffer Output { int write; } write; void main() { write.write = VALUE; } */ const MODULE: [u8; 480] = [ 3, 2, 35, 7, 0, 0, 1, 0, 1, 0, 8, 0, 14, 0, 0, 0, 0, 0, 0, 0, 17, 0, 2, 0, 1, 0, 0, 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76, 46, 115, 116, 100, 46, 52, 53, 48, 0, 0, 0, 0, 14, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 15, 0, 5, 0, 5, 0, 0, 0, 4, 0, 0, 0, 109, 97, 105, 110, 0, 0, 0, 0, 16, 0, 6, 0, 4, 0, 0, 0, 17, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 3, 0, 3, 0, 2, 0, 0, 0, 194, 1, 0, 0, 5, 0, 4, 0, 4, 0, 0, 0, 109, 97, 105, 110, 0, 0, 0, 0, 5, 0, 4, 0, 7, 0, 0, 0, 79, 117, 116, 112, 117, 116, 0, 0, 6, 0, 5, 0, 7, 0, 0, 0, 0, 0, 0, 0, 119, 114, 105, 116, 101, 0, 0, 0, 5, 0, 4, 0, 9, 0, 0, 0, 119, 114, 105, 116, 101, 0, 0, 0, 5, 0, 4, 0, 11, 0, 0, 0, 86, 65, 76, 85, 69, 0, 0, 0, 72, 0, 5, 0, 7, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 71, 0, 3, 0, 7, 0, 0, 0, 3, 0, 0, 0, 71, 0, 4, 0, 9, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0, 9, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0, 11, 0, 0, 0, 1, 0, 0, 0, 83, 0, 0, 0, 19, 0, 2, 0, 2, 0, 0, 0, 33, 0, 3, 0, 3, 0, 0, 0, 2, 0, 0, 0, 21, 0, 4, 0, 6, 0, 0, 0, 32, 0, 0, 0, 1, 0, 0, 0, 30, 0, 3, 0, 7, 0, 0, 0, 6, 0, 0, 0, 32, 0, 4, 0, 8, 0, 0, 0, 2, 0, 0, 0, 7, 0, 0, 0, 59, 0, 4, 0, 8, 0, 0, 0, 9, 0, 0, 0, 2, 0, 0, 0, 43, 0, 4, 0, 6, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 50, 0, 4, 0, 6, 0, 0, 0, 11, 0, 0, 0, 239, 190, 173, 222, 32, 0, 4, 0, 12, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, 54, 0, 5, 0, 2, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 248, 0, 2, 0, 5, 0, 0, 0, 65, 0, 5, 0, 12, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 62, 0, 3, 0, 13, 0, 0, 0, 11, 0, 0, 0, 253, 0, 1, 0, 56, 0, 1, 0, ]; ShaderModule::new(device.clone(), &MODULE).unwrap() }; let shader = unsafe { static NAME: [u8; 5] = [109, 97, 105, 110, 0]; // "main" module.compute_entry_point( CStr::from_ptr(NAME.as_ptr() as *const _), [DescriptorSetDesc::new([Some(DescriptorDesc { ty: DescriptorDescTy::Buffer(DescriptorBufferDesc { dynamic: Some(false), storage: true, }), array_count: 1, stages: ShaderStages { compute: true, ..ShaderStages::none() }, readonly: true, })])], None, SpecConsts::descriptors(), ) }; #[derive(Debug, Copy, Clone)] #[allow(non_snake_case)] #[repr(C)] struct SpecConsts { VALUE: i32, } unsafe impl SpecializationConstants for SpecConsts { fn descriptors() -> &'static [SpecializationMapEntry] { static DESCRIPTORS: [SpecializationMapEntry; 1] = [SpecializationMapEntry { constant_id: 83, offset: 0, size: 4, }]; &DESCRIPTORS } } let pipeline = Arc::new( ComputePipeline::new( device.clone(), &shader, &SpecConsts { VALUE: 0x12345678 }, None, ) .unwrap(), ); let data_buffer = CpuAccessibleBuffer::from_data(device.clone(), BufferUsage::all(), false, 0).unwrap(); let layout = pipeline.layout().descriptor_set_layouts().get(0).unwrap(); let set = PersistentDescriptorSet::start(layout.clone()) .add_buffer(data_buffer.clone()) .unwrap() .build() .unwrap(); let mut cbb = AutoCommandBufferBuilder::primary( device.clone(), queue.family(), CommandBufferUsage::OneTimeSubmit, ) .unwrap(); cbb.dispatch([1, 1, 1], pipeline.clone(), set, ()).unwrap(); let cb = cbb.build().unwrap(); let future = now(device.clone()) .then_execute(queue.clone(), cb) .unwrap() .then_signal_fence_and_flush() .unwrap(); future.wait(None).unwrap(); let data_buffer_content = data_buffer.read().unwrap(); assert_eq!(*data_buffer_content, 0x12345678); } }