use ash::vk;
use concinnity_core::gfx::render_types::LineVertex;
use concinnity_core::render::buffer_growth::grow_capacity;
use concinnity_core::render::error::RenderResult;
use super::allocator::{DeviceAllocator, PooledBuffer};
use super::context::VkContext;
use super::descriptor_layout::{Binding, PoolSizes};
use super::pipeline_desc::{Blend, GraphicsPipelineDesc};
use super::resources::{alloc_descriptor_sets, create_descriptor_set_layout};
use super::set_writes::SetWrites;
use crate::vulkan::builtin_shaders::CompileProgram;
use crate::vulkan::owned::{
OwnedDescriptorPool, OwnedFramebuffer, OwnedPipeline, OwnedPipelineLayout, OwnedRenderPass,
OwnedSetLayout, VkDevice,
};
use crate::vulkan::record::Recorder;
const OCCLUDED_ALPHA: f32 = 0.12;
const MIN_VERTEX_CAPACITY: u64 = 64 * 1024;
pub(in crate::vulkan) use concinnity_core::render::uniforms::LineView;
pub(in crate::vulkan) struct LineState {
pub resources: Option<LineResources>,
pub build_failed: bool,
}
impl LineState {
pub(in crate::vulkan) fn empty() -> Self {
Self {
resources: None,
build_failed: false,
}
}
}
struct VertexSlot {
buffer: PooledBuffer,
capacity: u64,
}
pub(in crate::vulkan) struct LineResources {
render_pass: OwnedRenderPass,
pub(in crate::vulkan) pipeline: OwnedPipeline,
pipeline_layout: OwnedPipelineLayout,
_view_set_layout: OwnedSetLayout,
_descriptor_pool: OwnedDescriptorPool,
view_ubos: Vec<PooledBuffer>,
vertex_slots: Vec<VertexSlot>,
view_sets: Vec<vk::DescriptorSet>,
framebuffers: Vec<OwnedFramebuffer>,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct LineDeviceContext<'a> {
pub(in crate::vulkan) alloc: &'a DeviceAllocator,
pub(in crate::vulkan) device: &'a VkDevice,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct LinePassTargets<'a> {
pub(in crate::vulkan) hdr_format: vk::Format,
pub(in crate::vulkan) hdr_resolve_views: &'a [vk::ImageView],
pub(in crate::vulkan) depth_views: &'a [vk::ImageView],
pub(in crate::vulkan) extent: vk::Extent2D,
}
impl LineResources {
fn new(
ctx: LineDeviceContext,
targets: LinePassTargets,
frames: usize,
msaa: bool,
hot_reload: bool,
) -> RenderResult<Self> {
let LineDeviceContext { alloc, device } = ctx;
let LinePassTargets {
hdr_format,
hdr_resolve_views,
depth_views,
extent,
} = targets;
let render_pass = create_line_render_pass(device, hdr_format)?;
let view_set_layout = create_descriptor_set_layout(device, &view_set_bindings())?;
let pipeline_layout = create_line_pipeline_layout(device, view_set_layout.handle())?;
let (vert_spv, frag_spv) = compile_line_shaders(hot_reload, msaa)?;
let pipeline = create_line_pipeline(
device,
render_pass.handle(),
pipeline_layout.handle(),
&vert_spv,
&frag_spv,
)?;
let view_size = std::mem::size_of::<LineView>() as u64;
let mut view_ubos = Vec::with_capacity(frames);
let mut vertex_slots = 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,
)?);
vertex_slots.push(new_vertex_slot(alloc, MIN_VERTEX_CAPACITY)?);
}
let descriptor_pool = create_line_descriptor_pool(device, frames)?;
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() {
SetWrites::new(set)
.uniform_buffer(0, view_ubos[i].buffer(), view_size)
.sampled_image(1, depth_views[i.min(depth_views.len().saturating_sub(1))])
.apply(device);
}
let mut framebuffers = Vec::with_capacity(frames);
for &view in hdr_resolve_views.iter().take(frames) {
framebuffers.push(create_line_framebuffer(
device,
render_pass.handle(),
view,
extent,
)?);
}
Ok(Self {
render_pass,
pipeline,
pipeline_layout,
_view_set_layout: view_set_layout,
_descriptor_pool: descriptor_pool,
view_ubos,
vertex_slots,
view_sets,
framebuffers,
})
}
pub(in crate::vulkan) fn rebuild(
&mut self,
device: &VkDevice,
hdr_resolve_views: &[vk::ImageView],
depth_views: &[vk::ImageView],
extent: vk::Extent2D,
) -> RenderResult<()> {
self.framebuffers.clear();
for &view in hdr_resolve_views.iter().take(self.view_ubos.len()) {
self.framebuffers.push(create_line_framebuffer(
device,
self.render_pass.handle(),
view,
extent,
)?);
}
for (i, &set) in self.view_sets.iter().enumerate() {
SetWrites::new(set)
.sampled_image(1, depth_views[i.min(depth_views.len().saturating_sub(1))])
.apply(device);
}
Ok(())
}
pub(in crate::vulkan) fn destroy(&mut self, _device: &VkDevice) {
self.framebuffers.clear();
self.view_ubos.clear();
self.vertex_slots.clear();
}
}
fn new_vertex_slot(alloc: &DeviceAllocator, capacity: u64) -> RenderResult<VertexSlot> {
let buffer = alloc.create_buffer(
capacity,
vk::BufferUsageFlags::VERTEX_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?;
Ok(VertexSlot { buffer, capacity })
}
fn create_line_framebuffer(
device: &VkDevice,
render_pass: vk::RenderPass,
view: vk::ImageView,
extent: vk::Extent2D,
) -> RenderResult<OwnedFramebuffer> {
let attachments = [view];
let info = vk::FramebufferCreateInfo::default()
.render_pass(render_pass)
.attachments(&attachments)
.width(extent.width.max(1))
.height(extent.height.max(1))
.layers(1);
device
.create_framebuffer(&info)
.map_err(|e| super::error::map_vk_result(e, "line framebuffer"))
}
fn create_line_render_pass(device: &VkDevice, format: vk::Format) -> RenderResult<OwnedRenderPass> {
let attachment = 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 dep_in = vk::SubpassDependency::default()
.src_subpass(vk::SUBPASS_EXTERNAL)
.dst_subpass(0)
.src_stage_mask(
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
| vk::PipelineStageFlags::FRAGMENT_SHADER,
)
.src_access_mask(vk::AccessFlags::SHADER_READ | vk::AccessFlags::COLOR_ATTACHMENT_WRITE)
.dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
.dst_access_mask(
vk::AccessFlags::COLOR_ATTACHMENT_WRITE | vk::AccessFlags::COLOR_ATTACHMENT_READ,
);
let dep_out = 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 deps = [dep_in, dep_out];
let info = vk::RenderPassCreateInfo::default()
.attachments(std::slice::from_ref(&attachment))
.subpasses(std::slice::from_ref(&subpass))
.dependencies(&deps);
device
.create_render_pass(&info)
.map_err(|e| super::error::map_vk_result(e, "line render pass"))
}
fn view_set_bindings() -> [Binding; 2] {
use vk::DescriptorType as T;
let frag = vk::ShaderStageFlags::FRAGMENT;
[
(0, T::UNIFORM_BUFFER, vk::ShaderStageFlags::VERTEX | frag),
(1, T::SAMPLED_IMAGE, frag),
]
}
fn create_line_pipeline_layout(
device: &VkDevice,
view_set_layout: vk::DescriptorSetLayout,
) -> RenderResult<OwnedPipelineLayout> {
let set_layouts = [view_set_layout];
let info = vk::PipelineLayoutCreateInfo::default().set_layouts(&set_layouts);
device
.create_pipeline_layout(&info)
.map_err(|e| super::error::map_vk_result(e, "line pipeline layout"))
}
fn create_line_descriptor_pool(
device: &VkDevice,
frames: usize,
) -> RenderResult<OwnedDescriptorPool> {
let frames = frames as u32;
let sizes = PoolSizes::default()
.sets(&view_set_bindings(), frames)
.build();
let info = vk::DescriptorPoolCreateInfo::default()
.max_sets(frames)
.pool_sizes(&sizes);
device
.create_descriptor_pool(&info)
.map_err(|e| super::error::map_vk_result(e, "line descriptor pool"))
}
fn compile_line_shaders(hot_reload: bool, msaa: bool) -> RenderResult<(Vec<u8>, Vec<u8>)> {
let vert = super::builtin_shaders::LINE_VERT.compile(hot_reload)?;
let frag = super::builtin_shaders::LINE_FRAG
.at(msaa)
.compile(hot_reload)?;
Ok((vert, frag))
}
pub(in crate::vulkan) fn rebuild_line_pipeline(
device: &VkDevice,
lines: &LineResources,
msaa: bool,
hot_reload: bool,
) -> RenderResult<OwnedPipeline> {
let (vert_spv, frag_spv) = compile_line_shaders(hot_reload, msaa)?;
create_line_pipeline(
device,
lines.render_pass.handle(),
lines.pipeline_layout.handle(),
&vert_spv,
&frag_spv,
)
}
fn create_line_pipeline(
device: &VkDevice,
render_pass: vk::RenderPass,
layout: vk::PipelineLayout,
vert_spv: &[u8],
frag_spv: &[u8],
) -> RenderResult<OwnedPipeline> {
let bindings = [vk::VertexInputBindingDescription::default()
.binding(0)
.stride(std::mem::size_of::<LineVertex>() as u32)
.input_rate(vk::VertexInputRate::VERTEX)];
let attrs = [
vk::VertexInputAttributeDescription::default()
.location(0)
.binding(0)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(0),
vk::VertexInputAttributeDescription::default()
.location(1)
.binding(0)
.format(vk::Format::R32_SFLOAT)
.offset(12),
vk::VertexInputAttributeDescription::default()
.location(2)
.binding(0)
.format(vk::Format::R32G32B32A32_SFLOAT)
.offset(16),
];
GraphicsPipelineDesc {
vertex_bindings: &bindings,
vertex_attributes: &attrs,
..GraphicsPipelineDesc::fullscreen(
vert_spv,
frag_spv,
layout,
render_pass,
&[Blend::AlphaOver],
)
}
.build(device, "line")
}
impl VkContext {
pub(in crate::vulkan) fn ensure_line_pipeline(
&mut self,
frame_idx: usize,
vertices: &[LineVertex],
) {
if vertices.is_empty() || self.lines.build_failed {
return;
}
if self.lines.resources.is_none() {
let depth_views: Vec<vk::ImageView> = self
.targets
.depth_images
.iter()
.map(|img| img.view)
.collect();
let hdr_resolve_views: Vec<vk::ImageView> = self
.targets
.hdr_resolve_images
.iter()
.map(|img| img.view)
.collect();
let built = LineResources::new(
LineDeviceContext {
alloc: &self.hw.alloc,
device: &self.hw.device,
},
LinePassTargets {
hdr_format: super::context::HDR_FORMAT,
hdr_resolve_views: &hdr_resolve_views,
depth_views: &depth_views,
extent: self.targets.render_extent,
},
self.frames_in_flight,
self.targets.msaa_samples != vk::SampleCountFlags::TYPE_1,
self.hot_reload.enabled,
);
match built {
Ok(r) => self.lines.resources = Some(r),
Err(e) => {
self.lines.build_failed = true;
tracing::error!("line pipeline: {}", e);
return;
}
}
}
if let Err(e) =
self.grow_line_vertex_slot(frame_idx, std::mem::size_of_val(vertices) as u64)
{
self.lines.build_failed = true;
tracing::error!("line vertex buffer: {}", e);
}
}
fn grow_line_vertex_slot(&mut self, frame_idx: usize, needed: u64) -> RenderResult<()> {
let Some(lines) = self.lines.resources.as_mut() else {
return Ok(());
};
let Some(slot) = lines.vertex_slots.get_mut(frame_idx) else {
return Ok(());
};
let Some(capacity) = grow_capacity(slot.capacity, needed, MIN_VERTEX_CAPACITY) else {
return Ok(());
};
*slot = new_vertex_slot(&self.hw.alloc, capacity)?;
Ok(())
}
pub(in crate::vulkan) fn encode_lines(
&self,
rec: &Recorder<'_>,
frame_idx: usize,
vp: [[f32; 4]; 4],
vertices: &[LineVertex],
) {
let Some(lines) = self.lines.resources.as_ref() else {
return;
};
if vertices.is_empty() {
return;
}
let Some(slot) = lines.vertex_slots.get(frame_idx) else {
return;
};
let bytes = std::mem::size_of_val(vertices) as u64;
if bytes > slot.capacity {
return;
}
let extent = self.targets.render_extent;
let view_uni = LineView {
vp,
occluded_alpha: OCCLUDED_ALPHA,
_pad: [0.0; 3],
};
lines.view_ubos[frame_idx].write_val(0, &view_uni);
slot.buffer.write_slice(0, vertices);
let vp_state = 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);
rec.begin_render_pass(
&lines.render_pass,
&lines.framebuffers[frame_idx],
vk::Rect2D::default().extent(extent),
&[],
);
rec.set_viewport(&vp_state);
rec.set_scissor(&scissor);
rec.bind_pipeline(vk::PipelineBindPoint::GRAPHICS, &lines.pipeline);
rec.bind_descriptor_sets(
vk::PipelineBindPoint::GRAPHICS,
&lines.pipeline_layout,
0,
std::slice::from_ref(&lines.view_sets[frame_idx]),
&[],
);
rec.bind_vertex_buffers(0, &[slot.buffer.buffer()], &[0]);
rec.draw(vertices.len() as u32, 1, 0, 0);
rec.end_render_pass();
self.inc_draw_calls(1);
}
}