use ash::vk;
use crate::vulkan::owned::{
OwnedDescriptorPool, OwnedFramebuffer, OwnedPipeline, OwnedPipelineLayout, OwnedRenderPass,
OwnedSampler, OwnedSetLayout, VkDevice,
};
use super::super::context::{HDR_FORMAT, 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) struct ReflectionCompositeResources {
pub(in crate::vulkan) output: GpuImage,
output_framebuffer: OwnedFramebuffer,
blur: GpuImage,
blur_framebuffer: OwnedFramebuffer,
blur_extent: vk::Extent2D,
render_pass: OwnedRenderPass,
_blur_set_layout: OwnedSetLayout,
_composite_set_layout: OwnedSetLayout,
blur_pipeline_layout: OwnedPipelineLayout,
composite_pipeline_layout: OwnedPipelineLayout,
blur_pso: OwnedPipeline,
composite_pso: OwnedPipeline,
_descriptor_pool: OwnedDescriptorPool,
blur_sets: Vec<vk::DescriptorSet>,
composite_sets: Vec<vk::DescriptorSet>,
sampler: OwnedSampler,
blur_scale: u32,
}
unsafe impl Send for ReflectionCompositeResources {}
pub(in crate::vulkan) struct ReflectionCompositeShaders {
pub vs: Vec<u8>,
pub blur_fs: Vec<u8>,
pub composite_fs: Vec<u8>,
}
pub(in crate::vulkan) fn compile_reflection_composite_shaders(
hot_reload: bool,
) -> Result<ReflectionCompositeShaders, String> {
use super::super::{builtins, slang_builtins};
let ctx = builtins::Ctx::plain(hot_reload);
Ok(ReflectionCompositeShaders {
vs: slang_builtins::FULLSCREEN_VERT.compile(&ctx)?,
blur_fs: slang_builtins::REFLECTION_BLUR.compile(&ctx)?,
composite_fs: slang_builtins::REFLECTION_COMPOSITE.compile(&ctx)?,
})
}
pub(in crate::vulkan) struct RebuiltReflectionComposite {
pub blur: OwnedPipeline,
pub composite: OwnedPipeline,
}
pub(in crate::vulkan) fn rebuild_reflection_composite_pipelines(
device: &VkDevice,
rc: &ReflectionCompositeResources,
hot_reload: bool,
) -> Result<RebuiltReflectionComposite, String> {
let shaders = compile_reflection_composite_shaders(hot_reload)?;
let blur = create_composite_pipeline(
device,
rc.render_pass.handle(),
rc.blur_pipeline_layout.handle(),
&shaders.vs,
&shaders.blur_fs,
)?;
let composite = create_composite_pipeline(
device,
rc.render_pass.handle(),
rc.composite_pipeline_layout.handle(),
&shaders.vs,
&shaders.composite_fs,
)?;
Ok(RebuiltReflectionComposite { blur, composite })
}
fn create_composite_render_pass(device: &VkDevice) -> Result<OwnedRenderPass, String> {
let attachment = vk::AttachmentDescription::default()
.format(HDR_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!("reflection composite render pass: {e}"))
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct CompositeInputViews<'a> {
pub hdr_resolve_views: &'a [vk::ImageView],
pub normal_depth_views: &'a [vk::ImageView],
pub roughness_views: &'a [vk::ImageView],
}
fn create_target(ctx: &GpuUploadContext, width: u32, height: u32) -> Result<GpuImage, String> {
let &GpuUploadContext {
alloc,
device,
command_pool,
queue,
} = ctx;
let pooled = create_image(
alloc,
&super::super::texture::ImageSpec {
width,
height,
format: HDR_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, image, HDR_FORMAT, vk::ImageAspectFlags::COLOR)?;
Ok(GpuImage::from_pooled(pooled, view))
}
fn create_composite_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 reflection composite pso: {e}"))?;
Ok(pipeline)
}
impl ReflectionCompositeResources {
pub(in crate::vulkan) fn new(
ctx: &GpuUploadContext,
width: u32,
height: u32,
frames: usize,
blur_scale: u32,
views: &CompositeInputViews,
hot_reload: bool,
) -> Result<Self, String> {
let device = ctx.device;
let blur_scale = blur_scale.max(1);
let render_pass = create_composite_render_pass(device)?;
let sampler_binding = |b: u32| {
(
b,
vk::DescriptorType::COMBINED_IMAGE_SAMPLER,
vk::ShaderStageFlags::FRAGMENT,
)
};
let blur_set_layout =
create_descriptor_set_layout(device, &[sampler_binding(0), sampler_binding(1)])?;
let composite_set_layout = create_descriptor_set_layout(
device,
&[
sampler_binding(0),
sampler_binding(1),
sampler_binding(2),
sampler_binding(3),
sampler_binding(4),
],
)?;
let make_layout = |set_layout: vk::DescriptorSetLayout, name: &str| -> Result<_, String> {
let layouts = [set_layout];
device
.create_pipeline_layout(
&vk::PipelineLayoutCreateInfo::default().set_layouts(&layouts),
)
.map_err(|e| format!("{name}: {e}"))
};
let blur_pipeline_layout = make_layout(blur_set_layout.handle(), "reflection blur layout")?;
let composite_pipeline_layout =
make_layout(composite_set_layout.handle(), "reflection composite layout")?;
let shaders = compile_reflection_composite_shaders(hot_reload)?;
let blur_pso = create_composite_pipeline(
device,
render_pass.handle(),
blur_pipeline_layout.handle(),
&shaders.vs,
&shaders.blur_fs,
)?;
let composite_pso = create_composite_pipeline(
device,
render_pass.handle(),
composite_pipeline_layout.handle(),
&shaders.vs,
&shaders.composite_fs,
)?;
let f = frames as u32;
let pool_sizes = [vk::DescriptorPoolSize::default()
.ty(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.descriptor_count(f * 7)];
let descriptor_pool = device
.create_descriptor_pool(
&vk::DescriptorPoolCreateInfo::default()
.pool_sizes(&pool_sizes)
.max_sets(f * 2),
)
.map_err(|e| format!("reflection composite descriptor pool: {e}"))?;
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 composite_layouts: Vec<_> =
(0..frames).map(|_| composite_set_layout.handle()).collect();
let composite_sets =
alloc_descriptor_sets(device, descriptor_pool.handle(), &composite_layouts)?;
let sampler = create_sampler_linear_clamp(device)?;
let mut me = Self {
output: GpuImage::null(),
output_framebuffer: OwnedFramebuffer::null(),
blur: GpuImage::null(),
blur_framebuffer: OwnedFramebuffer::null(),
blur_extent: vk::Extent2D::default(),
render_pass,
_blur_set_layout: blur_set_layout,
_composite_set_layout: composite_set_layout,
blur_pipeline_layout,
composite_pipeline_layout,
blur_pso,
composite_pso,
_descriptor_pool: descriptor_pool,
blur_sets,
composite_sets,
sampler,
blur_scale,
};
me.build_targets(ctx, width, height)?;
me.wire_sets(device, views);
Ok(me)
}
fn build_targets(
&mut self,
ctx: &GpuUploadContext,
width: u32,
height: u32,
) -> Result<(), String> {
let device = ctx.device;
let w = width.max(1);
let h = height.max(1);
let bw = (w / self.blur_scale).max(1);
let bh = (h / self.blur_scale).max(1);
self.output = create_target(ctx, w, h)?;
self.blur = create_target(ctx, bw, bh)?;
self.blur_extent = vk::Extent2D {
width: bw,
height: bh,
};
let make_fb = |view: vk::ImageView, fw: u32, fh: u32| -> Result<OwnedFramebuffer, String> {
device
.create_framebuffer(
&vk::FramebufferCreateInfo::default()
.render_pass(self.render_pass.handle())
.attachments(std::slice::from_ref(&view))
.width(fw)
.height(fh)
.layers(1),
)
.map_err(|e| format!("reflection composite framebuffer: {e}"))
};
self.output_framebuffer = make_fb(self.output.view, w, h)?;
self.blur_framebuffer = make_fb(self.blur.view, bw, bh)?;
Ok(())
}
fn wire_sets(&self, device: &VkDevice, views: &CompositeInputViews) {
let &CompositeInputViews {
hdr_resolve_views,
normal_depth_views,
roughness_views,
} = views;
let img = |view: vk::ImageView| {
vk::DescriptorImageInfo::default()
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.image_view(view)
.sampler(self.sampler.handle())
};
let write = |set: vk::DescriptorSet, binding: u32, info: &vk::DescriptorImageInfo| {
let w = vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(binding)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.image_info(std::slice::from_ref(info));
unsafe { device.update_descriptor_sets(std::slice::from_ref(&w), &[]) };
};
let pick = |views: &[vk::ImageView], i: usize| views[i % views.len().max(1)];
let blur_info = img(self.blur.view);
for i in 0..self.blur_sets.len() {
let rough = img(pick(roughness_views, i));
let placeholder = img(pick(hdr_resolve_views, i));
write(self.blur_sets[i], 0, &placeholder);
write(self.blur_sets[i], 1, &rough);
let scene = img(pick(hdr_resolve_views, i));
let nd = img(pick(normal_depth_views, i));
write(self.composite_sets[i], 0, &placeholder);
write(self.composite_sets[i], 1, &scene);
write(self.composite_sets[i], 2, &nd);
write(self.composite_sets[i], 3, &rough);
write(self.composite_sets[i], 4, &blur_info);
}
}
fn destroy_targets(&mut self, _device: &VkDevice) {
self.output_framebuffer = OwnedFramebuffer::null();
self.blur_framebuffer = OwnedFramebuffer::null();
self.output = GpuImage::null();
self.blur = GpuImage::null();
}
pub(in crate::vulkan) fn rebuild(
&mut self,
ctx: &GpuUploadContext,
width: u32,
height: u32,
views: &CompositeInputViews,
) -> Result<(), String> {
self.destroy_targets(ctx.device);
self.build_targets(ctx, width, height)?;
self.wire_sets(ctx.device, views);
Ok(())
}
pub(in crate::vulkan) fn swap_pipelines(&mut self, rebuilt: RebuiltReflectionComposite) {
self.blur_pso = rebuilt.blur;
self.composite_pso = rebuilt.composite;
}
pub(in crate::vulkan) fn destroy(&mut self, device: &VkDevice) {
self.destroy_targets(device);
}
}
impl VkContext {
pub(in crate::vulkan) fn encode_reflection_composite(
&self,
cmd: vk::CommandBuffer,
reflection_view: vk::ImageView,
frame_idx: usize,
) {
let Some(rc) = &self.reflection_composite else {
return;
};
let device = &self.device;
let refl = vk::DescriptorImageInfo::default()
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.image_view(reflection_view)
.sampler(rc.sampler.handle());
let repoint = [
vk::WriteDescriptorSet::default()
.dst_set(rc.blur_sets[frame_idx])
.dst_binding(0)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.image_info(std::slice::from_ref(&refl)),
vk::WriteDescriptorSet::default()
.dst_set(rc.composite_sets[frame_idx])
.dst_binding(0)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.image_info(std::slice::from_ref(&refl)),
];
unsafe { device.update_descriptor_sets(&repoint, &[]) };
self.begin_fullscreen_pass_sized(
cmd,
rc.render_pass.handle(),
rc.blur_framebuffer.handle(),
rc.blur_extent,
);
unsafe {
device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, rc.blur_pso.handle());
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
rc.blur_pipeline_layout.handle(),
0,
std::slice::from_ref(&rc.blur_sets[frame_idx]),
&[],
);
device.cmd_draw(cmd, 3, 1, 0, 0);
}
self.end_fullscreen_pass(cmd);
self.begin_fullscreen_pass(cmd, rc.render_pass.handle(), rc.output_framebuffer.handle());
unsafe {
device.cmd_bind_pipeline(
cmd,
vk::PipelineBindPoint::GRAPHICS,
rc.composite_pso.handle(),
);
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
rc.composite_pipeline_layout.handle(),
0,
std::slice::from_ref(&rc.composite_sets[frame_idx]),
&[],
);
device.cmd_draw(cmd, 3, 1, 0, 0);
}
self.end_fullscreen_pass(cmd);
}
}
#[cfg(test)]
mod tests {
#[test]
fn reflection_composite_shaders_compile() {
if !crate::slangc_gate::slangc_available() {
return;
}
let shaders = super::compile_reflection_composite_shaders(false)
.expect("reflection composite shaders compile");
assert!(super::is_spirv(&shaders.vs));
assert!(super::is_spirv(&shaders.blur_fs));
assert!(super::is_spirv(&shaders.composite_fs));
}
}