use ash::vk;
use concinnity_core::gfx::render_types::{GpuDrawArgs, GpuObjectData};
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::uniforms::{GBufferView, ModelHistoryParams};
use concinnity_core::render::view_history::{ViewFrame, ViewHistory};
use super::super::allocator::{DeviceAllocator, PooledBuffer};
use super::super::context::VkContext;
use super::super::descriptor_layout::Binding;
use super::super::pipeline::{MAIN_VERTEX_ATTRS, MeshPipelineTargets, spirv_words};
use super::super::pipeline_desc::{Blend, Depth, GraphicsPipelineDesc, compute_pipeline};
use super::super::resources::{alloc_descriptor_sets, create_descriptor_set_layout};
use super::super::set_writes::SetWrites;
use super::super::spirv_inputs::input_locations;
use super::super::texture::*;
use super::gbuffer_sky::GbufferSky;
use crate::vulkan::builtin_shaders::CompileProgram;
use crate::vulkan::depth;
use crate::vulkan::owned::{
OwnedFramebuffer, OwnedPipeline, OwnedPipelineLayout, OwnedRenderPass, OwnedSetLayout, VkDevice,
};
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 =
std::mem::size_of::<GBufferView>() as vk::DeviceSize;
pub(in crate::vulkan) fn create_prepass_render_pass(
device: &VkDevice,
) -> RenderResult<OwnedRenderPass> {
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| crate::vulkan::error::map_vk_result(e, "gbuffer prepass render pass"))
}
pub(in crate::vulkan) const PREPASS_TARGETS: [Blend; 3] = [Blend::Opaque; 3];
const VERTEX_56_DUAL_BINDINGS: [vk::VertexInputBindingDescription; 2] = [
vk::VertexInputBindingDescription {
binding: 0,
stride: 56,
input_rate: vk::VertexInputRate::VERTEX,
},
vk::VertexInputBindingDescription {
binding: 1,
stride: 56,
input_rate: vk::VertexInputRate::VERTEX,
},
];
const PREPASS_VERTEX_ATTRIBUTES: [vk::VertexInputAttributeDescription; 6] = [
MAIN_VERTEX_ATTRS[0],
MAIN_VERTEX_ATTRS[1],
MAIN_VERTEX_ATTRS[2],
MAIN_VERTEX_ATTRS[3],
MAIN_VERTEX_ATTRS[4],
vk::VertexInputAttributeDescription {
location: 5,
binding: 1,
format: vk::Format::R32G32B32_SFLOAT,
offset: 0,
},
];
pub(in crate::vulkan) struct PrepassLayout {
pub(in crate::vulkan) set_layout: OwnedSetLayout,
pub(in crate::vulkan) pipeline_layout: OwnedPipelineLayout,
}
pub(in crate::vulkan) fn build_prepass_layout(
device: &VkDevice,
global_set_layout: vk::DescriptorSetLayout,
bindless_set_layout: vk::DescriptorSetLayout,
) -> RenderResult<PrepassLayout> {
let set_layout = create_descriptor_set_layout(
device,
&history_set_bindings(vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT),
)?;
let layouts = [global_set_layout, bindless_set_layout, set_layout.handle()];
let pipeline_layout = device
.create_pipeline_layout(&vk::PipelineLayoutCreateInfo::default().set_layouts(&layouts))
.map_err(|e| crate::vulkan::error::map_vk_result(e, "gbuffer prepass pipeline layout"))?;
Ok(PrepassLayout {
set_layout,
pipeline_layout,
})
}
pub(in crate::vulkan) fn create_prepass_pipeline(
device: &VkDevice,
targets: MeshPipelineTargets<'_>,
) -> RenderResult<OwnedPipeline> {
let read = input_locations(&spirv_words(targets.vert_spv)?);
let attributes = prepass_attributes(&read)?;
GraphicsPipelineDesc {
depth: Depth::write(),
vertex_bindings: &VERTEX_56_DUAL_BINDINGS,
vertex_attributes: &attributes,
..GraphicsPipelineDesc::fullscreen(
targets.vert_spv,
targets.frag_spv,
targets.layout,
targets.render_pass,
&PREPASS_TARGETS,
)
}
.build(device, "gbuffer prepass")
}
fn prepass_attributes(read: &[u32]) -> RenderResult<Vec<vk::VertexInputAttributeDescription>> {
if let Some(missing) = read
.iter()
.find(|l| !PREPASS_VERTEX_ATTRIBUTES.iter().any(|a| a.location == **l))
{
return Err(RenderError::ShaderCompile(format!(
"G-buffer pre-pass vertex stage reads input location {missing}, which the pre-pass \
vertex layout does not offer"
)));
}
Ok(PREPASS_VERTEX_ATTRIBUTES
.iter()
.filter(|a| read.contains(&a.location))
.copied()
.collect())
}
pub(in crate::vulkan) struct GbufferBindless {
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 prepass_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,
) -> RenderResult<GbufferBindless> {
let GbufferDeviceCtx { alloc, device } = ctx;
let GbufferBindlessDescriptors {
descriptor_pool,
prepass_set_layout,
} = descriptors;
let GbufferBindlessScene { n_cull, frames } = scene;
let GbufferBindlessRecords {
object_buffers,
draw_args_buffers,
} = records;
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(|_| prepass_set_layout).collect();
let sets = alloc_descriptor_sets(device, descriptor_pool, &set_layouts)?;
for (f, &set) in sets.iter().enumerate() {
SetWrites::new(set)
.uniform_buffer(0, gb.view_ubo_buffers[f].buffer(), GBUFFER_VIEW_UBO_SIZE)
.storage_buffer(
1,
prev_model_buffers[(f + frames - 1) % frames].buffer(),
buf_size,
)
.storage_buffer(2, draw_args_buffers[f].buffer(), draw_args_size)
.apply(device);
}
let history = build_model_history(
ctx,
descriptor_pool,
&prev_model_buffers,
object_buffers,
ModelHistoryScene { n_cull, frames },
hot_reload,
)?;
Ok(GbufferBindless {
sets,
prev_model_buffers,
history,
})
}
fn history_set_bindings(stages: vk::ShaderStageFlags) -> [Binding; 3] {
use vk::DescriptorType as T;
[
(0, T::UNIFORM_BUFFER, stages),
(1, T::STORAGE_BUFFER, stages),
(2, T::STORAGE_BUFFER, stages),
]
}
#[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,
) -> RenderResult<ModelHistoryPipeline> {
let GbufferDeviceCtx { alloc, device } = ctx;
let ModelHistoryScene { n_cull, frames } = scene;
let cs = super::super::builtin_shaders::MODEL_HISTORY.compile(hot_reload)?;
let set_layout =
create_descriptor_set_layout(device, &history_set_bindings(vk::ShaderStageFlags::COMPUTE))?;
let layouts = [set_layout.handle()];
let pipeline_layout = device
.create_pipeline_layout(&vk::PipelineLayoutCreateInfo::default().set_layouts(&layouts))
.map_err(|e| crate::vulkan::error::map_vk_result(e, "model history pipeline layout"))?;
let pipeline = compute_pipeline(device, pipeline_layout.handle(), &cs, "model history")?;
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);
SetWrites::new(set)
.uniform_buffer(0, params_buf.buffer(), params_size)
.storage_buffer(1, object_buffers[f].buffer(), object_size)
.storage_buffer(2, history_buffers[slot].buffer(), history_size)
.apply(device);
}
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>,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct GbufferFrame {
pub normal_depth: vk::ImageView,
pub roughness: vk::ImageView,
pub velocity: vk::ImageView,
}
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) view_history: ViewHistory,
pub(in crate::vulkan) sky: GbufferSky,
}
#[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,
hot_reload: bool,
) -> RenderResult<Self> {
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 sky = GbufferSky::build(
device,
prepass_render_pass.handle(),
&view_ubo_buffers,
hot_reload,
)?;
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(),
view_history: ViewHistory::default(),
sky,
};
me.build_targets(ctx, queue, extent, pooled)?;
Ok(me)
}
fn build_targets(
&mut self,
ctx: GbufferDeviceCtx,
queue: GbufferQueueCtx,
extent: GbufferExtent,
pooled: &GbufferPooled,
) -> RenderResult<()> {
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_else(|| {
RenderError::Other("gbuffer: pooled normal_depth slot out of range".to_string())
})?;
let roughness = *pooled.roughness.get(f).ok_or_else(|| {
RenderError::Other("gbuffer: pooled roughness slot out of range".to_string())
})?;
let velocity = *pooled.velocity.get(f).ok_or_else(|| {
RenderError::Other("gbuffer: pooled velocity slot out of range".to_string())
})?;
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| {
crate::vulkan::error::map_vk_result(e, "gbuffer prepass framebuffer")
})?;
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 frame(&self, frame: usize) -> Option<GbufferFrame> {
Some(GbufferFrame {
normal_depth: self.normal_depth_images.get(frame)?.view,
roughness: self.roughness_images.get(frame)?.view,
velocity: self.velocity_images.get(frame)?.view,
})
}
pub(in crate::vulkan) fn has_targets(&self) -> bool {
!self.framebuffers.is_empty()
}
pub(in crate::vulkan) fn normal_depth_views(&self) -> Vec<vk::ImageView> {
self.normal_depth_images
.iter()
.map(|img| img.view)
.collect()
}
pub(in crate::vulkan) fn roughness_views(&self) -> Vec<vk::ImageView> {
self.roughness_images.iter().map(|img| img.view).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,
) -> RenderResult<()> {
self.destroy_targets(ctx.device);
let built = self.build_targets(ctx, queue, extent, pooled);
if built.is_err() {
self.destroy_targets(ctx.device);
}
built
}
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],
pub elapsed: f32,
pub cam_pos: [f32; 3],
}
impl VkContext {
pub(in crate::vulkan) fn gbuffer_targets(&self) -> Option<&GbufferResources> {
self.gbuffer.as_ref().filter(|gb| gb.has_targets())
}
pub(in crate::vulkan) fn reads_motion(&self) -> bool {
self.taa.is_some()
|| self.upscale.is_some()
|| self.ssgi.as_ref().is_some_and(|s| s.settings.contributes())
}
pub(in crate::vulkan) fn enable_gbuffer_prepass(&mut self) -> RenderResult<()> {
if let (Some(gb), Some(prepass), None) = (
self.gbuffer.as_ref(),
self.cull.prepass_layout.as_ref(),
self.cull.model_history.as_ref(),
) {
let built = build_gbuffer_bindless(
GbufferDeviceCtx {
alloc: &self.hw.alloc,
device: &self.hw.device,
},
GbufferBindlessDescriptors {
descriptor_pool: self.descriptors.descriptor_pool.handle(),
prepass_set_layout: prepass.set_layout.handle(),
},
GbufferBindlessRecords {
object_buffers: &self.cull.object_buffers,
draw_args_buffers: &self.cull.draw_args_buffers,
},
gb,
GbufferBindlessScene {
n_cull: self.cull.bucket_stride,
frames: self.frames_in_flight,
},
self.hot_reload.enabled,
)?;
self.cull.gbuffer_sets = built.sets;
self.cull.prev_model_buffers = built.prev_model_buffers;
self.cull.model_history = Some(built.history);
self.state.model_history.borrow_mut().request_prime();
}
self.sync_prepass_pipelines();
Ok(())
}
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,
elapsed,
cam_pos,
} = view;
let Some(framebuffer) = gb.framebuffers.get(frame_idx) else {
return;
};
let device = &self.hw.device;
let extent = self.targets.render_extent;
let cur = ViewFrame {
vp: cur_vp,
elapsed,
cam_pos,
};
let view_uni = GBufferView::new(
jittered_vp,
self.state.view.matrix,
cur,
gb.view_history.prev_or(cur),
velocity_active,
);
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] },
},
depth::CLEAR_VALUE,
];
let rp_begin = vk::RenderPassBeginInfo::default()
.render_pass(gb.prepass_render_pass.handle())
.framebuffer(framebuffer.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);
self.encode_raymarch_prepass(cmd, frame_idx, &view, &view_uni);
if self.draws_sky(self.state.view.mode) {
gb.sky.encode(device, cmd, frame_idx);
self.inc_draw_calls(1);
}
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.hw.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.hw.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.hw.device;
let pipeline = self.cull.prepass_pipeline.as_ref();
let Some(prepass) = self.cull.prepass_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_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
prepass.pipeline_layout.handle(),
0,
&[
self.descriptors.global_sets[frame_idx],
self.cull.bindless_sets[frame_idx],
gset,
],
&[],
);
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 let (true, Some(pipeline)) = (prefix > 0, pipeline) {
device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, pipeline.handle());
device.cmd_draw_indexed_indirect(cmd, indirect, 0, prefix, stride);
self.inc_draw_calls(1);
}
}
if prefix > 0 {
self.inc_draw_calls(self.draw_prepass_bucket_regions(cmd, indirect, prefix));
}
if self.state.draw.n_skinned > 0
&& let (Some(pipeline), Some(cur)) = (pipeline, 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_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, pipeline.handle());
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.state.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 {
#[test]
fn gbuffer_sky_shaders_compile() {
use crate::vulkan::builtin_shaders::{CompileProgram, GBUFFER_SKY_FRAG, GBUFFER_SKY_VERT};
concinnity_shader::require_dxc!();
GBUFFER_SKY_VERT
.compile(false)
.expect("gbuffer sky vertex compiles");
GBUFFER_SKY_FRAG
.compile(false)
.expect("gbuffer sky fragment compiles");
}
#[test]
fn the_prepass_binds_exactly_what_its_module_reads() {
let bound = |read: &[u32]| -> Vec<u32> {
super::prepass_attributes(read)
.expect("offered")
.iter()
.map(|a| a.location)
.collect()
};
assert_eq!(bound(&[0, 1, 2, 4, 5]), [0, 1, 2, 4, 5]);
assert_eq!(bound(&[5, 0]), [0, 5]);
assert!(bound(&[]).is_empty());
}
#[test]
fn an_input_the_layout_does_not_offer_is_an_error() {
assert!(super::prepass_attributes(&[0, 6]).is_err());
assert!(super::prepass_attributes(&[9]).is_err());
}
#[test]
fn the_prepass_offers_every_attribute_its_vertex_shader_reads() {
use super::{PREPASS_VERTEX_ATTRIBUTES, input_locations, spirv_words};
use crate::vulkan::builtin_shaders::{CompileProgram, MAIN_PREPASS_VERT};
concinnity_shader::require_dxc!();
let vs = MAIN_PREPASS_VERT
.compile(false)
.expect("pre-pass vertex compiles");
let read = input_locations(&spirv_words(&vs).expect("whole words"));
let offered: Vec<u32> = PREPASS_VERTEX_ATTRIBUTES
.iter()
.map(|a| a.location)
.collect();
assert!(
read.iter().all(|l| offered.contains(l)),
"{read:?} vs {offered:?}"
);
assert!(read.contains(&0) && read.contains(&5), "{read:?}");
}
}