use ash::vk;
use concinnity_core::components::{GlassPanel, WaterSurface};
use concinnity_core::gfx::mesh_payload::Vertex;
use concinnity_core::gfx::render_types::RtParams;
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::fullscreen::align_up;
use concinnity_core::render::post::rt_reflections::RtParamsInputs;
use concinnity_core::render::reactive_mask::ReactiveWrite;
pub(in crate::vulkan) use concinnity_core::render::uniforms::TransparentView;
use concinnity_core::render::transparent::SeeThroughMesh;
use concinnity_core::render::uniforms::GlassMeshParams;
use super::allocator::{DeviceAllocator, PooledBuffer};
use super::context::{HDR_FORMAT, VkContext};
use super::descriptor_layout::{Binding, PoolSizes};
use super::pipeline_desc::{Blend, Depth, GraphicsPipelineDesc};
use super::reactive_mask::{self, WriterRenderPasses};
use super::resources::{alloc_descriptor_sets, create_descriptor_set_layout};
use super::set_writes::SetWrites;
use super::texture::{
GpuImage, GpuUploadContext, ImageSpec, LayoutTransition, SubresourceRange, create_image,
create_image_view, one_shot_submit, transition_image_layout_range, upload_texture,
};
use super::wire_cache::WireCache;
use crate::vulkan::depth;
use crate::vulkan::owned::{
OwnedDescriptorPool, OwnedFramebuffer, OwnedPipeline, OwnedPipelineLayout, OwnedRenderPass,
OwnedSetLayout, VkDevice,
};
#[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, PartialEq, Eq)]
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,
}
pub(in crate::vulkan) use concinnity_core::render::transparent::Producer;
pub(in crate::vulkan) struct TransparentRecord {
vertex_buffer: PooledBuffer,
index_buffer: PooledBuffer,
index_count: u32,
_params_ubo: PooledBuffer,
params_set: vk::DescriptorSet,
visible: bool,
center: [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 center: [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<'_>,
) -> RenderResult<Self> {
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_ubo,
params_set,
visible: upload.visible,
center: upload.center,
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 reflection_flat_pso: Option<OwnedPipeline>,
pub reflection_textured_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,
}
}
fn reflection_pipeline(&self, textured: bool) -> Option<&OwnedPipeline> {
match textured {
true => self.reflection_textured_pso.as_ref(),
false => self.reflection_flat_pso.as_ref(),
}
}
}
pub(in crate::vulkan) struct GlassMeshProducer {
pipeline_flat: OwnedPipeline,
pipeline_textured: Option<OwnedPipeline>,
reflection_flat: OwnedPipeline,
reflection_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,
center: [f32; 3],
}
impl GlassMeshProducer {
pub(in crate::vulkan) fn new(
ctx: &ProducerCtx,
pipelines: TracedGlassPipelines,
object_indices: Vec<usize>,
) -> RenderResult<Self> {
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 = PoolSizes::default()
.sets(¶ms_set_bindings(), sets_needed)
.build();
let pool = device
.create_descriptor_pool(
&vk::DescriptorPoolCreateInfo::default()
.max_sets(sets_needed.max(1))
.pool_sizes(&sizes),
)
.map_err(|e| super::error::map_vk_result(e, "glass mesh descriptor pool"))?;
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.stand_in_view,
ctx.sampler,
);
}
}
let TracedGlassPipelines {
shade_flat: pipeline_flat,
shade_textured: pipeline_textured,
reflection_flat,
reflection_textured,
} = pipelines;
Ok(Self {
pipeline_flat,
pipeline_textured,
reflection_flat,
reflection_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,
}
}
fn reflection_pipeline(&self, textured: bool) -> &OwnedPipeline {
match textured {
true => self
.reflection_textured
.as_ref()
.expect("rt_textured_ready gated the frame on every producer's textured pipeline"),
false => &self.reflection_flat,
}
}
}
pub(in crate::vulkan) struct TracedGlassPipelines {
pub shade_flat: OwnedPipeline,
pub shade_textured: Option<OwnedPipeline>,
pub reflection_flat: OwnedPipeline,
pub reflection_textured: Option<OwnedPipeline>,
}
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,
wired_accel: WireCache<TransparentRtDynamic>,
}
pub(in crate::vulkan) struct TransparentResources {
render_passes: WriterRenderPasses,
pipeline_layout: OwnedPipelineLayout,
_view_set_layout: OwnedSetLayout,
_params_set_layout: OwnedSetLayout,
_descriptor_pool: OwnedDescriptorPool,
view_ubos: Vec<PooledBuffer>,
view_sets: Vec<FrameViewSets>,
scene_images: Vec<vk::Image>,
framebuffers: Vec<OwnedFramebuffer>,
snapshot: GpuImage,
glass: Option<TransparentProducer>,
water: Option<TransparentProducer>,
glass_mesh: Option<GlassMeshProducer>,
rt: Option<TransparentRt>,
reflection_render_pass: OwnedRenderPass,
reflection: Option<GlassReflectionLayers>,
empty_layer: GpuImage,
}
use concinnity_core::render::transparent::ordered_visible;
fn rt_set_bindings() -> [Binding; 7] {
use vk::DescriptorType as T;
let frag = vk::ShaderStageFlags::FRAGMENT;
[
(0, T::UNIFORM_BUFFER, frag),
(1, T::ACCELERATION_STRUCTURE_KHR, frag),
(2, T::STORAGE_BUFFER, frag),
(3, T::STORAGE_BUFFER, frag),
(4, T::STORAGE_BUFFER, frag),
(5, T::STORAGE_BUFFER, frag),
(6, T::STORAGE_BUFFER, frag),
]
}
impl TransparentRt {
fn wire_static(&self, device: &VkDevice, vertex_buffer: vk::Buffer, index_buffer: vk::Buffer) {
for (&set, params) in self.sets.iter().zip(&self.params_buffers) {
SetWrites::new(set)
.uniform_buffer(0, params.buffer(), size_of::<RtParams>() as vk::DeviceSize)
.apply(device);
}
self.rewire_geometry(device, vertex_buffer, index_buffer);
}
fn rewire_geometry(
&self,
device: &VkDevice,
vertex_buffer: vk::Buffer,
index_buffer: vk::Buffer,
) {
for &set in &self.sets {
SetWrites::new(set)
.storage_buffer(3, vertex_buffer, vk::WHOLE_SIZE)
.storage_buffer(4, index_buffer, vk::WHOLE_SIZE)
.apply(device);
}
}
fn wire_dynamic(&mut self, device: &VkDevice, frame_idx: usize, dynamic: TransparentRtDynamic) {
if !self.wired_accel.changed(frame_idx, dynamic) {
return;
}
let TransparentRtDynamic {
tlas,
geom_buffer,
geom_size,
deformed,
skinned_indices,
} = dynamic;
let sidx_buffer = if skinned_indices != vk::Buffer::null() {
skinned_indices
} else {
self.dummy_ssbo.buffer()
};
SetWrites::new(self.sets[frame_idx])
.acceleration_structure(1, tlas)
.storage_buffer(2, geom_buffer, geom_size)
.storage_buffer(5, deformed, vk::WHOLE_SIZE)
.storage_buffer(6, sidx_buffer, vk::WHOLE_SIZE)
.apply(device);
}
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,
) -> RenderResult<TransparentRt> {
let set_layout = create_descriptor_set_layout(device, &rt_set_bindings())?;
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| super::error::map_vk_result(e, "transparent rt flat pipeline layout"))?;
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| {
super::error::map_vk_result(e, "transparent rt textured pipeline layout")
})?,
)
}
_ => 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 = PoolSizes::default().sets(&rt_set_bindings(), f).build();
let pool = device
.create_descriptor_pool(
&vk::DescriptorPoolCreateInfo::default()
.pool_sizes(&pool_sizes)
.max_sets(f),
)
.map_err(|e| super::error::map_vk_result(e, "transparent rt descriptor pool"))?;
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 mut rt = TransparentRt {
_set_layout: set_layout,
layout_flat,
layout_textured,
params_buffers,
sets,
_pool: pool,
dummy_ssbo,
wired_accel: WireCache::new(frames),
};
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,
mask: Option<ReactiveWrite>,
) -> RenderResult<OwnedRenderPass> {
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 attachments = device.writer_targets().attachments(color, mask);
let color_refs: Vec<_> = (0..attachments.len() as u32)
.map(|i| {
vk::AttachmentReference::default()
.attachment(i)
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
})
.collect();
let subpass = vk::SubpassDescription::default()
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
.color_attachments(&color_refs);
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(&attachments)
.subpasses(std::slice::from_ref(&subpass))
.dependencies(std::slice::from_ref(&dependency));
device
.create_render_pass(&info)
.map_err(|e| super::error::map_vk_result(e, "transparent render pass"))
}
fn create_reflection_render_pass(device: &VkDevice) -> RenderResult<OwnedRenderPass> {
let color = vk::AttachmentDescription::default()
.format(HDR_FORMAT)
.samples(vk::SampleCountFlags::TYPE_1)
.load_op(vk::AttachmentLoadOp::CLEAR)
.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 depth = vk::AttachmentDescription::default()
.format(REFLECTION_DEPTH_FORMAT)
.samples(vk::SampleCountFlags::TYPE_1)
.load_op(vk::AttachmentLoadOp::CLEAR)
.store_op(vk::AttachmentStoreOp::DONT_CARE)
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(vk::ImageLayout::UNDEFINED)
.final_layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
let color_ref = vk::AttachmentReference::default()
.attachment(0)
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
let depth_ref = vk::AttachmentReference::default()
.attachment(1)
.layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
let subpass = vk::SubpassDescription::default()
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
.color_attachments(std::slice::from_ref(&color_ref))
.depth_stencil_attachment(&depth_ref);
let fragment_tests =
vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS | vk::PipelineStageFlags::LATE_FRAGMENT_TESTS;
let dependencies = [
vk::SubpassDependency::default()
.src_subpass(vk::SUBPASS_EXTERNAL)
.dst_subpass(0)
.src_stage_mask(
fragment_tests
| vk::PipelineStageFlags::FRAGMENT_SHADER
| vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
)
.src_access_mask(
vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE
| vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
)
.dst_stage_mask(
fragment_tests
| vk::PipelineStageFlags::FRAGMENT_SHADER
| vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
)
.dst_access_mask(
vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ
| vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE
| vk::AccessFlags::COLOR_ATTACHMENT_WRITE
| vk::AccessFlags::SHADER_READ,
),
vk::SubpassDependency::default()
.src_subpass(0)
.dst_subpass(vk::SUBPASS_EXTERNAL)
.src_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
.src_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE)
.dst_stage_mask(vk::PipelineStageFlags::FRAGMENT_SHADER)
.dst_access_mask(vk::AccessFlags::SHADER_READ),
];
let attachments = [color, depth];
let info = vk::RenderPassCreateInfo::default()
.attachments(&attachments)
.subpasses(std::slice::from_ref(&subpass))
.dependencies(&dependencies);
device
.create_render_pass(&info)
.map_err(|e| super::error::map_vk_result(e, "glass reflection render pass"))
}
const REFLECTION_DEPTH_FORMAT: vk::Format = vk::Format::D32_SFLOAT;
struct GlassReflectionLayers {
layers: [GpuImage; 2],
_depth: GpuImage,
framebuffers: [OwnedFramebuffer; 2],
extent: vk::Extent2D,
}
impl GlassReflectionLayers {
fn new(
alloc: &DeviceAllocator,
device: &VkDevice,
render_pass: vk::RenderPass,
extent: vk::Extent2D,
) -> RenderResult<Self> {
let image = |format: vk::Format, usage: vk::ImageUsageFlags, aspect| -> RenderResult<_> {
let pooled = create_image(
alloc,
&ImageSpec {
width: extent.width,
height: extent.height,
format,
tiling: vk::ImageTiling::OPTIMAL,
usage,
mem_props: vk::MemoryPropertyFlags::DEVICE_LOCAL,
samples: vk::SampleCountFlags::TYPE_1,
},
)?;
let view = create_image_view(device, pooled.image(), format, aspect)?;
Ok(GpuImage::from_pooled(pooled, view))
};
let layer = || {
image(
HDR_FORMAT,
vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::SAMPLED,
vk::ImageAspectFlags::COLOR,
)
};
let layers = [layer()?, layer()?];
let depth = image(
REFLECTION_DEPTH_FORMAT,
vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT,
vk::ImageAspectFlags::DEPTH,
)?;
let framebuffer = |color: &GpuImage| {
let attachments = [color.view, depth.view];
device
.create_framebuffer(
&vk::FramebufferCreateInfo::default()
.render_pass(render_pass)
.attachments(&attachments)
.width(extent.width)
.height(extent.height)
.layers(1),
)
.map_err(|e| super::error::map_vk_result(e, "glass reflection framebuffer"))
};
let framebuffers = [framebuffer(&layers[0])?, framebuffer(&layers[1])?];
Ok(Self {
layers,
_depth: depth,
framebuffers,
extent,
})
}
}
#[derive(Clone, Copy)]
struct FrameViewSets {
scene: vk::DescriptorSet,
layers: [vk::DescriptorSet; 2],
}
impl FrameViewSets {
const COUNT: usize = 3;
}
fn view_set_bindings() -> [Binding; 6] {
use vk::DescriptorType as T;
let frag = vk::ShaderStageFlags::FRAGMENT;
[
(0, T::UNIFORM_BUFFER, vk::ShaderStageFlags::VERTEX | frag),
(1, T::SAMPLED_IMAGE, frag),
(2, T::SAMPLED_IMAGE, frag),
(3, T::SAMPLED_IMAGE, frag),
(4, T::SAMPLED_IMAGE, frag),
(5, T::SAMPLER, frag),
]
}
fn params_set_bindings() -> [Binding; 3] {
use vk::DescriptorType as T;
let frag = vk::ShaderStageFlags::FRAGMENT;
[
(0, T::UNIFORM_BUFFER, vk::ShaderStageFlags::VERTEX | frag),
(1, T::SAMPLED_IMAGE, frag),
(2, T::SAMPLER, frag),
]
}
fn create_descriptor_pool(
device: &VkDevice,
frames: usize,
records: usize,
) -> RenderResult<OwnedDescriptorPool> {
let v = (frames * FrameViewSets::COUNT) as u32;
let r = records as u32;
let sizes = PoolSizes::default()
.sets(&view_set_bindings(), v)
.sets(¶ms_set_bindings(), r)
.build();
let info = vk::DescriptorPoolCreateInfo::default()
.max_sets(v + r)
.pool_sizes(&sizes);
device
.create_descriptor_pool(&info)
.map_err(|e| super::error::map_vk_result(e, "transparent descriptor pool"))
}
#[derive(Clone, Copy)]
struct ViewSetImages {
snapshot_view: vk::ImageView,
depth_view: vk::ImageView,
reflection: [vk::ImageView; 2],
}
fn write_view_set_statics(
device: &VkDevice,
set: vk::DescriptorSet,
view_ubo: vk::Buffer,
sampler: vk::Sampler,
) {
SetWrites::new(set)
.uniform_buffer(0, view_ubo, size_of::<TransparentView>() as u64)
.sampler(5, sampler)
.apply(device);
}
fn write_view_set_images(device: &VkDevice, set: vk::DescriptorSet, inputs: ViewSetImages) {
SetWrites::new(set)
.sampled_image(1, inputs.snapshot_view)
.sampled_image(2, inputs.depth_view)
.sampled_image(3, inputs.reflection[0])
.sampled_image(4, inputs.reflection[1])
.apply(device);
}
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,
) {
SetWrites::new(set)
.buffer(
0,
vk::DescriptorType::UNIFORM_BUFFER,
params_ubo,
params_offset,
params_size,
)
.sampled_image(1, planar_view)
.sampler(2, sampler)
.apply(device);
}
fn write_planar_view(device: &VkDevice, set: vk::DescriptorSet, view: vk::ImageView) {
SetWrites::new(set).sampled_image(1, view).apply(device);
}
#[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,
) -> RenderResult<OwnedPipeline> {
let shaders = TransparentShaders {
vert_spv,
frag_spv,
vertex_input,
};
transparent_pipeline(
device,
render_pass,
layout,
shaders,
TransparentOutput::Scene,
)
}
pub(in crate::vulkan) fn create_glass_reflection_pipeline(
device: &VkDevice,
render_pass: vk::RenderPass,
layout: vk::PipelineLayout,
vert_spv: &[u8],
frag_spv: &[u8],
vertex_input: TransparentVertexInput,
) -> RenderResult<OwnedPipeline> {
let shaders = TransparentShaders {
vert_spv,
frag_spv,
vertex_input,
};
transparent_pipeline(
device,
render_pass,
layout,
shaders,
TransparentOutput::ReflectionLayer,
)
}
struct TransparentShaders<'a> {
vert_spv: &'a [u8],
frag_spv: &'a [u8],
vertex_input: TransparentVertexInput,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum TransparentOutput {
Scene,
ReflectionLayer,
}
fn transparent_pipeline(
device: &VkDevice,
render_pass: vk::RenderPass,
layout: vk::PipelineLayout,
shaders: TransparentShaders,
output: TransparentOutput,
) -> RenderResult<OwnedPipeline> {
let TransparentShaders {
vert_spv,
frag_spv,
vertex_input,
} = shaders;
let binding = [vk::VertexInputBindingDescription {
binding: 0,
stride: size_of::<Vertex>() as u32,
input_rate: vk::VertexInputRate::VERTEX,
}];
let attr = |location: u32, offset: u32| vk::VertexInputAttributeDescription {
location,
binding: 0,
format: vk::Format::R32G32B32_SFLOAT,
offset,
};
let attributes: &[vk::VertexInputAttributeDescription] = match vertex_input {
TransparentVertexInput::Position => &[attr(0, 0)],
TransparentVertexInput::PositionAndNormal => &[attr(0, 0), attr(1, 12)],
};
let (targets, depth): (&[Blend], Depth) = match output {
TransparentOutput::Scene => (device.writer_targets().blends(), Depth::Off),
TransparentOutput::ReflectionLayer => (&[Blend::Opaque], Depth::write()),
};
GraphicsPipelineDesc {
depth,
vertex_bindings: &binding,
vertex_attributes: attributes,
..GraphicsPipelineDesc::fullscreen(vert_spv, frag_spv, layout, render_pass, targets)
}
.build(device, "transparent")
}
fn create_snapshot(
alloc: &DeviceAllocator,
device: &VkDevice,
command_pool: vk::CommandPool,
queue: vk::Queue,
width: u32,
height: u32,
) -> RenderResult<GpuImage> {
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 hot_reload: bool,
pub reflection_divisor: u32,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct TransparentSceneTargets<'a> {
pub scene_views: &'a [vk::ImageView],
pub scene_images: &'a [vk::Image],
pub reactive_mask_views: &'a [vk::ImageView],
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 reflection_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 stand_in_view: vk::ImageView,
pub planar_target_views: &'a [vk::ImageView],
pub sampler: vk::Sampler,
pub msaa: bool,
pub hot_reload: bool,
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.stand_in_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 reactive_mask_views: &'a [vk::ImageView],
pub depth_views: &'a [vk::ImageView],
pub planar_target_views: &'a [vk::ImageView],
pub reflection_divisor: u32,
}
impl TransparentResources {
pub(in crate::vulkan) fn new(
ctx: TransparentDeviceCtx,
config: TransparentBuildConfig,
scene: TransparentSceneTargets,
content: TransparentContent,
rt_setup: TransparentRtSetup,
) -> RenderResult<Self> {
let TransparentDeviceCtx {
alloc,
instance,
device,
physical_device,
command_pool,
queue,
} = ctx;
let TransparentBuildConfig {
frames,
msaa_samples,
width,
height,
global_set_layout,
hot_reload,
reflection_divisor,
} = config;
let TransparentSceneTargets {
scene_views,
scene_images,
reactive_mask_views,
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_passes = WriterRenderPasses::new(device.writer_targets(), |mask| {
create_transparent_render_pass(device, HDR_FORMAT, mask)
})?;
let reflection_render_pass = create_reflection_render_pass(device)?;
let view_set_layout = create_descriptor_set_layout(device, &view_set_bindings())?;
let params_set_layout = create_descriptor_set_layout(device, ¶ms_set_bindings())?;
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| super::error::map_vk_result(e, "transparent pipeline layout"))?
};
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 * FrameViewSets::COUNT)
.map(|_| view_set_layout.handle())
.collect();
let view_sets: Vec<FrameViewSets> =
alloc_descriptor_sets(device, descriptor_pool.handle(), &view_layouts)?
.chunks_exact(FrameViewSets::COUNT)
.map(|sets| FrameViewSets {
scene: sets[0],
layers: [sets[1], sets[2]],
})
.collect();
let empty_layer = upload_texture(
&GpuUploadContext {
alloc,
device,
command_pool,
queue,
},
1,
1,
&[0u8; 4],
)?;
let framebuffers = create_framebuffers(
device,
render_passes.compatible(),
(scene_views, reactive_mask_views),
width,
height,
)?;
let producer_ctx = ProducerCtx {
alloc,
device,
render_pass: render_passes.compatible(),
reflection_render_pass: reflection_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(),
stand_in_view: empty_layer.view,
planar_target_views: content.planar_target_views,
sampler,
msaa,
hot_reload,
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,
};
let mut me = Self {
render_passes,
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,
glass,
water,
glass_mesh,
rt,
reflection_render_pass,
reflection: None,
empty_layer,
};
me.reflection =
me.build_reflection_layers(alloc, device, width, height, reflection_divisor)?;
for (sets, ubo) in me.view_sets.iter().zip(&me.view_ubos) {
for set in [sets.scene, sets.layers[0], sets.layers[1]] {
write_view_set_statics(device, set, ubo.buffer(), sampler);
}
}
me.write_view_sets(device, depth_views);
Ok(me)
}
fn build_reflection_layers(
&self,
alloc: &DeviceAllocator,
device: &VkDevice,
width: u32,
height: u32,
divisor: u32,
) -> RenderResult<Option<GlassReflectionLayers>> {
let traced = self
.glass
.as_ref()
.is_some_and(|p| p.reflection_flat_pso.is_some())
|| self.glass_mesh.is_some();
if divisor <= 1 || !traced {
return Ok(None);
}
let extent = vk::Extent2D {
width: (width / divisor).max(1),
height: (height / divisor).max(1),
};
GlassReflectionLayers::new(alloc, device, self.reflection_render_pass.handle(), extent)
.map(Some)
}
fn write_view_sets(&self, device: &VkDevice, depth_views: &[vk::ImageView]) {
let empty = self.empty_layer.view;
let (scene, first) = match &self.reflection {
Some(r) => ([r.layers[0].view, r.layers[1].view], r.layers[0].view),
None => ([self.snapshot.view; 2], empty),
};
for (i, sets) in self.view_sets.iter().enumerate() {
let inputs = |reflection| ViewSetImages {
snapshot_view: self.snapshot.view,
depth_view: depth_views[i.min(depth_views.len().saturating_sub(1))],
reflection,
};
write_view_set_images(device, sets.scene, inputs(scene));
write_view_set_images(device, sets.layers[0], inputs([empty; 2]));
write_view_set_images(device, sets.layers[1], inputs([first, empty]));
}
}
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(
&mut self,
device: &VkDevice,
frame_idx: usize,
dynamic: TransparentRtDynamic,
) {
if let Some(rt) = self.rt.as_mut() {
rt.wire_dynamic(device, frame_idx, dynamic);
}
}
pub(in crate::vulkan) fn forget_rt_dynamic(&mut self) {
if let Some(rt) = self.rt.as_mut() {
rt.wired_accel.reset();
}
}
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 centers = |p: &Option<TransparentProducer>| -> Vec<([f32; 3], bool)> {
p.as_ref()
.map(|p| p.records.iter().map(|r| (r.center, r.visible)).collect())
.unwrap_or_default()
};
ordered_visible(¢ers(&self.glass), ¢ers(&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,
) -> RenderResult<()> {
let TransparentDeviceCtx {
alloc,
device,
command_pool,
queue,
..
} = ctx;
let TransparentRebuildTargets {
scene_views,
scene_images,
reactive_mask_views,
depth_views,
planar_target_views,
reflection_divisor,
} = 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_passes.compatible(),
(scene_views, reactive_mask_views),
width,
height,
)?;
self.scene_images = scene_images.to_vec();
self.reflection = None;
self.reflection =
self.build_reflection_layers(alloc, device, width, height, reflection_divisor)?;
self.write_view_sets(device, depth_views);
for producer in [self.glass.as_ref(), self.water.as_ref()]
.into_iter()
.flatten()
{
for r in &producer.records {
if let Some(&view) = r.planar_slot.and_then(|s| planar_target_views.get(s)) {
write_planar_view(device, r.params_set, view);
}
}
}
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.reflection = None;
self.empty_layer = GpuImage::null();
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, mask_views): (&[vk::ImageView], &[vk::ImageView]),
width: u32,
height: u32,
) -> RenderResult<Vec<OwnedFramebuffer>> {
let mut out = Vec::with_capacity(scene_views.len());
for (&view, &mask) in scene_views.iter().zip(mask_views) {
let attachments = device.writer_targets().views(view, mask);
let info = vk::FramebufferCreateInfo::default()
.render_pass(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, "transparent framebuffer"))?;
out.push(fb);
}
Ok(out)
}
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.state
.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.scene.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.state.draw.objects.get(idx) else {
continue;
};
if !obj.visible || !obj.resident || obj.material.see_through == 0 {
continue;
}
let mesh = SeeThroughMesh::new(obj, cam, prefilter_mip_count);
ring.write_val(
(slot as u64 * producer.params_stride) as usize,
&mesh.params,
);
draws.push(GlassMeshDraw {
index_offset: mesh.index_offset as u32,
index_count: mesh.index_count as u32,
base_vertex: mesh.base_vertex,
params_set: producer.params_sets[frame_idx * count + slot],
center: mesh.center,
});
}
draws
}
pub(in crate::vulkan) fn build_transparent_view(
&self,
vp: [[f32; 4]; 4],
cam_pos: [f32; 3],
time: f32,
) -> TransparentView {
TransparentView::new(
&self.pass_camera(vp, cam_pos, time),
&self.uniforms.light_uniforms,
)
}
pub(in crate::vulkan) fn encode_transparent(
&self,
cmd: vk::CommandBuffer,
frame_idx: usize,
view: &TransparentView,
fov_y_radians: f32,
aspect: f32,
mask: ReactiveWrite,
) -> RenderResult<bool> {
let Some(transparent) = self.transparent.as_ref() else {
return Ok(false);
};
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_centers: Vec<[f32; 3]> = mesh_draws.iter().map(|d| d.center).collect();
let order = transparent.draw_order(&mesh_centers, cam);
if order.is_empty() {
return Ok(false);
}
let reflection_layers = transparent.reflection.as_ref().filter(|_| {
rt_live
&& order
.iter()
.any(|&(kind, _)| matches!(kind, Producer::Glass | Producer::GlassMesh))
});
let device = &self.hw.device;
let extent = self.targets.render_extent;
let scene_image = *transparent
.scene_images
.get(frame_idx)
.ok_or_else(|| RenderError::Other("transparent: scene image index OOB".to_string()))?;
let snapshot = transparent.snapshot.image;
transparent
.view_ubos
.get(frame_idx)
.ok_or_else(|| RenderError::Other("transparent: view_ubos index OOB".to_string()))?
.write_val(0, view);
if rt_live {
let rtres = self.rt_reflections.as_ref().ok_or_else(|| {
RenderError::Other("transparent rt_live but rt_reflections missing".to_string())
})?;
let rt = transparent.rt.as_ref().ok_or_else(|| {
RenderError::Other("transparent rt_live but rt pipelines missing".to_string())
})?;
let v = self.state.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.scene.prefilter_mip_count as f32,
sky_rot: self.state.view.sky_rot,
});
let params = RtParams {
trace_divisor: if reflection_layers.is_some() {
params.trace_divisor
} else {
1.0
},
..params
};
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 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(),
};
let frame = TransparentFrame {
cmd,
frame_idx,
layout,
rt_live,
textured,
order: &order,
mesh_draws: &mesh_draws,
};
if let Some(layers) = reflection_layers {
self.encode_glass_reflection_layers(transparent, layers, &frame);
}
let clears = [reactive_mask::REACTIVE_MASK_CLEAR; 2];
let rp_begin = vk::RenderPassBeginInfo::default()
.render_pass(transparent.render_passes.get(mask))
.framebuffer(transparent.framebuffers[frame_idx].handle())
.render_area(vk::Rect2D::default().extent(extent))
.clear_values(&clears);
unsafe {
device.cmd_begin_render_pass(cmd, &rp_begin, vk::SubpassContents::INLINE);
set_flipped_viewport(device, cmd, extent);
}
self.bind_transparent_sets(transparent, &frame, transparent.view_sets[frame_idx].scene);
let mut bound: Option<Producer> = None;
for &(kind, i) in &order {
if bound != Some(kind) {
let pipeline = match kind {
Producer::GlassMesh => transparent
.glass_mesh
.as_ref()
.expect("the draw order only names live producers")
.pipeline(textured),
_ => transparent.producer(kind).pipeline(rt_live, textured),
};
unsafe {
device.cmd_bind_pipeline(
cmd,
vk::PipelineBindPoint::GRAPHICS,
pipeline.handle(),
);
}
bound = Some(kind);
}
self.draw_transparent_entry(transparent, &frame, kind, i);
}
unsafe { device.cmd_end_render_pass(cmd) };
Ok(true)
}
fn encode_glass_reflection_layers(
&self,
transparent: &TransparentResources,
layers: &GlassReflectionLayers,
frame: &TransparentFrame,
) {
let device = &self.hw.device;
let clears = [
vk::ClearValue {
color: vk::ClearColorValue { float32: [0.0; 4] },
},
depth::CLEAR_VALUE,
];
for layer in 0..2 {
let rp_begin = vk::RenderPassBeginInfo::default()
.render_pass(transparent.reflection_render_pass.handle())
.framebuffer(layers.framebuffers[layer].handle())
.render_area(vk::Rect2D::default().extent(layers.extent))
.clear_values(&clears);
unsafe {
device.cmd_begin_render_pass(frame.cmd, &rp_begin, vk::SubpassContents::INLINE);
set_flipped_viewport(device, frame.cmd, layers.extent);
}
self.bind_transparent_sets(
transparent,
frame,
transparent.view_sets[frame.frame_idx].layers[layer],
);
for &(kind, i) in frame.order {
let pipeline = match kind {
Producer::GlassMesh => transparent
.glass_mesh
.as_ref()
.map(|m| m.reflection_pipeline(frame.textured)),
_ => transparent
.producer(kind)
.reflection_pipeline(frame.textured),
};
let Some(pipeline) = pipeline else {
continue;
};
unsafe {
device.cmd_bind_pipeline(
frame.cmd,
vk::PipelineBindPoint::GRAPHICS,
pipeline.handle(),
);
}
self.draw_transparent_entry(transparent, frame, kind, i);
}
unsafe { device.cmd_end_render_pass(frame.cmd) };
}
}
fn bind_transparent_sets(
&self,
transparent: &TransparentResources,
frame: &TransparentFrame,
view_set: vk::DescriptorSet,
) {
let device = &self.hw.device;
let bind = |index: u32, set: vk::DescriptorSet| {
unsafe {
device.cmd_bind_descriptor_sets(
frame.cmd,
vk::PipelineBindPoint::GRAPHICS,
frame.layout,
index,
std::slice::from_ref(&set),
&[],
);
}
};
bind(0, view_set);
bind(2, self.descriptors.global_sets[frame.frame_idx]);
if frame.rt_live {
let r = transparent
.rt
.as_ref()
.expect("rt_live implies the RT pipelines");
bind(3, r.sets[frame.frame_idx]);
if frame.textured {
bind(4, self.cull.bindless_sets[frame.frame_idx]);
}
}
}
fn draw_transparent_entry(
&self,
transparent: &TransparentResources,
frame: &TransparentFrame,
kind: Producer,
i: usize,
) {
let device = &self.hw.device;
let cmd = frame.cmd;
unsafe {
if kind == Producer::GlassMesh {
let d = &frame.mesh_draws[i];
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
frame.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);
} else {
let r = &transparent.producer(kind).records[i];
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
frame.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);
}
}
struct TransparentFrame<'a> {
cmd: vk::CommandBuffer,
frame_idx: usize,
layout: vk::PipelineLayout,
rt_live: bool,
textured: bool,
order: &'a [(Producer, usize)],
mesh_draws: &'a [GlassMeshDraw],
}
fn set_flipped_viewport(device: &VkDevice, cmd: vk::CommandBuffer, extent: vk::Extent2D) {
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);
unsafe {
device.cmd_set_viewport(cmd, 0, std::slice::from_ref(&vp));
device.cmd_set_scissor(cmd, 0, std::slice::from_ref(&scissor));
}
}