use ash::vk;
use crate::vulkan::owned::{
OwnedDescriptorPool, OwnedFramebuffer, OwnedPipeline, OwnedPipelineLayout, OwnedRenderPass,
OwnedSampler, OwnedSetLayout, VkDevice,
};
use crate::vulkan::record::Recorder;
use crate::gfx::render_types::SsaoParams;
use super::super::allocator::DeviceAllocator;
use super::super::context::VkContext;
use super::super::pipeline::*;
use super::super::resources::{alloc_descriptor_sets, create_descriptor_set_layout};
use super::super::texture::*;
pub(in crate::vulkan) const SSAO_OCCLUSION_FORMAT: vk::Format = vk::Format::R8_UNORM;
pub(in crate::vulkan) struct SsaoResources {
pub(in crate::vulkan) settings: crate::gfx::ssao::SsaoSettings,
pub(in crate::vulkan) fullscreen_render_pass: OwnedRenderPass,
pub(in crate::vulkan) blur_render_pass: OwnedRenderPass,
pub(in crate::vulkan) _kernel_set_layout: OwnedSetLayout,
pub(in crate::vulkan) kernel_layout: OwnedPipelineLayout,
pub(in crate::vulkan) kernel_pso: OwnedPipeline,
pub(in crate::vulkan) _blur_set_layout: OwnedSetLayout,
pub(in crate::vulkan) blur_layout: OwnedPipelineLayout,
pub(in crate::vulkan) blur_pso: OwnedPipeline,
pub(in crate::vulkan) kernel_sets: Vec<vk::DescriptorSet>,
pub(in crate::vulkan) blur_sets: Vec<vk::DescriptorSet>,
pub(in crate::vulkan) _descriptor_pool: OwnedDescriptorPool,
pub(in crate::vulkan) sampler: OwnedSampler,
pub(in crate::vulkan) ao_raw: GpuImage,
pub(in crate::vulkan) kernel_framebuffer: OwnedFramebuffer,
pub(in crate::vulkan) blur_framebuffers: Vec<OwnedFramebuffer>,
}
pub(in crate::vulkan) struct SsaoShaders {
pub fullscreen_vs: Vec<u8>,
pub kernel_fs: Vec<u8>,
pub blur_fs: Vec<u8>,
}
pub(in crate::vulkan) fn compile_ssao_shaders(hot_reload: bool) -> Result<SsaoShaders, String> {
use super::super::{builtins, slang_builtins};
let ctx = builtins::Ctx::plain(hot_reload);
Ok(SsaoShaders {
fullscreen_vs: slang_builtins::FULLSCREEN_VERT.compile(&ctx)?,
kernel_fs: slang_builtins::SSAO_KERNEL.compile(&ctx)?,
blur_fs: slang_builtins::SSAO_BLUR.compile(&ctx)?,
})
}
pub(in crate::vulkan) struct RebuiltSsaoPipelines {
pub kernel: OwnedPipeline,
pub blur: OwnedPipeline,
}
pub(in crate::vulkan) fn rebuild_ssao_pipelines(
device: &VkDevice,
ssao: &SsaoResources,
hot_reload: bool,
) -> Result<RebuiltSsaoPipelines, String> {
let shaders = compile_ssao_shaders(hot_reload)?;
let kernel = create_fullscreen_pipeline(
device,
ssao.fullscreen_render_pass.handle(),
ssao.kernel_layout.handle(),
&shaders.fullscreen_vs,
&shaders.kernel_fs,
)?;
let blur = create_fullscreen_pipeline(
device,
ssao.blur_render_pass.handle(),
ssao.blur_layout.handle(),
&shaders.fullscreen_vs,
&shaders.blur_fs,
)?;
Ok(RebuiltSsaoPipelines { kernel, blur })
}
impl SsaoResources {
pub(in crate::vulkan) fn swap_pipelines(&mut self, rebuilt: RebuiltSsaoPipelines) {
self.kernel_pso = rebuilt.kernel;
self.blur_pso = rebuilt.blur;
}
}
fn create_fullscreen_render_pass(device: &VkDevice) -> Result<OwnedRenderPass, String> {
let attachment = vk::AttachmentDescription::default()
.format(SSAO_OCCLUSION_FORMAT)
.samples(vk::SampleCountFlags::TYPE_1)
.load_op(vk::AttachmentLoadOp::DONT_CARE)
.store_op(vk::AttachmentStoreOp::STORE)
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(vk::ImageLayout::UNDEFINED)
.final_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);
let color_ref = vk::AttachmentReference::default()
.attachment(0)
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
let subpass = vk::SubpassDescription::default()
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
.color_attachments(std::slice::from_ref(&color_ref));
let dep = vk::SubpassDependency::default()
.src_subpass(vk::SUBPASS_EXTERNAL)
.dst_subpass(0)
.src_stage_mask(
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
| vk::PipelineStageFlags::FRAGMENT_SHADER,
)
.src_access_mask(vk::AccessFlags::SHADER_READ | vk::AccessFlags::COLOR_ATTACHMENT_WRITE)
.dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
.dst_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE);
let info = vk::RenderPassCreateInfo::default()
.attachments(std::slice::from_ref(&attachment))
.subpasses(std::slice::from_ref(&subpass))
.dependencies(std::slice::from_ref(&dep));
device
.create_render_pass(&info)
.map_err(|e| format!("SSAO fullscreen render pass: {e}"))
}
fn create_blur_render_pass(device: &VkDevice) -> Result<OwnedRenderPass, String> {
let attachment = vk::AttachmentDescription::default()
.format(SSAO_OCCLUSION_FORMAT)
.samples(vk::SampleCountFlags::TYPE_1)
.load_op(vk::AttachmentLoadOp::DONT_CARE)
.store_op(vk::AttachmentStoreOp::STORE)
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
.final_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
let color_ref = vk::AttachmentReference::default()
.attachment(0)
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
let subpass = vk::SubpassDescription::default()
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
.color_attachments(std::slice::from_ref(&color_ref));
let dep = vk::SubpassDependency::default()
.src_subpass(vk::SUBPASS_EXTERNAL)
.dst_subpass(0)
.src_stage_mask(
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
| vk::PipelineStageFlags::FRAGMENT_SHADER,
)
.src_access_mask(vk::AccessFlags::SHADER_READ | vk::AccessFlags::COLOR_ATTACHMENT_WRITE)
.dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
.dst_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE);
let info = vk::RenderPassCreateInfo::default()
.attachments(std::slice::from_ref(&attachment))
.subpasses(std::slice::from_ref(&subpass))
.dependencies(std::slice::from_ref(&dep));
device
.create_render_pass(&info)
.map_err(|e| format!("SSAO blur render pass: {e}"))
}
fn create_ao_target(
alloc: &DeviceAllocator,
device: &VkDevice,
width: u32,
height: u32,
) -> Result<GpuImage, String> {
let pooled = create_image(
alloc,
&super::super::texture::ImageSpec {
width,
height,
format: SSAO_OCCLUSION_FORMAT,
tiling: vk::ImageTiling::OPTIMAL,
usage: vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::SAMPLED,
mem_props: vk::MemoryPropertyFlags::DEVICE_LOCAL,
samples: vk::SampleCountFlags::TYPE_1,
},
)?;
let view = create_image_view(
device,
pooled.image(),
SSAO_OCCLUSION_FORMAT,
vk::ImageAspectFlags::COLOR,
)?;
Ok(GpuImage::from_pooled(pooled, view))
}
fn create_fullscreen_pipeline(
device: &VkDevice,
render_pass: vk::RenderPass,
layout: vk::PipelineLayout,
vert_spv: &[u8],
frag_spv: &[u8],
) -> Result<OwnedPipeline, String> {
let vert_mod = spv_module(device, vert_spv)?;
let frag_mod = spv_module(device, frag_spv)?;
let entry = std::ffi::CString::new("main").unwrap();
let stages = [
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::VERTEX)
.module(vert_mod.handle())
.name(&entry),
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::FRAGMENT)
.module(frag_mod.handle())
.name(&entry),
];
let vert_input = vk::PipelineVertexInputStateCreateInfo::default();
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
.topology(vk::PrimitiveTopology::TRIANGLE_LIST);
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
.viewport_count(1)
.scissor_count(1);
let raster = vk::PipelineRasterizationStateCreateInfo::default()
.polygon_mode(vk::PolygonMode::FILL)
.line_width(1.0)
.cull_mode(vk::CullModeFlags::NONE)
.front_face(vk::FrontFace::COUNTER_CLOCKWISE);
let multisample = vk::PipelineMultisampleStateCreateInfo::default()
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
let depth = vk::PipelineDepthStencilStateCreateInfo::default()
.depth_test_enable(false)
.depth_write_enable(false)
.depth_compare_op(vk::CompareOp::ALWAYS);
let blend_attach = vk::PipelineColorBlendAttachmentState::default()
.color_write_mask(vk::ColorComponentFlags::R)
.blend_enable(false);
let blend = vk::PipelineColorBlendStateCreateInfo::default()
.attachments(std::slice::from_ref(&blend_attach));
let dyn_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dyn_states);
let info = vk::GraphicsPipelineCreateInfo::default()
.stages(&stages)
.vertex_input_state(&vert_input)
.input_assembly_state(&input_assembly)
.viewport_state(&viewport_state)
.rasterization_state(&raster)
.multisample_state(&multisample)
.depth_stencil_state(&depth)
.color_blend_state(&blend)
.dynamic_state(&dynamic)
.layout(layout)
.render_pass(render_pass)
.subpass(0);
let pipeline = crate::vulkan::pipeline_cache::create_graphics_pipeline(device, &info)
.map_err(|e| format!("create ssao fullscreen pso: {e}"))?;
Ok(pipeline)
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct SsaoDeviceCtx<'a> {
pub alloc: &'a DeviceAllocator,
pub device: &'a VkDevice,
}
impl SsaoResources {
pub(in crate::vulkan) fn new(
ctx: &SsaoDeviceCtx,
width: u32,
height: u32,
frames: usize,
settings: crate::gfx::ssao::SsaoSettings,
ao_views: &[vk::ImageView],
hot_reload: bool,
) -> Result<Self, String> {
let device = ctx.device;
let fullscreen_render_pass = create_fullscreen_render_pass(device)?;
let blur_render_pass = create_blur_render_pass(device)?;
let kernel_set_layout = create_descriptor_set_layout(
device,
&[(
0,
vk::DescriptorType::COMBINED_IMAGE_SAMPLER,
vk::ShaderStageFlags::FRAGMENT,
)],
)?;
let blur_set_layout = create_descriptor_set_layout(
device,
&[
(
0,
vk::DescriptorType::COMBINED_IMAGE_SAMPLER,
vk::ShaderStageFlags::FRAGMENT,
),
(
1,
vk::DescriptorType::COMBINED_IMAGE_SAMPLER,
vk::ShaderStageFlags::FRAGMENT,
),
],
)?;
let params_push = vk::PushConstantRange::default()
.stage_flags(vk::ShaderStageFlags::FRAGMENT)
.offset(0)
.size(std::mem::size_of::<SsaoParams>() as u32);
let kernel_set_layouts = [kernel_set_layout.handle()];
let kernel_layout = device
.create_pipeline_layout(
&vk::PipelineLayoutCreateInfo::default()
.set_layouts(&kernel_set_layouts)
.push_constant_ranges(std::slice::from_ref(¶ms_push)),
)
.map_err(|e| format!("ssao kernel layout: {e}"))?;
let blur_set_layouts = [blur_set_layout.handle()];
let blur_layout = device
.create_pipeline_layout(
&vk::PipelineLayoutCreateInfo::default().set_layouts(&blur_set_layouts),
)
.map_err(|e| format!("ssao blur layout: {e}"))?;
let shaders = compile_ssao_shaders(hot_reload)?;
let kernel_pso = create_fullscreen_pipeline(
device,
fullscreen_render_pass.handle(),
kernel_layout.handle(),
&shaders.fullscreen_vs,
&shaders.kernel_fs,
)?;
let blur_pso = create_fullscreen_pipeline(
device,
blur_render_pass.handle(),
blur_layout.handle(),
&shaders.fullscreen_vs,
&shaders.blur_fs,
)?;
let pool_sizes = [vk::DescriptorPoolSize::default()
.ty(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.descriptor_count(frames as u32 * 3)];
let descriptor_pool = device
.create_descriptor_pool(
&vk::DescriptorPoolCreateInfo::default()
.pool_sizes(&pool_sizes)
.max_sets(frames as u32 * 2),
)
.map_err(|e| format!("ssao descriptor pool: {e}"))?;
let kernel_layouts: Vec<_> = (0..frames).map(|_| kernel_set_layout.handle()).collect();
let kernel_sets = alloc_descriptor_sets(device, descriptor_pool.handle(), &kernel_layouts)?;
let blur_layouts: Vec<_> = (0..frames).map(|_| blur_set_layout.handle()).collect();
let blur_sets = alloc_descriptor_sets(device, descriptor_pool.handle(), &blur_layouts)?;
let sampler = create_sampler_linear_clamp(device)?;
let mut me = Self {
settings,
fullscreen_render_pass,
blur_render_pass,
_kernel_set_layout: kernel_set_layout,
kernel_layout,
kernel_pso,
_blur_set_layout: blur_set_layout,
blur_layout,
blur_pso,
kernel_sets,
blur_sets,
_descriptor_pool: descriptor_pool,
sampler,
ao_raw: GpuImage::null(),
kernel_framebuffer: OwnedFramebuffer::null(),
blur_framebuffers: Vec::new(),
};
me.build_targets(ctx, width, height, ao_views)?;
me.wire_kernel_and_blur_sets(device, &[]);
Ok(me)
}
fn build_targets(
&mut self,
ctx: &SsaoDeviceCtx,
width: u32,
height: u32,
ao_views: &[vk::ImageView],
) -> Result<(), String> {
let SsaoDeviceCtx { alloc, device } = ctx;
let w = width.max(1);
let h = height.max(1);
self.ao_raw = create_ao_target(alloc, device, w, h)?;
self.kernel_framebuffer = device
.create_framebuffer(
&vk::FramebufferCreateInfo::default()
.render_pass(self.fullscreen_render_pass.handle())
.attachments(std::slice::from_ref(&self.ao_raw.view))
.width(w)
.height(h)
.layers(1),
)
.map_err(|e| format!("ssao kernel framebuffer: {e}"))?;
let mut blur_framebuffers = Vec::with_capacity(ao_views.len());
for &ao_view in ao_views {
let fb = device
.create_framebuffer(
&vk::FramebufferCreateInfo::default()
.render_pass(self.blur_render_pass.handle())
.attachments(std::slice::from_ref(&ao_view))
.width(w)
.height(h)
.layers(1),
)
.map_err(|e| format!("ssao blur framebuffer: {e}"))?;
blur_framebuffers.push(fb);
}
self.blur_framebuffers = blur_framebuffers;
Ok(())
}
fn wire_kernel_and_blur_sets(&self, device: &VkDevice, gbuffer_views: &[vk::ImageView]) {
let raw_info = vk::DescriptorImageInfo::default()
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.image_view(self.ao_raw.view)
.sampler(self.sampler.handle());
for f in 0..self.kernel_sets.len() {
let gb_view = if gbuffer_views.is_empty() {
self.ao_raw.view
} else {
gbuffer_views[f % gbuffer_views.len()]
};
let gb_info = vk::DescriptorImageInfo::default()
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.image_view(gb_view)
.sampler(self.sampler.handle());
let writes = [
vk::WriteDescriptorSet::default()
.dst_set(self.kernel_sets[f])
.dst_binding(0)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.image_info(std::slice::from_ref(&gb_info)),
vk::WriteDescriptorSet::default()
.dst_set(self.blur_sets[f])
.dst_binding(0)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.image_info(std::slice::from_ref(&raw_info)),
vk::WriteDescriptorSet::default()
.dst_set(self.blur_sets[f])
.dst_binding(1)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.image_info(std::slice::from_ref(&gb_info)),
];
unsafe { device.update_descriptor_sets(&writes, &[]) };
}
}
pub(in crate::vulkan) fn wire_kernel_and_blur_sets_gbuffer(
&self,
device: &VkDevice,
gbuffer_views: &[vk::ImageView],
) {
self.wire_kernel_and_blur_sets(device, gbuffer_views);
}
fn destroy_targets(&mut self, _device: &VkDevice) {
if !self.kernel_framebuffer.is_null() {
self.kernel_framebuffer = OwnedFramebuffer::null();
self.blur_framebuffers.clear();
self.ao_raw = GpuImage::null();
}
}
pub(in crate::vulkan) fn rebuild(
&mut self,
ctx: &SsaoDeviceCtx,
width: u32,
height: u32,
gbuffer_views: &[vk::ImageView],
ao_views: &[vk::ImageView],
) -> Result<(), String> {
let device = ctx.device;
self.destroy_targets(device);
self.build_targets(ctx, width, height, ao_views)?;
self.wire_kernel_and_blur_sets(device, gbuffer_views);
Ok(())
}
pub(in crate::vulkan) fn destroy(&mut self, device: &VkDevice) {
self.destroy_targets(device);
}
}
impl VkContext {
pub(in crate::vulkan) fn encode_ssao(
&self,
rec: &Recorder<'_>,
frame_idx: usize,
fov_y_radians: f32,
aspect: f32,
) {
let Some(ssao) = &self.ssao else {
return;
};
let extent = self.render_extent;
let params = ssao.settings.params(fov_y_radians, aspect);
let area = vk::Rect2D::default().extent(extent);
rec.begin_render_pass(
&ssao.fullscreen_render_pass,
&ssao.kernel_framebuffer,
area,
&[],
);
rec.set_full_viewport(extent);
rec.bind_pipeline(vk::PipelineBindPoint::GRAPHICS, &ssao.kernel_pso);
rec.bind_descriptor_sets(
vk::PipelineBindPoint::GRAPHICS,
&ssao.kernel_layout,
0,
std::slice::from_ref(&ssao.kernel_sets[frame_idx]),
&[],
);
rec.push_constants(
&ssao.kernel_layout,
vk::ShaderStageFlags::FRAGMENT,
0,
¶ms,
);
rec.draw_fullscreen_triangle();
rec.end_render_pass();
rec.begin_render_pass(
&ssao.blur_render_pass,
&ssao.blur_framebuffers[frame_idx],
area,
&[],
);
rec.set_full_viewport(extent);
rec.bind_pipeline(vk::PipelineBindPoint::GRAPHICS, &ssao.blur_pso);
rec.bind_descriptor_sets(
vk::PipelineBindPoint::GRAPHICS,
&ssao.blur_layout,
0,
std::slice::from_ref(&ssao.blur_sets[frame_idx]),
&[],
);
rec.draw_fullscreen_triangle();
rec.end_render_pass();
}
}