blob: 06013749909cae01b1e4ac0c33119d9f3de9e9b8 [file] [log] [blame]
// Copyright (c) 2016 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// 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<ComputePipeline>` into an `Arc<ComputePipelineAbstract>` 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<PipelineLayout>,
}
struct Inner {
pipeline: ash::vk::Pipeline,
device: Arc<Device>,
}
impl ComputePipeline {
/// Builds a new `ComputePipeline`.
pub fn new<Cs, Css>(
device: Arc<Device>,
shader: &Cs,
spec_constants: &Css,
cache: Option<Arc<PipelineCache>>,
) -> Result<ComputePipeline, ComputePipelineCreationError>
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::<Result<Vec<_>, 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<Cs, Css>(
device: Arc<Device>,
shader: &Cs,
spec_constants: &Css,
pipeline_layout: Arc<PipelineLayout>,
cache: Option<Arc<PipelineCache>>,
) -> Result<ComputePipeline, ComputePipelineCreationError>
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<Cs, Css>(
device: Arc<Device>,
shader: &Cs,
spec_constants: &Css,
pipeline_layout: Arc<PipelineLayout>,
cache: Option<Arc<PipelineCache>>,
) -> Result<ComputePipeline, ComputePipelineCreationError>
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, "<Vulkan compute pipeline {:?}>", self.inner.pipeline)
}
}
impl ComputePipeline {
/// Returns the `Device` this compute pipeline was created with.
#[inline]
pub fn device(&self) -> &Arc<Device> {
&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<PipelineLayout>;
}
unsafe impl ComputePipelineAbstract for ComputePipeline {
#[inline]
fn inner(&self) -> ComputePipelineSys {
ComputePipelineSys(self.inner.pipeline, PhantomData)
}
#[inline]
fn layout(&self) -> &Arc<PipelineLayout> {
&self.pipeline_layout
}
}
unsafe impl<T> ComputePipelineAbstract for T
where
T: SafeDeref,
T::Target: ComputePipelineAbstract,
{
#[inline]
fn inner(&self) -> ComputePipelineSys {
(**self).inner()
}
#[inline]
fn layout(&self) -> &Arc<PipelineLayout> {
(**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<Device> {
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<OomError> for ComputePipelineCreationError {
#[inline]
fn from(err: OomError) -> ComputePipelineCreationError {
ComputePipelineCreationError::OomError(err)
}
}
impl From<PipelineLayoutCreationError> for ComputePipelineCreationError {
#[inline]
fn from(err: PipelineLayoutCreationError) -> ComputePipelineCreationError {
ComputePipelineCreationError::PipelineLayoutCreationError(err)
}
}
impl From<PipelineLayoutSupersetError> for ComputePipelineCreationError {
#[inline]
fn from(err: PipelineLayoutSupersetError) -> ComputePipelineCreationError {
ComputePipelineCreationError::IncompatiblePipelineLayout(err)
}
}
impl From<Error> 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);
}
}