use ash::vk;
use concinnity_core::gfx::frustum::Frustum;
use concinnity_core::gfx::render_types::ClusterParams;
use concinnity_core::render::error::RenderResult;
use concinnity_core::render::planar_reflection::{self, PlanarReflectors};
use concinnity_core::transform::mat4_inverse;
use concinnity_core::transform::mat4_mul;
use super::allocator::{DeviceAllocator, PooledBuffer};
use super::context::{HDR_FORMAT, VkContext};
use super::descriptor_layout::{PoolSizes, global_set};
use super::draw::ViewUniforms;
use super::global_set::{GlobalBindings, GlobalSetContents};
use super::graph_exec::GraphFrameParams;
use super::light_cull::{
LightCullInputs, cluster_list_size, light_cull_set_bindings, write_light_cull_set,
};
use super::probe::FaceArea;
use super::record::Recorder;
use super::resources::alloc_descriptor_sets;
use super::set_writes::SetWrites;
use super::texture::{
GpuImage, ImageSpec, create_image, create_image_view, one_shot_submit, transition_image_layout,
};
use crate::vulkan::owned::{OwnedDescriptorPool, OwnedFramebuffer, OwnedRenderPass, VkDevice};
pub(in crate::vulkan) const MAX_PLANAR_PLANES: usize = planar_reflection::MAX_PLANAR_PLANES;
const PLANAR_CLIP_BIAS: f32 = 0.02;
const PLANAR_DEPTH_FORMAT: vk::Format = vk::Format::D32_SFLOAT;
const PLANAR_CROP_MARGIN: u32 = 2;
pub(in crate::vulkan) struct PlanarReflectionSet {
layout: PlanarReflectors,
frames: usize,
sample_count: vk::SampleCountFlags,
width: u32,
height: u32,
main_render_pass: vk::RenderPass,
color: Option<GpuImage>,
depth: GpuImage,
targets: Vec<GpuImage>,
framebuffers: Vec<OwnedFramebuffer>,
view_bufs: Vec<PooledBuffer>,
global_sets: Vec<vk::DescriptorSet>,
cluster_params_bufs: Vec<PooledBuffer>,
cluster_lists: Vec<PooledBuffer>,
cluster_sets: Vec<vk::DescriptorSet>,
cull_indirect_bufs: Vec<PooledBuffer>,
cull_status_bufs: Vec<PooledBuffer>,
cull_sets: Vec<vk::DescriptorSet>,
hiz_set: Option<vk::DescriptorSet>,
hiz_ubo: Option<PooledBuffer>,
_pool: OwnedDescriptorPool,
}
pub(in crate::vulkan) struct PlanarCullSources<'a> {
pub(in crate::vulkan) frame_object_buffers: &'a [PooledBuffer],
pub(in crate::vulkan) frame_draw_args_buffers: &'a [PooledBuffer],
pub(in crate::vulkan) cull_set_layout: vk::DescriptorSetLayout,
pub(in crate::vulkan) cull_count: usize,
pub(in crate::vulkan) hiz: Option<(vk::DescriptorSetLayout, vk::ImageView)>,
}
unsafe impl Send for PlanarReflectionSet {}
unsafe impl Sync for PlanarReflectionSet {}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct PlanarDevice<'a> {
pub(in crate::vulkan) alloc: &'a DeviceAllocator,
pub(in crate::vulkan) device: &'a VkDevice,
pub(in crate::vulkan) command_pool: vk::CommandPool,
pub(in crate::vulkan) queue: vk::Queue,
}
#[derive(Clone, Copy)]
struct PlanarTargetDims {
sample_count: vk::SampleCountFlags,
width: u32,
height: u32,
plane_count: usize,
}
fn create_targets(
gpu: PlanarDevice<'_>,
dims: PlanarTargetDims,
) -> RenderResult<(Option<GpuImage>, GpuImage, Vec<GpuImage>)> {
let PlanarDevice {
alloc,
device,
command_pool,
queue,
} = gpu;
let PlanarTargetDims {
sample_count,
width,
height,
plane_count,
} = dims;
let msaa = sample_count != vk::SampleCountFlags::TYPE_1;
let w = width.max(1);
let h = height.max(1);
let color = if msaa {
let pooled = create_image(
alloc,
&ImageSpec {
width: w,
height: h,
format: HDR_FORMAT,
tiling: vk::ImageTiling::OPTIMAL,
usage: vk::ImageUsageFlags::COLOR_ATTACHMENT,
mem_props: vk::MemoryPropertyFlags::DEVICE_LOCAL,
samples: sample_count,
},
)?;
let img = pooled.image();
let view = create_image_view(device, img, HDR_FORMAT, vk::ImageAspectFlags::COLOR)?;
Some(GpuImage::from_pooled(pooled, view))
} else {
None
};
let pooled = create_image(
alloc,
&ImageSpec {
width: w,
height: h,
format: PLANAR_DEPTH_FORMAT,
tiling: vk::ImageTiling::OPTIMAL,
usage: vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT,
mem_props: vk::MemoryPropertyFlags::DEVICE_LOCAL,
samples: sample_count,
},
)?;
let depth_img = pooled.image();
let depth_view = create_image_view(
device,
depth_img,
PLANAR_DEPTH_FORMAT,
vk::ImageAspectFlags::DEPTH,
)?;
let depth = GpuImage::from_pooled(pooled, depth_view);
let mut targets = Vec::with_capacity(plane_count);
for _ in 0..plane_count {
let pooled = create_image(
alloc,
&ImageSpec {
width: w,
height: h,
format: HDR_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 img = pooled.image();
let view = create_image_view(device, img, HDR_FORMAT, vk::ImageAspectFlags::COLOR)?;
targets.push(GpuImage::from_pooled(pooled, view));
}
one_shot_submit(device, command_pool, queue, |cmd| {
for target in &targets {
transition_image_layout(
device,
cmd,
target.image,
vk::ImageLayout::UNDEFINED,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
vk::ImageAspectFlags::COLOR,
);
}
})?;
Ok((color, depth, targets))
}
struct PlanarFramebufferInputs<'a> {
main_render_pass: vk::RenderPass,
sample_count: vk::SampleCountFlags,
color: Option<&'a GpuImage>,
depth: &'a GpuImage,
targets: &'a [GpuImage],
width: u32,
height: u32,
}
fn create_framebuffers(
device: &VkDevice,
inputs: PlanarFramebufferInputs<'_>,
) -> RenderResult<Vec<OwnedFramebuffer>> {
let PlanarFramebufferInputs {
main_render_pass,
sample_count,
color,
depth,
targets,
width,
height,
} = inputs;
let msaa = sample_count != vk::SampleCountFlags::TYPE_1;
let mut out = Vec::with_capacity(targets.len());
for target in targets {
let attachments: Vec<vk::ImageView> = if msaa {
vec![
color
.expect("a multisampled planar target has a color image")
.view,
depth.view,
target.view,
]
} else {
vec![target.view, depth.view]
};
let info = vk::FramebufferCreateInfo::default()
.render_pass(main_render_pass)
.attachments(&attachments)
.width(width.max(1))
.height(height.max(1))
.layers(1);
let fb = device
.create_framebuffer(&info)
.map_err(|e| super::error::map_vk_result(e, "planar framebuffer"))?;
out.push(fb);
}
Ok(out)
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct PlanarConfig {
pub(in crate::vulkan) frames: usize,
pub(in crate::vulkan) sample_count: vk::SampleCountFlags,
pub(in crate::vulkan) width: u32,
pub(in crate::vulkan) height: u32,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct PlanarGlobalSet<'a> {
pub(in crate::vulkan) layout: vk::DescriptorSetLayout,
pub(in crate::vulkan) bindings: GlobalBindings<'a>,
}
#[derive(Clone, Copy)]
struct MirrorBuffers {
view: vk::Buffer,
cluster_params: vk::Buffer,
cluster_lists: vk::Buffer,
}
fn global_contents(
bindings: &GlobalBindings<'_>,
mirror: MirrorBuffers,
frame: usize,
) -> GlobalSetContents {
bindings
.off_camera(
mirror.view,
bindings.uniforms.light_ubo_buffers[frame].buffer(),
bindings.shadow.ubos[frame].buffer(),
)
.with_clusters(mirror.cluster_params, mirror.cluster_lists)
}
impl PlanarReflectionSet {
pub(in crate::vulkan) fn new(
gpu: PlanarDevice<'_>,
config: PlanarConfig,
reflectors: PlanarReflectors,
main_render_pass: &OwnedRenderPass,
globals: PlanarGlobalSet<'_>,
cull: PlanarCullSources<'_>,
) -> RenderResult<Self> {
let PlanarDevice { alloc, device, .. } = gpu;
let PlanarConfig {
frames,
sample_count,
width: render_w,
height: render_h,
} = config;
let (width, height) = reflectors.target_size(render_w, render_h);
let plane_count = reflectors.planes().len();
let (color, depth, targets) = create_targets(
gpu,
PlanarTargetDims {
sample_count,
width,
height,
plane_count,
},
)?;
let framebuffers = create_framebuffers(
device,
PlanarFramebufferInputs {
main_render_pass: main_render_pass.handle(),
sample_count,
color: color.as_ref(),
depth: &depth,
targets: &targets,
width,
height,
},
)?;
let host = vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT;
let view_size = std::mem::size_of::<ViewUniforms>() as u64;
let ring = plane_count * frames;
let mut view_bufs = Vec::with_capacity(ring);
for _ in 0..ring {
view_bufs.push(alloc.create_buffer(
view_size,
vk::BufferUsageFlags::UNIFORM_BUFFER,
host,
)?);
}
let cluster_params_size = std::mem::size_of::<ClusterParams>() as u64;
let mut cluster_params_bufs = Vec::with_capacity(ring);
for _ in 0..ring {
cluster_params_bufs.push(alloc.create_buffer(
cluster_params_size,
vk::BufferUsageFlags::UNIFORM_BUFFER,
host,
)?);
}
let mut cluster_lists = Vec::with_capacity(plane_count);
for _ in 0..plane_count {
cluster_lists.push(alloc.create_buffer(
cluster_list_size(),
vk::BufferUsageFlags::STORAGE_BUFFER,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)?);
}
use concinnity_core::gfx::render_types::{GpuDrawArgs, GpuObjectData};
let object_range = (cull.cull_count * std::mem::size_of::<GpuObjectData>()).max(4) as u64;
let args_range = (cull.cull_count * std::mem::size_of::<GpuDrawArgs>()).max(4) as u64;
let indirect_size =
(cull.cull_count * std::mem::size_of::<vk::DrawIndexedIndirectCommand>()).max(4) as u64;
let status_size = (cull.cull_count * std::mem::size_of::<u32>()).max(4) as u64;
let mut cull_indirect_bufs = Vec::with_capacity(ring);
let mut cull_status_bufs = Vec::with_capacity(ring);
for _ in 0..ring {
cull_indirect_bufs.push(alloc.create_buffer(
indirect_size,
vk::BufferUsageFlags::STORAGE_BUFFER | vk::BufferUsageFlags::INDIRECT_BUFFER,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)?);
cull_status_bufs.push(alloc.create_buffer(
status_size,
vk::BufferUsageFlags::STORAGE_BUFFER,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)?);
}
let has_hiz = u32::from(cull.hiz.is_some());
let ring_sets = ring as u32;
let pool_sizes = PoolSizes::default()
.sets(&global_set(), ring_sets)
.sets(&light_cull_set_bindings(), ring_sets)
.add(vk::DescriptorType::STORAGE_BUFFER, ring_sets * 4)
.add(vk::DescriptorType::UNIFORM_BUFFER, has_hiz)
.add(vk::DescriptorType::SAMPLED_IMAGE, has_hiz)
.build();
let pool_info = vk::DescriptorPoolCreateInfo::default()
.pool_sizes(&pool_sizes)
.max_sets(ring_sets * 3 + has_hiz);
let pool = device
.create_descriptor_pool(&pool_info)
.map_err(|e| super::error::map_vk_result(e, "planar descriptor pool"))?;
let PlanarGlobalSet { layout, bindings } = globals;
let mirror = |i: usize| MirrorBuffers {
view: view_bufs[i].buffer(),
cluster_params: cluster_params_bufs[i].buffer(),
cluster_lists: cluster_lists[i / frames].buffer(),
};
let global_sets = alloc_descriptor_sets(device, pool.handle(), &vec![layout; ring])?;
for (i, &set) in global_sets.iter().enumerate() {
global_contents(&bindings, mirror(i), i % frames).write(device, set);
}
let cluster_layouts = vec![bindings.light_cull.set_layout.handle(); ring];
let cluster_sets = alloc_descriptor_sets(device, pool.handle(), &cluster_layouts)?;
for (i, &set) in cluster_sets.iter().enumerate() {
write_light_cull_set(
device,
set,
&LightCullInputs {
params: cluster_params_bufs[i].buffer(),
lights: bindings.uniforms.local_light_buffer.buffer(),
lists: cluster_lists[i / frames].buffer(),
probe_records: Some(bindings.probes.stand_in_records.buffer()),
},
);
}
let cull_layouts: Vec<_> = (0..ring).map(|_| cull.cull_set_layout).collect();
let cull_sets = alloc_descriptor_sets(device, pool.handle(), &cull_layouts)?;
for (i, &set) in cull_sets.iter().enumerate() {
let frame = i % frames;
SetWrites::new(set)
.storage_buffer(0, cull.frame_object_buffers[frame].buffer(), object_range)
.storage_buffer(1, cull.frame_draw_args_buffers[frame].buffer(), args_range)
.storage_buffer(2, cull_indirect_bufs[i].buffer(), indirect_size)
.storage_buffer(3, cull_status_bufs[i].buffer(), status_size)
.apply(device);
}
let (hiz_set, hiz_ubo) = match cull.hiz {
Some((layout, view)) => {
let (set, ubo) =
super::hiz::off_camera_read_set(alloc, device, pool.handle(), layout, view)?;
(Some(set), Some(ubo))
}
None => (None, None),
};
Ok(Self {
layout: reflectors,
frames,
sample_count,
width,
height,
main_render_pass: main_render_pass.handle(),
color,
depth,
targets,
framebuffers,
view_bufs,
global_sets,
cluster_params_bufs,
cluster_lists,
cluster_sets,
cull_indirect_bufs,
cull_status_bufs,
cull_sets,
hiz_set,
hiz_ubo,
_pool: pool,
})
}
pub(in crate::vulkan) fn plane_count(&self) -> usize {
self.layout.planes().len()
}
pub(in crate::vulkan) fn frame_plan(
&self,
view_proj: [[f32; 4]; 4],
) -> planar_reflection::PlanarFramePlan {
self.layout.frame_plan(view_proj)
}
pub(in crate::vulkan) fn rewrite_global_binding(
&self,
device: &VkDevice,
bindings: &GlobalBindings<'_>,
binding: u32,
) {
for (i, &set) in self.global_sets.iter().enumerate() {
global_contents(bindings, self.mirror_buffers(i), i % self.frames)
.write_binding(device, set, binding);
}
}
fn mirror_buffers(&self, i: usize) -> MirrorBuffers {
MirrorBuffers {
view: self.view_bufs[i].buffer(),
cluster_params: self.cluster_params_bufs[i].buffer(),
cluster_lists: self.cluster_lists[i / self.frames].buffer(),
}
}
pub(in crate::vulkan) fn target_view(&self, slot: usize) -> vk::ImageView {
self.targets[slot].view
}
pub(in crate::vulkan) fn rewrite_hiz_view(&self, device: &VkDevice, view: vk::ImageView) {
if let Some(set) = self.hiz_set {
super::hiz::rewrite_read_set_view(device, set, view);
}
}
pub(in crate::vulkan) fn rebuild(
&mut self,
gpu: PlanarDevice<'_>,
render_w: u32,
render_h: u32,
) -> RenderResult<()> {
let device = gpu.device;
let (width, height) = self.layout.target_size(render_w, render_h);
let (color, depth, targets) = create_targets(
gpu,
PlanarTargetDims {
sample_count: self.sample_count,
width,
height,
plane_count: self.layout.planes().len(),
},
)?;
let framebuffers = create_framebuffers(
device,
PlanarFramebufferInputs {
main_render_pass: self.main_render_pass,
sample_count: self.sample_count,
color: color.as_ref(),
depth: &depth,
targets: &targets,
width,
height,
},
)?;
self.color = color;
self.depth = depth;
self.targets = targets;
self.framebuffers = framebuffers;
self.width = width;
self.height = height;
Ok(())
}
pub(in crate::vulkan) fn destroy(&mut self, _device: &VkDevice) {
self.color = None;
self.depth = GpuImage::null();
self.framebuffers.clear();
self.targets.clear();
self.view_bufs.clear();
self.cluster_params_bufs.clear();
self.cluster_lists.clear();
self.cluster_sets.clear();
self.cull_indirect_bufs.clear();
self.cull_status_bufs.clear();
self.hiz_ubo = None;
self.global_sets.clear();
self.cull_sets.clear();
}
}
impl VkContext {
pub(in crate::vulkan) fn encode_planar_reflections(
&self,
cmd: vk::CommandBuffer,
params: &GraphFrameParams<'_>,
) -> RenderResult<()> {
let Some(set) = self.planar_reflection.as_ref() else {
return Ok(());
};
let Some(&bindless_set) = self.cull.bindless_sets.get(params.frame_idx) else {
return Ok(());
};
let crops =
params
.planar
.crops(set.plane_count(), set.width, set.height, PLANAR_CROP_MARGIN);
let crops = crops.as_slice();
if crops.is_empty() {
return Ok(());
}
let proj = mat4_mul(params.vp_mat, mat4_inverse(self.state.view.matrix));
let prefilter_mip_count = self.scene.prefilter_mip_count as f32;
let extent = vk::Extent2D {
width: set.width,
height: set.height,
};
for &(slot, crop) in crops {
let oriented =
planar_reflection::orient_plane_toward(set.layout.planes()[slot], params.cam_pos);
let m = planar_reflection::planar_matrices(
self.state.view.matrix,
proj,
params.cam_pos,
oriented,
PLANAR_CLIP_BIAS,
);
let view = ViewUniforms {
vp: m.view_proj,
view: m.view,
elapsed: params.elapsed,
reflections_enabled: 0.0,
cam_pos: [m.eye[0], m.eye[1], m.eye[2]],
prefilter_mip_count,
shade_mode: 0.0,
ambient_occlusion: 0.0,
sky_rot: self.state.view.sky_rot,
};
let ring = slot * set.frames + params.frame_idx;
set.view_bufs[ring].write_val(0, &view);
let frustum = Frustum::from_camera(
crop.crop_view_projection(m.view_proj, set.width, set.height),
self.state.view.view_distance,
);
self.encode_probe_cull(cmd, set.cull_sets[ring], set.hiz_set, &frustum, m.eye);
let cluster_params = ClusterParams {
num_probes: 0,
..params.cluster_params.with_camera(&m.cluster_camera(
params.cluster_params.z_near,
self.uniforms.cluster_reach.range(
m.eye,
params.cluster_params.z_near,
&[],
self.state.view.view_distance,
),
set.width,
set.height,
))
};
set.cluster_params_bufs[ring].write_val(0, &cluster_params);
if cluster_params.use_clusters != 0 {
self.encode_mirror_light_cull(cmd, set, slot, ring);
}
let attachment_waw = vk::MemoryBarrier::default()
.src_access_mask(
vk::AccessFlags::COLOR_ATTACHMENT_WRITE
| vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE,
)
.dst_access_mask(
vk::AccessFlags::COLOR_ATTACHMENT_WRITE
| vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE,
);
unsafe {
self.hw.device.cmd_pipeline_barrier(
cmd,
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
| vk::PipelineStageFlags::LATE_FRAGMENT_TESTS,
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
| vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS,
vk::DependencyFlags::empty(),
std::slice::from_ref(&attachment_waw),
&[],
&[],
);
}
self.encode_main_into_face(
cmd,
set.framebuffers[slot].handle(),
FaceArea {
extent,
render_area: vk::Rect2D {
offset: vk::Offset2D {
x: crop.x as i32,
y: crop.y as i32,
},
extent: vk::Extent2D {
width: crop.width,
height: crop.height,
},
},
},
set.global_sets[ring],
bindless_set,
set.cull_indirect_bufs[ring].buffer(),
);
}
let mut barriers = [vk::ImageMemoryBarrier::default(); MAX_PLANAR_PLANES];
debug_assert!(crops.len() <= MAX_PLANAR_PLANES);
let n = crops.len().min(MAX_PLANAR_PLANES);
for (barrier, &(slot, _)) in barriers.iter_mut().zip(crops) {
*barrier = vk::ImageMemoryBarrier::default()
.src_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE)
.dst_access_mask(vk::AccessFlags::SHADER_READ)
.old_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.image(set.targets[slot].image)
.subresource_range(vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
});
}
unsafe {
self.hw.device.cmd_pipeline_barrier(
cmd,
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
vk::PipelineStageFlags::FRAGMENT_SHADER,
vk::DependencyFlags::empty(),
&[],
&[],
&barriers[..n],
);
}
Ok(())
}
}
impl VkContext {
fn encode_mirror_light_cull(
&self,
cmd: vk::CommandBuffer,
set: &PlanarReflectionSet,
slot: usize,
ring: usize,
) {
let lists = set.cluster_lists[slot].buffer();
let barrier = |src: vk::AccessFlags, dst: vk::AccessFlags| {
vk::BufferMemoryBarrier::default()
.src_access_mask(src)
.dst_access_mask(dst)
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.buffer(lists)
.offset(0)
.size(vk::WHOLE_SIZE)
};
let rec = unsafe { Recorder::assume_recording(&self.hw.device, cmd) };
rec.pipeline_barrier(
vk::PipelineStageFlags::FRAGMENT_SHADER,
vk::PipelineStageFlags::COMPUTE_SHADER,
&[],
&[barrier(
vk::AccessFlags::SHADER_READ,
vk::AccessFlags::SHADER_WRITE,
)],
&[],
);
self.bin_clusters(&rec, set.cluster_sets[ring]);
rec.pipeline_barrier(
vk::PipelineStageFlags::COMPUTE_SHADER,
vk::PipelineStageFlags::FRAGMENT_SHADER,
&[],
&[barrier(
vk::AccessFlags::SHADER_WRITE,
vk::AccessFlags::SHADER_READ,
)],
&[],
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn planar_capacity_is_four() {
assert_eq!(MAX_PLANAR_PLANES, 4);
assert_eq!(MAX_PLANAR_PLANES, planar_reflection::MAX_PLANAR_PLANES);
}
}