use ash::vk;
use concinnity_core::gfx::frustum::Frustum;
use concinnity_core::render::error::RenderResult;
use concinnity_core::render::planar_reflection;
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::resources::{alloc_descriptor_sets, write_storage_buffer};
use super::texture::{GpuImage, ImageSpec, create_image, create_image_view};
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;
pub(in crate::vulkan) fn pane_plane(normal: [f32; 3], center: [f32; 3]) -> [f32; 4] {
[
normal[0],
normal[1],
normal[2],
-(normal[0] * center[0] + normal[1] * center[1] + normal[2] * center[2]),
]
}
pub(in crate::vulkan) struct PlanarReflectionSet {
planes: Vec<[f32; 4]>,
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>,
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,
}
#[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 } = 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));
}
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>,
}
fn global_contents(
bindings: &GlobalBindings<'_>,
view: vk::Buffer,
frame: usize,
) -> GlobalSetContents {
bindings.off_camera(
view,
bindings.uniforms.light_ubo_buffers[frame].buffer(),
bindings.shadow.ubos[frame].buffer(),
)
}
impl PlanarReflectionSet {
pub(in crate::vulkan) fn new(
gpu: PlanarDevice<'_>,
config: PlanarConfig,
planes: &[[f32; 4]],
main_render_pass: &OwnedRenderPass,
globals: PlanarGlobalSet<'_>,
cull: PlanarCullSources<'_>,
) -> RenderResult<Self> {
let PlanarDevice { alloc, device } = gpu;
let PlanarConfig {
frames,
sample_count,
width,
height,
} = config;
let plane_count = 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,
)?);
}
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)
.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 * 2 + 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 global_sets = alloc_descriptor_sets(device, pool.handle(), &vec![layout; ring])?;
for (i, &set) in global_sets.iter().enumerate() {
global_contents(&bindings, view_bufs[i].buffer(), i % frames).write(device, set);
}
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;
write_storage_buffer(
device,
set,
0,
cull.frame_object_buffers[frame].buffer(),
object_range,
);
write_storage_buffer(
device,
set,
1,
cull.frame_draw_args_buffers[frame].buffer(),
args_range,
);
write_storage_buffer(
device,
set,
2,
cull_indirect_bufs[i].buffer(),
indirect_size,
);
write_storage_buffer(device, set, 3, cull_status_bufs[i].buffer(), status_size);
}
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 {
planes: planes.to_vec(),
frames,
sample_count,
width,
height,
main_render_pass: main_render_pass.handle(),
color,
depth,
targets,
framebuffers,
view_bufs,
global_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.planes.len()
}
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.view_bufs[i].buffer(), i % self.frames)
.write_binding(device, set, binding);
}
}
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,
alloc: &DeviceAllocator,
device: &VkDevice,
width: u32,
height: u32,
) -> RenderResult<()> {
let (color, depth, targets) = create_targets(
PlanarDevice { alloc, device },
PlanarTargetDims {
sample_count: self.sample_count,
width,
height,
plane_count: self.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.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,
frame_idx: usize,
vp_mat: [[f32; 4]; 4],
cam_pos: [f32; 3],
elapsed: f32,
) -> RenderResult<()> {
let Some(set) = self.planar_reflection.as_ref() else {
return Ok(());
};
let Some(&bindless_set) = self.cull.bindless_sets.get(frame_idx) else {
return Ok(());
};
let proj = mat4_mul(vp_mat, mat4_inverse(self.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 in 0..set.plane_count() {
let oriented = planar_reflection::orient_plane_toward(set.planes[slot], cam_pos);
let m = planar_reflection::planar_matrices(
self.view.matrix,
proj,
cam_pos,
oriented,
PLANAR_CLIP_BIAS,
);
let view = ViewUniforms {
vp: m.view_proj,
view: m.view,
elapsed,
reflections_enabled: 0.0,
cam_pos: [m.eye[0], m.eye[1], m.eye[2]],
prefilter_mip_count,
shade_mode: 0.0,
_end_pad: 0.0,
sky_rot: self.view.sky_rot,
};
let ring = slot * set.frames + frame_idx;
set.view_bufs[ring].write_val(0, &view);
let frustum = Frustum::from_view_projection(m.view_proj);
self.encode_probe_cull(cmd, set.cull_sets[ring], set.hiz_set, &frustum, m.eye);
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(),
extent,
set.global_sets[ring],
bindless_set,
set.cull_indirect_bufs[ring].buffer(),
);
}
let mut barriers = [vk::ImageMemoryBarrier::default(); MAX_PLANAR_PLANES];
debug_assert!(set.targets.len() <= MAX_PLANAR_PLANES);
let n = set.targets.len().min(MAX_PLANAR_PLANES);
for (slot, t) in set.targets.iter().take(n).enumerate() {
barriers[slot] = 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(t.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(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pane_plane_passes_through_center_with_unit_normal() {
let p = pane_plane([0.0, 0.0, 1.0], [1.0, 2.0, 3.0]);
assert_eq!([p[0], p[1], p[2]], [0.0, 0.0, 1.0]);
let signed = p[0] * 1.0 + p[1] * 2.0 + p[2] * 3.0 + p[3];
assert!(signed.abs() < 1e-5, "center lies on the plane");
}
#[test]
fn pane_plane_offset_is_negative_normal_dot_center() {
let n = [0.6, 0.0, 0.8];
let c = [2.0, 5.0, -1.0];
let p = pane_plane(n, c);
let expect_d = -(n[0] * c[0] + n[1] * c[1] + n[2] * c[2]);
assert!((p[3] - expect_d).abs() < 1e-5);
}
#[test]
fn planar_capacity_is_four() {
assert_eq!(MAX_PLANAR_PLANES, 4);
assert_eq!(MAX_PLANAR_PLANES, planar_reflection::MAX_PLANAR_PLANES);
}
}