use ash::vk;
use concinnity_core::gfx::frustum::Frustum;
use concinnity_core::render::decal::DecalRecord;
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::transform::mat4_inverse;
use std::cell::Cell;
use concinnity_core::render::uniforms::DecalView;
use super::allocator::{DeviceAllocator, PooledBuffer};
use super::context::VkContext;
use super::descriptor_layout::{Binding, PoolSizes};
use super::pipeline_desc::{Blend, GraphicsPipelineDesc, Raster};
use super::resources::{alloc_descriptor_sets, create_descriptor_set_layout, source_set_bindings};
use super::set_writes::SetWrites;
use super::texture::GpuImage;
use crate::vulkan::builtin_shaders::CompileProgram;
use crate::vulkan::owned::{
OwnedDescriptorPool, OwnedFramebuffer, OwnedPipeline, OwnedPipelineLayout, OwnedRenderPass,
OwnedSetLayout, VkDevice,
};
pub(in crate::vulkan) const MAX_DECALS: usize = 256;
const CUBE_VERTS: [f32; 24] = [
-0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5,
0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5,
];
const CUBE_INDICES: [u16; 36] = [
0, 2, 1, 0, 3, 2, 4, 5, 6, 4, 6, 7, 0, 1, 5, 0, 5, 4, 3, 6, 2, 3, 7, 6, 0, 4, 7, 0, 7, 3, 1, 2, 6, 1, 6, 5,
];
const PARAMS_STRIDE: u64 = 256;
pub(in crate::vulkan) struct DecalResources {
pub(in crate::vulkan) render_pass: OwnedRenderPass,
pub(in crate::vulkan) pipeline: OwnedPipeline,
pub(in crate::vulkan) pipeline_layout: OwnedPipelineLayout,
pub(in crate::vulkan) _view_set_layout: OwnedSetLayout,
pub(in crate::vulkan) _albedo_set_layout: OwnedSetLayout,
pub(in crate::vulkan) _descriptor_pool: OwnedDescriptorPool,
pub(in crate::vulkan) vertex_buffer: PooledBuffer,
pub(in crate::vulkan) index_buffer: PooledBuffer,
pub(in crate::vulkan) view_ubos: Vec<PooledBuffer>,
pub(in crate::vulkan) params_ubos: Vec<PooledBuffer>,
pub(in crate::vulkan) view_sets: Vec<vk::DescriptorSet>,
pub(in crate::vulkan) albedo_sets: Vec<vk::DescriptorSet>,
pub(in crate::vulkan) framebuffers: Vec<OwnedFramebuffer>,
pub(in crate::vulkan) decal_texture_slots: Cell<[usize; MAX_DECALS]>,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct DecalDeviceContext<'a> {
pub(in crate::vulkan) alloc: &'a DeviceAllocator,
pub(in crate::vulkan) device: &'a VkDevice,
pub(in crate::vulkan) command_pool: vk::CommandPool,
pub(in crate::vulkan) queue: vk::Queue,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct DecalPassTargets<'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) sampler: vk::Sampler,
pub(in crate::vulkan) extent: vk::Extent2D,
}
impl DecalResources {
pub(in crate::vulkan) fn new(
ctx: DecalDeviceContext,
targets: DecalPassTargets,
frames: usize,
msaa: bool,
hot_reload: bool,
) -> RenderResult<Self> {
let DecalDeviceContext {
alloc,
device,
command_pool,
queue,
} = ctx;
let DecalPassTargets {
hdr_format,
hdr_resolve_views,
depth_views,
sampler,
extent,
} = targets;
let render_pass = create_decal_render_pass(device, hdr_format)?;
let view_set_layout = create_descriptor_set_layout(device, &view_set_bindings())?;
let albedo_set_layout = create_descriptor_set_layout(device, &source_set_bindings(1))?;
let pipeline_layout = create_decal_pipeline_layout(
device,
view_set_layout.handle(),
albedo_set_layout.handle(),
)?;
let (vert_spv, frag_spv) = compile_decal_shaders(hot_reload, msaa)?;
let pipeline = create_decal_pipeline(
device,
render_pass.handle(),
pipeline_layout.handle(),
&vert_spv,
&frag_spv,
)?;
let vertex_buffer = upload_static_buffer(
alloc,
device,
command_pool,
queue,
bytemuck_cast(&CUBE_VERTS),
vk::BufferUsageFlags::VERTEX_BUFFER,
)?;
let index_buffer = upload_static_buffer(
alloc,
device,
command_pool,
queue,
bytemuck_cast(&CUBE_INDICES),
vk::BufferUsageFlags::INDEX_BUFFER,
)?;
let mut view_ubos = Vec::with_capacity(frames);
for _ in 0..frames {
view_ubos.push(alloc.create_buffer(
std::mem::size_of::<DecalView>() as u64,
vk::BufferUsageFlags::UNIFORM_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?);
}
let params_total = PARAMS_STRIDE * MAX_DECALS as u64;
let mut params_ubos = Vec::with_capacity(frames);
for _ in 0..frames {
params_ubos.push(alloc.create_buffer(
params_total,
vk::BufferUsageFlags::UNIFORM_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?);
}
let descriptor_pool = create_decal_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(),
std::mem::size_of::<DecalView>() as u64,
)
.buffer(
1,
vk::DescriptorType::UNIFORM_BUFFER_DYNAMIC,
params_ubos[i].buffer(),
0,
PARAMS_STRIDE,
)
.sampled_image(2, depth_views[i.min(depth_views.len().saturating_sub(1))])
.apply(device);
}
let albedo_layouts: Vec<_> = (0..MAX_DECALS)
.map(|_| albedo_set_layout.handle())
.collect();
let albedo_sets = alloc_descriptor_sets(device, descriptor_pool.handle(), &albedo_layouts)?;
for &set in &albedo_sets {
SetWrites::new(set).sampler(1, sampler).apply(device);
}
let mut framebuffers = Vec::with_capacity(frames);
for &view in hdr_resolve_views.iter().take(frames) {
let attachments = [view];
let fb_info = vk::FramebufferCreateInfo::default()
.render_pass(render_pass.handle())
.attachments(&attachments)
.width(extent.width.max(1))
.height(extent.height.max(1))
.layers(1);
let fb = device
.create_framebuffer(&fb_info)
.map_err(|e| super::error::map_vk_result(e, "decal framebuffer"))?;
framebuffers.push(fb);
}
Ok(Self {
render_pass,
pipeline,
pipeline_layout,
_view_set_layout: view_set_layout,
_albedo_set_layout: albedo_set_layout,
_descriptor_pool: descriptor_pool,
vertex_buffer,
index_buffer,
view_ubos,
params_ubos,
view_sets,
albedo_sets,
framebuffers,
decal_texture_slots: Cell::new([usize::MAX; MAX_DECALS]),
})
}
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()) {
let attachments = [view];
let fb_info = vk::FramebufferCreateInfo::default()
.render_pass(self.render_pass.handle())
.attachments(&attachments)
.width(extent.width.max(1))
.height(extent.height.max(1))
.layers(1);
let fb = device
.create_framebuffer(&fb_info)
.map_err(|e| super::error::map_vk_result(e, "decal framebuffer (rebuild)"))?;
self.framebuffers.push(fb);
}
for (i, &set) in self.view_sets.iter().enumerate() {
SetWrites::new(set)
.sampled_image(2, 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.params_ubos.clear();
self.vertex_buffer = PooledBuffer::null();
self.index_buffer = PooledBuffer::null();
}
}
fn create_decal_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, "decal render pass"))
}
fn view_set_bindings() -> [Binding; 3] {
use vk::DescriptorType as T;
let vert_frag = vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT;
[
(0, T::UNIFORM_BUFFER, vert_frag),
(1, T::UNIFORM_BUFFER_DYNAMIC, vert_frag),
(2, T::SAMPLED_IMAGE, vk::ShaderStageFlags::FRAGMENT),
]
}
fn create_decal_pipeline_layout(
device: &VkDevice,
view_set_layout: vk::DescriptorSetLayout,
albedo_set_layout: vk::DescriptorSetLayout,
) -> RenderResult<OwnedPipelineLayout> {
let set_layouts = [view_set_layout, albedo_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, "decal pipeline layout"))
}
fn create_decal_descriptor_pool(
device: &VkDevice,
frames: usize,
) -> RenderResult<OwnedDescriptorPool> {
let frames = frames as u32;
let max_decals = MAX_DECALS as u32;
let sizes = PoolSizes::default()
.sets(&view_set_bindings(), frames)
.sets(&source_set_bindings(1), max_decals)
.build();
let info = vk::DescriptorPoolCreateInfo::default()
.max_sets(frames + max_decals)
.pool_sizes(&sizes);
device
.create_descriptor_pool(&info)
.map_err(|e| super::error::map_vk_result(e, "decal descriptor pool"))
}
fn compile_decal_shaders(hot_reload: bool, msaa: bool) -> RenderResult<(Vec<u8>, Vec<u8>)> {
let vert = super::builtin_shaders::DECAL_VERT.compile(hot_reload)?;
let frag = super::builtin_shaders::DECAL_FRAG
.at(msaa)
.compile(hot_reload)?;
Ok((vert, frag))
}
pub(in crate::vulkan) fn rebuild_decal_pipeline(
device: &VkDevice,
decals: &DecalResources,
msaa: bool,
hot_reload: bool,
) -> RenderResult<OwnedPipeline> {
let (vert_spv, frag_spv) = compile_decal_shaders(hot_reload, msaa)?;
create_decal_pipeline(
device,
decals.render_pass.handle(),
decals.pipeline_layout.handle(),
&vert_spv,
&frag_spv,
)
}
fn create_decal_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(12) .input_rate(vk::VertexInputRate::VERTEX)];
let attrs = [vk::VertexInputAttributeDescription::default()
.location(0)
.binding(0)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(0)];
GraphicsPipelineDesc {
raster: Raster {
cull: vk::CullModeFlags::FRONT,
..Raster::default()
},
vertex_bindings: &bindings,
vertex_attributes: &attrs,
..GraphicsPipelineDesc::fullscreen(
vert_spv,
frag_spv,
layout,
render_pass,
&[Blend::AlphaOver],
)
}
.build(device, "decal")
}
fn upload_static_buffer(
alloc: &DeviceAllocator,
device: &VkDevice,
command_pool: vk::CommandPool,
queue: vk::Queue,
data: &[u8],
usage: vk::BufferUsageFlags,
) -> RenderResult<PooledBuffer> {
let size = data.len() as vk::DeviceSize;
let staging = alloc.create_buffer(
size,
vk::BufferUsageFlags::TRANSFER_SRC,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?;
staging.write_bytes(0, data);
let buf = alloc.create_buffer(
size,
usage | vk::BufferUsageFlags::TRANSFER_DST,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)?;
super::texture::one_shot_submit(device, command_pool, queue, |cmd| {
let region = vk::BufferCopy::default().size(size);
unsafe {
device.cmd_copy_buffer(
cmd,
staging.buffer(),
buf.buffer(),
std::slice::from_ref(®ion),
)
};
})?;
Ok(buf)
}
fn bytemuck_cast<T: bytemuck::NoUninit>(s: &[T]) -> &[u8] {
bytemuck::cast_slice(s)
}
impl VkContext {
pub(in crate::vulkan) fn encode_decals(
&self,
cmd: vk::CommandBuffer,
frame_idx: usize,
vp: [[f32; 4]; 4],
frustum: &Frustum,
) {
let decals = match &self.decal.resources {
Some(s) => s,
None => return,
};
let mut visible = self.decal.set.visible(frustum).peekable();
if visible.peek().is_none() {
return;
}
let device = &self.hw.device;
let extent = self.targets.render_extent;
let inv_vp = mat4_inverse(vp);
let viewport_pix = [extent.width as f32, extent.height as f32];
let view_uni = DecalView {
vp,
inv_vp,
viewport: viewport_pix,
_pad: [0.0; 2],
};
decals.view_ubos[frame_idx].write_val(0, &view_uni);
let rp_begin = vk::RenderPassBeginInfo::default()
.render_pass(decals.render_pass.handle())
.framebuffer(decals.framebuffers[frame_idx].handle())
.render_area(vk::Rect2D::default().extent(extent));
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);
unsafe {
device.cmd_begin_render_pass(cmd, &rp_begin, vk::SubpassContents::INLINE);
device.cmd_set_viewport(cmd, 0, std::slice::from_ref(&vp_state));
device.cmd_set_scissor(cmd, 0, std::slice::from_ref(&scissor));
device.cmd_bind_pipeline(
cmd,
vk::PipelineBindPoint::GRAPHICS,
decals.pipeline.handle(),
);
device.cmd_bind_vertex_buffers(cmd, 0, &[decals.vertex_buffer.buffer()], &[0]);
device.cmd_bind_index_buffer(
cmd,
decals.index_buffer.buffer(),
0,
vk::IndexType::UINT16,
);
}
for decal in visible {
if decal.take_upload(frame_idx) {
decals.params_ubos[frame_idx]
.write_val(decal.id * PARAMS_STRIDE as usize, decal.params);
}
let dynamic_offset = (decal.id as u64 * PARAMS_STRIDE) as u32;
unsafe {
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
decals.pipeline_layout.handle(),
0,
std::slice::from_ref(&decals.view_sets[frame_idx]),
std::slice::from_ref(&dynamic_offset),
);
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
decals.pipeline_layout.handle(),
1,
std::slice::from_ref(&decals.albedo_sets[decal.id]),
&[],
);
device.cmd_draw_indexed(cmd, 36, 1, 0, 0, 0);
}
self.inc_draw_calls(1);
}
unsafe {
device.cmd_end_render_pass(cmd);
}
}
}
impl VkContext {
pub(crate) fn add_decal(&mut self, record: DecalRecord) -> RenderResult<usize> {
let last_tex = self.scene.textures.len().saturating_sub(1);
let tex_idx = record.texture_slot.min(last_tex);
let id = self.decal.set.insert(record).map_err(|_| {
RenderError::Other(format!("add_decal: MAX_DECALS ({MAX_DECALS}) exceeded"))
})?;
let decals = self.decal.resources.as_ref().ok_or_else(|| {
RenderError::Other("add_decal: decal pipeline unavailable".to_string())
})?;
write_albedo_image(
&self.hw.device,
decals.albedo_sets[id],
self.scene.textures[tex_idx].view,
);
let mut slots = decals.decal_texture_slots.get();
slots[id] = tex_idx;
decals.decal_texture_slots.set(slots);
Ok(id)
}
pub(crate) fn remove_decal(&mut self, decal_id: usize) -> RenderResult<()> {
self.decal
.set
.remove(decal_id)
.map_err(|e| RenderError::Other(format!("remove_decal: id {decal_id} {e}")))?;
if let Some(decals) = &self.decal.resources {
let mut slots = decals.decal_texture_slots.get();
slots[decal_id] = usize::MAX;
decals.decal_texture_slots.set(slots);
}
Ok(())
}
pub(in crate::vulkan) fn decal_samples_slot(&self, slot: usize) -> bool {
match &self.decal.resources {
Some(s) => s.decal_texture_slots.get().contains(&slot),
None => false,
}
}
pub(in crate::vulkan) fn rewrite_decal_albedo_slot(&self, slot: usize) {
let decals = match &self.decal.resources {
Some(s) => s,
None => return,
};
let slots = decals.decal_texture_slots.get();
let last_tex = self.scene.textures.len().saturating_sub(1);
for (id, &tex_slot) in slots.iter().enumerate() {
if tex_slot == slot {
let view = self.scene.textures[tex_slot.min(last_tex)].view;
write_albedo_image(&self.hw.device, decals.albedo_sets[id], view);
}
}
}
}
fn write_albedo_image(device: &VkDevice, set: vk::DescriptorSet, view: vk::ImageView) {
SetWrites::new(set).sampled_image(0, view).apply(device);
}
impl VkContext {
pub(in crate::vulkan) fn upload_initial_decals(
&mut self,
records: Vec<DecalRecord>,
) -> RenderResult<()> {
if records.len() > MAX_DECALS {
return Err(RenderError::Other(format!(
"decals: {} authored decals exceed MAX_DECALS ({})",
records.len(),
MAX_DECALS
)));
}
for rec in records {
self.add_decal(rec)?;
}
Ok(())
}
}
type _Marker = GpuImage;