use ash::vk;
use concinnity_core::gfx::transform::mat4_inverse;
use crate::vulkan::owned::{
OwnedDescriptorPool, OwnedFramebuffer, OwnedPipeline, OwnedPipelineLayout, OwnedRenderPass,
OwnedSetLayout, VkDevice,
};
use super::allocator::{DeviceAllocator, PooledBuffer};
use crate::components::{GlassPanel, WaterSurface};
use crate::gfx::mesh_payload::Vertex;
use crate::gfx::render_types::RtParams;
use crate::gfx::rt_reflections::RtParamsInputs;
use super::context::{HDR_FORMAT, VkContext};
use super::pipeline::spv_module;
use super::resources::{alloc_descriptor_sets, create_descriptor_set_layout};
use super::texture::{
GpuImage, ImageSpec, LayoutTransition, SubresourceRange, create_image, create_image_view,
one_shot_submit, transition_image_layout_range,
};
use concinnity_render::uniforms::GlassMeshParams;
pub(in crate::vulkan) use concinnity_render::uniforms::TransparentView;
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct TransparentRtInputs {
pub tlas: vk::AccelerationStructureKHR,
pub geom_buffer: vk::Buffer,
pub geom_size: vk::DeviceSize,
pub deformed_verts: vk::Buffer,
pub skinned_indices: vk::Buffer,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct TransparentRtDynamic {
pub tlas: vk::AccelerationStructureKHR,
pub geom_buffer: vk::Buffer,
pub geom_size: vk::DeviceSize,
pub deformed: vk::Buffer,
pub skinned_indices: vk::Buffer,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(in crate::vulkan) enum Producer {
Glass,
Water,
GlassMesh,
}
pub(in crate::vulkan) struct TransparentRecord {
vertex_buffer: PooledBuffer,
index_buffer: PooledBuffer,
index_count: u32,
params_ubo: PooledBuffer,
params_size: u64,
params_set: vk::DescriptorSet,
visible: bool,
centre: [f32; 3],
planar_slot: Option<usize>,
}
pub(in crate::vulkan) struct RecordUpload<'a> {
pub vertices: &'a [Vertex],
pub indices: &'a [u16],
pub params: &'a [u8],
pub visible: bool,
pub centre: [f32; 3],
pub planar_slot: Option<usize>,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct RecordDescriptors<'a> {
pub device: &'a VkDevice,
pub pool: vk::DescriptorPool,
pub params_set_layout: vk::DescriptorSetLayout,
pub planar_view: vk::ImageView,
pub sampler: vk::Sampler,
}
impl TransparentRecord {
pub(in crate::vulkan) fn upload(
alloc: &DeviceAllocator,
descriptors: RecordDescriptors,
upload: RecordUpload<'_>,
) -> Result<Self, String> {
let host = vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT;
let vb_bytes = std::mem::size_of_val(upload.vertices) as u64;
let ib_bytes = std::mem::size_of_val(upload.indices) as u64;
let vertex_buffer =
alloc.create_buffer(vb_bytes, vk::BufferUsageFlags::VERTEX_BUFFER, host)?;
let index_buffer =
alloc.create_buffer(ib_bytes, vk::BufferUsageFlags::INDEX_BUFFER, host)?;
vertex_buffer.write_slice(0, upload.vertices);
index_buffer.write_slice(0, upload.indices);
let params_ubo = alloc.create_buffer(
upload.params.len() as u64,
vk::BufferUsageFlags::UNIFORM_BUFFER,
host,
)?;
params_ubo.write_slice(0, upload.params);
let params_set = alloc_descriptor_sets(
descriptors.device,
descriptors.pool,
&[descriptors.params_set_layout],
)?[0];
write_params_set(
descriptors.device,
params_set,
params_ubo.buffer(),
upload.params.len() as u64,
descriptors.planar_view,
descriptors.sampler,
);
Ok(Self {
vertex_buffer,
index_buffer,
index_count: upload.indices.len() as u32,
params_ubo,
params_size: upload.params.len() as u64,
params_set,
visible: upload.visible,
centre: upload.centre,
planar_slot: upload.planar_slot,
})
}
}
pub(in crate::vulkan) struct TransparentProducer {
pub pipeline: OwnedPipeline,
pub flat_rt_pso: Option<OwnedPipeline>,
pub textured_rt_pso: Option<OwnedPipeline>,
pub records: Vec<TransparentRecord>,
}
impl TransparentProducer {
fn pipeline(&self, rt_live: bool, textured: bool) -> &OwnedPipeline {
match (rt_live, textured) {
(true, true) => self
.textured_rt_pso
.as_ref()
.expect("rt_textured_ready gated the frame on every producer's textured pipeline"),
(true, false) => self
.flat_rt_pso
.as_ref()
.expect("rt_pipelines_ready gated the frame on every producer's flat RT pipeline"),
_ => &self.pipeline,
}
}
}
pub(in crate::vulkan) struct GlassMeshProducer {
pipeline_flat: OwnedPipeline,
pipeline_textured: Option<OwnedPipeline>,
object_indices: Vec<usize>,
params_buffers: Vec<PooledBuffer>,
params_stride: u64,
params_sets: Vec<vk::DescriptorSet>,
_pool: OwnedDescriptorPool,
}
struct GlassMeshDraw {
index_offset: u32,
index_count: u32,
base_vertex: i32,
params_set: vk::DescriptorSet,
centre: [f32; 3],
}
impl GlassMeshProducer {
pub(in crate::vulkan) fn new(
ctx: &ProducerCtx,
pipeline_flat: OwnedPipeline,
pipeline_textured: Option<OwnedPipeline>,
object_indices: Vec<usize>,
) -> Result<Self, String> {
let device = ctx.device;
let count = object_indices.len();
let frames = ctx.frames;
let params_stride = align_up(
std::mem::size_of::<GlassMeshParams>() as u64,
ctx.ubo_offset_alignment,
);
let mut params_buffers = Vec::with_capacity(frames);
for _ in 0..frames {
params_buffers.push(ctx.alloc.create_buffer(
params_stride * count.max(1) as u64,
vk::BufferUsageFlags::UNIFORM_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?);
}
let sets_needed = (frames * count) as u32;
let sizes = [
vk::DescriptorPoolSize {
ty: vk::DescriptorType::UNIFORM_BUFFER,
descriptor_count: sets_needed,
},
vk::DescriptorPoolSize {
ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER,
descriptor_count: sets_needed,
},
];
let pool = device
.create_descriptor_pool(
&vk::DescriptorPoolCreateInfo::default()
.max_sets(sets_needed.max(1))
.pool_sizes(&sizes),
)
.map_err(|e| format!("glass mesh descriptor pool: {e}"))?;
let layouts: Vec<_> = (0..frames * count).map(|_| ctx.params_set_layout).collect();
let params_sets = alloc_descriptor_sets(device, pool.handle(), &layouts)?;
for frame in 0..frames {
for slot in 0..count {
write_params_set_at(
device,
params_sets[frame * count + slot],
params_buffers[frame].buffer(),
slot as u64 * params_stride,
std::mem::size_of::<GlassMeshParams>() as u64,
ctx.snapshot_view,
ctx.sampler,
);
}
}
Ok(Self {
pipeline_flat,
pipeline_textured,
object_indices,
params_buffers,
params_stride,
params_sets,
_pool: pool,
})
}
fn pipeline(&self, textured: bool) -> &OwnedPipeline {
match textured {
true => self
.pipeline_textured
.as_ref()
.expect("rt_textured_ready gated the frame on every producer's textured pipeline"),
false => &self.pipeline_flat,
}
}
}
struct TransparentRt {
_set_layout: OwnedSetLayout,
layout_flat: OwnedPipelineLayout,
layout_textured: Option<OwnedPipelineLayout>,
params_buffers: Vec<PooledBuffer>,
sets: Vec<vk::DescriptorSet>,
_pool: OwnedDescriptorPool,
dummy_ssbo: PooledBuffer,
}
pub(in crate::vulkan) struct TransparentResources {
render_pass: OwnedRenderPass,
pipeline_layout: OwnedPipelineLayout,
_view_set_layout: OwnedSetLayout,
_params_set_layout: OwnedSetLayout,
_descriptor_pool: OwnedDescriptorPool,
view_ubos: Vec<PooledBuffer>,
view_sets: Vec<vk::DescriptorSet>,
scene_images: Vec<vk::Image>,
framebuffers: Vec<OwnedFramebuffer>,
snapshot: GpuImage,
sampler: vk::Sampler,
glass: Option<TransparentProducer>,
water: Option<TransparentProducer>,
glass_mesh: Option<GlassMeshProducer>,
rt: Option<TransparentRt>,
}
fn align_up(size: u64, align: u64) -> u64 {
let align = align.max(1);
size.div_ceil(align) * align
}
fn sort_distance(centre: [f32; 3], cam: [f32; 3]) -> f32 {
let dx = centre[0] - cam[0];
let dy = centre[1] - cam[1];
let dz = centre[2] - cam[2];
(dx * dx + dy * dy + dz * dz).sqrt()
}
fn ordered_visible(
glass: &[([f32; 3], bool)],
water: &[([f32; 3], bool)],
meshes: &[[f32; 3]],
cam: [f32; 3],
) -> Vec<(Producer, usize)> {
let live_of = |records: &[([f32; 3], bool)], kind: Producer| -> Vec<(Producer, usize)> {
records
.iter()
.enumerate()
.filter(|(_, (_, vis))| *vis)
.map(|(i, _)| (kind, i))
.collect()
};
let live: Vec<(Producer, usize)> = live_of(glass, Producer::Glass)
.into_iter()
.chain(live_of(water, Producer::Water))
.chain((0..meshes.len()).map(|i| (Producer::GlassMesh, i)))
.collect();
let dists: Vec<f32> = live
.iter()
.map(|&(kind, i)| {
let centre = match kind {
Producer::Glass => glass[i].0,
Producer::Water => water[i].0,
Producer::GlassMesh => meshes[i],
};
sort_distance(centre, cam)
})
.collect();
crate::gfx::transparent::back_to_front_order(&dists)
.into_iter()
.map(|oi| live[oi])
.collect()
}
fn create_rt_set_layout(device: &VkDevice) -> Result<OwnedSetLayout, String> {
let frag = vk::ShaderStageFlags::FRAGMENT;
create_descriptor_set_layout(
device,
&[
(0, vk::DescriptorType::UNIFORM_BUFFER, frag),
(1, vk::DescriptorType::ACCELERATION_STRUCTURE_KHR, frag),
(2, vk::DescriptorType::STORAGE_BUFFER, frag),
(3, vk::DescriptorType::STORAGE_BUFFER, frag),
(4, vk::DescriptorType::STORAGE_BUFFER, frag),
(5, vk::DescriptorType::STORAGE_BUFFER, frag),
(6, vk::DescriptorType::STORAGE_BUFFER, frag),
],
)
}
impl TransparentRt {
fn wire_static(&self, device: &VkDevice, vertex_buffer: vk::Buffer, index_buffer: vk::Buffer) {
for (i, &set) in self.sets.iter().enumerate() {
let ubo_info = vk::DescriptorBufferInfo::default()
.buffer(self.params_buffers[i].buffer())
.offset(0)
.range(std::mem::size_of::<RtParams>() as vk::DeviceSize);
let writes = [vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.buffer_info(std::slice::from_ref(&ubo_info))];
unsafe { device.update_descriptor_sets(&writes, &[]) };
}
self.rewire_geometry(device, vertex_buffer, index_buffer);
}
fn rewire_geometry(
&self,
device: &VkDevice,
vertex_buffer: vk::Buffer,
index_buffer: vk::Buffer,
) {
let verts_info = vk::DescriptorBufferInfo::default()
.buffer(vertex_buffer)
.offset(0)
.range(vk::WHOLE_SIZE);
let indices_info = vk::DescriptorBufferInfo::default()
.buffer(index_buffer)
.offset(0)
.range(vk::WHOLE_SIZE);
for &set in &self.sets {
let writes = [
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(3)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(std::slice::from_ref(&verts_info)),
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(4)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(std::slice::from_ref(&indices_info)),
];
unsafe { device.update_descriptor_sets(&writes, &[]) };
}
}
fn wire_dynamic(&self, device: &VkDevice, frame_idx: usize, dynamic: TransparentRtDynamic) {
let TransparentRtDynamic {
tlas,
geom_buffer,
geom_size,
deformed,
skinned_indices,
} = dynamic;
let set = self.sets[frame_idx];
let accels = [tlas];
let mut accel_write = vk::WriteDescriptorSetAccelerationStructureKHR::default()
.acceleration_structures(&accels);
let mut tlas_write = vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(1)
.descriptor_type(vk::DescriptorType::ACCELERATION_STRUCTURE_KHR)
.push_next(&mut accel_write);
tlas_write.descriptor_count = 1;
let geom_info = vk::DescriptorBufferInfo::default()
.buffer(geom_buffer)
.offset(0)
.range(geom_size);
let geom_write = vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(2)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(std::slice::from_ref(&geom_info));
let deformed_info = vk::DescriptorBufferInfo::default()
.buffer(deformed)
.offset(0)
.range(vk::WHOLE_SIZE);
let deformed_write = vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(5)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(std::slice::from_ref(&deformed_info));
let sidx_buffer = if skinned_indices != vk::Buffer::null() {
skinned_indices
} else {
self.dummy_ssbo.buffer()
};
let sidx_info = vk::DescriptorBufferInfo::default()
.buffer(sidx_buffer)
.offset(0)
.range(vk::WHOLE_SIZE);
let sidx_write = vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(6)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(std::slice::from_ref(&sidx_info));
unsafe {
device
.update_descriptor_sets(&[tlas_write, geom_write, deformed_write, sidx_write], &[])
};
}
fn destroy(&mut self, _device: &VkDevice) {
self.params_buffers.clear();
self.dummy_ssbo = PooledBuffer::null();
}
}
#[derive(Clone, Copy)]
struct RtSetLayouts {
view: vk::DescriptorSetLayout,
params: vk::DescriptorSetLayout,
global: vk::DescriptorSetLayout,
bindless: Option<vk::DescriptorSetLayout>,
}
fn build_transparent_rt(
alloc: &DeviceAllocator,
instance: &ash::Instance,
device: &VkDevice,
physical_device: vk::PhysicalDevice,
frames: usize,
layouts: RtSetLayouts,
geometry: TransparentRtGeometry,
) -> Result<TransparentRt, String> {
let set_layout = create_rt_set_layout(device)?;
let flat_layouts = [
layouts.view,
layouts.params,
layouts.global,
set_layout.handle(),
];
let layout_flat = device
.create_pipeline_layout(&vk::PipelineLayoutCreateInfo::default().set_layouts(&flat_layouts))
.map_err(|e| format!("transparent rt flat pipeline layout: {e}"))?;
let max_bound_sets = unsafe { instance.get_physical_device_properties(physical_device) }
.limits
.max_bound_descriptor_sets;
let layout_textured = match layouts.bindless {
Some(bsl) if max_bound_sets >= 5 => {
let set_layouts = [
layouts.view,
layouts.params,
layouts.global,
set_layout.handle(),
bsl,
];
Some(
device
.create_pipeline_layout(
&vk::PipelineLayoutCreateInfo::default().set_layouts(&set_layouts),
)
.map_err(|e| format!("transparent rt textured pipeline layout: {e}"))?,
)
}
_ => None,
};
let params_size = std::mem::size_of::<RtParams>() as vk::DeviceSize;
let mut params_buffers = Vec::with_capacity(frames);
for _ in 0..frames {
params_buffers.push(alloc.create_buffer(
params_size,
vk::BufferUsageFlags::UNIFORM_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?);
}
let f = frames as u32;
let pool_sizes = [
vk::DescriptorPoolSize::default()
.ty(vk::DescriptorType::UNIFORM_BUFFER)
.descriptor_count(f),
vk::DescriptorPoolSize::default()
.ty(vk::DescriptorType::ACCELERATION_STRUCTURE_KHR)
.descriptor_count(f),
vk::DescriptorPoolSize::default()
.ty(vk::DescriptorType::STORAGE_BUFFER)
.descriptor_count(f * 5),
];
let pool = device
.create_descriptor_pool(
&vk::DescriptorPoolCreateInfo::default()
.pool_sizes(&pool_sizes)
.max_sets(f),
)
.map_err(|e| format!("transparent rt descriptor pool: {e}"))?;
let set_handles: Vec<_> = (0..frames).map(|_| set_layout.handle()).collect();
let sets = alloc_descriptor_sets(device, pool.handle(), &set_handles)?;
let dummy_ssbo = alloc.create_buffer(
16,
vk::BufferUsageFlags::STORAGE_BUFFER,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)?;
let rt = TransparentRt {
_set_layout: set_layout,
layout_flat,
layout_textured,
params_buffers,
sets,
_pool: pool,
dummy_ssbo,
};
rt.wire_static(device, geometry.vertex_buffer, geometry.index_buffer);
if let Some(inputs) = geometry.rt_inputs {
for i in 0..frames {
rt.wire_dynamic(
device,
i,
TransparentRtDynamic {
tlas: inputs.tlas,
geom_buffer: inputs.geom_buffer,
geom_size: inputs.geom_size,
deformed: inputs.deformed_verts,
skinned_indices: inputs.skinned_indices,
},
);
}
}
Ok(rt)
}
fn create_transparent_render_pass(
device: &VkDevice,
format: vk::Format,
) -> Result<OwnedRenderPass, String> {
let color = vk::AttachmentDescription::default()
.format(format)
.samples(vk::SampleCountFlags::TYPE_1)
.load_op(vk::AttachmentLoadOp::LOAD)
.store_op(vk::AttachmentStoreOp::STORE)
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.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 dependency = vk::SubpassDependency::default()
.src_subpass(vk::SUBPASS_EXTERNAL)
.dst_subpass(0)
.src_stage_mask(
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT | vk::PipelineStageFlags::TRANSFER,
)
.src_access_mask(vk::AccessFlags::empty())
.dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
.dst_access_mask(
vk::AccessFlags::COLOR_ATTACHMENT_READ | vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
);
let info = vk::RenderPassCreateInfo::default()
.attachments(std::slice::from_ref(&color))
.subpasses(std::slice::from_ref(&subpass))
.dependencies(std::slice::from_ref(&dependency));
device
.create_render_pass(&info)
.map_err(|e| format!("transparent render pass: {e}"))
}
fn create_view_set_layout(device: &VkDevice) -> Result<OwnedSetLayout, String> {
let frag = vk::ShaderStageFlags::FRAGMENT;
let bindings = [
vk::DescriptorSetLayoutBinding::default()
.binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.descriptor_count(1)
.stage_flags(vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT),
vk::DescriptorSetLayoutBinding::default()
.binding(1)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.descriptor_count(1)
.stage_flags(frag),
vk::DescriptorSetLayoutBinding::default()
.binding(2)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.descriptor_count(1)
.stage_flags(frag),
];
let info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings);
device
.create_descriptor_set_layout(&info)
.map_err(|e| format!("transparent view set layout: {e}"))
}
fn create_params_set_layout(device: &VkDevice) -> Result<OwnedSetLayout, String> {
let bindings = [
vk::DescriptorSetLayoutBinding::default()
.binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.descriptor_count(1)
.stage_flags(vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT),
vk::DescriptorSetLayoutBinding::default()
.binding(1)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.descriptor_count(1)
.stage_flags(vk::ShaderStageFlags::FRAGMENT),
];
let info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings);
device
.create_descriptor_set_layout(&info)
.map_err(|e| format!("transparent params set layout: {e}"))
}
fn create_descriptor_pool(
device: &VkDevice,
frames: usize,
records: usize,
) -> Result<OwnedDescriptorPool, String> {
let f = frames as u32;
let r = records as u32;
let sizes = [
vk::DescriptorPoolSize {
ty: vk::DescriptorType::UNIFORM_BUFFER,
descriptor_count: f + r,
},
vk::DescriptorPoolSize {
ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER,
descriptor_count: 2 * f + r,
},
];
let info = vk::DescriptorPoolCreateInfo::default()
.max_sets(f + r)
.pool_sizes(&sizes);
device
.create_descriptor_pool(&info)
.map_err(|e| format!("transparent descriptor pool: {e}"))
}
fn write_view_set(
device: &VkDevice,
set: vk::DescriptorSet,
view_ubo: vk::Buffer,
snapshot_view: vk::ImageView,
depth_view: vk::ImageView,
sampler: vk::Sampler,
) {
let view_info = vk::DescriptorBufferInfo::default()
.buffer(view_ubo)
.offset(0)
.range(std::mem::size_of::<TransparentView>() as u64);
let img = |view: vk::ImageView| {
vk::DescriptorImageInfo::default()
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.image_view(view)
.sampler(sampler)
};
let snapshot_info = img(snapshot_view);
let depth_info = img(depth_view);
let writes = [
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.buffer_info(std::slice::from_ref(&view_info)),
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(1)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.image_info(std::slice::from_ref(&snapshot_info)),
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(2)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.image_info(std::slice::from_ref(&depth_info)),
];
unsafe { device.update_descriptor_sets(&writes, &[]) };
}
fn write_params_set(
device: &VkDevice,
set: vk::DescriptorSet,
params_ubo: vk::Buffer,
params_size: u64,
planar_view: vk::ImageView,
sampler: vk::Sampler,
) {
write_params_set_at(
device,
set,
params_ubo,
0,
params_size,
planar_view,
sampler,
);
}
fn write_params_set_at(
device: &VkDevice,
set: vk::DescriptorSet,
params_ubo: vk::Buffer,
params_offset: u64,
params_size: u64,
planar_view: vk::ImageView,
sampler: vk::Sampler,
) {
let info = vk::DescriptorBufferInfo::default()
.buffer(params_ubo)
.offset(params_offset)
.range(params_size);
let planar_info = vk::DescriptorImageInfo::default()
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.image_view(planar_view)
.sampler(sampler);
let writes = [
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.buffer_info(std::slice::from_ref(&info)),
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(1)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.image_info(std::slice::from_ref(&planar_info)),
];
unsafe { device.update_descriptor_sets(&writes, &[]) };
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(in crate::vulkan) enum TransparentVertexInput {
Position,
PositionAndNormal,
}
pub(in crate::vulkan) fn create_transparent_pipeline(
device: &VkDevice,
render_pass: vk::RenderPass,
layout: vk::PipelineLayout,
vert_spv: &[u8],
frag_spv: &[u8],
vertex_input: TransparentVertexInput,
) -> Result<OwnedPipeline, String> {
let vert = spv_module(device, vert_spv)?;
let frag = spv_module(device, frag_spv)?;
let entry = std::ffi::CString::new("main").unwrap();
let stages = [
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::VERTEX)
.module(vert.handle())
.name(&entry),
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::FRAGMENT)
.module(frag.handle())
.name(&entry),
];
let binding = vk::VertexInputBindingDescription::default()
.binding(0)
.stride(std::mem::size_of::<Vertex>() as u32)
.input_rate(vk::VertexInputRate::VERTEX);
let attr = |location: u32, offset: u32| {
vk::VertexInputAttributeDescription::default()
.location(location)
.binding(0)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(offset)
};
let attributes: &[vk::VertexInputAttributeDescription] = match vertex_input {
TransparentVertexInput::Position => &[attr(0, 0)],
TransparentVertexInput::PositionAndNormal => &[attr(0, 0), attr(1, 12)],
};
let vertex_input_state = vk::PipelineVertexInputStateCreateInfo::default()
.vertex_binding_descriptions(std::slice::from_ref(&binding))
.vertex_attribute_descriptions(attributes);
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)
.cull_mode(vk::CullModeFlags::NONE)
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
.line_width(1.0);
let multisample = vk::PipelineMultisampleStateCreateInfo::default()
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
let depth_stencil = vk::PipelineDepthStencilStateCreateInfo::default()
.depth_test_enable(false)
.depth_write_enable(false);
let blend_attachment = vk::PipelineColorBlendAttachmentState::default()
.blend_enable(true)
.src_color_blend_factor(vk::BlendFactor::SRC_ALPHA)
.dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
.color_blend_op(vk::BlendOp::ADD)
.src_alpha_blend_factor(vk::BlendFactor::SRC_ALPHA)
.dst_alpha_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
.alpha_blend_op(vk::BlendOp::ADD)
.color_write_mask(vk::ColorComponentFlags::RGBA);
let blend_attachments = [blend_attachment];
let blend_state = vk::PipelineColorBlendStateCreateInfo::default()
.logic_op_enable(false)
.attachments(&blend_attachments);
let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
let info = vk::GraphicsPipelineCreateInfo::default()
.stages(&stages)
.vertex_input_state(&vertex_input_state)
.input_assembly_state(&input_assembly)
.viewport_state(&viewport_state)
.rasterization_state(&raster)
.multisample_state(&multisample)
.depth_stencil_state(&depth_stencil)
.color_blend_state(&blend_state)
.dynamic_state(&dynamic)
.layout(layout)
.render_pass(render_pass);
let pipeline = crate::vulkan::pipeline_cache::create_graphics_pipeline(device, &info)
.map_err(|e| format!("create transparent pipeline: {e}"))?;
Ok(pipeline)
}
fn create_snapshot(
alloc: &DeviceAllocator,
device: &VkDevice,
command_pool: vk::CommandPool,
queue: vk::Queue,
width: u32,
height: u32,
) -> Result<GpuImage, String> {
let pooled = create_image(
alloc,
&ImageSpec {
width: width.max(1),
height: height.max(1),
format: HDR_FORMAT,
tiling: vk::ImageTiling::OPTIMAL,
usage: vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::TRANSFER_DST,
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_range(
device,
cmd,
image,
LayoutTransition {
old_layout: vk::ImageLayout::UNDEFINED,
new_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
aspect: vk::ImageAspectFlags::COLOR,
},
SubresourceRange {
base_layer: 0,
layer_count: 1,
base_mip: 0,
mip_count: 1,
},
);
})?;
let view = create_image_view(device, image, HDR_FORMAT, vk::ImageAspectFlags::COLOR)?;
Ok(GpuImage::from_pooled(pooled, view))
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct TransparentDeviceCtx<'a> {
pub alloc: &'a DeviceAllocator,
pub instance: &'a ash::Instance,
pub device: &'a VkDevice,
pub physical_device: vk::PhysicalDevice,
pub command_pool: vk::CommandPool,
pub queue: vk::Queue,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct TransparentBuildConfig {
pub frames: usize,
pub msaa_samples: vk::SampleCountFlags,
pub width: u32,
pub height: u32,
pub global_set_layout: vk::DescriptorSetLayout,
pub probe_cube_count: u32,
pub hot_reload: bool,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct TransparentSceneTargets<'a> {
pub scene_views: &'a [vk::ImageView],
pub scene_images: &'a [vk::Image],
pub depth_views: &'a [vk::ImageView],
pub sampler: vk::Sampler,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct TransparentContent<'a> {
pub glass_panels: &'a [GlassPanel],
pub glass_planar_slots: &'a [Option<usize>],
pub water_surfaces: &'a [WaterSurface],
pub water_planar_slots: &'a [Option<usize>],
pub planar_target_views: &'a [vk::ImageView],
pub seethrough_mesh_indices: &'a [usize],
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct TransparentRtSetup {
pub rt_capable: bool,
pub vertex_buffer: vk::Buffer,
pub index_buffer: vk::Buffer,
pub rt_inputs: Option<TransparentRtInputs>,
pub bindless_set_layout: Option<vk::DescriptorSetLayout>,
pub bindless_pool_size: usize,
}
#[derive(Clone, Copy)]
struct TransparentRtGeometry {
vertex_buffer: vk::Buffer,
index_buffer: vk::Buffer,
rt_inputs: Option<TransparentRtInputs>,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct ProducerCtx<'a> {
pub alloc: &'a DeviceAllocator,
pub device: &'a VkDevice,
pub render_pass: vk::RenderPass,
pub layout: vk::PipelineLayout,
pub rt_layout_flat: Option<vk::PipelineLayout>,
pub rt_layout_textured: Option<vk::PipelineLayout>,
pub pool: vk::DescriptorPool,
pub params_set_layout: vk::DescriptorSetLayout,
pub snapshot_view: vk::ImageView,
pub planar_target_views: &'a [vk::ImageView],
pub sampler: vk::Sampler,
pub msaa: bool,
pub hot_reload: bool,
pub probe_cube_count: u32,
pub bindless_pool_size: usize,
pub frames: usize,
pub ubo_offset_alignment: u64,
}
impl<'a> ProducerCtx<'a> {
pub(in crate::vulkan) fn record_descriptors(
&self,
planar_slot: Option<usize>,
) -> RecordDescriptors<'a> {
RecordDescriptors {
device: self.device,
pool: self.pool,
params_set_layout: self.params_set_layout,
planar_view: planar_slot
.and_then(|s| self.planar_target_views.get(s).copied())
.unwrap_or(self.snapshot_view),
sampler: self.sampler,
}
}
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct TransparentRebuildTargets<'a> {
pub scene_views: &'a [vk::ImageView],
pub scene_images: &'a [vk::Image],
pub depth_views: &'a [vk::ImageView],
pub planar_target_views: &'a [vk::ImageView],
}
impl TransparentResources {
pub(in crate::vulkan) fn new(
ctx: TransparentDeviceCtx,
config: TransparentBuildConfig,
scene: TransparentSceneTargets,
content: TransparentContent,
rt_setup: TransparentRtSetup,
) -> Result<Self, String> {
let TransparentDeviceCtx {
alloc,
instance,
device,
physical_device,
command_pool,
queue,
} = ctx;
let TransparentBuildConfig {
frames,
msaa_samples,
width,
height,
global_set_layout,
probe_cube_count,
hot_reload,
} = config;
let TransparentSceneTargets {
scene_views,
scene_images,
depth_views,
sampler,
} = scene;
let TransparentRtSetup {
rt_capable,
vertex_buffer,
index_buffer,
rt_inputs,
bindless_set_layout,
bindless_pool_size,
} = rt_setup;
let msaa = msaa_samples != vk::SampleCountFlags::TYPE_1;
let render_pass = create_transparent_render_pass(device, HDR_FORMAT)?;
let view_set_layout = create_view_set_layout(device)?;
let params_set_layout = create_params_set_layout(device)?;
let set_layouts = [
view_set_layout.handle(),
params_set_layout.handle(),
global_set_layout,
];
let pipeline_layout = {
let info = vk::PipelineLayoutCreateInfo::default().set_layouts(&set_layouts);
device
.create_pipeline_layout(&info)
.map_err(|e| format!("transparent pipeline layout: {e}"))?
};
let rt = if rt_capable {
match build_transparent_rt(
alloc,
instance,
device,
physical_device,
frames,
RtSetLayouts {
view: view_set_layout.handle(),
params: params_set_layout.handle(),
global: global_set_layout,
bindless: bindless_set_layout,
},
TransparentRtGeometry {
vertex_buffer,
index_buffer,
rt_inputs,
},
) {
Ok(rt) => Some(rt),
Err(e) => {
tracing::warn!(
"transparent RT setup failed ({e}); using the probe / planar path"
);
None
}
}
} else {
None
};
let snapshot = create_snapshot(alloc, device, command_pool, queue, width, height)?;
let view_size = std::mem::size_of::<TransparentView>() as u64;
let mut view_ubos = Vec::with_capacity(frames);
for _ in 0..frames {
view_ubos.push(alloc.create_buffer(
view_size,
vk::BufferUsageFlags::UNIFORM_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?);
}
let records = content.glass_panels.len() + content.water_surfaces.len();
let descriptor_pool = create_descriptor_pool(device, frames, records)?;
let view_layouts: Vec<_> = (0..frames).map(|_| view_set_layout.handle()).collect();
let view_sets = alloc_descriptor_sets(device, descriptor_pool.handle(), &view_layouts)?;
for (i, &set) in view_sets.iter().enumerate() {
write_view_set(
device,
set,
view_ubos[i].buffer(),
snapshot.view,
depth_views[i.min(depth_views.len().saturating_sub(1))],
sampler,
);
}
let framebuffers =
create_framebuffers(device, render_pass.handle(), scene_views, width, height)?;
let producer_ctx = ProducerCtx {
alloc,
device,
render_pass: render_pass.handle(),
layout: pipeline_layout.handle(),
rt_layout_flat: rt.as_ref().map(|r| r.layout_flat.handle()),
rt_layout_textured: rt
.as_ref()
.and_then(|r| r.layout_textured.as_ref())
.map(|l| l.handle()),
pool: descriptor_pool.handle(),
params_set_layout: params_set_layout.handle(),
snapshot_view: snapshot.view,
planar_target_views: content.planar_target_views,
sampler,
msaa,
hot_reload,
probe_cube_count,
bindless_pool_size,
frames,
ubo_offset_alignment: unsafe {
instance.get_physical_device_properties(physical_device)
}
.limits
.min_uniform_buffer_offset_alignment,
};
let glass = if content.glass_panels.is_empty() {
None
} else {
Some(super::glass::build_glass_producer(
producer_ctx,
content.glass_panels,
content.glass_planar_slots,
)?)
};
let water = if content.water_surfaces.is_empty() {
None
} else {
Some(super::water::build_water_producer(
producer_ctx,
content.water_surfaces,
content.water_planar_slots,
)?)
};
let glass_mesh = match (
content.seethrough_mesh_indices.is_empty(),
producer_ctx.rt_layout_flat,
) {
(false, Some(flat_layout)) => match super::glass::build_glass_mesh_producer(
producer_ctx,
flat_layout,
content.seethrough_mesh_indices,
) {
Ok(p) => Some(p),
Err(e) => {
tracing::warn!(
"see-through glass mesh pipeline build failed ({e}); those meshes render opaque"
);
None
}
},
_ => None,
};
Ok(Self {
render_pass,
pipeline_layout,
_view_set_layout: view_set_layout,
_params_set_layout: params_set_layout,
_descriptor_pool: descriptor_pool,
view_ubos,
view_sets,
scene_images: scene_images.to_vec(),
framebuffers,
snapshot,
sampler,
glass,
water,
glass_mesh,
rt,
})
}
pub(in crate::vulkan) fn rt_pipelines_ready(&self) -> bool {
self.rt.is_some()
&& self.glass.as_ref().is_none_or(|p| p.flat_rt_pso.is_some())
&& self.water.as_ref().is_none_or(|p| p.flat_rt_pso.is_some())
}
pub(in crate::vulkan) fn seethrough_mesh_indices(&self) -> &[usize] {
self.glass_mesh
.as_ref()
.map(|p| p.object_indices.as_slice())
.unwrap_or_default()
}
pub(in crate::vulkan) fn mesh_pipelines_ready(&self) -> bool {
self.glass_mesh.is_some()
}
fn rt_textured_ready(&self) -> bool {
self.rt
.as_ref()
.is_some_and(|r| r.layout_textured.is_some())
&& self
.glass
.as_ref()
.is_none_or(|p| p.textured_rt_pso.is_some())
&& self
.water
.as_ref()
.is_none_or(|p| p.textured_rt_pso.is_some())
&& self
.glass_mesh
.as_ref()
.is_none_or(|p| p.pipeline_textured.is_some())
}
pub(in crate::vulkan) fn wire_rt_dynamic(
&self,
device: &VkDevice,
frame_idx: usize,
dynamic: TransparentRtDynamic,
) {
if let Some(rt) = self.rt.as_ref() {
rt.wire_dynamic(device, frame_idx, dynamic);
}
}
pub(in crate::vulkan) fn wire_rt_geometry(
&self,
device: &VkDevice,
vertex_buffer: vk::Buffer,
index_buffer: vk::Buffer,
) {
if let Some(rt) = self.rt.as_ref() {
rt.rewire_geometry(device, vertex_buffer, index_buffer);
}
}
pub(in crate::vulkan) fn water_planar_slot_live(&self) -> bool {
self.water.as_ref().is_some_and(|p| {
p.records
.iter()
.any(|r| r.visible && r.planar_slot.is_some())
})
}
pub(in crate::vulkan) fn any_visible(&self) -> bool {
let live = |p: &Option<TransparentProducer>| {
p.as_ref()
.is_some_and(|p| p.records.iter().any(|r| r.visible))
};
live(&self.glass) || live(&self.water)
}
fn draw_order(&self, meshes: &[[f32; 3]], cam: [f32; 3]) -> Vec<(Producer, usize)> {
let centres = |p: &Option<TransparentProducer>| -> Vec<([f32; 3], bool)> {
p.as_ref()
.map(|p| p.records.iter().map(|r| (r.centre, r.visible)).collect())
.unwrap_or_default()
};
ordered_visible(¢res(&self.glass), ¢res(&self.water), meshes, cam)
}
fn producer(&self, kind: Producer) -> &TransparentProducer {
match kind {
Producer::Glass => self.glass.as_ref(),
Producer::Water => self.water.as_ref(),
Producer::GlassMesh => {
unreachable!("mesh draws are per-frame and never resolve to a static record")
}
}
.expect("the draw order only names live producers")
}
pub(in crate::vulkan) fn rebuild(
&mut self,
ctx: TransparentDeviceCtx,
width: u32,
height: u32,
targets: TransparentRebuildTargets,
) -> Result<(), String> {
let TransparentDeviceCtx {
alloc,
device,
command_pool,
queue,
..
} = ctx;
let TransparentRebuildTargets {
scene_views,
scene_images,
depth_views,
planar_target_views,
} = targets;
let old = std::mem::replace(
&mut self.snapshot,
create_snapshot(alloc, device, command_pool, queue, width, height)?,
);
drop(old);
self.framebuffers = create_framebuffers(
device,
self.render_pass.handle(),
scene_views,
width,
height,
)?;
self.scene_images = scene_images.to_vec();
for (i, &set) in self.view_sets.iter().enumerate() {
write_view_set(
device,
set,
self.view_ubos[i].buffer(),
self.snapshot.view,
depth_views[i.min(depth_views.len().saturating_sub(1))],
self.sampler,
);
}
for producer in [self.glass.as_ref(), self.water.as_ref()]
.into_iter()
.flatten()
{
for r in &producer.records {
let planar_view = r
.planar_slot
.and_then(|s| planar_target_views.get(s).copied())
.unwrap_or(self.snapshot.view);
write_params_set(
device,
r.params_set,
r.params_ubo.buffer(),
r.params_size,
planar_view,
self.sampler,
);
}
}
Ok(())
}
pub(in crate::vulkan) fn destroy(&mut self, device: &VkDevice) {
if let Some(mut rt) = self.rt.take() {
rt.destroy(device);
}
self.glass = None;
self.water = None;
self.glass_mesh = None;
self.view_ubos.clear();
self.snapshot = GpuImage::null();
self.framebuffers.clear();
self.scene_images.clear();
}
}
fn create_framebuffers(
device: &VkDevice,
render_pass: vk::RenderPass,
scene_views: &[vk::ImageView],
width: u32,
height: u32,
) -> Result<Vec<OwnedFramebuffer>, String> {
let mut out = Vec::with_capacity(scene_views.len());
for &view in scene_views {
let info = vk::FramebufferCreateInfo::default()
.render_pass(render_pass)
.attachments(std::slice::from_ref(&view))
.width(width.max(1))
.height(height.max(1))
.layers(1);
let fb = device
.create_framebuffer(&info)
.map_err(|e| format!("transparent framebuffer: {e}"))?;
out.push(fb);
}
Ok(out)
}
const GLASS_MESH_REFRACTION: f32 = 0.02;
const GLASS_MESH_FRESNEL_POWER: f32 = 1.0;
impl VkContext {
pub(in crate::vulkan) fn seethrough_meshes_enabled(&self) -> bool {
self.transparent
.as_ref()
.is_some_and(|t| t.mesh_pipelines_ready())
}
pub(in crate::vulkan) fn mesh_glass_active(&self) -> bool {
self.seethrough_meshes_enabled() && self.rt_transparent_active()
}
pub(in crate::vulkan) fn mesh_glass_visible(&self) -> bool {
self.mesh_glass_active()
&& self.transparent.as_ref().is_some_and(|t| {
t.seethrough_mesh_indices().iter().any(|&i| {
self.draw
.objects
.get(i)
.is_some_and(|o| o.visible && o.resident)
})
})
}
fn collect_mesh_draws(
&self,
transparent: &TransparentResources,
frame_idx: usize,
cam: [f32; 3],
) -> Vec<GlassMeshDraw> {
let Some(producer) = transparent.glass_mesh.as_ref() else {
return Vec::new();
};
let count = producer.object_indices.len();
let Some(ring) = producer.params_buffers.get(frame_idx) else {
return Vec::new();
};
let prefilter_mip_count = self.prefilter_mip_count as f32;
let mut draws = Vec::with_capacity(count);
for (slot, &idx) in producer.object_indices.iter().enumerate() {
let Some(obj) = self.draw.objects.get(idx) else {
continue;
};
if !obj.visible || !obj.resident || obj.material.see_through == 0 {
continue;
}
let centre = [
0.5 * (obj.bb_min[0] + obj.bb_max[0]),
0.5 * (obj.bb_min[1] + obj.bb_max[1]),
0.5 * (obj.bb_min[2] + obj.bb_max[2]),
];
let d = crate::gfx::lod::camera_distance(obj, cam);
let (index_offset, index_count) = obj.active_lod(d);
let t = obj.material.tint;
let params = GlassMeshParams {
model: obj.model,
tint: [t[0], t[1], t[2], 0.0],
opacity: obj.material.opacity,
refraction_strength: GLASS_MESH_REFRACTION,
fresnel_power: GLASS_MESH_FRESNEL_POWER,
prefilter_mip_count,
};
ring.write_val((slot as u64 * producer.params_stride) as usize, ¶ms);
draws.push(GlassMeshDraw {
index_offset: index_offset as u32,
index_count: index_count as u32,
base_vertex: obj.base_vertex,
params_set: producer.params_sets[frame_idx * count + slot],
centre,
});
}
draws
}
pub(in crate::vulkan) fn build_transparent_view(
&self,
vp: [[f32; 4]; 4],
cam_pos: [f32; 3],
time: f32,
) -> TransparentView {
TransparentView {
vp,
inv_vp: mat4_inverse(vp),
camera_pos: [cam_pos[0], cam_pos[1], cam_pos[2], 0.0],
viewport: [
self.render_extent.width as f32,
self.render_extent.height as f32,
],
time,
prefilter_mip_count: self.prefilter_mip_count as f32,
}
}
pub(in crate::vulkan) fn encode_transparent(
&self,
cmd: vk::CommandBuffer,
frame_idx: usize,
view: &TransparentView,
fov_y_radians: f32,
aspect: f32,
) -> Result<(), String> {
let Some(transparent) = self.transparent.as_ref() else {
return Ok(());
};
let cam = [view.camera_pos[0], view.camera_pos[1], view.camera_pos[2]];
let rt_live = self.rt_transparent_active();
let textured =
rt_live && self.cull.bindless_pipeline.is_some() && transparent.rt_textured_ready();
let mesh_draws = if rt_live {
self.collect_mesh_draws(transparent, frame_idx, cam)
} else {
Vec::new()
};
let mesh_centres: Vec<[f32; 3]> = mesh_draws.iter().map(|d| d.centre).collect();
let order = transparent.draw_order(&mesh_centres, cam);
if order.is_empty() {
return Ok(());
}
let device = &self.device;
let extent = self.render_extent;
let scene_image = *transparent
.scene_images
.get(frame_idx)
.ok_or("transparent: scene image index OOB")?;
let snapshot = transparent.snapshot.image;
transparent
.view_ubos
.get(frame_idx)
.ok_or("transparent: view_ubos index OOB")?
.write_val(0, view);
if rt_live {
let rtres = self
.rt_reflections
.as_ref()
.ok_or("transparent rt_live but rt_reflections missing")?;
let rt = transparent
.rt
.as_ref()
.ok_or("transparent rt_live but rt pipelines missing")?;
let v = self.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 = rtres.settings.params(RtParamsInputs {
fov_y_radians,
aspect,
inv_view_rot,
cam_pos: cam,
sun_dir: self.fog.sun_dir,
sun_color: self.fog.sun_color,
prefilter_mip_count: self.prefilter_mip_count as f32,
});
rt.params_buffers[frame_idx].write_val(0, ¶ms);
}
let color_range = vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
};
let color_barrier = |image: vk::Image,
old: vk::ImageLayout,
new: vk::ImageLayout,
src: vk::AccessFlags,
dst: vk::AccessFlags| {
vk::ImageMemoryBarrier::default()
.src_access_mask(src)
.dst_access_mask(dst)
.old_layout(old)
.new_layout(new)
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.image(image)
.subresource_range(color_range)
};
let scene_to_src = color_barrier(
scene_image,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
vk::AccessFlags::COLOR_ATTACHMENT_WRITE | vk::AccessFlags::SHADER_READ,
vk::AccessFlags::TRANSFER_READ,
);
let snapshot_to_dst = color_barrier(
snapshot,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
vk::AccessFlags::SHADER_READ,
vk::AccessFlags::TRANSFER_WRITE,
);
unsafe {
device.cmd_pipeline_barrier(
cmd,
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
| vk::PipelineStageFlags::FRAGMENT_SHADER,
vk::PipelineStageFlags::TRANSFER,
vk::DependencyFlags::empty(),
&[],
&[],
&[scene_to_src, snapshot_to_dst],
);
let region = vk::ImageCopy::default()
.src_subresource(vk::ImageSubresourceLayers {
aspect_mask: vk::ImageAspectFlags::COLOR,
mip_level: 0,
base_array_layer: 0,
layer_count: 1,
})
.dst_subresource(vk::ImageSubresourceLayers {
aspect_mask: vk::ImageAspectFlags::COLOR,
mip_level: 0,
base_array_layer: 0,
layer_count: 1,
})
.extent(vk::Extent3D {
width: extent.width,
height: extent.height,
depth: 1,
});
device.cmd_copy_image(
cmd,
scene_image,
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
snapshot,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
std::slice::from_ref(®ion),
);
}
let snapshot_to_read = color_barrier(
snapshot,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
vk::AccessFlags::TRANSFER_WRITE,
vk::AccessFlags::SHADER_READ,
);
let scene_to_read = color_barrier(
scene_image,
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
vk::AccessFlags::TRANSFER_READ,
vk::AccessFlags::COLOR_ATTACHMENT_READ,
);
unsafe {
device.cmd_pipeline_barrier(
cmd,
vk::PipelineStageFlags::TRANSFER,
vk::PipelineStageFlags::FRAGMENT_SHADER
| vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
vk::DependencyFlags::empty(),
&[],
&[],
&[snapshot_to_read, scene_to_read],
);
}
let rp_begin = vk::RenderPassBeginInfo::default()
.render_pass(transparent.render_pass.handle())
.framebuffer(transparent.framebuffers[frame_idx].handle())
.render_area(vk::Rect2D::default().extent(extent));
let vp = vk::Viewport {
x: 0.0,
y: extent.height as f32,
width: extent.width as f32,
height: -(extent.height as f32),
min_depth: 0.0,
max_depth: 1.0,
};
let scissor = vk::Rect2D::default().extent(extent);
let layout = match (rt_live, transparent.rt.as_ref()) {
(true, Some(r)) if textured => r
.layout_textured
.as_ref()
.expect("textured implies a textured layout")
.handle(),
(true, Some(r)) => r.layout_flat.handle(),
_ => transparent.pipeline_layout.handle(),
};
unsafe {
device.cmd_begin_render_pass(cmd, &rp_begin, vk::SubpassContents::INLINE);
device.cmd_set_viewport(cmd, 0, std::slice::from_ref(&vp));
device.cmd_set_scissor(cmd, 0, std::slice::from_ref(&scissor));
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
layout,
0,
std::slice::from_ref(&transparent.view_sets[frame_idx]),
&[],
);
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
layout,
2,
std::slice::from_ref(&self.descriptors.global_sets[frame_idx]),
&[],
);
if rt_live {
let r = transparent
.rt
.as_ref()
.expect("rt_live implies the RT pipelines");
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
layout,
3,
std::slice::from_ref(&r.sets[frame_idx]),
&[],
);
if textured {
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
layout,
4,
std::slice::from_ref(&self.cull.bindless_sets[frame_idx]),
&[],
);
}
}
let mut bound: Option<Producer> = None;
for &(kind, i) in &order {
if kind == Producer::GlassMesh {
let mesh = transparent
.glass_mesh
.as_ref()
.expect("the draw order only names live producers");
if bound != Some(kind) {
device.cmd_bind_pipeline(
cmd,
vk::PipelineBindPoint::GRAPHICS,
mesh.pipeline(textured).handle(),
);
bound = Some(kind);
}
let d = &mesh_draws[i];
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
layout,
1,
std::slice::from_ref(&d.params_set),
&[],
);
device.cmd_bind_vertex_buffers(
cmd,
0,
&[self.geometry.vertex_buffer.buffer()],
&[0],
);
device.cmd_bind_index_buffer(
cmd,
self.geometry.index_buffer.buffer(),
0,
vk::IndexType::UINT32,
);
device.cmd_draw_indexed(
cmd,
d.index_count,
1,
d.index_offset,
d.base_vertex,
0,
);
self.inc_draw_calls(1);
continue;
}
let producer = transparent.producer(kind);
if bound != Some(kind) {
device.cmd_bind_pipeline(
cmd,
vk::PipelineBindPoint::GRAPHICS,
producer.pipeline(rt_live, textured).handle(),
);
bound = Some(kind);
}
let r = &producer.records[i];
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
layout,
1,
std::slice::from_ref(&r.params_set),
&[],
);
device.cmd_bind_vertex_buffers(cmd, 0, &[r.vertex_buffer.buffer()], &[0]);
device.cmd_bind_index_buffer(
cmd,
r.index_buffer.buffer(),
0,
vk::IndexType::UINT16,
);
device.cmd_draw_indexed(cmd, r.index_count, 1, 0, 0, 0);
self.inc_draw_calls(1);
}
device.cmd_end_render_pass(cmd);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sort_distance_is_euclidean_and_monotone() {
let cam = [0.0, 0.0, 0.0];
let near = sort_distance([0.0, 0.0, 1.0], cam);
let far = sort_distance([0.0, 0.0, 5.0], cam);
assert!((near - 1.0).abs() < 1e-5);
assert!((far - 5.0).abs() < 1e-5);
assert!(far > near);
}
#[test]
fn ordered_visible_excludes_hidden_and_sorts_back_to_front() {
let glass = [
([0.0, 0.0, 5.0], true),
([0.0, 0.0, 9.0], false),
([0.0, 0.0, 3.0], true),
];
let order = ordered_visible(&glass, &[], &[], [0.0, 0.0, 0.0]);
assert_eq!(order, vec![(Producer::Glass, 0), (Producer::Glass, 2)]);
}
#[test]
fn ordered_visible_interleaves_the_two_producers() {
let glass = [([0.0, 0.0, 9.0], true), ([0.0, 0.0, 1.0], true)];
let water = [([0.0, 0.0, 5.0], true), ([0.0, 0.0, 7.0], false)];
let order = ordered_visible(&glass, &water, &[], [0.0, 0.0, 0.0]);
assert_eq!(
order,
vec![
(Producer::Glass, 0),
(Producer::Water, 0),
(Producer::Glass, 1),
]
);
}
#[test]
fn align_up_rounds_to_the_next_multiple() {
assert_eq!(align_up(96, 256), 256);
assert_eq!(align_up(96, 64), 128);
assert_eq!(align_up(128, 64), 128);
assert_eq!(align_up(0, 256), 0);
assert_eq!(align_up(96, 0), 96);
assert_eq!(align_up(96, 1), 96);
}
#[test]
fn ordered_visible_interleaves_mesh_draws_with_the_static_producers() {
let glass = [([0.0, 0.0, 9.0], true)];
let water = [([0.0, 0.0, 3.0], true)];
let meshes = [[0.0, 0.0, 6.0], [0.0, 0.0, 1.0]];
let order = ordered_visible(&glass, &water, &meshes, [0.0, 0.0, 0.0]);
assert_eq!(
order,
vec![
(Producer::Glass, 0),
(Producer::GlassMesh, 0),
(Producer::Water, 0),
(Producer::GlassMesh, 1),
]
);
}
#[test]
fn ordered_visible_orders_meshes_alone_back_to_front() {
let meshes = [[0.0, 0.0, 2.0], [0.0, 0.0, 8.0]];
let order = ordered_visible(&[], &[], &meshes, [0.0, 0.0, 0.0]);
assert_eq!(
order,
vec![(Producer::GlassMesh, 1), (Producer::GlassMesh, 0)]
);
}
#[test]
fn ordered_visible_is_empty_with_no_visible_records() {
let glass = [([0.0, 0.0, 5.0], false)];
let water = [([0.0, 0.0, 3.0], false)];
assert!(ordered_visible(&glass, &water, &[], [0.0, 0.0, 0.0]).is_empty());
}
}