use ash::vk;
use concinnity_core::gfx::transform::IDENTITY;
use crate::vulkan::owned::{
OwnedFramebuffer, OwnedPipeline, OwnedPipelineLayout, OwnedRenderPass, OwnedSetLayout, VkDevice,
};
use concinnity_core::gfx::render_types::{GpuDrawArgs, GpuObjectData};
use concinnity_core::render::uniforms::{GBufferView, ModelHistoryParams};
use super::super::allocator::{DeviceAllocator, PooledBuffer};
use super::super::context::VkContext;
use super::super::pipeline::*;
use super::super::resources::{alloc_descriptor_sets, create_descriptor_set_layout};
use super::super::texture::*;
use crate::vulkan::slang_builtins::SlangCompile;
const MODEL_HISTORY_THREADGROUP: usize = 64;
pub(in crate::vulkan) const GBUFFER_NORMAL_DEPTH_FORMAT: vk::Format =
vk::Format::R16G16B16A16_SFLOAT;
pub(in crate::vulkan) const GBUFFER_ROUGHNESS_FORMAT: vk::Format = vk::Format::R8_UNORM;
pub(in crate::vulkan) const GBUFFER_VELOCITY_FORMAT: vk::Format = vk::Format::R16G16_SFLOAT;
pub(in crate::vulkan) const GBUFFER_VIEW_UBO_SIZE: vk::DeviceSize = 256;
fn create_prepass_render_pass(device: &VkDevice) -> Result<OwnedRenderPass, String> {
let attachments = [
vk::AttachmentDescription::default()
.format(GBUFFER_NORMAL_DEPTH_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),
vk::AttachmentDescription::default()
.format(GBUFFER_ROUGHNESS_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),
vk::AttachmentDescription::default()
.format(GBUFFER_VELOCITY_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),
vk::AttachmentDescription::default()
.format(vk::Format::D32_SFLOAT)
.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::DEPTH_STENCIL_ATTACHMENT_OPTIMAL),
];
let color_refs = [
vk::AttachmentReference::default()
.attachment(0)
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL),
vk::AttachmentReference::default()
.attachment(1)
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL),
vk::AttachmentReference::default()
.attachment(2)
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL),
];
let depth_ref = vk::AttachmentReference::default()
.attachment(3)
.layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
let subpass = vk::SubpassDescription::default()
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
.color_attachments(&color_refs)
.depth_stencil_attachment(&depth_ref);
let dep = vk::SubpassDependency::default()
.src_subpass(vk::SUBPASS_EXTERNAL)
.dst_subpass(0)
.src_stage_mask(
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
| vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS
| vk::PipelineStageFlags::FRAGMENT_SHADER,
)
.src_access_mask(vk::AccessFlags::SHADER_READ)
.dst_stage_mask(
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
| vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS,
)
.dst_access_mask(
vk::AccessFlags::COLOR_ATTACHMENT_WRITE
| vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE,
);
let info = vk::RenderPassCreateInfo::default()
.attachments(&attachments)
.subpasses(std::slice::from_ref(&subpass))
.dependencies(std::slice::from_ref(&dep));
device
.create_render_pass(&info)
.map_err(|e| format!("gbuffer prepass render pass: {e}"))
}
#[derive(Clone, Copy)]
struct PrepassPipelineTargets {
render_pass: vk::RenderPass,
layout: vk::PipelineLayout,
}
struct PrepassPipelineShaders<'a> {
vert_spv: &'a [u8],
frag_spv: &'a [u8],
bindings: &'a [vk::VertexInputBindingDescription],
attrs: &'a [vk::VertexInputAttributeDescription],
}
fn create_prepass_pipeline(
device: &VkDevice,
targets: PrepassPipelineTargets,
shaders: PrepassPipelineShaders,
) -> Result<OwnedPipeline, String> {
let PrepassPipelineTargets {
render_pass,
layout,
} = targets;
let PrepassPipelineShaders {
vert_spv,
frag_spv,
bindings,
attrs,
} = shaders;
let vert_mod = spv_module(device, vert_spv)?;
let frag_mod = spv_module(device, frag_spv)?;
let entry = std::ffi::CString::new("main").unwrap();
let stages = [
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::VERTEX)
.module(vert_mod.handle())
.name(&entry),
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::FRAGMENT)
.module(frag_mod.handle())
.name(&entry),
];
let vert_input = vk::PipelineVertexInputStateCreateInfo::default()
.vertex_binding_descriptions(bindings)
.vertex_attribute_descriptions(attrs);
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)
.line_width(1.0)
.cull_mode(vk::CullModeFlags::NONE)
.front_face(vk::FrontFace::COUNTER_CLOCKWISE);
let multisample = vk::PipelineMultisampleStateCreateInfo::default()
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
let depth = vk::PipelineDepthStencilStateCreateInfo::default()
.depth_test_enable(true)
.depth_write_enable(true)
.depth_compare_op(vk::CompareOp::LESS);
let blend_attaches = [
vk::PipelineColorBlendAttachmentState::default()
.color_write_mask(vk::ColorComponentFlags::RGBA)
.blend_enable(false),
vk::PipelineColorBlendAttachmentState::default()
.color_write_mask(vk::ColorComponentFlags::RGBA)
.blend_enable(false),
vk::PipelineColorBlendAttachmentState::default()
.color_write_mask(vk::ColorComponentFlags::RGBA)
.blend_enable(false),
];
let blend = vk::PipelineColorBlendStateCreateInfo::default().attachments(&blend_attaches);
let dyn_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dyn_states);
let info = vk::GraphicsPipelineCreateInfo::default()
.stages(&stages)
.vertex_input_state(&vert_input)
.input_assembly_state(&input_assembly)
.viewport_state(&viewport_state)
.rasterization_state(&raster)
.multisample_state(&multisample)
.depth_stencil_state(&depth)
.color_blend_state(&blend)
.dynamic_state(&dynamic)
.layout(layout)
.render_pass(render_pass)
.subpass(0);
let pipeline = crate::vulkan::pipeline_cache::create_graphics_pipeline(device, &info)
.map_err(|e| format!("create gbuffer prepass pso: {e}"))?;
Ok(pipeline)
}
fn vertex_56_dual_input() -> (
[vk::VertexInputBindingDescription; 2],
[vk::VertexInputAttributeDescription; 4],
) {
let bindings = [
vk::VertexInputBindingDescription::default()
.binding(0)
.stride(56)
.input_rate(vk::VertexInputRate::VERTEX),
vk::VertexInputBindingDescription::default()
.binding(1)
.stride(56)
.input_rate(vk::VertexInputRate::VERTEX),
];
let attrs = [
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(0)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(0),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(1)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(12),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(3)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(36),
vk::VertexInputAttributeDescription::default()
.binding(1)
.location(5)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(0),
];
(bindings, attrs)
}
pub(in crate::vulkan) struct GbufferBindless {
pub(in crate::vulkan) pipeline: OwnedPipeline,
pub(in crate::vulkan) pipeline_layout: OwnedPipelineLayout,
pub(in crate::vulkan) set_layout: OwnedSetLayout,
pub(in crate::vulkan) sets: Vec<vk::DescriptorSet>,
pub(in crate::vulkan) prev_model_buffers: Vec<PooledBuffer>,
pub(in crate::vulkan) history: ModelHistoryPipeline,
}
pub(in crate::vulkan) struct ModelHistoryPipeline {
pub(in crate::vulkan) pipeline: OwnedPipeline,
pub(in crate::vulkan) pipeline_layout: OwnedPipelineLayout,
pub(in crate::vulkan) _set_layout: OwnedSetLayout,
pub(in crate::vulkan) sets: Vec<vk::DescriptorSet>,
pub(in crate::vulkan) prime: std::sync::atomic::AtomicBool,
pub(in crate::vulkan) _params: PooledBuffer,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct GbufferDeviceCtx<'a> {
pub alloc: &'a DeviceAllocator,
pub device: &'a VkDevice,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct GbufferBindlessDescriptors {
pub descriptor_pool: vk::DescriptorPool,
pub bindless_set_layout: vk::DescriptorSetLayout,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct GbufferBindlessRecords<'a> {
pub object_buffers: &'a [PooledBuffer],
pub draw_args_buffers: &'a [PooledBuffer],
}
pub(in crate::vulkan) struct GbufferBindlessScene {
pub n_cull: usize,
pub frames: usize,
}
pub(in crate::vulkan) fn build_gbuffer_bindless(
ctx: GbufferDeviceCtx,
descriptors: GbufferBindlessDescriptors,
records: GbufferBindlessRecords,
gb: &GbufferResources,
scene: GbufferBindlessScene,
hot_reload: bool,
) -> Result<GbufferBindless, String> {
use super::super::builtins;
let GbufferDeviceCtx { alloc, device } = ctx;
let GbufferBindlessDescriptors {
descriptor_pool,
bindless_set_layout,
} = descriptors;
let GbufferBindlessScene { n_cull, frames } = scene;
let GbufferBindlessRecords {
object_buffers,
draw_args_buffers,
} = records;
let compile_ctx = builtins::Ctx::plain(hot_reload);
let vs = super::super::slang_builtins::GBUFFER_BINDLESS_VERT.compile(&compile_ctx)?;
let fs = super::super::slang_builtins::GBUFFER_BINDLESS_FRAG.compile(&compile_ctx)?;
let set_layout = create_descriptor_set_layout(
device,
&[
(
0,
vk::DescriptorType::UNIFORM_BUFFER,
vk::ShaderStageFlags::VERTEX,
),
(
1,
vk::DescriptorType::STORAGE_BUFFER,
vk::ShaderStageFlags::VERTEX,
),
(
2,
vk::DescriptorType::STORAGE_BUFFER,
vk::ShaderStageFlags::VERTEX,
),
],
)?;
let layouts = [set_layout.handle(), bindless_set_layout];
let pipeline_layout = device
.create_pipeline_layout(&vk::PipelineLayoutCreateInfo::default().set_layouts(&layouts))
.map_err(|e| format!("gbuffer bindless pipeline layout: {e}"))?;
let (bindings, attrs) = vertex_56_dual_input();
let pipeline = create_prepass_pipeline(
device,
PrepassPipelineTargets {
render_pass: gb.prepass_render_pass.handle(),
layout: pipeline_layout.handle(),
},
PrepassPipelineShaders {
vert_spv: &vs,
frag_spv: &fs,
bindings: &bindings,
attrs: &attrs,
},
)?;
let buf_size = (n_cull * std::mem::size_of::<[[f32; 4]; 4]>()) as u64;
let mut prev_model_buffers = Vec::with_capacity(frames);
for _ in 0..frames {
prev_model_buffers.push(alloc.create_buffer(
buf_size,
vk::BufferUsageFlags::STORAGE_BUFFER,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)?);
}
let draw_args_size = (n_cull * std::mem::size_of::<GpuDrawArgs>()) as u64;
let set_layouts: Vec<_> = (0..frames).map(|_| set_layout.handle()).collect();
let sets = alloc_descriptor_sets(device, descriptor_pool, &set_layouts)?;
for (f, &set) in sets.iter().enumerate() {
let view_info = vk::DescriptorBufferInfo::default()
.buffer(gb.view_ubo_buffers[f].buffer())
.offset(0)
.range(GBUFFER_VIEW_UBO_SIZE);
let pm_info = vk::DescriptorBufferInfo::default()
.buffer(prev_model_buffers[(f + frames - 1) % frames].buffer())
.offset(0)
.range(buf_size);
let da_info = vk::DescriptorBufferInfo::default()
.buffer(draw_args_buffers[f].buffer())
.offset(0)
.range(draw_args_size);
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::STORAGE_BUFFER)
.buffer_info(std::slice::from_ref(&pm_info)),
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(2)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(std::slice::from_ref(&da_info)),
];
unsafe { device.update_descriptor_sets(&writes, &[]) };
}
let history = build_model_history(
ctx,
descriptor_pool,
&prev_model_buffers,
object_buffers,
ModelHistoryScene { n_cull, frames },
hot_reload,
)?;
Ok(GbufferBindless {
pipeline,
pipeline_layout,
set_layout,
sets,
prev_model_buffers,
history,
})
}
#[derive(Clone, Copy)]
struct ModelHistoryScene {
n_cull: usize,
frames: usize,
}
fn build_model_history(
ctx: GbufferDeviceCtx,
descriptor_pool: vk::DescriptorPool,
history_buffers: &[PooledBuffer],
object_buffers: &[PooledBuffer],
scene: ModelHistoryScene,
hot_reload: bool,
) -> Result<ModelHistoryPipeline, String> {
let GbufferDeviceCtx { alloc, device } = ctx;
let ModelHistoryScene { n_cull, frames } = scene;
let compile_ctx = super::super::builtins::Ctx::plain(hot_reload);
let cs = super::super::slang_builtins::MODEL_HISTORY.compile(&compile_ctx)?;
let set_layout = create_descriptor_set_layout(
device,
&[
(
0,
vk::DescriptorType::UNIFORM_BUFFER,
vk::ShaderStageFlags::COMPUTE,
),
(
1,
vk::DescriptorType::STORAGE_BUFFER,
vk::ShaderStageFlags::COMPUTE,
),
(
2,
vk::DescriptorType::STORAGE_BUFFER,
vk::ShaderStageFlags::COMPUTE,
),
],
)?;
let layouts = [set_layout.handle()];
let pipeline_layout = device
.create_pipeline_layout(&vk::PipelineLayoutCreateInfo::default().set_layouts(&layouts))
.map_err(|e| format!("model history pipeline layout: {e}"))?;
let pipeline = create_cull_pipeline(device, pipeline_layout.handle(), &cs)?;
let params = ModelHistoryParams {
record_count: n_cull as u32,
_pad: [0; 3],
};
let params_size = std::mem::size_of::<ModelHistoryParams>() as u64;
let params_buf = alloc.create_buffer(
params_size,
vk::BufferUsageFlags::UNIFORM_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?;
params_buf.write_val(0, ¶ms);
let object_size = (n_cull * std::mem::size_of::<GpuObjectData>()) as u64;
let history_size = (n_cull * std::mem::size_of::<[[f32; 4]; 4]>()) as u64;
let set_layouts: Vec<_> = (0..frames * frames).map(|_| set_layout.handle()).collect();
let sets = alloc_descriptor_sets(device, descriptor_pool, &set_layouts)?;
for (i, &set) in sets.iter().enumerate() {
let (f, slot) = (i / frames, i % frames);
let p_info = vk::DescriptorBufferInfo::default()
.buffer(params_buf.buffer())
.offset(0)
.range(params_size);
let o_info = vk::DescriptorBufferInfo::default()
.buffer(object_buffers[f].buffer())
.offset(0)
.range(object_size);
let h_info = vk::DescriptorBufferInfo::default()
.buffer(history_buffers[slot].buffer())
.offset(0)
.range(history_size);
let writes = [
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.buffer_info(std::slice::from_ref(&p_info)),
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(1)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(std::slice::from_ref(&o_info)),
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(2)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(std::slice::from_ref(&h_info)),
];
unsafe { device.update_descriptor_sets(&writes, &[]) };
}
Ok(ModelHistoryPipeline {
pipeline,
pipeline_layout,
_set_layout: set_layout,
sets,
prime: std::sync::atomic::AtomicBool::new(false),
_params: params_buf,
})
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct PooledTarget {
pub image: vk::Image,
pub view: vk::ImageView,
}
#[derive(Clone, Default)]
pub(in crate::vulkan) struct GbufferPooled {
pub normal_depth: Vec<PooledTarget>,
pub roughness: Vec<PooledTarget>,
pub velocity: Vec<PooledTarget>,
}
pub(in crate::vulkan) struct GbufferResources {
pub(in crate::vulkan) prepass_render_pass: OwnedRenderPass,
pub(in crate::vulkan) view_ubo_buffers: Vec<PooledBuffer>,
pub(in crate::vulkan) normal_depth_images: Vec<PooledTarget>,
pub(in crate::vulkan) roughness_images: Vec<PooledTarget>,
pub(in crate::vulkan) velocity_images: Vec<PooledTarget>,
pub(in crate::vulkan) depth_images: Vec<GpuImage>,
pub(in crate::vulkan) framebuffers: Vec<OwnedFramebuffer>,
pub(in crate::vulkan) prev_view_proj: [[f32; 4]; 4],
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct GbufferQueueCtx {
pub command_pool: vk::CommandPool,
pub queue: vk::Queue,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct GbufferExtent {
pub width: u32,
pub height: u32,
pub frames: usize,
}
impl GbufferResources {
pub(in crate::vulkan) fn new(
ctx: GbufferDeviceCtx,
queue: GbufferQueueCtx,
extent: GbufferExtent,
pooled: &GbufferPooled,
) -> Result<Self, String> {
let GbufferDeviceCtx { alloc, device } = ctx;
let GbufferExtent { frames, .. } = extent;
let prepass_render_pass = create_prepass_render_pass(device)?;
let mut view_ubo_buffers = Vec::with_capacity(frames);
for _ in 0..frames {
let buf = alloc.create_buffer(
GBUFFER_VIEW_UBO_SIZE,
vk::BufferUsageFlags::UNIFORM_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?;
view_ubo_buffers.push(buf);
}
let mut me = Self {
prepass_render_pass,
view_ubo_buffers,
normal_depth_images: Vec::new(),
roughness_images: Vec::new(),
velocity_images: Vec::new(),
depth_images: Vec::new(),
framebuffers: Vec::new(),
prev_view_proj: IDENTITY,
};
me.build_targets(ctx, queue, extent, pooled)?;
Ok(me)
}
fn build_targets(
&mut self,
ctx: GbufferDeviceCtx,
queue: GbufferQueueCtx,
extent: GbufferExtent,
pooled: &GbufferPooled,
) -> Result<(), String> {
let GbufferDeviceCtx { alloc, device } = ctx;
let GbufferQueueCtx {
command_pool,
queue,
} = queue;
let GbufferExtent {
width,
height,
frames,
} = extent;
let w = width.max(1);
let h = height.max(1);
for f in 0..frames {
let normal_depth = *pooled
.normal_depth
.get(f)
.ok_or("gbuffer: pooled normal_depth slot out of range")?;
let roughness = *pooled
.roughness
.get(f)
.ok_or("gbuffer: pooled roughness slot out of range")?;
let velocity = *pooled
.velocity
.get(f)
.ok_or("gbuffer: pooled velocity slot out of range")?;
let depth = create_depth_image(
&GpuUploadContext {
alloc,
device,
command_pool,
queue,
},
w,
h,
vk::SampleCountFlags::TYPE_1,
)?;
let attachments = [normal_depth.view, roughness.view, velocity.view, depth.view];
let framebuffer = device
.create_framebuffer(
&vk::FramebufferCreateInfo::default()
.render_pass(self.prepass_render_pass.handle())
.attachments(&attachments)
.width(w)
.height(h)
.layers(1),
)
.map_err(|e| format!("gbuffer prepass framebuffer: {e}"))?;
self.normal_depth_images.push(normal_depth);
self.roughness_images.push(roughness);
self.velocity_images.push(velocity);
self.depth_images.push(depth);
self.framebuffers.push(framebuffer);
}
Ok(())
}
pub(in crate::vulkan) fn normal_depth_view(&self, frame: usize) -> vk::ImageView {
self.normal_depth_images[frame].view
}
pub(in crate::vulkan) fn roughness_view(&self, frame: usize) -> vk::ImageView {
self.roughness_images[frame].view
}
pub(in crate::vulkan) fn velocity_view(&self, frame: usize) -> vk::ImageView {
self.velocity_images[frame].view
}
pub(in crate::vulkan) fn normal_depth_views(&self) -> Vec<vk::ImageView> {
(0..self.normal_depth_images.len())
.map(|f| self.normal_depth_view(f))
.collect()
}
pub(in crate::vulkan) fn roughness_views(&self) -> Vec<vk::ImageView> {
(0..self.roughness_images.len())
.map(|f| self.roughness_view(f))
.collect()
}
pub(in crate::vulkan) fn velocity_views(&self) -> Vec<vk::ImageView> {
(0..self.velocity_images.len())
.map(|f| self.velocity_view(f))
.collect()
}
fn destroy_targets(&mut self, _device: &VkDevice) {
self.framebuffers.clear();
self.normal_depth_images.clear();
self.roughness_images.clear();
self.velocity_images.clear();
self.depth_images.clear();
}
pub(in crate::vulkan) fn rebuild(
&mut self,
ctx: GbufferDeviceCtx,
queue: GbufferQueueCtx,
extent: GbufferExtent,
pooled: &GbufferPooled,
) -> Result<(), String> {
self.destroy_targets(ctx.device);
self.build_targets(ctx, queue, extent, pooled)?;
Ok(())
}
pub(in crate::vulkan) fn destroy(&mut self, device: &VkDevice) {
self.destroy_targets(device);
}
}
pub(in crate::vulkan) struct GbufferPrepassView {
pub jittered_vp: [[f32; 4]; 4],
pub cur_vp: [[f32; 4]; 4],
}
impl VkContext {
pub(in crate::vulkan) fn encode_gbuffer_prepass(
&self,
gb: &GbufferResources,
cmd: vk::CommandBuffer,
frame_idx: usize,
view: GbufferPrepassView,
velocity_active: bool,
) {
let GbufferPrepassView {
jittered_vp,
cur_vp,
} = view;
let device = &self.device;
let extent = self.render_extent;
let prev_vp = if velocity_active {
gb.prev_view_proj
} else {
cur_vp
};
let view_uni = GBufferView {
jittered_vp,
cur_vp,
prev_vp,
view: self.view.matrix,
};
gb.view_ubo_buffers[frame_idx].write_val(0, &view_uni);
let clears = [
vk::ClearValue {
color: vk::ClearColorValue {
float32: [0.0, 0.0, 0.0, 0.0],
},
},
vk::ClearValue {
color: vk::ClearColorValue {
float32: [1.0, 0.0, 0.0, 0.0],
},
},
vk::ClearValue {
color: vk::ClearColorValue { float32: [0.0; 4] },
},
vk::ClearValue {
depth_stencil: vk::ClearDepthStencilValue {
depth: 1.0,
stencil: 0,
},
},
];
let rp_begin = vk::RenderPassBeginInfo::default()
.render_pass(gb.prepass_render_pass.handle())
.framebuffer(gb.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) };
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));
}
self.encode_gbuffer_prepass_gpu_driven(cmd, frame_idx, velocity_active);
unsafe { device.cmd_end_render_pass(cmd) };
self.encode_model_history(cmd, frame_idx);
}
fn encode_model_history(&self, cmd: vk::CommandBuffer, frame_idx: usize) {
let Some(history) = self.cull.model_history.as_ref() else {
return;
};
let records = self.cull_count();
if records == 0 {
return;
}
let frames = self.cull.prev_model_buffers.len();
let slots = match history
.prime
.swap(false, std::sync::atomic::Ordering::Relaxed)
{
true => 0..frames,
false => frame_idx..frame_idx + 1,
};
let device = &self.device;
let groups = records.div_ceil(MODEL_HISTORY_THREADGROUP) as u32;
for slot in slots {
let Some(&set) = history.sets.get(frame_idx * frames + slot) else {
continue;
};
unsafe {
self.model_history_barrier(
cmd,
slot,
(
vk::AccessFlags::SHADER_READ,
vk::AccessFlags::SHADER_WRITE,
vk::PipelineStageFlags::VERTEX_SHADER,
vk::PipelineStageFlags::COMPUTE_SHADER,
),
);
device.cmd_bind_pipeline(
cmd,
vk::PipelineBindPoint::COMPUTE,
history.pipeline.handle(),
);
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::COMPUTE,
history.pipeline_layout.handle(),
0,
&[set],
&[],
);
device.cmd_dispatch(cmd, groups, 1, 1);
self.model_history_barrier(
cmd,
slot,
(
vk::AccessFlags::SHADER_WRITE,
vk::AccessFlags::SHADER_READ,
vk::PipelineStageFlags::COMPUTE_SHADER,
vk::PipelineStageFlags::VERTEX_SHADER,
),
);
}
}
}
unsafe fn model_history_barrier(
&self,
cmd: vk::CommandBuffer,
slot: usize,
deps: (
vk::AccessFlags,
vk::AccessFlags,
vk::PipelineStageFlags,
vk::PipelineStageFlags,
),
) {
let Some(buf) = self.cull.prev_model_buffers.get(slot) else {
return;
};
let (src_access, dst_access, src_stage, dst_stage) = deps;
let barrier = vk::BufferMemoryBarrier::default()
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.buffer(buf.buffer())
.offset(0)
.size(vk::WHOLE_SIZE)
.src_access_mask(src_access)
.dst_access_mask(dst_access);
unsafe {
self.device.cmd_pipeline_barrier(
cmd,
src_stage,
dst_stage,
vk::DependencyFlags::empty(),
&[],
std::slice::from_ref(&barrier),
&[],
)
};
}
fn encode_gbuffer_prepass_gpu_driven(
&self,
cmd: vk::CommandBuffer,
frame_idx: usize,
velocity_active: bool,
) {
let device = &self.device;
let (Some(pipeline), Some(layout)) = (
self.cull.gbuffer_bindless_pipeline.as_ref(),
self.cull.gbuffer_bindless_pipeline_layout.as_ref(),
) else {
return;
};
let Some(indirect) = self
.cull
.indirect_buffers
.get(frame_idx)
.map(|b| b.buffer())
else {
return;
};
let Some(&gset) = self.cull.gbuffer_sets.get(frame_idx) else {
return;
};
let stride = std::mem::size_of::<vk::DrawIndexedIndirectCommand>() as u32;
let prefix = self.skinned_record_base() as u32;
unsafe {
device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, pipeline.handle());
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
layout.handle(),
0,
&[gset, self.cull.bindless_sets[frame_idx]],
&[],
);
device.cmd_bind_vertex_buffers(
cmd,
0,
&[
self.geometry.vertex_buffer.buffer(),
self.geometry.vertex_buffer.buffer(),
],
&[0, 0],
);
device.cmd_bind_index_buffer(
cmd,
self.geometry.index_buffer.buffer(),
0,
vk::IndexType::UINT32,
);
if prefix > 0 {
device.cmd_draw_indexed_indirect(cmd, indirect, 0, prefix, stride);
self.inc_draw_calls(1);
}
}
if prefix > 0 {
self.inc_draw_calls(self.draw_bucket_regions_shared_pipeline(cmd, indirect, prefix));
}
if self.draw.n_skinned > 0
&& let Some(cur) = self.skinned.deformed.get(frame_idx)
{
let frames = self.frames_in_flight.max(1);
let use_prev = velocity_active
&& frames >= 2
&& self
.skinned
.deformed_primed
.load(std::sync::atomic::Ordering::Relaxed);
let prev_idx = if use_prev {
(frame_idx + frames - 1) % frames
} else {
frame_idx
};
let prev = self.skinned.deformed.get(prev_idx).unwrap_or(cur);
unsafe {
device.cmd_bind_vertex_buffers(cmd, 0, &[cur.buffer, prev.buffer], &[0, 0]);
device.cmd_bind_index_buffer(
cmd,
self.skinned.index_buffer.buffer(),
0,
vk::IndexType::UINT32,
);
device.cmd_draw_indexed_indirect(
cmd,
indirect,
(self.skinned_record_base() * stride as usize) as u64,
self.draw.n_skinned as u32,
stride,
);
}
self.inc_draw_calls(1);
self.skinned
.deformed_primed
.store(true, std::sync::atomic::Ordering::Relaxed);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gb_view_uniforms_fits_ubo_allocation() {
assert!(std::mem::size_of::<GBufferView>() as u64 <= GBUFFER_VIEW_UBO_SIZE);
}
#[test]
fn gbuffer_shaders_compile() {
if !crate::slangc_gate::slangc_available() {
return;
}
let ctx = super::super::super::builtins::Ctx::plain(false);
super::super::super::slang_builtins::GBUFFER_BINDLESS_VERT
.compile(&ctx)
.expect("gbuffer bindless vertex compiles");
super::super::super::slang_builtins::GBUFFER_BINDLESS_FRAG
.compile(&ctx)
.expect("gbuffer bindless fragment compiles");
}
}