use ash::vk;
use crate::vulkan::owned::{
OwnedDescriptorPool, OwnedFramebuffer, OwnedPipeline, OwnedPipelineLayout, OwnedRenderPass,
OwnedSampler, OwnedSetLayout, VkDevice,
};
use crate::gfx::fullscreen::{FullscreenPass, encode_fullscreen};
use crate::gfx::render_types::SsrParams;
use crate::vulkan::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::*;
use crate::vulkan::slang_builtins::SlangCompile;
pub(in crate::vulkan) const SSR_OUTPUT_FORMAT: vk::Format = vk::Format::R16G16B16A16_SFLOAT;
pub(in crate::vulkan) struct SsrResources {
pub(in crate::vulkan) settings: crate::gfx::ssr::SsrSettings,
pub(in crate::vulkan) resolve_render_pass: OwnedRenderPass,
pub(in crate::vulkan) _resolve_set_layout: OwnedSetLayout,
pub(in crate::vulkan) resolve_layout: OwnedPipelineLayout,
pub(in crate::vulkan) resolve_pso: OwnedPipeline,
probe_cube_count: u32,
pub(in crate::vulkan) resolve_sets: Vec<vk::DescriptorSet>,
pub(in crate::vulkan) _descriptor_pool: OwnedDescriptorPool,
pub(in crate::vulkan) sampler: OwnedSampler,
pub(in crate::vulkan) output: GpuImage,
pub(in crate::vulkan) resolve_framebuffer: OwnedFramebuffer,
}
pub(in crate::vulkan) struct SsrShaders {
pub fullscreen_vs: Vec<u8>,
pub resolve_fs: Vec<u8>,
}
pub(in crate::vulkan) fn compile_ssr_shaders(
hot_reload: bool,
probe_cube_count: u32,
) -> Result<SsrShaders, String> {
use super::super::{builtins, slang_builtins};
let ctx = builtins::Ctx {
probe_count: probe_cube_count as usize,
..builtins::Ctx::plain(hot_reload)
};
Ok(SsrShaders {
fullscreen_vs: slang_builtins::FULLSCREEN_VERT.compile(&ctx)?,
resolve_fs: slang_builtins::SSR_RESOLVE.compile(&ctx)?,
})
}
pub(in crate::vulkan) struct RebuiltSsrPipelines {
pub resolve: OwnedPipeline,
}
pub(in crate::vulkan) fn rebuild_ssr_pipelines(
device: &VkDevice,
ssr: &SsrResources,
hot_reload: bool,
) -> Result<RebuiltSsrPipelines, String> {
let shaders = compile_ssr_shaders(hot_reload, ssr.probe_cube_count)?;
let resolve = create_resolve_pipeline(
device,
ssr.resolve_render_pass.handle(),
ssr.resolve_layout.handle(),
&shaders.fullscreen_vs,
&shaders.resolve_fs,
)?;
Ok(RebuiltSsrPipelines { resolve })
}
impl SsrResources {
pub(in crate::vulkan) fn swap_pipelines(&mut self, rebuilt: RebuiltSsrPipelines) {
self.resolve_pso = rebuilt.resolve;
}
}
fn create_resolve_render_pass(device: &VkDevice) -> Result<OwnedRenderPass, String> {
let attachment = vk::AttachmentDescription::default()
.format(SSR_OUTPUT_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::COLOR_ATTACHMENT_WRITE | vk::AccessFlags::SHADER_READ)
.dst_stage_mask(
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
| vk::PipelineStageFlags::FRAGMENT_SHADER,
)
.dst_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE | vk::AccessFlags::SHADER_READ);
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!("SSR resolve render pass: {e}"))
}
pub(in crate::vulkan) struct SsrGpuContext<'a> {
pub alloc: &'a DeviceAllocator,
pub device: &'a VkDevice,
pub command_pool: vk::CommandPool,
pub queue: vk::Queue,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct SsrExtent {
pub width: u32,
pub height: u32,
}
fn create_output_target(gpu: &SsrGpuContext, extent: SsrExtent) -> Result<GpuImage, String> {
let &SsrGpuContext {
alloc,
device,
command_pool,
queue,
} = gpu;
let SsrExtent { width, height } = extent;
let pooled = create_image(
alloc,
&ImageSpec {
width,
height,
format: SSR_OUTPUT_FORMAT,
tiling: vk::ImageTiling::OPTIMAL,
usage: vk::ImageUsageFlags::COLOR_ATTACHMENT
| vk::ImageUsageFlags::SAMPLED
| vk::ImageUsageFlags::TRANSFER_SRC,
mem_props: vk::MemoryPropertyFlags::DEVICE_LOCAL,
samples: vk::SampleCountFlags::TYPE_1,
},
)?;
let image = pooled.image();
one_shot_submit(device, command_pool, queue, |cmd| {
transition_image_layout(
device,
cmd,
image,
vk::ImageLayout::UNDEFINED,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
vk::ImageAspectFlags::COLOR,
);
})?;
let view = create_image_view(
device,
pooled.image(),
SSR_OUTPUT_FORMAT,
vk::ImageAspectFlags::COLOR,
)?;
Ok(GpuImage::from_pooled(pooled, view))
}
fn create_resolve_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::RGBA)
.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 ssr resolve pso: {e}"))?;
Ok(pipeline)
}
pub(in crate::vulkan) struct SsrInitInputs<'a> {
pub settings: crate::gfx::ssr::SsrSettings,
pub hdr_resolve_views: &'a [vk::ImageView],
pub prefilter_view: vk::ImageView,
pub cube_sampler: vk::Sampler,
pub global_set_layout: vk::DescriptorSetLayout,
pub probe_cube_count: u32,
}
pub(in crate::vulkan) struct SsrResolveInputs<'a> {
pub hdr_resolve_views: &'a [vk::ImageView],
pub gbuffer_views: &'a [vk::ImageView],
pub roughness_views: &'a [vk::ImageView],
pub prefilter_view: vk::ImageView,
pub cube_sampler: vk::Sampler,
}
impl SsrResources {
pub(in crate::vulkan) fn new(
gpu: &SsrGpuContext,
extent: SsrExtent,
frames: usize,
inputs: SsrInitInputs,
hot_reload: bool,
) -> Result<Self, String> {
let device = gpu.device;
let SsrInitInputs {
settings,
hdr_resolve_views,
prefilter_view,
cube_sampler,
global_set_layout,
probe_cube_count,
} = inputs;
let resolve_render_pass = create_resolve_render_pass(device)?;
let resolve_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,
),
(
2,
vk::DescriptorType::COMBINED_IMAGE_SAMPLER,
vk::ShaderStageFlags::FRAGMENT,
),
(
3,
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::<SsrParams>() as u32);
let resolve_set_layouts = [resolve_set_layout.handle(), global_set_layout];
let resolve_layout = device
.create_pipeline_layout(
&vk::PipelineLayoutCreateInfo::default()
.set_layouts(&resolve_set_layouts)
.push_constant_ranges(std::slice::from_ref(¶ms_push)),
)
.map_err(|e| format!("ssr resolve layout: {e}"))?;
let shaders = compile_ssr_shaders(hot_reload, probe_cube_count)?;
let resolve_pso = create_resolve_pipeline(
device,
resolve_render_pass.handle(),
resolve_layout.handle(),
&shaders.fullscreen_vs,
&shaders.resolve_fs,
)?;
let pool_sizes = [vk::DescriptorPoolSize::default()
.ty(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.descriptor_count(frames as u32 * 4)];
let descriptor_pool = device
.create_descriptor_pool(
&vk::DescriptorPoolCreateInfo::default()
.pool_sizes(&pool_sizes)
.max_sets(frames as u32),
)
.map_err(|e| format!("ssr descriptor pool: {e}"))?;
let resolve_layouts_vec: Vec<_> =
(0..frames).map(|_| resolve_set_layout.handle()).collect();
let resolve_sets =
alloc_descriptor_sets(device, descriptor_pool.handle(), &resolve_layouts_vec)?;
let sampler = create_sampler_linear_clamp(device)?;
let mut me = Self {
settings,
resolve_render_pass,
_resolve_set_layout: resolve_set_layout,
resolve_layout,
resolve_pso,
probe_cube_count,
resolve_sets,
_descriptor_pool: descriptor_pool,
sampler,
output: GpuImage::null(),
resolve_framebuffer: OwnedFramebuffer::null(),
};
me.build_targets(gpu, extent)?;
me.wire_resolve_sets(
device,
hdr_resolve_views,
&[],
&[],
prefilter_view,
cube_sampler,
);
Ok(me)
}
fn build_targets(&mut self, gpu: &SsrGpuContext, extent: SsrExtent) -> Result<(), String> {
let device = gpu.device;
let w = extent.width.max(1);
let h = extent.height.max(1);
self.output = create_output_target(
gpu,
SsrExtent {
width: w,
height: h,
},
)?;
self.resolve_framebuffer = device
.create_framebuffer(
&vk::FramebufferCreateInfo::default()
.render_pass(self.resolve_render_pass.handle())
.attachments(std::slice::from_ref(&self.output.view))
.width(w)
.height(h)
.layers(1),
)
.map_err(|e| format!("ssr resolve framebuffer: {e}"))?;
Ok(())
}
pub(in crate::vulkan) fn wire_resolve_sets(
&self,
device: &VkDevice,
hdr_resolve_views: &[vk::ImageView],
gbuffer_views: &[vk::ImageView],
roughness_views: &[vk::ImageView],
prefilter_view: vk::ImageView,
cube_sampler: vk::Sampler,
) {
let cube_info = vk::DescriptorImageInfo::default()
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.image_view(prefilter_view)
.sampler(cube_sampler);
for (i, &set) in self.resolve_sets.iter().enumerate() {
let scene_placeholder = hdr_resolve_views[i % hdr_resolve_views.len().max(1)];
let gb_view = if gbuffer_views.is_empty() {
scene_placeholder
} else {
gbuffer_views[i % gbuffer_views.len()]
};
let rough_view = if roughness_views.is_empty() {
scene_placeholder
} else {
roughness_views[i % roughness_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 rough_info = vk::DescriptorImageInfo::default()
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.image_view(rough_view)
.sampler(self.sampler.handle());
let scene_view = hdr_resolve_views[i % hdr_resolve_views.len().max(1)];
let scene_info = vk::DescriptorImageInfo::default()
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.image_view(scene_view)
.sampler(self.sampler.handle());
let writes = [
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(0)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.image_info(std::slice::from_ref(&scene_info)),
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(1)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.image_info(std::slice::from_ref(&gb_info)),
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(2)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.image_info(std::slice::from_ref(&rough_info)),
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(3)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.image_info(std::slice::from_ref(&cube_info)),
];
unsafe { device.update_descriptor_sets(&writes, &[]) };
}
}
fn destroy_targets(&mut self, _device: &VkDevice) {
if !self.resolve_framebuffer.is_null() {
self.resolve_framebuffer = OwnedFramebuffer::null();
self.output = GpuImage::null();
}
}
pub(in crate::vulkan) fn rebuild(
&mut self,
gpu: &SsrGpuContext,
extent: SsrExtent,
inputs: SsrResolveInputs,
) -> Result<(), String> {
let device = gpu.device;
let SsrResolveInputs {
hdr_resolve_views,
gbuffer_views,
roughness_views,
prefilter_view,
cube_sampler,
} = inputs;
self.destroy_targets(device);
self.build_targets(gpu, extent)?;
self.wire_resolve_sets(
device,
hdr_resolve_views,
gbuffer_views,
roughness_views,
prefilter_view,
cube_sampler,
);
Ok(())
}
pub(in crate::vulkan) fn destroy(&mut self, device: &VkDevice) {
self.destroy_targets(device);
}
}
impl VkContext {
pub(in crate::vulkan) fn encode_ssr_resolve(
&self,
cmd: vk::CommandBuffer,
frame_idx: usize,
fov_y_radians: f32,
aspect: f32,
cam_pos: [f32; 3],
) {
let Some(ssr) = &self.ssr else { return };
encode_fullscreen(
&SsrResolvePass {
ctx: self,
ssr,
frame_idx,
fov_y_radians,
aspect,
cam_pos,
},
&cmd,
);
self.encode_reflection_composite(cmd, ssr.output.view, frame_idx);
}
}
struct SsrResolvePass<'a> {
ctx: &'a VkContext,
ssr: &'a SsrResources,
frame_idx: usize,
fov_y_radians: f32,
aspect: f32,
cam_pos: [f32; 3],
}
impl FullscreenPass for SsrResolvePass<'_> {
type Rec = vk::CommandBuffer;
fn begin(&self, cmd: &Self::Rec) {
self.ctx.begin_fullscreen_pass(
*cmd,
self.ssr.resolve_render_pass.handle(),
self.ssr.resolve_framebuffer.handle(),
);
}
fn draw(&self, cmd: &Self::Rec) {
let cmd = *cmd;
let device = &self.ctx.device;
let v = self.ctx.view.matrix;
let inv_view_rot = [
[v[0][0], v[1][0], v[2][0], 0.0],
[v[0][1], v[1][1], v[2][1], 0.0],
[v[0][2], v[1][2], v[2][2], 0.0],
[0.0, 0.0, 0.0, 1.0],
];
let params = self.ssr.settings.params(
self.fov_y_radians,
self.aspect,
inv_view_rot,
self.cam_pos,
self.ctx.prefilter_mip_count as f32,
);
unsafe {
device.cmd_bind_pipeline(
cmd,
vk::PipelineBindPoint::GRAPHICS,
self.ssr.resolve_pso.handle(),
);
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
self.ssr.resolve_layout.handle(),
0,
std::slice::from_ref(&self.ssr.resolve_sets[self.frame_idx]),
&[],
);
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
self.ssr.resolve_layout.handle(),
1,
std::slice::from_ref(&self.ctx.descriptors.global_sets[self.frame_idx]),
&[],
);
device.cmd_push_constants(
cmd,
self.ssr.resolve_layout.handle(),
vk::ShaderStageFlags::FRAGMENT,
0,
std::slice::from_raw_parts(
¶ms as *const SsrParams as *const u8,
std::mem::size_of::<SsrParams>(),
),
);
device.cmd_draw(cmd, 3, 1, 0, 0);
}
}
fn end(&self, cmd: &Self::Rec) {
self.ctx.end_fullscreen_pass(*cmd);
}
}
#[cfg(test)]
mod tests {
#[test]
fn ssr_shaders_compile() {
if !crate::slangc_gate::slangc_available() {
return;
}
for probes in [1, concinnity_render::uniforms::MAX_PROBES as u32] {
let shaders = super::compile_ssr_shaders(false, probes).expect("ssr shaders compile");
assert!(super::is_spirv(&shaders.fullscreen_vs));
assert!(super::is_spirv(&shaders.resolve_fs));
}
}
}