librashader/librashader-runtime-vk/src/graphics_pipeline.rs
chyyran b81f415917 vk: fix lifetime issues with ash
Using `build` everywhere was causing miscompilations because of lifetimes leaking.
2023-02-15 22:57:20 -05:00

506 lines
17 KiB
Rust

use crate::{error, util};
use ash::vk;
use crate::error::FilterChainError;
use crate::framebuffer::OutputImage;
use crate::render_pass::VulkanRenderPass;
use librashader_cache::cache_pipeline;
use librashader_reflect::back::ShaderCompilerOutput;
use librashader_reflect::reflect::semantics::{TextureBinding, UboReflection};
use librashader_reflect::reflect::ShaderReflection;
use librashader_runtime::render_target::RenderTarget;
use std::ffi::CStr;
use std::sync::Arc;
use ash::vk::PushConstantRange;
const ENTRY_POINT: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"main\0") };
pub struct PipelineDescriptors {
pub replicas: u32,
pub layout_bindings: Vec<vk::DescriptorSetLayoutBinding>,
pub pool_sizes: Vec<vk::DescriptorPoolSize>,
}
impl PipelineDescriptors {
pub fn new(duplicates: u32) -> Self {
Self {
replicas: duplicates,
layout_bindings: vec![],
pool_sizes: vec![],
}
}
pub fn add_ubo_binding(&mut self, ubo_meta: Option<&UboReflection>) {
if let Some(ubo_meta) = ubo_meta && !ubo_meta.stage_mask.is_empty() {
let ubo_mask = util::binding_stage_to_vulkan_stage(ubo_meta.stage_mask);
self.layout_bindings.push(vk::DescriptorSetLayoutBinding {
binding: ubo_meta.binding,
descriptor_type: vk::DescriptorType::UNIFORM_BUFFER,
descriptor_count: 1,
stage_flags: ubo_mask,
p_immutable_samplers: std::ptr::null(),
});
self.pool_sizes.push(vk::DescriptorPoolSize {
ty: vk::DescriptorType::UNIFORM_BUFFER,
descriptor_count: self.replicas,
})
}
}
pub fn add_texture_bindings<'a>(&mut self, textures: impl Iterator<Item = &'a TextureBinding>) {
let texture_mask = vk::ShaderStageFlags::FRAGMENT;
for texture in textures {
self.layout_bindings.push(vk::DescriptorSetLayoutBinding {
binding: texture.binding,
descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER,
descriptor_count: 1,
stage_flags: texture_mask,
p_immutable_samplers: std::ptr::null(),
});
self.pool_sizes.push(vk::DescriptorPoolSize {
ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER,
descriptor_count: self.replicas,
})
}
}
pub fn bindings(&self) -> &[vk::DescriptorSetLayoutBinding] {
self.layout_bindings.as_ref()
}
pub fn create_descriptor_set_layout(
&self,
device: &ash::Device,
) -> error::Result<vk::DescriptorSetLayout> {
unsafe {
let layout = device.create_descriptor_set_layout(
&vk::DescriptorSetLayoutCreateInfo::builder()
.bindings(self.bindings()),
None,
)?;
Ok(layout)
}
}
}
pub struct PipelineLayoutObjects {
pub layout: vk::PipelineLayout,
pub pool: vk::DescriptorPool,
pub descriptor_sets: Vec<vk::DescriptorSet>,
pub descriptor_set_layout: [vk::DescriptorSetLayout; 1],
}
impl PipelineLayoutObjects {
pub fn new(
reflection: &ShaderReflection,
replicas: u32,
device: &ash::Device,
) -> error::Result<Self> {
let mut descriptors = PipelineDescriptors::new(replicas);
descriptors.add_ubo_binding(reflection.ubo.as_ref());
descriptors.add_texture_bindings(reflection.meta.texture_meta.values());
let descriptor_set_layout = [descriptors.create_descriptor_set_layout(device)?];
let pipeline_create_info =
vk::PipelineLayoutCreateInfo::builder().set_layouts(&descriptor_set_layout);
let push_constant_range = reflection.push_constant.as_ref()
.map(|push_constant| {
let stage_mask = util::binding_stage_to_vulkan_stage(push_constant.stage_mask);
[*vk::PushConstantRange::builder()
.stage_flags(stage_mask)
.size(push_constant.size)]
});
let push_constant_range: &[PushConstantRange] = push_constant_range
.as_ref()
.map_or(&[], |o| o);
let pipeline_create_info = pipeline_create_info
.push_constant_ranges(push_constant_range);
// let pipeline_create_info = if let Some(push_constant) = &reflection.push_constant {
// let stage_mask = util::binding_stage_to_vulkan_stage(push_constant.stage_mask);
// let push_constant_range = vk::PushConstantRange::builder()
// .stage_flags(stage_mask)
// .size(push_constant.size);
// let push_constant_range = [*push_constant_range];
// pipeline_create_info
// .push_constant_ranges(&push_constant_range)
// } else {
// pipeline_create_info
// };
let layout = unsafe { device.create_pipeline_layout(&pipeline_create_info, None)? };
let pool_info = vk::DescriptorPoolCreateInfo::builder()
.max_sets(replicas)
.pool_sizes(&descriptors.pool_sizes)
;
let pool = unsafe { device.create_descriptor_pool(&pool_info, None)? };
let mut descriptor_sets = Vec::new();
let alloc_info = vk::DescriptorSetAllocateInfo::builder()
.descriptor_pool(pool)
.set_layouts(&descriptor_set_layout)
;
for _ in 0..replicas {
let set = unsafe { device.allocate_descriptor_sets(&alloc_info)? };
descriptor_sets.push(set)
}
let descriptor_sets: Vec<vk::DescriptorSet> =
descriptor_sets.into_iter().flatten().collect();
Ok(PipelineLayoutObjects {
layout,
descriptor_set_layout,
descriptor_sets,
pool,
})
}
}
pub struct VulkanShaderModule {
shader: vk::ShaderModule,
device: ash::Device,
}
impl VulkanShaderModule {
pub fn new(
device: &ash::Device,
info: &vk::ShaderModuleCreateInfo,
) -> error::Result<VulkanShaderModule> {
Ok(VulkanShaderModule {
shader: unsafe { device.create_shader_module(info, None)? },
device: device.clone(),
})
}
}
impl Drop for VulkanShaderModule {
fn drop(&mut self) {
unsafe { self.device.destroy_shader_module(self.shader, None) }
}
}
pub struct VulkanGraphicsPipeline {
pub layout: PipelineLayoutObjects,
pub pipeline: vk::Pipeline,
pub render_pass: Option<VulkanRenderPass>,
device: Arc<ash::Device>,
vertex: VulkanShaderModule,
fragment: VulkanShaderModule,
cache: vk::PipelineCache,
}
impl VulkanGraphicsPipeline {
fn create_pipeline(
device: &ash::Device,
cache: &vk::PipelineCache,
pipeline_layout: &PipelineLayoutObjects,
vertex_module: &VulkanShaderModule,
fragment_module: &VulkanShaderModule,
render_pass: Option<&VulkanRenderPass>,
) -> error::Result<vk::Pipeline> {
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::builder()
.topology(vk::PrimitiveTopology::TRIANGLE_STRIP)
;
let vao_state = [
vk::VertexInputAttributeDescription {
location: 0,
binding: 0,
format: vk::Format::R32G32_SFLOAT,
offset: 0,
},
vk::VertexInputAttributeDescription {
location: 1,
binding: 0,
format: vk::Format::R32G32_SFLOAT,
offset: (2 * std::mem::size_of::<f32>()) as u32,
},
];
let input_binding = vk::VertexInputBindingDescription::builder()
.binding(0)
.stride(4 * std::mem::size_of::<f32>() as u32)
.input_rate(vk::VertexInputRate::VERTEX);
let input_binding = [*input_binding];
let pipeline_input_state = vk::PipelineVertexInputStateCreateInfo::builder()
.vertex_binding_descriptions(&input_binding)
.vertex_attribute_descriptions(&vao_state)
;
let raster_state = vk::PipelineRasterizationStateCreateInfo::builder()
.polygon_mode(vk::PolygonMode::FILL)
.cull_mode(vk::CullModeFlags::NONE)
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
.depth_clamp_enable(false)
.rasterizer_discard_enable(false)
.depth_bias_enable(false)
.line_width(1.0)
;
let attachments = vk::PipelineColorBlendAttachmentState::builder()
.blend_enable(false)
.color_write_mask(vk::ColorComponentFlags::from_raw(0xf));
let attachments = [*attachments];
let blend_state = vk::PipelineColorBlendStateCreateInfo::builder()
.attachments(&attachments)
;
let viewport_state = vk::PipelineViewportStateCreateInfo::builder()
.viewport_count(1)
.scissor_count(1)
;
let depth_stencil_state = vk::PipelineDepthStencilStateCreateInfo::builder()
.depth_test_enable(false)
.depth_write_enable(false)
.stencil_test_enable(false)
.depth_bounds_test_enable(false)
.min_depth_bounds(1.0)
.max_depth_bounds(1.0)
;
let multisample_state = vk::PipelineMultisampleStateCreateInfo::builder()
.rasterization_samples(vk::SampleCountFlags::TYPE_1)
;
let states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
let dynamic_state = vk::PipelineDynamicStateCreateInfo::builder()
.dynamic_states(&states)
;
let shader_stages = [
vk::PipelineShaderStageCreateInfo::builder()
.stage(vk::ShaderStageFlags::VERTEX)
.name(ENTRY_POINT)
.module(vertex_module.shader)
,
vk::PipelineShaderStageCreateInfo::builder()
.stage(vk::ShaderStageFlags::FRAGMENT)
.name(ENTRY_POINT)
.module(fragment_module.shader)
,
];
let shader_stages = [*shader_stages[0], *shader_stages[1]];
let mut pipeline_info = vk::GraphicsPipelineCreateInfo::builder()
.stages(&shader_stages)
.vertex_input_state(&pipeline_input_state)
.input_assembly_state(&input_assembly)
.rasterization_state(&raster_state)
.color_blend_state(&blend_state)
.multisample_state(&multisample_state)
.viewport_state(&viewport_state)
.depth_stencil_state(&depth_stencil_state)
.dynamic_state(&dynamic_state)
.layout(pipeline_layout.layout);
if let Some(render_pass) = render_pass {
pipeline_info = pipeline_info.render_pass(render_pass.handle)
}
let pipeline = unsafe {
// panic_safety: if this is successful this should return 1 pipelines.
device
.create_graphics_pipelines(*cache, &[*pipeline_info], None)
.map_err(|e| e.1)?[0]
};
Ok(pipeline)
}
pub fn new(
device: &Arc<ash::Device>,
shader_assembly: &ShaderCompilerOutput<Vec<u32>>,
reflection: &ShaderReflection,
replicas: u32,
render_pass_format: vk::Format,
bypass_cache: bool,
) -> error::Result<VulkanGraphicsPipeline> {
let pipeline_layout = PipelineLayoutObjects::new(reflection, replicas, device)?;
let vertex_info = vk::ShaderModuleCreateInfo::builder()
.code(shader_assembly.vertex.as_ref())
;
let fragment_info = vk::ShaderModuleCreateInfo::builder()
.code(shader_assembly.fragment.as_ref())
;
let vertex_module = VulkanShaderModule::new(device, &vertex_info)?;
let fragment_module = VulkanShaderModule::new(device, &fragment_info)?;
let mut render_pass = None;
if render_pass_format != vk::Format::UNDEFINED {
render_pass = Some(VulkanRenderPass::create_render_pass(
device,
render_pass_format,
)?);
}
let (pipeline, pipeline_cache) = cache_pipeline(
"vulkan",
&[&shader_assembly.vertex, &shader_assembly.fragment],
|pipeline_data| {
let mut cache_info = vk::PipelineCacheCreateInfo::builder();
if let Some(pipeline_data) = pipeline_data.as_ref() {
cache_info = cache_info.initial_data(pipeline_data);
}
let cache_info = cache_info;
let pipeline_cache = unsafe { device.create_pipeline_cache(&cache_info, None)? };
let pipeline = Self::create_pipeline(
&device,
&pipeline_cache,
&pipeline_layout,
&vertex_module,
&fragment_module,
render_pass.as_ref(),
)?;
Ok::<_, FilterChainError>((pipeline, pipeline_cache))
},
|(_pipeline, cache)| unsafe { Ok(device.get_pipeline_cache_data(*cache)?) },
bypass_cache,
)?;
Ok(VulkanGraphicsPipeline {
device: Arc::clone(device),
layout: pipeline_layout,
pipeline,
render_pass,
vertex: vertex_module,
fragment: fragment_module,
cache: pipeline_cache,
})
}
pub(crate) fn recompile(&mut self, format: vk::Format) -> error::Result<()> {
let mut new_renderpass = if self.render_pass.is_some() {
Some(VulkanRenderPass::create_render_pass(&self.device, format)?)
} else {
None
};
let mut new_pipeline = Self::create_pipeline(
&self.device,
&self.cache,
&self.layout,
&self.vertex,
&self.fragment,
new_renderpass.as_ref(),
)?;
std::mem::swap(&mut self.render_pass, &mut new_renderpass);
std::mem::swap(&mut self.pipeline, &mut new_pipeline);
unsafe {
if new_pipeline != vk::Pipeline::null() {
self.device.destroy_pipeline(new_pipeline, None)
}
}
Ok(())
}
#[inline(always)]
pub(crate) fn begin_rendering(
&self,
device: &ash::Device,
output: &RenderTarget<OutputImage>,
cmd: vk::CommandBuffer,
) -> error::Result<Option<vk::Framebuffer>> {
if let Some(render_pass) = &self.render_pass {
let attachments = [output.output.image_view];
let framebuffer = unsafe {
device.create_framebuffer(
&vk::FramebufferCreateInfo::builder()
.render_pass(render_pass.handle)
.attachments(&attachments)
.width(output.output.size.width)
.height(output.output.size.height)
.layers(1)
,
None,
)?
};
let clear_values = [vk::ClearValue {
color: vk::ClearColorValue {
float32: [0.0, 0.0, 0.0, 0.0],
},
}];
let render_pass_info = vk::RenderPassBeginInfo::builder()
.framebuffer(framebuffer)
.render_pass(render_pass.handle)
.clear_values(&clear_values)
// always render into the full output, regardless of viewport settings.
.render_area(vk::Rect2D {
offset: vk::Offset2D { x: 0, y: 0 },
extent: output.output.size.into(),
})
;
unsafe {
device.cmd_begin_render_pass(cmd, &render_pass_info, vk::SubpassContents::INLINE);
}
Ok(Some(framebuffer))
} else {
let attachments = vk::RenderingAttachmentInfo::builder()
.load_op(vk::AttachmentLoadOp::DONT_CARE)
.store_op(vk::AttachmentStoreOp::STORE)
.image_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
.image_view(output.output.image_view);
let attachments = [*attachments];
let rendering_info = vk::RenderingInfo::builder()
.layer_count(1)
.render_area(vk::Rect2D {
offset: vk::Offset2D { x: 0, y: 0 },
extent: output.output.size.into(),
})
.color_attachments(&attachments)
;
unsafe {
device.cmd_begin_rendering(cmd, &rendering_info);
}
Ok(None)
}
}
pub(crate) fn end_rendering(&self, device: &ash::Device, cmd: vk::CommandBuffer) {
unsafe {
if self.render_pass.is_none() {
device.cmd_end_rendering(cmd);
} else {
device.cmd_end_render_pass(cmd)
}
}
}
}
impl Drop for VulkanGraphicsPipeline {
fn drop(&mut self) {
unsafe {
if self.pipeline != vk::Pipeline::null() {
self.device.destroy_pipeline(self.pipeline, None)
}
if self.cache != vk::PipelineCache::null() {
self.device.destroy_pipeline_cache(self.cache, None)
}
}
}
}